站内搜索: 请输入搜索关键词
当前页面: 在线文档首页 > JBoss Seam 1.1.0 bate2 英文参考手册

Chapter 3. The contextual component model - JBoss Seam 1.1.0 bate2 英文参考手册

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.

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

3.1. Seam contexts

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.

The basic Seam contexts are:

  • 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. 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.

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.

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.

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

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. For example, "book hotel", "approve contract", "create order" are all conversations. You might like to think of a conversation implementing a single "use case", 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.

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.

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.

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.

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

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

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.

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.

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.

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.

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:

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.

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.

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.

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.

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.

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.

3.2. Seam components

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.

  • EJB 3.0 stateless session beans

  • EJB 3.0 stateful session beans

  • EJB 3.0 entity beans

  • JavaBeans

  • EJB 3.0 message-driven beans

3.2.1. Stateless session 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:

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

But a much better way is to define the interceptor in 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

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

@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.

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.

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

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

Unfortunately, JSF's expression language interprets a period as a property dereference. So, inside a JSF expression, we use $ to indicate a qualified component name:

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

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.

@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.)

@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.

@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.

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

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

Seam was designed to integrate tightly in a Java EE 5 environment. However, we understand that there are many projects which are not running in a full EE environment. We also realize the critical importance of easy unit and integration testing using frameworks such as TestNG and JUnit. So, we've made it easy to run Seam in Java SE environments by allowing you to boostrap certain critical infrastructure normally only found in EE environments by installing built-in Seam components.

For example, you can run your EJB3 components in Tomcat or an integration test suite just by installing the built-in component org.jboss.seam.core.ejb, which automatically bootstraps the JBoss Embeddable EJB3 container and deploys your EJB components.

Or, if you're not quite ready for the Brave New World of EJB 3.0, you can write a Seam application that uses only JavaBean components, together with Hibernate3 for persistence, by installing the built-in component org.jboss.seam.core.hibernate. When using Hibernate outside of a J2EE environment, you will also probably need a JTA transaction manager and JNDI server, which are available via the built-in component org.jboss.seam.core.microcontainer. This lets you use the bulletproof JTA/JCA pooling datasource from JBoss application server in an SE environment like Tomcat!

3.3. Configuring components

Seam provides two basic approaches to configuring components: configuration via property settings in a properties file or web.xml, and configuration via components.xml.

3.3.1. Configuring components via property settings

Seam components may be provided with configuration properties either via servlet context parameters, or via a properties file named seam.properties in the root of the classpath.

The configurable Seam component must expose a JavaBeans-style property setter methods for the configurable attributes. If a seam component named com.jboss.myapp.settings has a setter method named setLocale(), we can provide a property named com.jboss.myapp.settings.locale in the seam.properties file or as a servlet context parameter, and Seam will set the value of the locale attribute whenever it instantiates the component.

The same mechanism is used to configure Seam itself. For example, to set the conversation timeout, we provide a value for org.jboss.seam.core.manager.conversationTimeout in web.xml or seam.properties. (There is a built-in Seam component named org.jboss.seam.core.manager with a setter method named setConversationTimeout().)

3.3.2. Configuring components via components.xml

The components.xml file is a bit more powerful than property settings. It lets you:

  • Configure components that have been installed automatically—including both built-in components, and application components that have been annotated with the @Name annotation and picked up by Seam's deployment scanner.

  • Install classes with no @Name annotation as Seam components—this is most useful for certain kinds of infrastructural components which can be installed multiple times different names (for example Seam-managed persistence contexts).

  • Install components that do have a @Name annotation but are not installed by default (this is the case for certain built-in components).

  • Override the scope of a component.

Usually, Seam components are installed when the deployment scanner discovers a class with a @Name annotation sitting in an archive with a seam.properties file. The components.xml file lets us handle special cases where that is not the case.

For example, the following components.xml file installs the JBoss Embeddable EJB3 container:

<components>
    <component class="org.jboss.seam.core.Ejb"/>
</components>

This one installs and configures two different Seam-managed persistence contexts:

<components>

    <component name="customerDatabase" 
              class="org.jboss.seam.core.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName">java:/customerEntityManagerFactory</property>
    </component>
    
    <component name="accountingDatabase"
              class="org.jboss.seam.core.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName">java:/accountingEntityManagerFactory</property>
    </component>

</components>

This example creates a session-scoped Seam-managed persistence context (this is not recommended in practice):

<components>

    <component name="productDatabase"
              scope="session"
              class="org.jboss.seam.core.ManagedPersistenceContext">
        <property name="persistenceUnitJndiName">java:/productEntityManagerFactory</property>
    </component>

</components>

The <factory> declaration lets you specify a value or method binding expression that will be evaluated to initialize the value of a context variable when it is first referenced.

<components>

    <factory name="contact" method="#{contactManager.loadContact}" scope="CONVERSATION"/>

</components>

You can create an "alias" (a second name) for a Seam component like so:

<components>

    <factory name="user" value="#{actor}" scope="STATELESS"/>

</components>

You can even create an "alias" for a commonly used expression:

<components>

    <factory name="contact" value="#{contactManager.contact}" scope="STATELESS"/>

</components>

Sometimes we want to reuse the same components.xml file with minor changes during both deployment and testing. Seam let's you place wildcards of the form @wildcard@ in the components.xml file which can be replaced either by your Ant build script (at deployment time) or by providing a file named components.properties in the classpath (at development time). You'll see this approach used in the Seam examples.

3.3.3. Fine-grained configuration files

If you have a large number of components that need to be configured in XML, it makes much more sense to split up the information in components.properties into many small files. Seam lets you put configuration for a class named, for example, com.helloworld.Hello in a resource named com/helloworld/Hello.component.xml. (You might be familiar with this pattern, since it is the same one we use in Hibernate.) The root element of the file may be either a <components> or <component> element.

The first option lets you define multiple components in the file:

<components>
    <component class="com.helloworld.Hello" name="hello">
        <property name="name">#{user.name}</property>
    </component>
    <factory name="message" value="#{hello.message}"/>
</components>

The second option only lets you define or configure one component, but is less noisy:

<component name="hello">
    <property name="name">#{user.name}</property>
</component>

In the second option, the class name is implied by the file in which the component definition appears.

3.3.4. Configurable property types

Properties of string, primitive or primitive wrapper type may be configured just as you would expect:

org.jboss.seam.core.manager.conversationTimeout 60000
<component name="org.jboss.seam.core.manager">
    <property name="conversationTimeout">60000</property>
</component>

Arrays, sets and lists of strings or primitives are also supported:

org.jboss.seam.core.jbpm.processDefinitions order.jpdl.xml, return.jpdl.xml, inventory.jpdl.xml
<component name="org.jboss.seam.core.jbpm">
    <property name="processDefinitions">
        <value>order.jpdl.xml</value>
        <value>return.jpdl.xml</value>
        <value>inventory.jpdl.xml</value>
    </property>
</component>

Even maps with String-valued keys and string or primitive values are supported:

<component name="issueEditor">
    <property name="issueStatuses">
        <key>open</key> <value>open issue</value>
        <key>resolved</key> <value>issue resolved by developer</value>
        <key>closed</key> <value>resolution accepted by user</value>
    </property>
</component>

Finally, you may wire together components using a value-binding expression. Note that this is quite different to injection using @In, since it happens at component instantiation time instead of invocation time. It is therefore much more similar to the dependency injection facilities offered by traditional IoC containers like JSF or Spring.

<component name="policyPricingWorkingMemory"
          class="org.jboss.seam.drools.ManagedWorkingMemory">
    <property name="ruleBase">#{policyPricingRules}</property>
</component>

3.4. 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. 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:

  • 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

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:

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

or into a setter method:

@Name("loginAction")
@Stateless
@Interceptors(SeamInterceptor.class)
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").

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).

You can even inject the value of an expression:

@Name("loginAction")
@Stateless
@Interceptors(SeamInterceptor.class)
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
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login { 
    @Out User user;
    ... 
}

or from a getter method:

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

An attribute may be both injected and outjected:

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

or:

@Name("loginAction")
@Stateless
@Interceptors(SeamInterceptor.class)
public class LoginAction implements Login { 
    User user;
    
    @In
    public void setUser(User user) {
        this.user=user;
    }
    
    @Out
    public User getUser() {
        return user;
    }
    
    ... 
}

3.5. Lifecycle methods

Session bean and entity bean Seam components support all the usual EJB 3.0 lifecycle callback (@PostConstruct, @PreDestroy, etc). Seam extends all of these callbacks except @PreDestroy to JavaBean components. But Seam also defines its own component lifecycle callbacks.

The @Create method is called every time Seam instantiates a component. Unlike the @PostConstruct method, this method is called after the component is fully constructed by the EJB container, and has access to all the usual Seam functionality (bijection, etc). 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. Stateful session bean components must define a method annotated @Destroy @Remove.

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.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 built on top of Apache commons-logging 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);
}

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);
}

3.7. The Mutable interface

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 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;
    }
    
    ...
    
}

For session or conversation scoped entity bean components, Seam automatically forces replication to occur by calling setAttribute() once in every request. This strategy is not 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 Framework provides a great example of this pattern.

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 ... ;
    }
}

This pattern is especially useful if we have some heavyweight object that needs a cleanup operation when the context ends. In this case, the manager component may perform cleanup in the @Destroy method.