JBoss Seam: 具有上下文的组件模型

JBoss Seam   2009-02-04 14:55   阅读36   评论0  
字号:    

 

Chapter 3. The contextual component model
Prev     Next

Chapter 3. The contextual component model 具有上下文的组件模型

The two core concepts in Seam are the notion of a context and the notion of a component. Components are stateful objects, usually EJBs, and an instance of a component is associated with a context, and given a name in that context. Bijection provides a mechanism for aliasing internal component names (instance variables) to contextual names, allowing component trees to be dynamically assembled, and reassembled by Seam.

Seam中两个核心概念是上下文概念和组件概念。组件是有状态的对象,通常是EJB,一个组件的实例和一个上下文相关联,并且在这个上下文中被命名。双向注入提供了一种别名机制,给内部组件名字(实例变量)创建了上下文别名,允许组件树能够被动态的组装,以及被Seam重新组装。

Let's start by describing the contexts built in to Seam.

让我们开始介绍Seam的内置上下文。

3.1. Seam contexts Seam上下文

Seam contexts are created and destroyed by the framework. The application does not control context demarcation via explicit Java API calls. Context are usually implicit. In some cases, however, contexts are demarcated via annotations.

Seam上下文由Seam框架创建和销毁。应用并不显式调用Java API来控制上下文的划分。上下文通常都是隐含的。在某些情况下,上下文通过注释来划分。

The basic Seam contexts are:

基本的Seam上下文包括:

  • Stateless context 无状态上下文

  • Event (or request) context 事件(或请求)上下文

  • Page context 页面上下文

  • Conversation context 对话上下文

  • Session context 会话上下文

  • Business process context 业务流程上下文

  • Application context 应用上下文

You will recognize some of these contexts from servlet and related specifications. However, two of them might be new to you: conversation context, and business process context. One reason state management in web applications is so fragile and error-prone is that the three built-in contexts (request, session and application) are not especially meaningful from the point of view of the business logic.

你可能很快就能认出一些Servlet和相关规范中的上下文。但是有两个上下文你可能头一次遇到:对话上下文和业务流程上下文。Web应用的状态管理非常脆弱和容易出错的一个原因就是Web内置的上下文(请求,会话和应用)从业务流程的角度上来看并没有特别的意义。

A user login session, for example, is a fairly arbitrary construct in terms of the actual application work flow. Therefore, most Seam components are scoped to the conversation or business process contexts, since they are the contexts which are most meaningful in terms of the application.

例如,一个用户登录的会话,按照实际的应用工作流来说,是一个相当武断的构造。因此,大部分Seam组件都被指定在对话或者业务流程的上下文范围内,因为这些上下文更加具有应用意义。

Let's look at each context in turn.

让我们来依次看看每个上下文。

3.1.1. Stateless context 无状态上下文

Components which are truly stateless (stateless session beans, primarily) always live in the stateless context (this is really a non-context). Stateless components are not very interesting, and are arguably not very object-oriented. Nevertheless, they are important and often useful.

完全没有状态的组件(主要是无状态会话Bean)总是存在于无状态上下文中(这是完全没有上下文)。无状态组件并不有趣,并且和面向对象的思想相违背。但是,它们非常重要并且非常有用。

3.1.2. Event context 事件上下文

The event context is the "narrowest" stateful context, and is a generalization of the notion of the web request context to cover other kinds of events. Nevertheless, the event context associated with the lifecycle of a JSF request is the most important example of an event context, and the one you will work with most often. Components associated with the event context are destroyed at the end of the request, but their state is available and well-defined for at least the lifecycle of the request.

事件上下文是“最窄”的有状态的上下文,是负责其他类型事件的Web请求上下文的泛化。不过,和一个JSF请求的生命周期相关联的事件上下文是事件上下文的重要例子。这个事件上下文你会经常用到。和事件上下文相关联的组件在请求结束后被销毁,但是至少在请求的生命周期中组件的状态是可以获得的,并且经过了良好的定义。

When you invoke a Seam component via RMI, or Seam Remoting, the event context is created and destroyed just for the invocation.

当你通过RMI或者Seam Remoting调用Seam组件时,事件上下文只是为这个调用而被创建和销毁。

3.1.3. Page context 页面上下文

The page context allows you to associate state with a particular instance of a rendered page. You can initialize state in your event listener, or while actually rendering the page, and then have access to it from any event that originates from that page. This is especially useful for functionality like clickable lists, where the list is backed by changing data on the server side. The state is actually serialized to the client, so this construct is extremely robust with respect to multi-window operation and the back button.

页面上下文能够让你将状态和一个已被绘制的页面中特定的实例相关联。你可以在事件监听器中初始化状态,或者实际绘制这个页面的时候初始化这个状态。这对于由在服务器端变化的数据构成的可点击列表这样的功能非常有用。这个状态实际上被序列化发送给客户端,所以这样的构建非常强壮,可以解决多窗口操作和支持回退按钮。

3.1.4. Conversation context 对话上下文

The conversation context is a truly central concept in Seam. A conversation is a unit of work from the point of view of the user. It might span several interactions with the user, several requests, and several database transactions. But to the user, a conversation solves a single problem.

对话上下文是Seam中真正的核心概念。从用户角度出发,一个对话是一个工作单元。它可能跨越多个用户交互,多个请求,多个数据库的事务处理。但是对于用户来说,一个对话只解决一个单一的问题。

For example, "book hotel", "approve contract", "create order" are all conversations. You might like to think of a conversation implementing a single "use case" or "user story", but the relationship is not necessarily quite exact.

例如,“旅馆预定”,“合同批准”,“创建订单”都是对话。你肯能认为对话实现了单一的”用例“或者”用户故事“,但是这个关系并不非常确切。

A conversation holds state associated with "what the user is doing now, in this window". A single user may have multiple conversations in progress at any point in time, usually in multiple windows. The conversation context allows us to ensure that state from the different conversations does not collide and cause bugs.

一个对话持有关注”在这个窗口中,用户正在干什麽“的状态。一个单一用户可能在任何时间点上使用多个窗口进行多个对话。对话上下文能够让我们确保不同的对话之间不会相互冲突,产生Bug.

It might take you some time to get used to thinking of applications in terms of conversations. But once you get used to it, we think you'll love the notion, and never be able to not think in terms of conversations again!

也许你会费些时间来熟悉面向对话的应用。但是一旦一明白并熟悉了这个概念,我想你会喜欢它的,并且,你将不必考虑对话这个事情了。

Some conversations last for just a single request. Conversations that span multiple requests must be demarcated using annotations provided by Seam.

一些对话仅仅为了一个单一的请求而存在。跨越多个请求的对话必须通过Seam提供的注释来划分。

Some conversations are also tasks. A task is a conversation that is significant in terms of a long-running business process, and has the potential to trigger a business process state transition when it is successfully completed. Seam provides a special set of annotations for task demarcation.

一些对话其实也是任务。任务是一种长时间运行的业务流程的对话,当业务流程成功的完成后,任务能够触发业务流程状态的改变。Seam提供了任务划分的特定一组注释。

Conversations may be nested, with one conversation taking place "inside" a wider conversation. This is an advanced feature.

对话能够套嵌对话,一个对话可能被一个更”宽“的对话包含。这是一个高级的特性。

Usually, conversation state is actually held by Seam in the servlet session between requests. Seam implements configurable conversation timeout, automatically destroying inactive conversations, and thus ensuring that the state held by a single user login session does not grow without bound if the user abandons conversations.

通常,对话的状态在请求之间Servlet会话中由Seam实际持有。Seam实现了一个可配置的对话超时,能够自动的销毁失活的对话,从而保证了一个单一用户登录会话的状态不会由于用户放弃对话而无限制的增长。

Seam serializes processing of concurrent requests that take place in the same long-running conversation context, in the same process.

Seam将位于同一个流程中,同一个长时间运行的对话上下文中的并发请求序列化。

Alternatively, Seam may be configured to keep conversational state in the client browser.

另外,Seam也可以配置为在客户端浏览器中保留对话状态。

3.1.5. Session context 会话上下文

A session context holds state associated with the user login session. While there are some cases where it is useful to share state between several conversations, we generally frown on the use of session context for holding components other than global information about the logged in user.

会话上下文持有用户登录会话的状态。虽然有些时候我们需要在不同的对话中共享状态,但通常我们不会同意使用会话上下文来持有组件而不是登录用户的全局信息。

In a JSR-168 portal environment, the session context represents the portlet session.

在JSR-168 portal环境中,会话上下文代表portlet会话。

3.1.6. Business process context 业务流程上下文

The business process context holds state associated with the long running business process. This state is managed and made persistent by the BPM engine (JBoss jBPM). The business process spans multiple interactions with multiple users, so this state is shared between multiple users, but in a well-defined manner. The current task determines the current business process instance, and the lifecycle of the business process is defined externally using a process definition language, so there are no special annotations for business process demarcation.

业务流程上下文持有与长时间运行的业务流程相关的状态。这个状态由BPM引擎(JBoss jBPM)来管理和持久化。业务流程跨越多个用户的多个交互,所以这个状态以一种很好的方式在多用户间共享。当前任务决定当前业务流程实例,业务流程的生命周期通过外部的业务定义语言来定义,所以无需为业务流程的划分指定特别的注释。

3.1.7. Application context 应用上下文

The application context is the familiar servlet context from the servlet spec. Application context is mainly useful for holding static information such as configuration data, reference data or metamodels. For example, Seam stores its own configuration and metamodel in the application context.

应用上下文和Servlet规范中的Servlet上下文非常相像。应用上下文主要持有一些静态信息,例如配置数据,引用数据或者元模型。例如,Seam将自己的配置信息和元模型存储在应用上下文中。

3.1.8. Context variables 上下文变量

A context defines a namespace, a set of context variables. These work much the same as session or request attributes in the servlet spec. You may bind any value you like to a context variable, but usually we bind Seam component instances to context variables.

一个上下文定义一个名域,一系列上下文变量。这和Servlet规范中的会话和请求属性是一样的。你可以为一个上下文变量绑定任何值,但通常我们将Seam组件的实例绑定给上下文变量。

So, within a context, a component instance is identified by the context variable name (this is usually, but not always, the same as the component name). You may programatically access a named component instance in a particular scope via the Contexts class, which provides access to several thread-bound instances of the Context interface:

所以,在上下文中,一个组件实例通过上下文变量的名字来识别(上下文变量的名字通常和组件名字一样,但并不总是这样)。你可以编程通过Context类来访问某个特定范围的已经命名了的组件实例。Context类提供了很多线程绑定的实例的访问方法。

User user = (User) Contexts.getSessionContext().get("user");

You may also set or change the value associated with a name:

你也可以设置或改变一个名字关联的值:

Contexts.getSessionContext().set("user", user);

Usually, however, we obtain components from a context via injection, and put component instances into a context via outjection.

通常,我们通过注入方式从上下文中获得组件,通过注出方式将组件实例放入上下文。

3.1.9. Context search priority 上下文搜索优先权

Sometimes, as above, component instances are obtained from a particular known scope. Other times, all stateful scopes are searched, in priority order. The order is as follows:

某些时候,例如上面所说的,组件从某个特定的已经知道的范围中获得。但是有些时候,所有有状态的范围都要通过优先队列搜索,优先队列如下:

 

  • Event context  事件上下文

  • Page context  页面上下文

  • Conversation context  对话上下文

  • Session context  会话上下文

  • Business process context  业务流程上下文

  • Application context  应用上下文

You can perform a priority search by calling Contexts.lookupInStatefulContexts(). Whenever you access a component by name from a JSF page, a priority search occurs.

你可以调用Contexts.lookupInStatefuleContext()方法进行一个优先搜索。无论你何时在一个JSF页面中通过名字来访问组件,一个优先搜索都会发生。

3.1.10. Concurrency model 并发模型

Neither the servlet nor EJB specifications define any facilities for managing concurrent requests originating from the same client. The servlet container simply lets all threads run concurrently and leaves enforcing threadsafeness to application code. The EJB container allows stateless components to be accessed concurrently, and throws an exception if multiple threads access a stateful session bean.

无论是Servlet还是EJB规范都没有定义任何工具来负责管理从同一个客户端发出的并发请求。Servlet容器简单的让所有线程都并发运行,将线程安全性的工作仍给了应用代码。EJB容器允许并发访问无状态的组件,如果多个线程访问一个有状态的组件的话抛出异常。

This behavior might have been okay in old-style web applications which were based around fine-grained, synchronous requests. But for modern applications which make heavy use of many fine-grained, asynchronous (AJAX) requests, concurrency is a fact of life, and must be supported by the programming model. Seam weaves a concurrency management layer into its context model.

这样的行为在旧式Web应用下还可以,旧式Web应用基于细粒度(疑为笔误,应该是粗粒度)和同步请求。但是对于现代应用来说,大量的细粒度和异步请求(AJAX),并发请求是很常见的,需要被编程模型支持。Seam将并发管理层组织进它的上下文模型中。

The Seam session and application contexts are multithreaded. Seam will allow concurrent requests in a context to be processed concurrently. The event and page contexts are by nature single threaded. The business process context is strictly speaking multi-threaded, but in practice concurrency is sufficiently rare that this fact may be disregarded most of the time. Finally, Seam enforces a single thread per conversation per process model for the conversation context by serializing concurrent requests in the same long-running conversation context.

The Seam会话和应用上下文是多线程的。Seam允许在一个上下文中的并发请求被并发处理。事件和页面上下文天生就是是单线程的。业务流程上下文严格的讲是多线程的,但是实际应用中,并发的情况非常少,大部分情况下都可以忽略。最后,Seam通过将在同一个长时间运行的对话上下文中的并发请求序列化来强迫实现一个流程一个对话一个单线程的模型。

Since the session context is multithreaded, and often contains volatile state, session scope components are always protected by Seam from concurrent access. Seam serializes requests to session scope session beans and JavaBeans by default (and detects and breaks any deadlocks that occur). This is not the default behaviour for application scoped components however, since application scoped components do not usually hold volatile state and because synchronization at the global level is extremely expensive. However, you can force a serialized threading model on any session bean or JavaBean component by adding the @Synchronized annotation.

因为会话上下文是多线程的,经常包含可变的状态,所以Seam总是保护并发访问会话范围的组件。默认情况下,Seam将请求序列话至会话范围的会话Beans和JavaBeans(同时监测和破坏任何死锁)。这个并不是应用范围的组件默认的行为,因为应用范围的组件通常很少持有可变的状态,要知道全局水平上的同步代价非常高昂。虽然如此,你仍然可以在任何会话Bean和JavaBean组件前加上@Synchronized注释,强迫组件使用序列化线程模型。

This concurrency model means that AJAX clients can safely use volatile session and conversational state, without the need for any special work on the part of the developer.

这个并发模型意味着AJAX客户端可以安全的使用可变的会话和对话状态,不用增加开发者的任何工作。

3.2. Seam components  Seam组件

Seam components are POJOs (Plain Old Java Objects). In particular, they are JavaBeans or EJB 3.0 enterprise beans. While Seam does not require that components be EJBs and can even be used without an EJB 3.0 compliant container, Seam was designed with EJB 3.0 in mind and includes deep integration with EJB 3.0. Seam supports the following component types.

Seam组件是POJOs(普通的Java对象)。特别的,它们也可以是JavaBeans或者EJB3.0企业Bean。虽然Seam不要求组件是EJB甚至根本不需要EJB3.0兼容的容器,但是Seam是按照EJB3.0设计的,并且非常深的整合了EJB3.0。Seam支持下面的组件类型:

 

  • EJB 3.0 stateless session beans  EJB3.0无状态会话beans

  • EJB 3.0 stateful session beans  EJB3.0有状态会话beans

  • EJB 3.0 entity beans EJB3.0 实体beans

  • JavaBeans JavaBean

  • EJB 3.0 message-driven beans EJB3.0消息beans 

3.2.1. Stateless session beans 无状态会话beans

Stateless session bean components are not able to hold state across multiple invocations. Therefore, they usually work by operating upon the state of other components in the various Seam contexts. They may be used as JSF action listeners, but cannot provide properties to JSF components for display.

Stateless session beans always live in the stateless context.

Stateless session beans are the least interesting kind of Seam component.

3.2.2. Stateful session beans

Stateful session bean components are able to hold state not only across multiple invocations of the bean, but also across multiple requests. Application state that does not belong in the database should usually be held by stateful session beans. This is a major difference between Seam and many other web application frameworks. Instead of sticking information about the current conversation directly in the HttpSession, you should keep it in instance variables of a stateful session bean that is bound to the conversation context. This allows Seam to manage the lifecycle of this state for you, and ensure that there are no collisions between state relating to different concurrent conversations.

Stateful session beans are often used as JSF action listener, and as backing beans that provide properties to JSF components for display or form submission.

By default, stateful session beans are bound to the conversation context. They may never be bound to the page or stateless contexts.

Concurrent requests to session-scoped stateful session beans are always serialized by Seam.

3.2.3. Entity beans

Entity beans may be bound to a context variable and function as a seam component. Because entities have a persistent identity in addition to their contextual identity, entity instances are usually bound explicitly in Java code, rather than being instantiated implicitly by Seam.

Entity bean components do not support bijection or context demarcation. Nor does invocation of an entity bean trigger validation.

Entity beans are not usually used as JSF action listeners, but do often function as backing beans that provide properties to JSF components for display or form submission. In particular, it is common to use an entity as a backing bean, together with a stateless session bean action listener to implement create/update/delete type functionality.

By default, entity beans are bound to the conversation context. They may never be bound to the stateless context.

Note that it in a clustered environment is somewhat less efficient to bind an entity bean directly to a conversation or session scoped Seam context variable than it would be to hold a reference to the entity bean in a stateful session bean. For this reason, not all Seam applications define entity beans to be Seam components.

3.2.4. JavaBeans

Javabeans may be used just like a stateless or stateful session bean. However, they do not provide the functionality of a session bean (declarative transaction demarcation, declarative security, efficient clustered state replication, EJB 3.0 persistence, timeout methods, etc).

In a later chapter, we show you how to use Seam and Hibernate without an EJB container. In this use case, components are JavaBeans instead of session beans. Note, however, that in many application servers it is somewhat less efficient to cluster conversation or session scoped Seam JavaBean components than it is to cluster stateful session bean components.

By default, JavaBeans are bound to the event context.

Concurrent requests to session-scoped JavaBeans are always serialized by Seam.

3.2.5. Message-driven beans

Message-driven beans may function as a seam component. However, message-driven beans are called quite differently to other Seam components - instead of invoking them via the context variable, they listen for messages sent to a JMS queue or topic.

Message-driven beans may not be bound to a Seam context. Nor do they have access to the session or conversation state of their "caller". However, they do support bijection and some other Seam functionality.

3.2.6. Interception 拦截

In order to perform its magic (bijection, context demarcation, validation, etc), Seam must intercept component invocations. For JavaBeans, Seam is in full control of instantiation of the component, and no special configuration is needed. For entity beans, interception is not required since bijection and context demarcation are not defined. For session beans, we must register an EJB interceptor for the session bean component. We could use an annotation, as follows:

为了完成一些Seam的魔力(双向注射,上下文划分,验证等等),Seam必须拦截组件调用。对于JavaBeans,Seam完全控制了组件的实例化,不需要任何特定的配置。对于实体beans,没有必要进行拦截,因为没有定义双向注射和上下文划分。对于会话beans,我们必须为会话bean组件注册EJB拦截器。我们使用注释来实现这一点:

@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login {
...
}

But a much better way is to define the interceptor in ejb-jar.xml.

然而一个更好的方式是在ejb-jar.xml中定义拦截器。

<interceptors>
<interceptor>
<interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor>
</interceptors>

<assembly-descriptor>
<interceptor-binding>
<ejb-name>*</ejb-name>
<interceptor-class>org.jboss.seam.ejb.SeamInterceptor</interceptor-class>
</interceptor-binding>
</assembly-descriptor>

3.2.7. Component names 组件的名称

All seam components need a name. We can assign a name to a component using the @Name annotation:

所有Seam组件都要有名称,我们通过@Name注释给组件命名:

@Name("loginAction")
@Stateless
public class LoginAction implements Login {
...
}

This name is the seam component name and is not related to any other name defined by the EJB specification. However, seam component names work just like JSF managed bean names and you can think of the two concepts as identical.

这个名字是seam组件的名字和其他EJB规范定义的名字没有任何关系。但是,seam组件名字就像JSF管理bean的名字一样,这两个概念是一样的。

@Name is not the only way to define a component name, but we always need to specify the name somewhere. If we don't, then none of the other Seam annotations will function.

@Name不是唯一定义组件名字的方法,但是我们总要在某个地方指定名字。如果我们不这么做的话,其他seam注释也不会工作

Just like in JSF, a seam component instance is usually bound to a context variable with the same name as the component name. So, for example, we would access the LoginAction using Contexts.getStatelessContext().get("loginAction"). In particular, whenever Seam itself instantiates a component, it binds the new instance to a variable with the component name. However, again like JSF, it is possible for the application to bind a component to some other context variable by programmatic API call. This is only useful if a particular component serves more than one role in the system. For example, the currently logged in User might be bound to the currentUser session context variable, while a User that is the subject of some administration functionality might be bound to the user conversation context variable.

和JSF一样,一个seam组件的实例通常和一个同名的上下文变量绑定。例如我们使用Context.getStatelessContext().get("loginAction")方法来访问LoginAction.特别的,无论何时Seam本身初始化一个组件,都将这个新组件与同名的变量绑定。然而又和JSF一样,你可以通过程序API调用将组件和其他上下文变量绑定。这只有在一个组件有多个角色的系统中才有用处。例如当前登录的User可以绑定在currentUser会话上下文变量中,但是一个User也可以绑定到user对话上下文变量,如果这个User负责一些管理功能。

For very large applications, and for built-in seam components, qualified names are often used.

对于大型应用和内置的seam组件,通常使用符合标准的名字。

@Name("com.jboss.myapp.loginAction")
@Stateless
public class LoginAction implements Login {
...
}

We may use the qualified component name both in Java code and in JSF's expression language:

我们可以在Java代码和JSF表达语言中都是用符合标准的组件名字:

<h:commandButton type="submit" value="Login"
action="#{com.jboss.myapp.loginAction.login}"/>

Since this is noisy, Seam also provides a means of aliasing a qualified name to a simple name. Add a line like this to the components.xml file:

这个有点讨厌,Seam也提供了别名机制让你可以给符合标准的名字赋别名。例如在component.xml文件中添加一行:

<factory name="loginAction" scope="STATELESS" value="#{com.jboss.myapp.loginAction}"/>

All of the built-in Seam components have qualified names, but most of them are aliased to a simple name by the components.xml file included in the Seam jar.

所有内置的Seam组件都有符合标准的名字,但是大部分都拥有别名,这些配置在Seam jar文件中的component.xml文件中。

3.2.8. Defining the component scope 定义组件范围

We can override the default scope (context) of a component using the @Scope annotation. This lets us define what context a component instance is bound to, when it is instantiated by Seam.

我们可以使用@Scope注释来覆盖组件的默认范围(上下文)。这允许我们在Seam实例化组件实例的时候设置组件实例的上下文。

@Name("user")
@Entity
@Scope(SESSION)
public class User {
...
}

org.jboss.seam.ScopeType defines an enumeration of possible scopes.

3.2.9. Components with multiple roles 拥有多个角色的组件

Some Seam component classes can fulfill more than one role in the system. For example, we often have a User class which is usually used as a session-scoped component representing the current user but is used in user administration screens as a conversation-scoped component. The @Role annotation lets us define an additional named role for a component, with a different scope—it lets us bind the same component class to different context variables. (Any Seam component instance may be bound to multiple context variables, but this lets us do it at the class level, and take advantage of auto-instantiation.)

一些Seam组件类在系统中可以充当多个角色。例如,我们通常会有一个User类,这个类通常在会话范围内代表当前用户,但是在用户管理界面中也用到这个类作为对话范围组件。@Role注释让我们能够为组件定义一个附加的角色,可以有不同的范围,这样一个组件类可以绑定到不同的上下文变量中。(任何Seam组件实例可以和多个上下文变量绑定,但是这个注释让我们在类的级别上实现,充分利用了自动实例化的好处)。

@Name("user")
@Entity
@Scope(CONVERSATION)
@Role(name="currentUser", scope=SESSION)
public class User {
...
}

The @Roles annotation lets us specify as many additional roles as we like.

@Roles注释能让我们指定多个角色

@Name("user")
@Entity
@Scope(CONVERSATION)
@Roles({@Role(name="currentUser", scope=SESSION),
@Role(name="tempUser", scope=EVENT)})
public class User {
...
}

3.2.10. Built-in components 内置的组件

Like many good frameworks, Seam eats its own dogfood and is implemented mostly as a set of built-in Seam interceptors (see later) and Seam components. This makes it easy for applications to interact with built-in components at runtime or even customize the basic functionality of Seam by replacing the built-in components with custom implementations. The built-in components are defined in the Seam namespace org.jboss.seam.core and the Java package of the same name.

和其他不错的框架一样,Seam干了他自己该干的事情,实现了一套内置的Seam拦截器(参见后面)和Seam组件。这让应用能够非常容易的在运行时和内置组件交互,甚至通过使用定制的组件替换内置的组件来定制基本功能。内置的组件在Seam名域org.jboss.seam.core中定义,Java包有相同的名字。

The built-in components may be injected, just like any Seam components, but they also provide convenient static instance() methods:

内置的组件可以被注入,就像任何Seam组件一样,但是他们也提供了方便的instance()静态方法。

FacesMessages.instance().add("Welcome back, #{user.name}!");

3.3. Bijection 双向注射

Dependency injection or inversion of control is by now a familiar concept to most Java developers. Dependency injection allows a component to obtain a reference to another component by having the container "inject" the other component to a setter method or instance variable.

现在每个Java开发者都非常熟悉依赖注入或反转控制这些概念。依赖注入允许一个组件通过容器”注入“的方式获得另一个组件的引用,容器将另外的的组件注入setter或者实例变量中。

In all dependency injection implementations that we have seen, injection occurs when the component is constructed, and the reference does not subsequently change for the lifetime of the component instance. For stateless components, this is reasonable. From the point of view of a client, all instances of a particular stateless component are interchangeable.

在我们看到的所有依赖注入的实现中,注入发生在组件被构造时,在组件实例的生命周期内引用不能改变。对于无状态的组件来说,这是有道理的。从客户角度出发,一个特定的无状态的组件的所有实例都可以互换。

On the other hand, Seam emphasizes the use of stateful components. So traditional dependency injection is no longer a very useful construct. Seam introduces the notion of bijection as a generalization of injection. In contrast to injection, bijection is:

另一方面,Seam强调使用有状态的组件。所以传统的依赖注入不再是非常有用的构造了。Seam引入了注入的泛化概念双向注射。对比注入,双向注射是:

 

  • contextual - bijection is used to assemble stateful components from various different contexts (a component from a "wider" context may even have a reference to a component from a "narrower" context)

  • 上下文相关的-双向注射用来从各种不同的上下文中(一个来自”更宽“的上下文的组件可以拥有一个来自比它“更窄”的上下文中的组件的引用)组装有状态的组件 。

  • bidirectional - values are injected from context variables into attributes of the component being invoked, and also outjected from the component attributes back out to the context, allowing the component being invoked to manipulate the values of contextual variables simply by setting its own instance variables

  • 双向-值从上下文变量中注入到被调用的组件的属性中,并且可以从组件的属性中注出回上下文,允许组件被调用,通过简单的设置自己的实例变量来操作上下文变量的值

  • dynamic - since the value of contextual variables changes over time, and since Seam components are stateful, bijection takes place every time a component is invoked

  • 动态的-因为上下文变量的值随着时间而变化,还因为Seam组件是有状态的,每当一个组件被调用式都会发生双向注射

In essence, bijection lets you alias a context variable to a component instance variable, by specifying that the value of the instance variable is injected, outjected, or both. Of course, we use annotations to enable bijection.

本质上说,双向注射能够让你通过指定实例变量的值是注入,还是注出或者两者都是,从而将一个上下文变量别名为一个组件的实例变量。当然,我们使用注释来完成双向注射。

The @In annotation specifies that a value should be injected, either into an instance variable:

@In注释指定一个值应该被注入到一个实例变量

@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In User user;
...
}

or into a setter method:

或者注入到一个setter方法中:

@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;

@In
public void setUser(User user) {
this.user=user;
}

...
}

By default, Seam will do a priority search of all contexts, using the name of the property or instance variable that is being injected. You may wish to specify the context variable name explicitly, using, for example, @In("currentUser").

在默认情况下,Seam将使用注入的实例或者属性的名字对所有的上下文进行优先搜索。你可以显式的指定上下文变量的名字。例如@In("currentUser").

If you want Seam to create an instance of the component when there is no existing component instance bound to the named context variable, you should specify @In(create=true). If the value is optional (it can be null), specify @In(required=false).

For some components, it can be repetitive to have to specify @In(create=true) everywhere they are used. In such cases, you can annotate the component @AutoCreate, and then it will always be created, whenever needed, even without the explicit use of create=true.

You can even inject the value of an expression:

@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In("#{user.username}") String username;
...
}

(There is much more information about component lifecycle and injection in the next chapter.)

The @Out annotation specifies that an attribute should be outjected, either from an instance variable:

@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@Out User user;
...
}

or from a getter method:

@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;

@Out
public User getUser() {
return user;
}

...
}

An attribute may be both injected and outjected:

@Name("loginAction")
@Stateless
public class LoginAction implements Login {
@In @Out User user;
...
}

or:

@Name("loginAction")
@Stateless
public class LoginAction implements Login {
User user;

@In
public void setUser(User user) {
this.user=user;
}

@Out
public User getUser() {
return user;
}

...
}

3.4. Lifecycle methods

Session bean and entity bean Seam components support all the usual EJB 3.0 lifecycle callback (@PostConstruct, @PreDestroy, etc). But Seam also supports the use of any of these callbacks with JavaBean components. However, since these annotations are not available in a J2EE environment, Seam defines two additional component lifecycle callbacks, equivalent to @PostConstruct and @PreDestroy.

The @Create method is called after Seam instantiates a component. Components may define only one @Create method.

The @Destroy method is called when the context that the Seam component is bound to ends. Components may define only one @Destroy method.

In addition, stateful session bean components must define a method with no parameters annotated @Remove. This method is called by Seam when the context ends.

Finally, a related annotation is the @Startup annotation, which may be applied to any application or session scoped component. The @Startup annotation tells Seam to instantiate the component immediately, when the context begins, instead of waiting until it is first referenced by a client. It is possible to control the order of instantiation of startup components by specifying @Startup(depends={....}).

3.5. Conditional installation

The @Install annotation lets you control conditional installation of components that are required in some deployment scenarios and not in others. This is useful if:

  • You want to mock out some infrastructural component in tests.

  • You want change the implementation of a component in certain deployment scenarios.

  • You want to install some components only if their dependencies are available (useful for framework authors).

@Install works by letting you specify precedence and dependencies.

The precedence of a component is a number that Seam uses to decide which component to install when there are multiple classes with the same component name in the classpath. Seam will choose the component with the higher precendence. There are some predefined precedence values (in ascending order):

  1. BUILT_IN — the lowest precedece components are the components built in to Seam.

  2. FRAMEWORK — components defined by third-party frameworks may override built-in components, but are overridden by application components.

  3. APPLICATION — the default precedence. This is appropriate for most application components.

  4. DEPLOYMENT — for application components which are deployment-specific.

  5. MOCK — for mock objects used in testing.

Suppose we have a component named messageSender that talks to a JMS queue.

@Name("messageSender") 
public class MessageSender {
public void sendMessage() {
//do something with JMS
}
}

In our unit tests, we don't have a JMS queue available, so we would like to stub out this method. We'll create a mock component that exists in the classpath when unit tests are running, but is never deployed with the application:

@Name("messageSender") 
@Install(precedence=MOCK)
public class MockMessageSender extends MessageSender {
public void sendMessage() {
//do nothing!
}
}

The precedence helps Seam decide which version to use when it finds both components in the classpath.

This is nice if we are able to control exactly which classes are in the classpath. But if I'm writing a reusable framework with many dependecies, I don't want to have to break that framework across many jars. I want to be able to decide which components to install depending upon what other components are installed, and upon what classes are available in the classpath. The @Install annotation also controls this functionality. Seam uses this mechanism internally to enable conditional installation of many of the built-in components. However, you probably won't need to use it in your application.

3.6. Logging

Who is not totally fed up with seeing noisy code like this?

private static final Log log = LogFactory.getLog(CreateOrderAction.class);

public Order createOrder(User user, Product product, int quantity) {
if ( log.isDebugEnabled() ) {
log.debug("Creating new order for user: " + user.username() +
" product: " + product.name()
+ " quantity: " + quantity);
}
return new Order(user, product, quantity);
}

It is difficult to imagine how the code for a simple log message could possibly be more verbose. There is more lines of code tied up in logging than in the actual business logic! I remain totally astonished that the Java community has not come up with anything better in 10 years.

Seam provides a logging API that simplifies this code significantly:

@Logger private Log log;

public Order createOrder(User user, Product product, int quantity) {
log.debug("Creating new order for user: #0 product: #1 quantity: #2", user.username(), product.name(), quantity);
return new Order(user, product, quantity);
}

It doesn't matter if you declare the log variable static or not—it will work either way, except for entity bean components which require the log variable to be static.

Note that we don't need the noisy if ( log.isDebugEnabled() ) guard, since string concatenation happens inside the debug() method. Note also that we don't usually need to specify the log category explicitly, since Seam knows what component it is injecting the Log into.

If User and Product are Seam components available in the current contexts, it gets even better:

@Logger private Log log;

public Order createOrder(User user, Product product, int quantity) {
log.debug("Creating new order for user: #{user.username} product: #{product.name} quantity: #0", quantity);
return new Order(user, product, quantity);
}

Seam logging automagically chooses whether to send output to log4j or JDK logging. If log4j is in the classpath, Seam with use it. If it is not, Seam will use JDK logging.

3.7. The Mutable interface and @ReadOnly

Many application servers feature an amazingly broken implementation of HttpSession clustering, where changes to the state of mutable objects bound to the session are only replicated when the application calls setAttribute() explicitly. This is a source of bugs that can not effectively be tested for at development time, since they will only manifest when failover occurs. Furthermore, the actual replication message contains the entire serialized object graph bound to the session attribute, which is inefficient.

Of course, EJB stateful session beans must perform automatic dirty checking and replication of mutable state and a sophisticated EJB container can introduce optimizations such as attribute-level replication. Unfortunately, not all Seam users have the good fortune to be working in an environment that supports EJB 3.0. So, for session and conversation scoped JavaBean and entity bean components, Seam provides an extra layer of cluster-safe state management over the top of the web container session clustering.

For session or conversation scoped JavaBean components, Seam automatically forces replication to occur by calling setAttribute() once in every request that the component was invoked by the application. Of course, this strategy is inefficient for read-mostly components. You can control this behavior by implementing the org.jboss.seam.core.Mutable interface, or by extending org.jboss.seam.core.AbstractMutable, and writing your own dirty-checking logic inside the component. For example,

@Name("account")
public class Account extends AbstractMutable
{
private BigDecimal balance;

public void setBalance(BigDecimal balance)
{
setDirty(this.balance, balance);
this.balance = balance;
}

public BigDecimal getBalance()
{
return balance;
}

...

}

Or, you can use the @ReadOnly annotation to achieve a similar effect:

@Name("account")
public class Account
{
private BigDecimal balance;

public void setBalance(BigDecimal balance)
{
this.balance = balance;
}

@ReadOnly
public BigDecimal getBalance()
{
return balance;
}

...

}

For session or conversation scoped entity bean components, Seam automatically forces replication to occur by calling setAttribute() once in every request, unless the (conversation-scoped) entity is currently associated with a Seam-managed persistence context, in which case no replication is needed. This strategy is not necessarily efficient, so session or conversation scope entity beans should be used with care. You can always write a stateful session bean or JavaBean component to "manage" the entity bean instance. For example,

@Stateful
@Name("account")
public class AccountManager extends AbstractMutable
{
private Account account; // an entity bean

@Unwrap
public void getAccount()
{
return account;
}

...

}

Note that the EntityHome class in the Seam Application Framework provides a great example of managing an entity bean instance using a Seam component.

3.8. Factory and manager components

We often need to work with objects that are not Seam components. But we still want to be able to inject them into our components using @In and use them in value and method binding expressions, etc. Sometimes, we even need to tie them into the Seam context lifecycle (@Destroy, for example). So the Seam contexts can contain objects which are not Seam components, and Seam provides a couple of nice features that make it easier to work with non-component objects bound to contexts.

The factory component pattern lets a Seam component act as the instantiator for a non-component object. A factory method will be called when a context variable is referenced but has no value bound to it. We define factory methods using the @Factory annotation. The factory method binds a value to the context variable, and determines the scope of the bound value. There are two styles of factory method. The first style returns a value, which is bound to the context by Seam:

@Factory(scope=CONVERSATION)
public List<Customer> getCustomerList() {
return ... ;
}

The second style is a method of type void which binds the value to the context variable itself:

@DataModel List<Customer> customerList;

@Factory("customerList")
public void initCustomerList() {
customerList = ... ;
}

In both cases, the factory method is called when we reference the customerList context variable and its value is null, and then has no further part to play in the lifecycle of the value. An even more powerful pattern is the manager component pattern. In this case, we have a Seam component that is bound to a context variable, that manages the value of the context variable, while remaining invisible to clients.

A manager component is any component with an @Unwrap method. This method returns the value that will be visable to clients, and is called every time a context variable is referenced.

@Name("customerList")
@Scope(CONVERSATION)
public class CustomerListManager
{
...

@Unwrap
public List<Customer> getCustomerList() {
return ... ;
}
}

The manager component pattern is especially useful if we have an object where you need more control over the lifecycle of the component. For example, if you have a heavyweight object that needs a cleanup operation when the context ends you could @Unwrap the object, and perform cleanup in the @Destroy method of the manager component.

@Name("hens")
@Scope(APPLICATION)
public class HenHouse {

private Set<Hen> hens;

@Unwrap
public List<Hen> getHens() {
if (hens == null) {
// Setup our hens
}
return hens;
}

@Observer({"chickBorn", "chickenBoughtAtMarket"})
public addHen() {
hens.add(hen);
}

@Observer("chickenSoldAtMarket")
public removeHen() {
hens.remove(hen);
}

@Observer("foxGetsIn")
public addHen() {
hens.clear();
}
...
}

Here the managed component observes many events which change the underlying object. The component manages these actions itself, and because the object is unwrapped on every access, a consistent view is provided.


Prev  Up  Next
Chapter 2. Getting started with Seam, using seam-gen  Home  Chapter 4. Configuring Seam components

评论(?)
阅读(?)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
网易公司版权所有 ©1997-2009