6、Absolute inversion of control
This chapter covers
■ Wiring components together dynamically
■ Applying method interceptors
■ Raising and observing component events
■ Resolving context variables on demand
本章包括:
将部件“动态绑定”
应用方法拦截器
部件事件
应招式解析上下文变量
Inversion of control (IoC) is a pattern in aspect-oriented programming (AOP) that
espouses loose coupling, allowing the application to focus on consuming services
rather than locating them. Seam embraces the use of IoC not only to “wire” components
together but also to produce context variables and to enable components to
communicate with one another through event notifications. Often, when people
talk about IoC, they’re really talking about dependency injection (DI), one use of
IoC and the primary focus of this chapter.
SEAM使用反转控制,只是将部件串起来,提供上下文变量,让部件通过事件通知的方式来互相通信。
通常当人们谈论反转控制时,其实在中谈依赖注入。
Dependency injection is a key concept in POJO-based development that keeps
components loosely coupled. In the previous chapter, you learned how static DI can
be used to establish references from a component instance to its dependent objects
during instantiation. In this chapter, you’ll learn about another assembly mechanism
in Seam that links a component instance to its dependencies when it’s invoked, a device
known as injection. To complement injection, outjection facilitates exporting state
from a component instance after the component is invoked, effectively producing context
variables that can be used elsewhere in the application.
依赖注入是基于POJO开发的关键概念,目标是部件松耦合。
前一章已学了表态DI如何在部件实例化过程中,用于建立部件实例的参考。本章我们学另外一个集成机制,当部件被调用果,如何连接其实例到其依赖。也就是注入。
This chapter begins by introducing the four steps of bijection—injection, method
invocation, outjection, and disinjection. We then explore several derivatives of bijection.
One such variant manages “clickable lists” in a way that’s completely transparent
to the business component. The key to bijection is that it’s dynamic, meaning it happens
every time the instance is invoked, not just at creation time. Events, which you’ll
learn about next, go a step further toward decoupling components by allowing execution
logic to jump from notifier to observer without an explicit reference between the
participating components. Events let you add features without disrupting well-tested
code or relieve a single method from trying to handle too many concerns. Going
beyond the built-in behavior that Seam weaves into components, you’ll learn how to
create custom interceptors to handle your own cross-cutting logic. Shifting back to the
subject of context variables, you’ll discover how to use factory and manager components
to produce variables requiring more sophisticated instantiation, typically with
the help of bijection. Let’s begin by exploring what makes bijection so unique.
本章学习四步:
注入、方法调用、注出、解注。
一个变体是“可点击列表”。双向注入的特点是动态,每次触发时都是动作,而不是只在建立的时候。
事件,解耦更强,允许执行逻辑从通知者跳到观察者,而不用明确的参考。事件让你增加特性而不用打扰原来测试好了的代码,或只用一个方法来处理太多的问题。除了SEAM部件内建的行为,你会学到怎样建立自定义的拦截器,来处理你自己的交叉的逻辑。回到上下文变量主题,你将知道怎样用工厂和管理部件来产生变量来请求更复杂的实例,主要是用双向注入。
下面我们就看看双向注入。
6.1 Bijection: dependency injection evolved
Dependency injection is about as mainstream as MySpace or the iPod. Although it
may not be a hot topic at family gatherings (at least not mine), it’s the cornerstone of
POJO-based development. In DI, the container “injects” dependent objects into the
corresponding properties of a target object after instantiating it, establishing references
from the object to its dependencies (a process commonly referred to as bean wiring).
This strategy eliminates lookup logic within the object that might otherwise
make it reliant on a particular environment. The object is said to be loosely coupled
from the rest of the application. Unfortunately, it’s not loose enough.
双向注入:依赖注入的进化
依赖注入就像MySpace or the iPod,虽然不是家庭聚会的话题,却是基于POJO开发的基石。在DI中,在目标对象被初始化后,容器注入依赖对象到相应的属性,建立对象到其依赖的参考(这被称为bean wiring)。这种策略不再需要对象内的lookup逻辑, 这种逻辑容易形成对环境的依赖。这叫松耦合,不幸的是松得还不够。
For as much as DI has been studied, discussed, and presented, it hasn’t changed
much since its emergence and suffers from several limitations. The first is that the
injections are static, meaning they’re applied only once, immediately following instantiation
of the object. As long as the object exists, it’s stuck with these initial references,
failing to reflect the changing state of the application over time. Another limitation of
DI is that it’s focused only on the assembly of objects. It would be just as useful for an
object to be able to contribute back to the container by transferring its state to one or
more scoped variables.
自从DI出山以来,就没怎么变化过,这期间因为一些限制,已有很强的更新压力。第一,注入是表态的,这意味着紧跟着对象的实例化,只能应用一次,只要对象存在,它就一直是原始的参考。无法体现服务器状态的改变。另一个限制是只关注对象的整体。有些像一个对象可以将其状态传给一个或多个上下文变量来做为对容器的回馈。
These limitations highlight a general unawareness of context in the design. The
managed objects need to be made more aware of the application’s state and become
active participants.
这些限制体现了设计上对上下文的总体缺乏。被管理的对象需要了解更多服务器的状态,并积极参与活动。
6.1.1 Introducing bijection
Seam has responded to this call for change by introducing bijection. Under bijection,
the wiring of dependencies is done continuously throughout the lifetime of a
component instance, not just when the instance is created. In addition to injecting
values, bijection supports outjecting values. Outject? Pencil it into your dictionary—it’s
a new term. Outjecting is the act of promoting the value of a component property to
a context variable where it can be picked up by another component or referenced in
a JSF view, a page descriptor, or even a jBPM business process definition.
介绍双向注入
此模式下,依赖是持续整个部件实例的生命周期,而不只是建立时。向外注入是个新词。向外注入是将部件值放入到上下文变量,在那里可以被另一个上下文变量、JSF view、页描述符、jBPM业务定义得到。
The combination of injection and outjection is known as bijection. Bijection is
managed by a method interceptor. Injection occurs prior to the method being
invoked, and outjection occurs when the invocation is complete. You can think of the
context variables participating in this interaction as flowing through the component
as it’s invoked. Injection takes context variables from the Seam container and assigns
them to properties on the target instance, and outjection promotes the value of its
properties to context variables. Figure 6.1 provides a high-level conceptual diagram of
this mechanism.
内外注的结合就叫双注。双注由一个方法拦截器管理。注入在方法调用前,注出发生在方法执行后。你可以想像上下文变量伴随其绑定的部件参与这个交互。注入是将上下文变量从SEAM容器中取出并赋给目标实例的属性,注出是将属性值还给了上下文变量。图6.1显示了概括的机制。
Before you put your dictionary away, I’ll mention that Seam also disinjects values
from a component. In this step, any property that received an injection is assigned a
null value. Disinjection is Seam’s way of tying up loose ends. Seam can’t keep the
references up to date while the component instance is idle, so the values are cleared
to avoid state from lingering. The injections are restored the next time the component
is invoked.
Seam也disinjects。这种情况是在这一步,任何接受注入的属性被指定为空值,SEAM不能在部件实例空闲时保持对其的更新,所以将其清除。下一次部件调用时会再次注入。
NOTE
Bijection modifies the state of the component instance. Therefore, calls
to intercepted methods must be synchronized. Fortunately, Seam
efficiently synchronizes all components except for those in application
scope.
双注修改部件实例的状态,所以拦截器的方法必须是同步的。幸运的是除application scope之外的所有部件都被SEAM有效地同步了。
Although bijection may share some similarities with traditional DI, it’s genuinely a new
approach that makes context a primary concern. As you read through this section,
you’ll explore, in detail, how the bijection process works, how it affects the relationship
between components, and how to put it to use.
虽然双注与传统依注有些相似之处,更是关注上下文的一个新思想。
6.1.2 Bijection on the golf course
Before diving into the technical aspects of bijection, I want to start off with an analogy.
When you’re playing in a golf tournament, you want all of your focus on your game.
To help avoid distractions, you are provided a caddy, who acts as your assistant. The
role that the caddy plays between each stroke of the ball parallels that of bijection. In
this analogy, you are the component, the golf club is the dependent object, and the
stroke is the method call.
高尔夫课程的双注
在深钻技术细节前,我想以模拟开始。在高尔夫比赛中,你需要全神贯注,不想分心,你要让球童帮助你拿所有球杆。这球童就相当于双注,你是部件,球杆是依赖对象。击球是方法调用。
As you approach your golfball, in whatever context it may be lying—the green, the
fairway, a sand trap, on top of a warehouse, or the woods—you aren’t holding the golf
club that you need to take your stroke. The golf club is your dependency. To satisfy
this dependency, your caddy injects the golf club that’s appropriate for the context—a
wood, iron, wedge, or putter—into your hands. You are now ready to swing. Striking
the ball is the action, or, in the case of bijection, the method invocation. After taking
your stroke, the ball is outjected and lands in another context on the course—hopefully
avoiding bodies of water and sand traps. Outjection occurs as the result of a
method call. Once the shot is taken, the caddy disinjects the club, reclaiming the
dependency that was previously handed to you, and stores it in your bag for later use.
You walk away from the original spot of the ball the way you arrived, empty handed.
(You may still hold state, like your scorecard, but not the dependency.)
在高尔夫的各种环境,草地、航道、沙坑、库顶、树林,你不能用同一球杆。球童要在适当场合为你注入球杆。击球是action,在这种双注情况下,可以说是方法调用。当你击球后,球外注并进入另一个上下文环境。外注是方法调用的结果。一旦打完,球童会收走球杆,放进袋子等你下回再用。你会继续向下走,空着手(但你可能还是持有状态,如你的计分卡,但手中的确没有“依赖”)。
The caddy lets you concentrate on your golf game, rather than on the routine of
carrying the clubs, cleaning them, and remembering not to leave one behind. It’s an
inversion of control. In the same way, bijection allows you to concentrate on the business
logic rather than rounding up dependent objects needed to perform the logic
and distributing results afterward. As with DI, bijection makes components easy to
test in isolation or reuse since there’s no tight coupling to a container-specific
lookup mechanism.
球童使你专注于比赛,而不是拿杆。这是控制反转。同样道理,双注让你专注于业务逻辑而不是依赖对象的那些事儿。使用双注可以独立并重复地测试,这得益于没有与容器指定的lookup机制紧耦合。
With a general understanding of how bijection works, let’s dig into the technical
details of how you can add this capability to your components.
有了总体印象后,我们来深挖一下,看看如何改善你的部件。
6.1.3 Activating bijection
When Seam instantiates a component, it registers a handful of method interceptors
on the instance, an AOP technique for applying cross-cutting concerns. One of those
interceptors manages bijection. Like all method interceptors, the bijection interceptor
is triggered each time a method is called on the instance, referred to here as the
target method. The bijection interceptor wraps the call to the target method, performing
injections before the target method proceeds and then outjections, followed
by disinjections, after the target method executes—all of which takes place before the
method returns to the caller.
激活双注
当SEAM实例化一个部件,会用AOP注册一些方法拦截器。其中一个管理双注。和其它拦截器一样,在调用实例时,拦截器被触发。双注包装了对目标方法的调用,先注入再注出再解注。所有这些在方法发生在返回给调用者之前。
The properties that participate in bijection are designated using annotations. The
most common bijection annotations are @In and @Out, which define injection and
outjection points, respectively. Derivatives of these annotations also exist, but they’re
processed in fundamentally the same way. For now, we’re going to focus on @In and
@Out. Here’s the first version of the ProfileAction component, which uses both @In
and @Out. It selects a Golfer instance by ID from the injected EntityManager and
then promotes the instance to a context variable that can be accessed from the view.
Assume for now that the golfer ID is passed to the view() method defined below by a
parameterized expression bound to a UI command button.
参与双注的属性是用annotations声明。最常用的是@In and @Out,定义了注入和注出点。这些annotations的衍生物也存在,基本用同一种方式处理。这里我们重点看看@In and @Out。这是第一版的ProfileAction部件。它用ID来从注入的EntityManager选择Golfer实例,然后将实例关联到可被view读取的上下文变量。现在golfer ID是由绑定到UI命令按钮的参数传入view(),
@Name("profileAction")
public class ProfileAction {
@In protected EntityManager entityManager;
@Out protected Golfer selectedGolfer;
public String view(Long golferId) {
selectedGolfer = entityManager.find(Golfer.class, golferId);
return "/profile.xhtml";
}
}
While this component may look straightforward at first glance, upon further examination
you may question how the @In and @Out properties work, knowing that annotations
are just metadata. What happens is that Seam scans for @In and @Out
annotations when the component is registered and caches the metadata. The bijection
interceptor then interprets this metadata when a method is called on an instance
and applies bijection to its properties.
当SEAM扫描到@In and @Out,双注拦截器会在方法调用时对其属性应用双注。
When the bijection interceptor traps a call to
a component method, it first iterates over the
bean properties on the component marked with
an @In annotation and helps those properties
find the values for which they are searching. If
all the required @In annotations are satisfied,
the method call is allowed to proceed. From
within the method, the property values that were
initialized via injection can be accessed as if they
had been there all along.
当双注拦截器截到一个调用。首先与标有@In的部件属性进行通信,帮助其找到值。如果所有的@In处理完毕,方法调用才被处理。
If the method throws an exception, bijection
is interrupted and control is turned over to the
Seam exception handler. If the method completes
without exception, the bijection interceptor
postprocesses the method call. This time, it
iterates over the bean properties marked with an
@Out annotation and promotes the value of
these properties to context variables in the Seam
container. Finally, the properties that received
injections are cleared, wiping the slate clean for
the next invocation. The bijection process just
described is illustrated in the sequence diagram
in figure 6.2.
如果方法抛出异常,双注中断,控制转交线SEAM的异常处理器。如果正常完成,双注拦截器执行后续调用。轮到@Out表演。最终,接受注入的属性被清除,以待下一个调用。顺序表6.2显示了双注的过程。
That should give you enough technical
details to convince your boss that you know what
bijection is, but you may need to see some examples to become comfortable using it.
You’ll also need to figure out how Seam locates a value to inject and which context
variable is used when a property is outjected. Without these key details, bijection
remains a bit mystical.
这些皮毛哄哄老板足够用了。但要处在地使用,你还要知道SEAM怎样定位值来注入,注出时又使用了什么属性。
Most of the time, you’ll be using the injection piece of bijection. Let’s put a spin on
a well-known phrase by saying “no component is an island” and explore how injection is
used to “wire” components together dynamically.
大多数情况是使用注入。常言道:“没有部件是孤岛”。来看看注入是怎样在其间进行动态串联的。
6.2 Dynamic dependency @In-jection
Implementing business logic typically involves delegating work to other components.
A familiar delegate that shows up in nearly every database-oriented application is the
persistence manager (e.g., JPA EntityManager or Hibernate Session), which is used
to persist entities or read their state from the database. In the previous chapter, you
used component configuration to perform this injection—a form of static DI. That’s
fine if you’re injecting a stateful component into a short-lived component or injecting
a stateless component. However, as soon as you start using stateful components that
interact with other stateful components, you need a mechanism that keeps the references
up to date. Rather than trying to distinguish between the two cases, the recommended
way of hooking components together in a Seam application is to use
bijection. That way, you can always be sure the component will adapt to changes in
the application’s state.
@In的动态依赖
实现业务逻辑总是涉及对其它部件的代理工作。最熟悉的代理是实体管理器,(JPA EntityManager or Hibernate Session)。前一章你用靜态注入的表单来实现这类注入,当部件是短期的或是无状态部件,这种方式还可以。然而当你使用有状态部件并与其它有状态部件通讯时,你需要保存参考的状态。推荐的方法就是使用双注。
6.2.1 Declaring an injection point
Annotations come into play when configuring bijection as they did when defining
Seam components. The @In annotation, summarized in table 6.1, is placed above a
bean property of a component—either a field or a JavaBean-style property “setter”
method. Seam uses reflection to assign a value to properties marked with @In during
the first phase of bijection.
声明注入点
表6.1中表出的@In位于部件的bean属性,属性或JavaBean样式的setter方法都可以。Seam在双注的第一阶段使用反射来指定带有@In的属性值。
The value attribute on the @In annotation can be the name of a context variable or an
EL value expression, or it can be omitted. If the value attribute is omitted, the most common
case, the name of the context variable to search for is implied from the name of
the property (according to JavaBean naming conventions). By providing a context variable
name in the value attribute, the name of the context variable to search for can be
different than the name of the property into which it is injected. If the value attribute
uses EL notation, it is evaluated and the resolved value is injected into the property.
@In的值属性,可以是上下文变量名或EL表达式,也可以省略。如果值属性省略(这也是最常用方式),
上下文变量名由属性名暗示(遵循JavaBean命名规范)。通过在值属性中提供上下文变量名,变量名可与属性名不同。如果值属性是EL标记,分析值注入到属性。
Common use cases for the @In annotation include injecting a persistence manager,
a JSF form backing bean, or a built-in JSF component. All three types are needed
by the RegisterAction component, which was created in chapter 4. Listing 6.1 shows
the RegisterAction component using @In to supply all the dependent components
needed to perform registration. The @Logger and @In annotations are placed inline
to conserve space.
常用的@In包括注入持久管理器,JSF表单bean,或内建的JSF部件。第4章建立的RegisterAction部件需要所有这三种。
@Logger and @In可以写在一行以节约空间。
With these changes in place, you should be able to run the RegisterGolferIntegrationTest
and verify that the tests pass just as before. The fixture for the test remains
the same—only now, Seam wires the context variables into the component under test
dynamically using bijection.
经过这些改变,现在你可以运行RegisterGolferIntegrationTest来实现前面一样的功能。只是现在SEAM使用动态双注连接上下文变量。
There are several permutations for how Seam resolves the context variable to
inject. Let’s step through the decision process that occurs during the first phase of
bijection.
有几个阶段SEAM用来解析上下文变量。
6.2.2 The injection process
The decision process for performing an injection is illustrated in figure 6.3. You can
see that this process has two main branches: one where the value attribute of the @In
annotation is expressed in EL notation, and one where the value attribute is the context
variable name or a context variable name is implied. Use this diagram to follow
along with the discussion.
注入过程
图6.3示例了注入的决定过程。这过程有两个主要的分支。一个是值属性用EL标记,另一个值属性是上下文变量名或暗示着的上下文变量名。
Let’s begin by considering the case
where the value attribute of the @In
annotation is a context variable name. A
few subtle differences exist between this
case and when the value attribute of the
annotation uses EL notation.
在哪里,@In的值属性是上下文变量名?当值属性使用EL标记法时,这个差别细微。
Seam first looks to see if a scope is specified
in the annotation. If so, Seam looks
for the context variable name in this
scope. If a scope isn’t provided, Seam
performs a hierarchical search of the
stateful contexts, starting from the narrowest
scope, ScopeType.EVENT,1 and continuing
all the way to the broadest scope,
ScopeType.APPLICATION, as illustrated in
figure 6.4. The search stops at the first
non-null value that it finds and assigns that value to the property of the component.
Keep in mind that a context variable can hold any value, not just a component instance.
If the hierarchical context search fails to resolve a non-null value for a context variable,
Seam attempts to initialize a value, but only if the following two conditions are met:
■ The context variable name matches the name of a component.
■ The create flag is true on the @In annotation or the matched component supports
autocreate.
SEAM首先看是否annotation中指定了范围。如是,SEAM在这个范围查找上下文变量。如果没有提供范围,SEAM按层次查找有状态上下文,从最小的范围开始,ScopeType.EVENT,直到最宽的范围,ScopeType.APPLICATION。如图6.4所示。当遇到第一个非空值时停止,并将这个值指定到部件实例。
如果层次查询没法为上下文变量解析非空值,SEAM就初始化一个值。但还是要有下面两个前提。
上下文变量名与部件名匹配
@In的建立标志为true,或者对应的部件支持自动建立
If these conditions are met, Seam instantiates the component, binds the instance to a
context variable, then injects it into the property marked with the @In annotation.
Note that the instance may come from a factory component, covered in section 6.7.1.
If the context variable name doesn’t match the name of a component, there’s nothing
for Seam to create and therefore the value of the property remains null.
条件满足时,SEAM初始化部件,绑定实例到上下文变量,然后注入到标有@In的属性。
注意,实例也可能来自工厂实例,在6.7.1节中细述,如果上下文变量不能匹配部件名,SEAM也没什么可建的,因此属性值保持为空。
Before finishing its work, Seam validates against the required flag on @In. Requiring
a value protects against a NullPointerException. If the required flag is true, and
Seam wasn’t able to locate a value, the runtime exception RequiredException is
thrown. There is one exception to this rule. Required flags are never enforced on lifecycle
methods (this is true for both injection and outjection), though bijection does
still take place. In any case, if the required flag is false or not enforced, the property
remains uninitialized if Seam can’t locate a value.
在结束工作之前,SEAM再次校验@In上的请求标志,希望能避免NullPointerException。如果请求标志为true,并且Seam也不能定位值,RequiredException异常被抛出。虽然双注依然发生,Required flags从来不强加到生命周期方法(对注入、注出都是)。在任何情况下,如果请求标志是假或不强加,当SEAM找不到值时,属性保持未初始化。
TIP
If you find yourself making heavy use of the required flag to disable
required injections (and later outjections), it’s an indication that you’re
trying to make the component do too much. Refactor your monolithic
component into smaller, more focused components and use bijection or
static injection to allow them to collaborate.
如果你总是要用required flag为禁用被请求的注入(或注出)。这是一个暗示,你可能让部件做太多的事了。将你的部件重构成更小、更专注、使用双注或静态单注,使其互相合作。
When the value attribute uses EL notation, the semantics are much simpler. The work
of locating a value is delegated to the EL variable resolver. The same validation against
the required flag is performed as in the non-EL case.
If all such injections for a component succeed, the method invocation proceeds.
Before you check the @In annotation from your list of Seam concepts mastered, let’s
consider how the dynamic nature of the injection allows you to mix scopes and properly
handle nonserializable data.
当值属性使用EL标记,表达式很简单,定位值的工作委派给EL变量解析器,必填项的相同的校验操作和非EL的情况一样。
这个部件的所有注入成功后,方法调用开始。在你列出@In之前,让我们想想动态注入是怎样让你混合scopes并正确处理非序列化的数据。
6.2.3 Mixing scopes and serializability
As you know, every time a method on a
component is invoked, Seam performs
the lookup to resolve a value based on the
information provided in the @In annotation.
This dynamic lookup allows you to
inject a component instance stored in a
narrow scope into a component instance
stored in a wider scope. Figure 6.5 shows
a narrow-scoped component instance
being injected into a wider-scoped component instance at each invocation. If injection
were to only happen when the wider-scoped component instance was created, the
injected instance would be retained beyond the lifetime of its scope.
每次部件的方法被调用时,SEAM会基于@In中提供的信息去查询并解析值。这个动态查询让你将一个窄范围的部件实例注入到一个宽范围的部件实例。图6.5显示了窄范围实例注入到宽范围的情况。已经是最宽的不会再变宽。
Let’s pose this scenario as a question, plugging in actual scope names. What would
happen if a request-scoped variable were to be injected into a session-scoped object
via static DI? Recall that static DI occurs only once: when the object is created. In this
situation, as the request-scoped variable changes between HTTP requests, the sessionscoped
object retains the original value of the request-scoped variable and never gets
updated. This describes the behavior of most other IoC containers in existence today.
By using Seam’s dynamic DI, the same property on a session-scoped component would
always reflect the value of the request-scoped variable from the current request
because the injection is reapplied each time the component is invoked. Additionally,
the value isn’t retained beyond the end of the request since Seam breaks the reference
by disinjecting the value once the method call is complete.
我们来对号入座,插入实际范围名。如果通过静态DI将request-scoped范围注入到session-scoped会怎样?回忆静态DI只会在建立时发生一次。在这种情况下,request-scoped的变量在HTTP请求间变化,sessionscoped对象保持request-scoped原值,不会升级;这描述了大多数其它流行IoC容器的做法。
通过SEAM动态DI,session-scoped部件的一些属性将总是从当前请求中反射request-scoped变量的值,因为每次部件激活时,都会发生注入。另外,值不会在会话结束后还会保留,因为一旦方法调用结束,SEAM会解注。
Let’s consider another difficult scenario that’s cleared up thanks to the dynamic
nature of Seam’s DI: mixing nonserializable objects with serializable objects (a serializable
object is an instance of any class that implements java.io.Serializable). If a
nonserializable object is injected into the property of a serializable session-scoped
object, and that property isn’t cleared prior to session passivation, the session storage
mechanism will get tripped up by the nonserializable data. You could, of course, mark
the field as transient so that the value would be automatically cleared, hence allowing
the session to properly passivate. However, the real problem is that once the session
is restored, the transient field remains null, acting as a land mine in the form of a
NullPointerException that may be triggered by an unsuspecting block of code.
来考虑另一个例子,混合非序列化的对象到序列化的对象(序列化的对象是任何实现了java.io.Serializable的类),如果一个非序列化的类注入到序列化的类,如果这个属性没有在惰化session passivation前被清除,session的存储机制会被非序列的数据get tripped up。你当然会将字段标为transient,这样值会自动被清除,以使session正确地passivate。然而,真正的问题是一旦session保存了,transient字段保持为空,这像是在埋下了NullPointerException的地雷。将会被后面不小心的代码触发。
Applying the @In annotation to the field solves both parts of the aforementioned
problem. To begin with, the value assigned through the @In annotation is cleared in the
last phase of bijection, after the method on the component is invoked. Right out of the
box, injections are transient without you having to add the transient keyword on all the
fields marked with @In. But the real power of injection is that the injected values are
restored prior to the next method call. This continuous injection mechanism allows
objects coming out of passivation to be reinflated. Thus, disinjection paired with subsequent
injections should alleviate a common, yet trite, pain point that arises when working
with both session replication and the reactivation of a session after a server restart.
But the @In annotation isn’t the only type of injection that Seam supports. Next we
look at a couple of domain-specific injection variants.
使用@In到字段可以同时解决前面说的这两个问题。首先,由@In指定的值在双注的最后阶段,部件方法调用之后被清除。注入生来是transient的,不用加transient关键字。但双注真正的力量是注入值先于下个方法调用。这个持续的注入机制允许对象从惰化中恢复。这样,解注对应后续注入将减轻常见的工作于两个session复制品时痛点,当服务器重启后,重新激活session。
6.2.4 Injection variants
Seam supports several additional annotations for marking dynamic injection points.
The two you’ll learn about in this section are @RequestParameter and @Persistence-
Context. The first is used to inject an HTTP request parameter and the second an
EntityManager (JPA). Technically, the Java EE container handles injecting the
EntityManager into a property annotated with @PersistenceContext, but Seam follows
that up with a second injection pass. Later on, you’ll learn about two more injection
annotations in the section covering JSF data model selection. Of all the bijection
annotations, @RequestParameter is probably the easiest to grasp, so let’s start there.
注入变量
SEAM支持另外几类annotations来作为动态注入点。本节学两个@RequestParameter and @Persistence-Context.
前一个用于注入HTTP request parameter,后一个是HTTP request parameter。技术上Java EE容器用@PersistenceContext来注入EntityManager,但SEAM开了第二个注入通道。后面你会学两个处理JSF数据模型的。这些中,@RequestParameter是最易掌握的。
INJECTING A @REQUESTPARAMETER
The @RequestParameter annotation is used to inject an HTTP request parameter into
the property of a component, retrieved either from the query string or the form data.
You either specify a parameter name explicitly in the value attribute or let Seam
imply the parameter name from the name of the property. Unlike @In, however, a
value is never required.
注入一个@REQUESTPARAMETER参数
用于注入一个HTTP request parameter到部件属性,或者来自请求串或是表单。你或者明确指定参数到属性值,或让SEAM暗示参数名,不像@In,值不是必填的。
You can, of course, inject a request parameter into a string or string array property,
since request parameters are inherently strings. Going a step further, if the property’s
type is not a string or string array, Seam converts the value before injecting it using the
JSF converter registered for that type. If no converter is associated with the property’s
type, Seam throws a runtime exception. As a reminder, JSF converters implement the
javax.faces.convert.Converter interface and are registered in faces-config.xml or
by adding the @Converter annotation to a Seam component class.
可从串注入请求参数,或串数组属性,因为请求参数是从串中继承的。再进一步,如果属性的类型不是串或串数组,SEAM用JSF转换器对其进行转换。如果没有转换器与属性类型关联,则抛出异常。JSF转换器实现javax.faces.convert.Converter接口,并在faces-config.xml中注册,或者在SEAM部件类中增加@Converter。
The @RequestParameter annotation is a convenient alternative to page parameters
for creating RESTful URLs. In fact, this approach is more “Seam-like” because it uses
an annotation rather than XML. The one benefit of page parameters that you lose,
however, is Seam rewriting links to propagate page parameters to the next request.
@RequestParameter是页面参数的方便的可用于建立RESTful URLs的替代物,事实上,这种方式更SEAM,因为用的是annotation而不是XML,失去的页面参数的好处是SEAM重写链接来传播页面参数到下一个请求。
In the Open 18 application, we want to create a page that displays a golfer’s profile,
which will be prepared by the ProfileAction component. Let’s design it so that the ID
of the golfer to display is passed to the URL using the request parameter golferId:
在Open 18应用中,我们要建立一个页面显示球员的简历,这会用ProfileAction部件,我们让球员的ID通过URL传递,
http://localhost:8080/open18/profile.seam?golferId=1
The golferId request parameter can be injected into this component by placing the
@RequestParameter annotation over a property with the same name. This injection
takes place whenever a method on the ProfileAction component is invoked.
Therefore, the golferId property can be used in the load() method to look up the
corresponding Golfer entity using the JPA EntityManager:
golferId请求参数可用@RequestParameter及相同的名字注入到这个部件,这个注入每当部件的ProfileAction方法被调用时执行一次。所以golferId属性可用于load()方法,用JPA实体管理器来查询Golfer实体。
@Name("profileAction")
public class ProfileAction {
@In protected EntityManager entityManager;
@RequestParameter protected Long golferId;
protected Golfer selectedGolfer;
public void load() {
if (golferId != null && golferId > 0) {
selectedGolfer = entityManager.find(Golfer.class, golferId);
}
if (selectedGolfer == null) {
throw new ProfileNotFoundException(golferId);
}
}
}
We have the load() method invoked when the /profile.seam path is requested by making
this method a page action for the corresponding view ID in the page descriptor:
当被方法load()标记的/profile.seam路径被请求,方法load()被调用,
The motivation for using a page action is to ensure that the profile can be retrieved
before committing to rendering the page. If an instance of Golfer is found, it’s stored
in the selectedGolfer property. However, if the golferId request parameter doesn’t
produce a Golfer instance, a custom runtime exception named ProfileNotFound-
Exception is thrown. This exception causes Seam to throw up a 404 error page, along
with a message stating that the profile could not be found, which happens because the
exception class is annotated with @HttpError:
使用页action的目的是确保球员简介可以在重画页面前检索,如果发现了Golfer实例,则保存在selectedGolfer实例,如果selectedGolfer请求参数不产生Golfer实例,一个定制的ProfileNotFoundException会抛出,这会使SEAM抛出404出错页及一些简介找不到的信息状态。这是因为异常因使用了@HttpError
@HttpError(errorCode = HttpServletResponse.SC_NOT_FOUND)
public class ProfileNotFoundException extends RuntimeException {
public ProfileNotFoundException(Long id) {
super(id == null ? "No profile was requested" :
"The requested profile does not exist: " + id);
}
}
That’s as far as we go with this example right now. You still need to learn how to promote
the value of the selectedGolfer property to a context variable that can be
accessed from the view, which the next section covers.
例子到此,我们依然需要知道selectedGolfer的值是怎样传给上下文变量,以被view引用。这在下节介绍。
Seam isn’t alone in its support for dynamically injecting values into annotated
properties. Java EE has its own set of annotations for declaring what the spec terms
a “resource injection.” For the most part, Seam stays out of the way and lets the
container handle the task. But Seam does get its hands dirty with the @Persistence-
Context annotation.
SEAM在支持动态注入方面并不是唯一一个,Java EE有自己的一套annotations,称为resource injection。大多数情况下,SEAM会让开,而让容器来处理。但在@Persistence-Context,SEAM的确是出手了。
AUGMENTING THE @PERSISTENCECONTEXT
The @PersistenceContext annotation is a Java EE annotation that marks a property
on a Java EE component that should receive a container-managed EntityManager
resource injection. After the EntityManager is injected by the Java EE container but
before the method invocation proceeds, Seam wraps the EntityManager in a proxy
object and injects it again. The proxy adds EL value expression support to Java Persistence
Query Lanaguage (JPQL) queries. But otherwise, the life cycle of the Entity-
Manager is controlled by the Java EE container. The @PersistenceContext annotation
is explained more in chapter 8 and the EntityManager proxy in chapter 9. Keep in
mind that this annotation is only relevant for Java EE managed components (e.g., JSF
managed beans or EJB session beans).
@PersistenceContext是Java EE的annotation,用于检索容器管理的EntityManager的资源注入。在EntityManager注入后,调用前,SEAM用代理对其包装,代理增加了EL表达式支持。但是EntityManager的生命周期是由Java EE容器控制的。@PersistenceContext将在第8章中细述,EntityManager代理是第9章。
Let’s pause for a moment to take a look around because you are now standing at
the ridge of where DI ends and bijection continues. Looking behind you, you see that
bijection has evolved DI by making the injection dynamic. Looking forward, you see
that there’s a whole other side of this pattern that has never before been explored. It’s
the flip side of injection known as outjection. Let’s explore it!
6.3 @Out-jecting context variables
Outjection is a way of pushing state held by a component out to the Seam container. You
can think of a component as the parent and the properties as its children. Outjection
is like sending the child off (perhaps kicking it out) to live in the world on its own. The
parent (the component) exports the child (the property) to a new address (the context
variable). Other people (components) can visit that child at the new address without
consulting the parent. The property value is now associated with its own context variable
that puts it on equal footing with other components in the Seam container.
注出上下文变量
注出是将部件状态注给SEAM容器,你可以将部件当作父母而将属性当作孩子,注出就像是长大后让孩子离开并开始其自己的生活。父母给孩子一个新的地址(上下文变量),其它人(部件)可以访问孩子而不用再征询其父母。
An outjection point is declared by adding the @Out annotation, summarized in
table 6.2, to a bean property—a field or JavaBean-style “getter” method. After a component
method is invoked, the value of the property is used to create a context variable or
bind to one that already exists. If a name isn’t provided in the value attribute, the name
of the property is used. As with the @In annotation, the value attribute on the @Out
annotation can be used to make the name of the context variable different from the
property name. Note that the @Out annotation doesn’t support EL notation as @In does.
注出点用@Out标记,在表6.2汇总,可位于字段或getter方法上,在部件方法调用后,属性名用于建立上下文变量,或绑定到一个已存在的之上。如果值属性中没有提供名字,则使用属性名。如同@In一样的方式。注意@Out不可以像@In一样支持EL标记。
The outjection process is not as complex as the injection process. However, there are
still decisions Seam must make before assigning the value of the property to a context
variable.
注出过程没有注入复杂,不管怎样,在指定属性值到上下文变量SEAM还是要做些判断。
6.3.1 The outjection process
Figure 6.6 shows the outjection process. As you can see, three main decisions are
made in this process: the context variable name to use, the scope in which to store the
context variable, and whether to permit a null value. Let’s step through this process.
The trickiest part to understand is how Seam infers a scope if one is not stated
explicitly. If you specify a scope in the @Out annotation, Seam assigns the value of the
property to a context variable in that scope. The name of the context variable is either
the name of the property or the override specified in the value attribute of the @Out
annotation. A context variable can’t be bound to the stateless context, so @Out doesn’t
permit use of this scope.
注出过程
图6.6显示了注出过程。要有三个判断:上下文变量名,上下文变量的范围,再有就是是否可为空。
对于SEAM来说最难的地方是判断是状态被明示。如果在@Out中指定,SEAM指定属性值到上下文变量。上下文变量名或用属性的,或用指定的。上下文变量不能被绑定到无状态上下文,所以@Out不允许用在这个范围。
If a scope isn’t specified, Seam first attempts to locate a component with the same
name as the target context variable. If one exists, and its type is equivalent to the
property’s type, the property value is outjected to the scope of that component. Note
that the matching component may have been defined using the @Role annotation.
Roles effectively centralize the target scope of an outjection, rather than defining it at
the outjection point. If the context variable name doesn’t match the name of a component
(or component role), Seam uses the scope of the host component (i.e., the
component on which this property resides). If the scope of the host component is
stateless, Seam chooses the event scope instead.
如果范围没有指定,SEAM首先用同名来定位部件作为上下文变量,如果存在,并且类型等同于属性名,属性值注出到部件的这个范围。注意注出的部件可能是用@Role定义的。Roles会有定位注出的范围,而不是在注出点。如果上下文变量不匹配于部件名(或部件角色),SEAM使用主部件的范围,如果主部件是无状态的,SEAM选择事件范围的。
In the final step, Seam processes the required flag. If the required flag is true (the
default) and the value being outjected is null, Seam throws the runtime exception
RequiredException. Recall that required flags are implicitly false on life-cycle methods.
Let’s look at a couple of use cases where outjection is useful.
最后一步,SEAM处理必填标志,如果标志为true(默认)注出值为空,SEAM抛出运行时异常RequiredException。回忆生命周期方法,必填标志暗示为false。
我们来看看一些注出的用例。
6.3.2 Outjection use cases
Outjection is useful for accomplishing two goals. You can use it to expose the model to
the view as a result of executing an action method, or you can use it to push data into
a longer-term scope so it’s available on subsequent requests. Let’s look at the two cases
in turn.
注出的用例
注出用于完成两个目标,当有了action方法的执行结果,可用其暴露模型到view。或者你可用它来将数据放入长期范围以便后续操作使用。
PREPARING THE MODEL FOR THE VIEW
The view isn’t very useful without data to display. Preparing this data is typically the
responsibility of the page action or the action method triggered from the previous page.
In Struts, you usually pass the model to the view by assigning values to HttpServlet-
Request attributes in the action. Managing variables in this way can be quite cumbersome
and couples your code tightly to the Java Servlet API.
为VIEW准备模型
数据是要拿出来看的。准备这些数据是页面action的责任,其被上一个页面触发。在Struts,你通常使用指定HttpServlet-Request属性的方法来将模型指定给view。这样管理变量很麻烦并会使你的代码与Java Servlet API紧耦合。
Before abandoning the Servlet API, let’s find a compromise that loosens this coupling
and makes the code less cumbersome. In chapter 4, you learned that Seam normalizes
the servlet contexts under a single API. You can inject one of these contexts
directly into a Seam component using bijection. A context behaves like a map, where
the keys are the context variable names. You can add a context variable to the event
scope with the following setup:
在放弃Servlet API之前,试一下妥协,减轻这种耦合并让代码不再麻烦。第4章中见过,SEAM让servlet上下文正规化到单独API,你可以使用双注直接注入这些上下文到SEAM部件。上下文的行为类似map,键是上下文变量名,你可以使用下列设置增加上下文变量到事件范围:
@In protected Context eventContext;
public void actionMethod() {
eventContext.set("message", "Hello World!");
}
Although this approach works, there’s a cleaner, more declarative way of accomplishing
the same task. You simply mark properties with the @Out annotation that you want
to have placed into the target context and your job is done. Here’s the same code taking
a declarative approach:
虽然这种方法可行,但有更清晰的方式可以完成同样的工作。只要简单地在你想放入目标上下文的地方用@Out标注属性就完事儿了。下面就是这种简单的方式:
@Out(scope = ScopeType.EVENT) protected String message;
public void actionMethod() {
message = "Hello World!";
}
Here you see that outjection decouples the context variable assignment from the business
logic in the action. (The scope attribute on the @Out annotation is only required
if the target scope is different from the scope of
the host component.) As far as the view is concerned,
it doesn’t care how the context variable
was prepared.
可以看到注出解耦了action中业务逻辑中上下文变量的指定。@Out的scope属性当目标scope不同于主部件的时是必须的。因为考虑的是view,它不在乎上下文变量是怎样准备的。
Let’s complete the golfer profile example
started earlier by making the selected golfer
available to the /profile.xhtml view. The goal is
to create the output shown in figure 6.7.
我们来完成高尔夫球员简介的例子,让被选中的球员在/profile.xhtml中可用。目的是建立表6.7这样的输出。
To extract the value of the selectedGolfer
field from the ProfileAction component
when the load() method is invoked, we add
the @Out annotation above the field. The value
of the field is then assigned to the equivalently
named context variable in the event context:
当load()方法被调用时,要从ProfileAction部件提取selectedGolfer字段值,我们在字段上加@Out,此字段值会用同名上下文变量指定到事件上下文。
@Name("profileAction")
public class ProfileAction {
@Out protected Golfer selectedGolfer;
...
public void load() { ... }
}
The selectedGolfer context variable can then be referenced in value expressions in
the /profile.xhtml page as shown here:
selectedGolfer上下文变量随后可以被/profile.xhtml page中的值表达式引用。
#{selectedGolfer.name}
Profile
Gender
#{selectedGolfer.gender}
Birthday
...
The other use case for outjection is to propagate state.
注出其它的用处在于传播状态。
KEEPING DATA IN SCOPE
Just because a field is outjected doesn’t mean it has to be used in the view. It’s quite
reasonable to outject a value just so that it can be injected into a component on a
subsequent request. Say goodbye to hidden form fields and the mentality of having to
manually propagate variables from one request to the next. Instead, you simply use
outjection to put a context variable on a shelf (a long-term scope such as the page,
conversation, or session scope) and pull it down when you need it again. In this case,
outjection decouples the mechanism of preserving state from the business logic. This
technique is especially useful in conversations, which are covered in chapter 7.
在SCOPE中保持状态
字段被注出不表示一定是用在view中,注出也可以只是为了在后续请求中注入另一个部件。从此可以跟隐含域及手工地向下一请求传播变量再见了。你只要简单地用注出将上下文变量上架(a long-term scope such as the page,conversation, or session scope),等用的时候拿过来。在这种情况下,注出解耦了保存数据与业务逻辑。这一技术在会话中很有用。第7章中讲。
Storing component instances out of Seam’s reach
You should not store a reference to a component instance in a place where Seam
can’t manage it, such as inside a collection. It should only be stored in a context
variable.
An object ceases to be an instance of a Seam component when you manage it
yourself. While storing it out of Seam’s reach won’t cause an error, Seam stops
intercepting its methods, and thus services such as bijection and events are no
longer applied to it.
If you need collection semantics, you should store the name of the component instead,
then look up each instance when you need it using Component.getInstance().
将部件保存到SEAM拿不到的地方
你不应这样做,如放到集合中,只应保存在上下文变量中。
存到SEAM之外会导致错误。SEAM拦截不到它的方法,所以双注及事件之类的服务无法使用。
When you began reading this chapter, you may have thought bijection a bit mysterious.
However, with the information covered up to this point, I doubt you’ll forget how
bijection works. If for some reason you do, just remember that injections happen
before a component method is invoked and outjections happen after the method call
is complete. Lastly, the injections are cleared just before the method returns. Repeat it
to yourself. That way, when you see the error message “@In attribute requires non-null
value” or “@Out attribute requires non-null value,” you’ll know what Seam is trying to
tell you. If all else fails, just ask your golf caddy. Now let’s use bijection to make lists in
JSF “clickable.”
刚进入这一章时,你可能觉得双注有些神秘。如果是这样,你可以简单地认为注入发生在部件方法调用之前,注出发生在之后。注入在方法返回前被清除,这样,当你再看到“@In attribute requires non-null value” or “@Out attribute requires non-null value,”信息时你就知道SAEM想告诉你什么了。下面我们用双注来生成JSF可点击列表。
6.3.3 Built-in @DataModel support
In JSF, UIData components, such as , are backed by a special collection
wrapper called a data model. A data model is a way for JSF to adapt various types of
collections to the UI component model in a consistent way and to support capturing a
row selection made by the user. The collection wrappers extend from the abstract
class javax.faces.DataModel and support the major collection types. Seam builds on
this set by adding support for the Query component from the Seam Application
Framework. The Query component manages the result of a JPQL/HQL query, which
Seam retrieves and wraps in a ListDataModel. You’ll learn about the Query component
in chapter 10. The mapping between the collection types and their wrappers is
shown in table 6.3.
内建@DataModel支持
JSF的UIData的部件。如由特殊集合包data model支持,data model是JSF为了适应UI部件模型的各种类型的集合而打造,支持用户在行上的点击。此集合包由抽象类javax.faces.DataModel扩展,SEAM从SEAM应用框架向其增加了Query部件。Query部件管理ListDataModel检索出的JPQL/HQL的结果,Query将在第10章介绍。集合类与包装类的对应见表6.3
So what do these wrappers have to do with bijection? To properly prepare collection
data to be used in a UIData component, you should wrap it in a JSF data model. Why
should you have to deal with this drudgery in your business logic? This task sounds
like something the framework should handle. Seam developers agree. For this purpose,
they created the @DataModel annotation, summarized in table 6.4. The @Data-
Model is used in place of the @Out annotation for outjecting one of the collection
types listed in table 6.4. Before the value of the @DataModel property is assigned
to the context variable during outjection, it’s first wrapped in the correct JSF Data-
Model implementation.
包装器应怎样来双注?为准备UIData要用的数据,你应将其包入JSF数据模型。为什么你非要在你的业务逻辑做这些苦工?这些事好像应于JSF来管理。SEAM开发展因此开发了@DataModel,汇总于表6.4,在注出过程,在@DataModel属性被指定到上下文变量前,先被包装进了恰当的JSF Data-Model实现。
Let’s use Seam’s data model support to display a
list of newly registered golfers on the home page,
which will be made clickable so that a user can
view the golfer’s profile. The goal of this section is
to produce the output shown in figure 6.8.
让我们用SEAMdata model在主页支持来显示新注册的高尔夫球员。其可以点击以显示球员简介,本节的目标就是产生图6.8那样的输出。
The business logic is responsible for fetching
the list of new golfers. We let Seam handle the
details of wrapping that collection in a JSF Data-
Model and exposing it to the view by adding the
@DataModel annotation, a variant of the @Out
annotation, to the newGolfers field:
业务逻辑就是取出新球员的列表,我们让SEAM来处理JSF Data-Model包装的细节,用@DataModel将其暴露给view,@DataModel可算作@Out的一个变种。
@Name("profileAction")
public class ProfileAction {
@DataModel protected List newGolfers;
...
}
Of course, that’s not enough for the newGolfers field to be outjected—heck, it’s not
even populated. First, we need a method that populates this collection. Then, we need
to figure out how to activate outjection. Let’s reuse the ProfileAction component.
We add the method findNewGolfers() to load the list of new golfers from the database.
To keep things interesting, we perform a little quick and dirty randomization on
the list:
当然,对于要注出的newGolfers这些是不够的。首先我们需要有方法产生这个集合,然后我们需要知道怎样激活注出。让我们重用ProfileAction部件,我们增加方法findNewGolfers()从数据库加载球员列表,为了有趣些,我们打乱了列表的顺序。
@Name("profileAction")
public class ProfileAction {
protected int newGolferPoolSize = 25;
protected int newGolferDisplaySize = 5;
@Out(required = false) protected Golfer selectedGolfer;
@DataModel protected List newGolfers;
...
public void findNewGolfers() {
newGolfers = entityManager
.createQuery(
"select g from Golfer g order by g.dateJoined desc")
.setMaxResults(newGolferPoolSize)
.getResultList();
Random rnd = new Random(System.currentTimeMillis());
while (newGolfers.size() > newGolferDisplaySize) {
newGolfers.remove(rnd.nextInt(newGolfers.size()));
}
}
}
As you’ve learned, when a method on a Seam component is executed, such as
findNewGolfers(), bijection takes place. That means once the invocation of this
method is complete, the value of the newGolfers field is going to be wrapped in a
JSF DataModel and outjected to the equivalently named context variable of the event
scope. The event scope is chosen since that is the scope of the host component and
the scope isn’t explicitly specified.
正如你学过的,当SEAM部件的方法执行,如findNewGolfers(),发生双注。这意味着一旦方法完成,newGolfers的值字段将包装进JSF DataModel并注出到事件范围的同名的上下文变量。event scope会采用,一旦主部件也是这个,或本部件的没有指定。
Notice that the required attribute on the @Out annotation above the selected-
Golfer property is now set to false. This directive is necessary since the find-
NewGolfers() method doesn’t assign a value to selectedGolfer and bijection would
otherwise complain that it isn’t set. This situation often arises when you use the same
component for more than one purpose. If you find yourself making excessive use of
the required directive, that’s an indication that the component is trying to do too
much and that you should divide it into smaller, more focused components.
注意selected-Golfer字段上的@Out上的required属性这时设置为false。这一指令现在是有必要的,因为find-NewGolfers()方法没有为selectedGolfer指定值。当你用同一部件于多个目的时,这种情况经常发生。如果你发现自己用这个required指令太多,则应对程序重构切分代码。
Once the findNewGolfers() method is invoked, the JSF view will have access to
the collection of new golfers using the value expression #{newGolfers}. The UIData
component that renders the list of golfers (in this case, ) sees a
ListDataModel rather than the java.util.List:
一旦方法findNewGolfers()调用,JSF view就可以用#{newGolfers}值表达式来访问新球员。
Cool New Golfers
#{_golfer.name}
TIP
You may notice in this code snippet that I prefix the iteration variable
(the golfer) with an underscore (_). I recommend this coding style to
make it clear that the variable is local to the iteration loop and to avoid
conflicts with existing context variables.
你可能注意到了代码片段中,我在迭代变量前加了_,这里我表示是本地的,避免与现有的上下文变量冲突。
The only remaining question is, “When is the findNewGolfers() method invoked?”
We want to execute this method eagerly when the home page is requested, not as a
result of a user-initiated action. Once again, we use a page action:
现在唯一 的问题是什么时候findNewGolfers()被调用。当主页被请求时,我们希望早些执行这个方法,而不是作为用户初始化action的结果。再一次我们使用page action:
Now, whenever the home page is requested, the findNewGolfers() method prepares
the newGolfers context variable for the view. With the @DataModel annotation, you
never have to think about the JSF DataModel interface; it’s completely transparent.
Seam even takes care of reinitializing the context variable whenever a change is
detected in the underlying collection. Your component is free to use properties that
are native Java collection types. Your UI component sees the outjected data as the
appropriate DataModel wrapper class. Where this pays off is in capturing a row selection
from the DataModel, again without having to tie your component to the JSF API.
这样,不管什么时候主页被请求,findNewGolfers()方法会为view准备newGolfers上下文变量。在@DataModel帮助下,你再也不用JSF DataModel接口,它是完全透明的。无论何时底层集合的变化被检测到,SEAM甚至会处理实例变量的再次初始化。你的部件可以随意使用Java集合类型的部件。你的UI部件将注出数据视作对应的DataModel包装类,不用将你的部件绑定到JSF API。
MAKING A @DATAMODELSELECTION
Presented with a list of golfers, the user of the application will want to interact with
that list. One common use case is “clickable lists.” To continue with our example, the
user clicks on the name of one of the golfers in the table and the application brings
up the golfer’s profile. This mechanism is a contrast to using a RESTful URL, though
the two can coexist.
做一个@DATAMODELSELECTION
显示出球员列表,用户可以访问。常用的用例是可点击列表,继续我们的例子,用户点击列表球员名字,程序显示显示出球员简介。这个机制是与RESTful URL的一个对比,两个机制可以同时存在。
In a clickable list, the user is performing an action on the data associated with the
row that receives the event. One such action is drilling down, which is demonstrated
here. Other actions include deleting and editing. The only limitation is that this
mechanism can be applied to only a single row at a time.
在可点击列表中,除了可以点击,还可以有删除和编辑,唯一的限制是每次一行。
How do you know which row was clicked? Seam works in conjunction with JSF to
take care of these details. One of the reasons for using a DataModel in a UIData component
is so that JSF can correlate an event triggered from a row in the table with the
data used to back that row. Seam can take the data from the selected row and inject it
into the component receiving the action using one of two @In-style annotations.
你怎么知道你点是哪一行?SEAM与JSF联合来处理这个。SEAM可以从选中的行拿到数据,使用那两个@In-style风格的annotations之一注入到接收action部件。
Clickable lists are effortless in Seam thanks to the @DataModelSelection annotation.
The @DataModelSelection annotation injects the value of the selected row in a
collection previously outjected by the @DataModel annotation and assigns it to the
corresponding component property. Seam also supports capturing the index of the
selected row using the @DataModelSelectionIndex annotation. You place either of
these two annotations, summarized in table 6.5, on a separate property of the same
component in which the @DataModel annotation is defined. Of course, the property
annotated with @DataModelSelection must have the same type as the items held in
the collection, while the property annotated with @DataModelSelectionIndex must
be a numeric type.
因为SEAM的@DataModelSelection,可点击列表很简单,@DataModelSelection注入选中的值到前面被@DataModel注出的集合,并指定到对应的部件属性。SEAM用@DataModelSelectionIndex支持选中行的索引,汇总于表6.5。你可放两个中任何一个到@DataModel定义的一个部件的分开的属性。@DataModelSelection必须有集合中相同的类型,而@DataModelSelectionIndex则是数值类型。
S
来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/21802202/viewspace-1023610/,如需转载,请注明出处,否则将追究法律责任。
转载于:http://blog.itpub.net/21802202/viewspace-1023610/