Infrastructure

It's essential for an application to have a strong infrastructure that enables application code to concentrate on addressing business problems without the distraction of handling "plumbing". This is why we choose to use J2EE; why we may choose to use EJB if we would otherwise need to write unduly complex code; and why adequate infrastructure classes are almost as important as an application server.

We usually need to build some infrastructure ourselves, but it's generally best to use existing infrastructure where it's an option.

Unfortunately there are no standards relating to infrastructure that simplifies the use of J2EE. Thus the choice will vary between projects. Possible sources of infrastructure include:

  • Existing in-house infrastructure. Most large organizations will have developed many common packages, though their quality varies highly. Often there is a lack of a coherent strategy and potential for reuse across projects is lost. This is an expensive lost opportunity; in this chapter we'll see a number of areas in which code can be reused and consistent practices adopted across projects, with significant benefits.

  • MVC web application frameworks. Some products, such as Struts, can provide a framework for web applications, rather than just an MVC implementation. However, MVC web frameworks are, naturally enough, intended only for use in web applications; thus predicating an application's overall infrastructure on one may make testing difficult and reduce the potential for reuse.

  • Open source application frameworks. No product in this space is anywhere near as well-known or widely adopted as leading MVC web application frameworks, and many products are highly complex. Consequently, I can't recommend one here.

  • Commercial products, such as a framework supplied with an application server or a framework promising to simplify J2EE development. Again, no product in this space has achieved wide usage. Offerings from application server vendors typically lock users into the application server in question, ruling out portability between application servers.

  • The infrastructure code described in this chapter, which addresses many common problems. This infrastructure is relatively simple, and makes extensive use of JavaBeans to minimize the dependence of application code on it, avoiding lock-in as far as possible.

In this chapter we'll look at the issue of infrastructure primarily through examining the support packages used in the sample application. This code — which is based on my experience of successful approaches in several real projects — illustrates some of the major issues and provides a simple solution to problems that I haven't seen well-addressed elsewhere. The source code of all the classes discussed and the supporting classes is included in the download with this book, so it can be used as a basis for your applications.

Important 

Emphasis on supporting infrastructure is a major difference between my approach in this book and much J2EE literature. Most J2EE books concentrate on J2EE APIs, rather than the reality of using them. Hence they tend to show quick and dirty approaches (for example, compromising correct error handling), or neglect productivity and maintainability considerations.

In this book, I'm trying to demonstrate the use of J2EE to meet business requirements, rather than as an end in itself. As a bonus, this means that the download accompanying this book includes much code that you can either use directly in your applications or use as the basis for your own code.

The approach discussed in this chapter is just one of many approaches for structuring J2EE applications. However, I've found the principles behind it to deliver high productivity and quality solutions in several real projects. The problems this infrastructure addresses and serves to illustrate are relevant to all J2EE applications.

Goals of a Strong Infrastructure

Using a strong standard infrastructure can deliver better applications, faster. A strong infrastructure makes this possible by achieving the following goals:

  • Allowing application code to concentrate on implementing business logic and other application functionality with a minimum of distraction. This reduces time to market by reducing development effort, and reduces costs throughout the project lifecycle by making application code more maintainable (because it is simpler and focused on the problem domain). This is the ultimate goal, which many of the following goals help us to achieve.

  • Separating configuration from Java code.

  • Facilitating the use of OO design by eliminating the need for common compromises.

  • Eliminating code duplication, by solving each problem only once. Once we have a good solution for a problem such as a complex API we should always use that solution, in whatever components or classes that encounter the problem

  • Concealing the complexity of J2EE APIs. We've already seen this with JDBC; other APIs that are candidates for a higher level of abstraction include JNDI and EJB access.

  • Ensuring correct error handling. We saw the importance of this when working with JDBC in Chapter 9.

  • Facilitating internationalization if required.

  • Enhancing productivity without compromising architectural principles. Without adequate infrastructure it is tempting to cut corners by adopting quick, hacky solutions that will cause ongoing problems. Appropriate infrastructure should encourage and facilitate the application of sound design principles.

  • Achieving consistency between applications within an organization. If all applications use the same infrastructure as well as the same application server and underlying technologies, productivity will be maximized, teamwork more effective, and risk reduced.

  • Ensuring that applications are easy to test. Where possible, a framework should allow application code to be tested without deployment on an application server.

Essentially we're going to use standard infrastructure to help achieve the goals that we identified in Chapter 4.

It's important that infrastructure isn't web-centric, even if we're developing a web application. This makes applications unnecessarily hard to test and dependent on one particular user interface. Thus infrastructure should not be limited to a web framework such as Struts (although such web frameworks are valuable, if used correctly). It's not enough to get business logic out of presentation layer artifacts such as JSP pages: business logic shouldn't be in web-specific classes at all. For examples, a Struts Action class is tied to the Servlet API. This makes Struts actions a poor place for business logic, which doesn't depend on the Servlet API. Thus while it is important to use a web application framework, the web-tier should remain a thin layer over a distinct business logic layer.

Infrastructure will often take the form of frameworks, rather than class libraries. Often inversion of control is essential to conceal complexity from application code. We saw the benefits of this approach with JDBC in Chapter 9.

A framework should be extensible. Although a well-designed framework will meet most requirements out of the box, it will fail unless it can be extended to meet unforeseen requirements. Use of interfaces within the framework will help to achieve this goal.

A framework should be easy to use. Unduly complex frameworks will be ignored by developers, and create problems of their own. Irrelevant, or seldom-used, capabilities are a key danger. The Pareto Principle is particularly relevant to framework design. Often it's best to settle for a simple solution to most problems than a comprehensive, but complex solution that makes many tasks harder than they should be (J2EE itself arguably falls into the latter category).

Let's consider the core infrastructure used in the sample application. Like most successful infrastructures, the infrastructure described here is layered. When using it we can choose from different levels of abstraction, each building on lower-level components.

Using a Framework to Configure Application Components

Let's begin by addressing the goals of externalizing configuration and facilitating the use of OO design.

The Problem

It's vital to adopt consistent practices in application configuration. While externalizing configuration (getting it out of Java code) is essential to parameterize applications, it's difficult to achieve without supporting infrastructure.

Without such infrastructure, configuration management is usually haphazard. J2EE applications often hold configuration in a variety of formats, such as properties files, XML documents (an increasingly popular choice), EJB JAR and WAR development descriptors, and database tables. In large applications there is often little consistency in how configuration is managed. For example, property naming conventions (if there are any) may differ between developers and for different classes.

More seriously, application classes are bloated by configuration management code irrelevant to their business responsibilities. Many application objects may read properties files or parse XML documents. Even if they use helper classes to simplify use of the underlying API, their business purpose is still obscured and they remain tied to the configuration format initially envisaged.

Equally harmfully, without a central infrastructure for configuration management, application code is likely to use a variety of approaches to locating application objects. The Singleton design pattern is often overused, resulting in problems we'll discuss below. Alternatively, some application objects may be bound to JNDI or attached to the global ServletContext (in web applications), both of which approaches may complicate testing outside a JNDI server. Each application object is usually configured independently.

Important 

Generic, reusable infrastructures should be used to centralize configuration management and "wire up" applications in a consistent way. In this section we'll look at how the infrastructure used by the sample application uses JavaBean-based configuration, ensuring consistency throughout applications.

In this section we'll look at an application framework that centralizes configuration management and solves the problems we've just discussed.

Using JavaBeans

We discussed the benefits of JavaBeans in Chapter 4. For example, bean-based manipulation is excellent for applying configuration data held outside Java code, and performing "data binding", such as from HTTP requests onto Java object state.

Important 

If we make all application components JavaBeans, we maximize our ability to separate configuration data from application code. We also ensure that application components can be configured in a consistent way, wherever configuration data is held. Even if we don't know an application's class at run time (as opposed to the interfaces it implements), we know how to configure it if it is a JavaBean.

An object doesn't need to implement any special interfaces or extend a special superclass to be a simple JavaBean, exposing bean properties but not supporting bean events. All that is required is a no-argument constructor and property methods following a simple naming convention. Unlike EJBs, JavaBeans impose no run-time overhead. However, manipulating beans is more complex, and requires the use of reflection to instantiate objects and invoke methods by name. The core JavaBeans API does not provide the ability to perform some useful operations on beans, such as:

  • Setting multiple properties in a single operation, throwing a single combined exception if one or more property setters fail.

  • Getting or setting nested properties – properties of properties, such as getSpouse().getAge().Java 1.4 introduces support for this, through the new java.beans.Statement and java.beans.Expression classes.

  • Supporting bean event propagation without complicating the implementation of JavaBeans. With only the standard beans API, bean classes will need to do their own plumbing by calling API classes such as java.beans.PropertyChangeSupport.

  • Providing a standard method to perform initialization on a bean after all bean properties have been set.

Since invoking methods by name and instantiating beans means handling exceptions from the core reflection package, error handling can also become cumbersome unless we use a higher level of abstraction.

Important 

The lowest layer of the framework used in the sample application is the com.interface21.beans package. This provides the ability to manipulate beans easily and adds the enhanced functionality described above, while respecting and avoiding duplicating the standard JavaBeans infrastructure.

The center of this package is the BeanWrapper interface and the default implementation, BeanWrapperImpl. A BeanWrapper object can be created given any JavaBean, and provides the ability to manipulate properties individually or in bulk and add listeners that will be notified when the bean is manipulated using the BeanWrapper. The most important methods in the BeanWrapper interface include:

    Object getPropertyValue (String propertyName) throws BeansException; 

This returns a property value, given a string name following JavaBeans conventions. The string "age" would return a property exposed via a getAge() accessor method. However, all BeanWrapper methods also support nested properties to arbitrary depth: the string "spouse.age" would try to get the age property on the object value of the spouse property. This would even work if the declared type of the spouse property didn't expose an age property, but the current value did.

The setPropertyValue() method is a parallel setter method with the following signature:

    void setPropertyValue (String propertyName, Object value)          throws PropertyVetoException, BeansException; 

This method throws the java.beans.PropertyVetoException to allow the use of event listeners to "veto" property changes, as described in the JavaBeans specification. The new value parameter can be converted from a string if necessary using the standard beans API PropertyEditor support (discussed below). Another setter method has the following signature:

    void setPropertyValue (PropertyValue pv)          throws PropertyVetoException, BeansException; 

This method is similar to the previous setPropertyValue() method, but takes a simple object holding the property name and value. This is necessary to allow combined property updates, performed by the following method:

    void setPropertyValues (PropertyValues pvs) throws BeansException; 

This sets a number of properties in a single bulk update. The PropertyValues parameter contains multiple PropertyValue objects. Property setting continues on exceptions encountered attempting to set individual properties. Any exceptions are saved in a single exception,

PropertyVetoExceptionsException, which is thrown once the entire operation is complete. Properties set successfully remain updated. Bulk property updates are useful in many situations, such as when populating JavaBeans from HTTP request parameters.

The BeanWrapper interface includes convenience methods to find whether properties exist, such as:

    boolean isReadableProperty (String propertyName); 

It is possible to obtain the object wrapped by a BeanWrapper using the following method:

    Object getWrappedInstance(); 

This enables us to move to ordinary Java invocation once bean manipulation is complete — for example, after initialization.

It is possible to add and remove event listeners that implement standard JavaBeans interfaces using methods such as the following:

    void addPropertyChangeListener (PropertyChangeListener 1);    void addVetoableChangeListener (VetoableChangeListener 1); 

This means that, if a BeanWrapper is used to manipulate its bean properties, property change events will be fired even if a JavaBean being manipulated does not implement property change support.

I took the important design decision to make the root of the beans package exception hierarchy, com.interface21.beans.BeansException, unchecked. As we usually use beans manipulation in initialization code, this is appropriate; failure to manipulate a named property will usually be fatal. However, it is still possible for application code to catch BeanExceptions if we choose. Several subclasses of BeanException are defined in the com.interface21.beans package, including:

  • TypeMismatchException, thrown when it is impossible to convert a property value to the required type

  • NotWritablePropertyException, thrown when an attempt is made to set a property that isn't writable

  • MethodInvocationException, thrown when a property getter or setter method throws an exception

Let's look at a simple example of using the BeanWrapper interface. TestBean is a concrete class that implements the interface ITestBean, which contains three properties: age(int),name(String), and spouse(Person), defined using normal JavaBeans naming conventions as follows:

    public interface ITestBean {      int getAge();      void setAge(int age);      String getName();      void setName(String name);      ITestBean getSpouse();      void setSpouse(ITestBean spouse);    } 

By creating a new BeanWrapper object we can easily set and get property values:

    TestBean rod = new TestBean();    BeanWrapper bw = new BeanWrapperImpl(rod);    bw.setPropertyValue("age", new Integer(32));    bw.setPropertyValue("name", "rod");    bw.setPropertyValue("spouse", new TestBean());    bw.setPropertyValue("spouse.name", "kerry");    bw.setPropertyValue("spouse.spouse", rod);    Integer age = (Integer) bw.getPropertyValue("age");    String name = (String) bw.getPropertyValue("name");    String spousesName = (String) bw.getPropertyValue("spouse.name"); 

Information obtained via reflection is cached for efficiency, so using a BeanWrapper imposes relatively little overhead.

Sometimes values to be set must be provided as strings (for example, the values of HTTP request parameters). The BeanWrapperImpl class can automatically convert a string representation to any primitive type. However, sometimes a string representation must be used to create a custom object. The com.interface21.beans package supports the standard JavaBeans API approach to this problem. We can register a java.beans.PropertyEditor implementation for the class in question, implementing the setAsText() method, and the BeanWrapper class and all classes that use it will automatically gain the ability to create the class from strings.

The JavaBeans API provides the java.beans.PropertyEditorSupport convenience class, which we can extend to implement property editors very easily. Consider the following class that can create TestBean objects from strings of the form <name>_<age>:

    class TestBeanEditor extends PropertyEditorSupport {      public void setAsText(String text) {        TestBean tb = new TestBean();        StringTokenizer st = new StringTokenizer(text, "_");        tb.setName(st.nextToken());        tb.setAge(Integer.parseInt (st.nextToken()));        setValue(tb);                                                                                                             }    } 

We ignore the GUI-oriented methods of the java.beans.PropertyEditor interface, inheriting the default implementations provided by the PropertyEditorSupport class. The setValu() method, highlighted in the above listing, sets the object value that will be returned by the PropertyEditor for the given input string.

We can register this PropertyEditor using standard JavaBeans machinery as follows:

    PropertyEditorManager.registerEditor(ITestBean.class, TestBeanEditor.class); 

Now we can set properties of type ITestBean as follows:

    TestBean rod = new TestBean();    BeanWrapper bw = new BeanWrapperImpl(rod);    bw.setPropertyValue("spouse", "Kerry_34");                                                                               

The BeanWrapperImpl class registers a number of property editors (defined in the com.interface21.beans.propertyeditors package) by default. These include support for java.util.Property objects (specified in any format supported by the java.util.Property class) and string arrays (specifying in CSV format).

Using a "Bean Factory"

The com.interface21.beans package provides powerful functionality, but it's too low-level for direct use in most cases. It's essentially a building block, on which we can base a higher level of abstraction that conceals the low-level details.

However easy we make bean-based "late binding", we usually want to use ordinary Java invocation in application code. It's simpler, faster (although the performance overhead of reflection is often exaggerated) and, unless we want to do something unusual, good design will normally mean that we know which interfaces (but not which concrete classes) we are working with.

At application startup, however, it's a different matter. The complexity can be managed by framework code; performance overhead is negligible, as initialization operations will account for a tiny proportion of total application activity; and, most importantly, there are very good reasons to use late binding in this situation.

Important 

The com.interface21.beans. factory package defines a way of obtaining beans by name from a central configuration repository using a "bean factory". The goal of a bean factory is to remove any need for individual Java objects to read configuration properties or instantiate objects. Instead, configuration is read by a central component, the bean factory, and the bean properties exposed by each application object are set to reflect configuration data. The factory then makes available instances of beans, each with a unique name. Sophisticated object graphs can be created within the bean factory, as managed beans may reference other beans within the same factory.

Our implementation of a bean factory uses the com.interface21.beans package, concealing its low-level concepts from application code.

To illustrate the power of this approach, consider the business objects in our sample application. Most of our business objects need access to two fundamental business objects: the Calendar object, which returns information about genres, shows, and performances, and the BoxOffice object, which implements the booking process. A single instance of each of the Calendar and BoxOffice should be shared across the whole application.

An obvious approach would be to make the Calendar and BoxOffice singletons, and for business objects requiring them to obtain references using their getInstance() methods. Such an approach is used in many applications, but it has the following disadvantages:

  • Using singletons makes it hard to work with interfaces. In the example, we would have hard-coded dependence on concrete classes Calendar and BoxOffice throughout our application. It would be impossible to switch to a different implementation.

  • Each singleton will need to look up its own configuration data — perhaps using properties, JNDI, or JDBC. It's likely that configuration management will be inconsistent, and singleton code will be cluttered with configuration management code that isn't relevant to its business purpose.

  • The inherent problems with the Singleton design pattern, such as inflexibility. What if we ever want more than one BoxOffice object?

These problems could be slightly relieved by separating object creation (in a traditional factory) from object implementation, but the major objections would remain.

The "bean factory" described here differs from a traditional factory in that it is generic. It can create objects of any type, based on configuration information supplied at run time. In my experience, the loss of type safety — an issue only on application initialization, and which is largely concealed within infrastructure code — is far more than made up by the enormous gain in flexibility.

Using the bean factory approach described below, Calendar and BoxOffice would be interfaces, not concrete classes. Implementations of each of these interfaces would expose JavaBean properties allowing a bean factory to configure them. Bean properties might be primitives, or might be objects, resolved by references to other beans in the bean factory. The work of configuration would be moved entirely out of application code into infrastructure.

Each business object requiring these objects would also be a JavaBean, and would expose calendar and boxOffice properties enabling the bean factory to "wire up" the application on initialization. Application classes would contain no irrelevant configuration management code, configuration management would be consistent, and application code would be wholly written to interfaces, rather than concrete classes. This would ensure that the application was totally "pluggable": the implementation of any interface could be switched in configuration without changing a line of Java code.

Note 

If the Calendar and BoxOffice objects were EJBs, client code could look them up via JNDI, which would also avoid dependence on a concrete class. However, an EJB container is a very heavyweight "bean factory" if it delivers no other value, and client code would be considerably complicated by EJB access code.

There's widespread awareness of the serious disadvantages of singletons. However, no superior alternative seems to be in widespread use. The core Java java.beans.beancontext package uses a similar approach to that discussed here, although it's complex, GUI-oriented, and appears to be rarely used. The Apache Avalon server framework is also somewhat similar, but more complex, and with a greater requirement on application code to implement framework interfaces (see http://jakarta.apache.org/avalon/index.html). JMX offers the potential for a widely adopted, standards-based solution, but is far more heavyweight and complex.

Perhaps the package that comes closest to the goals we've set is the Digester, formerly part of Struts and now a distinct open source project in Apache Commons. This uses a rule-based approach for configuring Java objects (usually, but not necessarily, JavaBeans) from an XML representation, which also relies on the use of reflection. The Digester is more flexible for XML-based mapping than the XML bean factory implementation described in this chapter, but is tied to XML, and cannot support mapping definitions held in other representations. The Digester is also arguably less intuitive, and typically requires the application developer to write more code (see http://jakarta.apache.org/commons/digester.html for more information).

The com.interface21.beans.factory.BeanFactory interface contains only two methods:

    public interface BeanFactory {      Object getBean(String name) throws BeansException;      Object getBean(String name, Class requiredType) throws BeansException;    } 

The first method enables beans to be obtained by name as follows:

    MyInterface mi = (MyInterface) getBean("myInterface"); 

The second method is a convenience method that throws an exception if the object found is not of the required type. The following line of code is the same as the above, with this added check:

    MyInterface mi = (MyInterface) getBean("myInterface", MyInterface.class); 

Depending on the implementation, the getBean() method may return a singleton (a shared instance returned by all getBean() calls with the same name argument in the current bean factory) or a prototype (an independent instance based on configuration managed by the bean factory). The shared instance approach is the more valuable, and is used most often. By maintaining a single bean factory, an application can avoid the need for individual application classes to be implemented as singletons: the "singleton" functionality is enforced by the bean factory. Should requirements change, we can choose how many bean factories we want to use in our application.

Once we've separated configuration management (handled by a framework bean factory implementation) from application code (in application-specific JavaBeans), we can easily hold configuration in any format we choose.

The bean definition format used most in the sample application is an XML document as used by the com.interface21.beans.factory.xml.XmlBeanFactory implementation of the BeanFactory interface.

Consider the same simple bean class (TestBean) we discussed above. Using the XML format, we can achieve the same object graph without any Java coding, as follows. The XML format is largely self-describing, with name attributes identifying property names and element values containing string representations of property values. Note the references to other beans within the bean factory, which I've highlighted:

    <bean name="rod"      singleton="true"      >      <property name="name">Rod</property>      <property name="age">31< /property>      <property name="spouse" beanRef="true">kerry</property>                                                                   </bean>    <bean name="kerry" >      <property name="name">Kerry</property>      <property name="age"> 34</property>      <property name="spouse" beanRef="true">rod</property>                                                                     </bean> 

With this definition we could obtain a reference to the shared instance of the first bean as follows:

    TestBean rod = (TestBean) beanFactory.getBean("rod");                                                                    

Another implementation of the BeanFactory interface, com.interface21.beans.factory.support.ListableBeanFactoryImpl, can read definitions from properties files. This is a simpler way of defining beans than XML documents, which can tie in with the core Java internationalization support provided by the java.util.ResourceBundle class. The above beans could be defined as follows using the properties syntax, which specifies the class of each bean and again sets property values as strings:

    rod.class=com.interface21.beans.TestBean    rod.name=Rod    rod.age=32    rod.spouse(ref)=kerry    kerry.class=com.interface21.beans.TestBean    kerry.name=Kerry    kerry.age=35    kerry.spouse(ref)=rod 

Note the special (ref) suffix used to indicate which properties are references to other beans defined in the same bean factory.

Another framework implementation used in the sample application, com.interface21.jndi.JndiBeanFactory, takes bean definitions from EJB environment variables, defined in ejb-jar.xml. This is a more verbose format (the way in which environment variables must be defined), so I'll show only the start of the above definitions using this syntax:

    <env-entry>      <env-entry-name>beans.rod.class</env-entry-name>      <env-entry-type>java.lang.String</env-entry-type>      <env-entry-value>com.interface21.beans.TestBean</env-entry-value>    </env-entry>    <env-entry>      <env-entry-name>beans.rod.name</env-entry-name>      <env-entry-type>java.lang.String</env-entry-type>      <env-entry-value>Rod</env-entry-value>    </env-entry>    ... 

The com.interface21.jndi.JndiBeanFactory implementation enables EJBs to use the same configuration management as other Java classes, and avoids the need to write verbose JNDI lookup code to retrieve environment variables in application EJBs.

There is no limit to potential implementations of the BeanFactory interface: for example, they could read data from a database, or deserialize persistent objects or create new objects without using reflection at all.

A useful subinterface of BeanFactory,ListableBeanFactory, adds the ability to query all definitions (while a BeanFactory implementation might load bean definitions only on request, a ListableBeanFactory, must know about all possible bean definitions). Our XmlBeanFactory is actually a ListableBeanFactory, as it parses the XML input as a DOM document. The ListableBeanFactory interface is defined as follows:

    public interface ListableBeanFactory extends BeanFactory {        String[] getBeanDefinitionNames();        int getBeanDefinitionCount();        String[] getBeanDefinitionNames(Class type);    } 

The ability to find all bean definitions of a given class or interface can be very useful during initialization: for example, it allows the web application framework described in the next chapter to obtain references to all classes that can handle HTTP requests.

Often a bean needs to know when all its properties have been set, so that it can validate its state and/or perform initialization. Our bean factory defines an approach for this. If a bean created by a bean factory implements the com.interface21.beans.factory.InitializingBean interface, it will receive a callback after all properties have been set. The InitializingBean interface includes a single method:

    public interface InitializingBean {      void afterPropertiesSet() throws Exception;    } 

A typical implementation of this method might check that one or more properties have been set to valid values, before performing initialization. The following example checks that a jndiName property has been set before trying to perform aJNDI lookup:

    public final void afterPropertiesSet() throws Exception {      if (this .jndiName == null || this.jndiName.equals (""))        throw new Exception ("Property 'jndiName' must be set on " +                             getClass().getName());        Object o = lookup(jndiName) ;        initialize (o);    } 

As all application objects will be created by a bean factory, we can use this in any application class that does not have its own lifecycle management solution.

Sometimes we may want to create a bean definition that performs custom processing before returning a bean instance. For example, we might need a bean definition that registers the returned bean as a JMS listener; creates an object of a specified class and returns a dynamic proxy wrapping it; or returns a bean resulting from a method invocation on another object. The bean factory concept supports "custom bean definitions", in which a custom bean definition class can perform any action before returning a bean.

A custom bean definition works as follows:

  • The bean factory loads the custom definition class, which must be specified. The custom definition class must be a JavaBean.

  • The bean factory sets properties on the custom definition class, which must be distinguished with a special syntax.

  • The configured custom bean definition class returns a BeanWrapper wrapping the returned bean. Before returning a BeanWrapper, the custom definition class can perform any processing it likes.

  • The bean factory sets properties on the returned BeanWrapper as if it were a normal bean definition.

The following custom bean definition illustrates this:

    <bean name="referenceDataListener"          definition    >      <customDefintion property="listenerBean">       com.wrox.expertj2ee.ticket.referencedata.support.DataUpdateJmListenerr      </customDefinition>      <customDefinition property="topicConnectionFactoryName">         jms/TopicFactory      </customDefinition>      <customDefinition property= "topicName">         jms/topic/referencedata      </customDefinition>      <property name="cachingCalendar" beanRef="true">calendar</property>    <bean> 

The definitionClass attribute of the <bean> element indicates that this is a custom bean definition, and supplies the custom definition class.

The <customDefinition> elements set properties on the custom definition. The meaning of properties will vary with custom definition class. In this example, the listenerBean property specifies the class of the actual returned bean. In the above list, the custom definition will create an instance of DataUpdateJmsListener and register it as a JMS listener.

The <property> element sets the cachingCalendar property of the actual returned bean. This mechanism makes the bean factory interface indefinitely extensible. It's particularly valuable for concealing the complexity of J2EE API usage.

The Application Context

The bean factory concept solves many problems, such those resulting from overuse of the Singleton design pattern. However, we can move to a higher level of abstraction to help to pull applications together.

Application Context Goals

Building on the bean factory, an application context provides a namespace for the JavaBeans that compose an application or subsystem, and the ability to share working objects at run time. The com.interface21.context.ApplicationContext interface extends the ListableBeanFactory interface, adding the ability to:

  • Publish events using the Observer design pattern. As we discussed in Chapter 4, the Observer design pattern provides the ability to decouple application components and achieve clean separation of concerns.

  • Participate in a hierarchy of application contexts. Application contexts can have parent contexts, allowing developers to choose the amount of configuration shared between subsystems.

  • Share working objects between application components at run time.

  • Look up messages by string name, allowing support for internationalization.

  • Facilitate testing. With an application context, as well as a bean factory, it's often possible to provide a test implementation that enables application code to be tested outside an application server.

  • Provide consistent configuration management in different types of applications. Application contexts behave the same way in different kinds of applications, such as Swing GUI applications or web applications, and inside and outside a J2EE application server.

The most important added methods in the ApplicationContext interface are:

    ApplicationContext getParent();    void publishEvent (ApplicationEvent e);    void shareObject (String key, Object o); 

An application context is a directory of cooperating beans that offers some additional services. The sample application uses the com.interface21.web.context.support.XmlWebApplicationContext ApplicationContext implementation, which is associated with a javax.servlet.ServletContext object provided by the web container, and which uses the XML bean definition format we've already seen. However other implementations do not require a web container, and allow testing (or the building of applications) without a web interface.

Each bean in the application context is given access to the application context if it requires. It simply needs to implement the optional com.interface21.framework.context.ApplicationContextAware callback interface, enabling it to access other beans, publish events, or look up messages. This interface contains only two methods, making it very simple to implement:

    public interface ApplicationContextAware {      void setApplicationContext (ApplicationContext ctx)        throws ApplicationContextException;      ApplicationContext getApplicationContext();    } 

However, this capability shouldn't be used without good reason. One of the strengths of the framework described here is that application code can benefit from it without depending on it. This minimizes lockin to the framework and retains the option of configuring the application differently.

Important 

Although it is possible for beans to register for notification of the application context they operate within by implementing an optional interface, there are no special requirements for beans used within a bean factory of application context.

Each application context has a parent context. If the parent context is null, it is the root of the application context hierarchy. If the parent context is non-null, the current context will pass to the parent requests for beans it cannot find, and requests for messages it cannot resolve. This enables us to determine precisely how much application state should be shared between different components.

The Observer support provided by an ApplicationContext is limited to the current server; it makes no effort to broadcast messages in a cluster (JMS Pub/Sub messaging should be used for this). However, a lightweight event publication framework limited to the current server is still very useful. Often there's no need for messages to be broadcast more widely. Consider, for example, a message that should result in the sending of an e-mail. Using the Observer design pattern, we are able to add listeners that perform such tasks without complicating the code of business objects that generate such events.

Web Application Context

An application of type com.interface21.web.context.WebApplicationcontext integrates with the javax.servletContext.

In a web application, the application context hierarchy works as follows:

  • The /WEB-INF/applicationContext.xml file defines the root context of the application. Definitions here are available to all objects managed by the WebApplicationContext. The root context is added to the ServletContext as an attribute, making it available to all web tier objects, including servlet filters and custom tags, which often fall outside the domain of application configuration management.

  • Each servlet using the framework has its own application context, a child of the root context, defined by a file with a name of the form /WEB-INF/<servletName>-servlet.xml.The servlet name is the value of the <servlet-name> subelement of the relevant <servlet> element in the web application's web.xml file. This allows us, for example, to have multiple controller servlets, each with a distinct namespace, but able to access global objects.

Our sample application uses only one servlet, with the name ticket, and requires no custom global configuration. Hence all bean definitions are placed in the /WEB-INF/ticket-servlet.xml file.

Testing Implications

The infrastructure I've described here is test-friendly: an important consideration.

Although it's possible to integrate an application context with the Servlet API and use a bean factory in an EJB, the core interfaces do not depend on J2EE APIs. Thus it is possible create a test application context enabling tests to be run against application objects without the need for a J2EE server. For example, a JUnit test case could create an XmlApplicationContext from the application context definition documents in a web application, and use it to get beans by name and test their behavior.

Summary of Application Configuration Infrastructure

The following UML diagram illustrates the relationship between the framework classes and interfaces we've described:

click to expand

I've omitted some of the implementation classes; the aim is to show the public interfaces. Note that application code doesn't need to work with these classes; it's possible to write an application that is "wired up" by an application context but doesn't depend on any framework classes.

The version of the framework used in the sample application doesn't support dynamic reconfiguration. I concluded that the value of such functionality did not justify the complexity it would introduce. However, it could be added if necessary; this approach is much more flexible than using singletons and other "hard-coded" approaches to configuration.

Later in this chapter we'll look in more detail at how the sample application's code is simplified by use of this framework.

Important 

The infrastructure described above enables us to code to interfaces and never concrete classes, relying on a central configuration manager (an "application context") to "wire up" the application on initialization. This completely removes configuration data and plumbing code from application objects, which merely need to expose JavaBean properties. This also has the advantage that the application code is not dependent on the supporting infrastructure — a problem that affects many frameworks.

Managing API Complexity

Now that we have a strong infrastructure for pulling our application together, let's move on to how infrastructure can be used to tackle the problem of J2EE API complexity. In the following section we'll look at how several of the key J2EE APIs can be made much easier to use and their use less error-prone.

Implementing EJBs

So long as we avoid using infrastructure components that violate the EJB specification (for example, by obtaining the current classloader, which rules out dynamic proxy solutions), there's nothing special about EJB code. However, by deriving application EJB implementation classes from generic superclasses we can greatly simplify EJB code.

Abstract Superclasses for EJBs

We should derive our EJBs from common superclasses that achieve the following goals:

  • Implement lifecycle methods that are irrelevant to bean implementation classes.

  • Where possible, force EJBs to implement methods from the home interface, which aren't required by the interfaces EJBs must implement.

  • Provide a consistent logging solution.

  • Avoid the need for EJBs to use JNDI to look up environment variables, simplifying configuration management.

The following UML class diagram shows the hierarchy of EJB superclasses offered by our generic infrastructure, and how they relate to the interfaces required by the EJB specification. All infrastructure classes are in the com.interface21.ejb.support package. We'll look at a complete code listing for each below.

Note 

Note that we don't attempt to provide superclasses for entity beans. Entity beans should use CMP, and shouldn't normally contain business logic, placing the onus on the container to provide infrastructure code.

click to expand

Although there are quite a few classes here, each class is simple and the inheritance hierarchy enables us to support stateless and stateful session beans and message-driven beans without code duplication. The classes above the horizontal line belong to the framework; those below the line show how application EJB implementation classes can extend these framework classes.

The root of the inheritance hierarchy — not used directly as a superclass by any application EJBs — is the AbstractEnterpriseBean class. This implements the javax.ejb.EnterpriseBean tag interface and provides standard logging and configuration management. The following is a complete listing:

    package com.interface21.ejb.support;    import java14.java.util.logging.Logger;    import javax.ejb.EJBEXception;    import javax.ejb.EnterpriseBean;    import com.interface21.beans.BeansException;    Import com.interface21.beans.factory.ListableBeanFactory;    import com.interface21.jndi.JndiBeanFactory;    public abstract class AbstractEnterpriseBean implements EnterpriseBean {      protected final Logger logger = Logger.getLogger(getClass().getName();      private  ListableBeanFactory beanFactory;      protected final ListableBeanFactory getBeanFactory() {        if (this. beanFactory == null) {          loadBeanFactory();        }        return this. beanFactory;      }      private void loadBeanFactory() {        logger. info ( "Loading bean factory") ;        try {          this.beanFactory =new JndiBeanfactory ("java:comp/env");        catch (beansFactory ex) {          throw new EJBException ("Cannot create bean factory", ex);        }      }    } 

The protected logger instance variable uses the Java 1.4 logging emulation package discussed in Chapter 4, or standard Java 1.4 logging if available. The getBeanFactory() method lazily loads a bean factory from environment variables. This is very useful, as it avoids the need to use JNDI to look up environment variables, yet allows parameterization at EJB deployment time. We can instantiate and configure helper objects such as DAOs, and put configuration variables in a simple bean.

We've replaced a low-level API with the same high-level API we use throughout our applications. By facilitating factoring functionality into helper classes (rather than EJB code) we promote good practice and improve testability. We can often test helper classes used in EJBs without an EJB container. For example, the following environment variables define a DAO for the sample application's BoxOffice EJB:

    <env-entry>      <env-entry-name>beans.dao.class</env-entry-name>      <env-entry-type> java.lang.String</env-entry-type>      <env-entry-value>          com.wrox.expertj2ee.ticket.boxoffice.support.jdbc.          JBoss3OORacleJdbcBoxOfficeDao      </env-entry-value>    </env-entry> 

This particular DAO doesn't require any properties, but these can be set using our bean factory conventions. Simply by editing the ejb-jar.xml deployment descriptor we can change the class of the DAO helper to any other object that implements the com.wrox.expertj2ee.ticket.boxoffice.support.BoxOfficeDao interface, without needing to change EJB code.

The EJB specification requires all session beans to implement the javax.ejb.SessionBean interface, shown below. This includes the setSessionContext() method and other lifecycle methods:

    package javax.ejb;    import java.rmi.RemoteException;    public interface SessionBean extends EnterpriseBean {      void ejbActivate() throws EJBException, RemoteException;      void ejbPassivate() throws EJBException, RemoteException;      void ejbRemove() throws EJBException, RemoteException;    void setSessionContext (SessionContext ctx)            throws EJBException, RemoteException;    } 

Thus all session beans can be derived from a simple subclass of our AbstractEnterpriseBean base class that implements the SetSessionContext() to store the SessionContext in an instance variable, and exposes the saved context via a protected getSessionContext() method. We also add an empty implementation of the required ejbRemove() lifecycle method, which can be overridden if necessary:

    package com.interface21. ejb.support;    import javax.ejb.SessionBean;    import javax.ejb.SessionContext;    public abstract class AbstractSessionBean extends AbstractEnterpriseBean        implements SessionBean {      private SessionContext SessionContext;      protected final SessionContext getSessionContext() {        return SessionContext;      }      public void setSessionContext (SessionContext SessionContext) {        logger.info ("setSessionContext");        this.SessionContext = SessionContext;      }      public void ejbRemove() {        logger.info ("AbstractSessionBean NOP ejbRemove");      }    } 

Stateful session beans can subclass this class directly. Stateful session beans often don't need to implement the ejbRemove() method, but they always need to ensure correct behavior on the ejbPassivate() and ejbActivate() lifecycle methods. We discussed how to implement these methods correctly in Chapter 10. Hence we leave these methods unimplemented in AbstractSessionBean.

By contrast, it is illegal for an EJB container to invoke the ejbPassivate() and ejbActivate() lifecycle methods on stateless session beans, although stateless session bean implementation classes are (illogically) required to implement them. Thus stateless session beans can be derived from a simple subclass of AbstractSessionBean, which implements these two lifecycle methods to throw an exception. There is no reason for any subclass to override this implementation, although we can't make these methods final, as it violates the EJB specification.

Our superclass for stateless session beans also forces Subclasses to implement a no-argument ejbCreate() method — matching the required create() method on the home interface — by defining this as an abstract method. This moves to compile time a check that would otherwise only take place on EJB deployment, as this method isn't required by the EJB API. Here is the complete listing of the AbstractStatelessSessionBean class, which should be used as a superclass by SLSB implementation classes:

    package com.interface21. ejb. support;    import javax.ejb.CreateException;    import javax.ejb.EJBException;    public abstract class AbstractStatelessSessionBean;        extends AbstractSessionBean { 

      public abstract void ejbCreate() throws CreateException;      public void ejbActivate()throws EJBException {        throw new IllegalStateException(          "ejbActivate must not be invoked on a stateless session bean");      }      public void ejbPassivate() throws EJBException {        throw new IllegalStateException(          "ejbPassivate must not be invoked on a stateless session bean");      }    } 

This leaves SLSB subclasses to implement an ejbCreate() method and their business methods. Remember that application EJBs will usually implement a "business methods" interface, ensuring that they match the methods defined on their component interface. In rare situations, an SLSB may override the ejbRemove() method defined in AbstractSessionBean. The implementation of the ejbCreate() method may use the bean factory available from the AbstractEnterpriseBean superclass to create helper objects such as DAOs. These superclasses can by used by EJBs with local or remote interfaces or both.

Let's look at an example, showing how the business methods interface pattern can be combined with our generic superclass to ensure that the compiler, not just deployment tools, enforces that an SLSB implementation class is valid. The following business methods interface defines the remote methods of a hypothetical EJB:

    import java.rmi.RemoteException;    public interface Calculator {      int getUncacheableValue() throws RemoteException;      int getCacheableValue() throws RemoteException;    } 

Following the business methods pattern described in the last chapter, the EJB's remote interface will contain no methods of its own, but will extend both this interface and javax.ejb.EJBObject:

    import javax.ejb.EJBObject;    public interface CalculatorRemote, extends EJBObject, Calculator {    } 

The bean implementation class will implement the business methods interface and extend AbstractStatelessSessionBean. This means that the compiler will force us to implement all the methods required for a valid EJB. I've highlighted the lines in the following listing showing how the ejbCreate() method can be implemented to load a helper object from the bean factory exposed by the AbstractEnterpriseBean base class:

    import java.rmi.RemoteException;    import javax. ejb.CreatException;    import com.interface21.ejb.support.AbstractStatelessSessionBean;    public class CalculatorEJB extends AbstractStatelessSessionBean        implements Calculator { 

      private MyHelper myHelper;      public void ejbCreate() {        this.myHelper = (MyHelper) getBeanFactory().getBean("myHelper");      } 

      public int getUncacheableValue() {      return 0;      }      public int getCacheableValue()  {        return 0 ;      }    } 

Our superclasses have delivered real value. This EJB implementation class contains no code irrelevant to its business purpose, and is easily able to load helper objects whose configuration is held entirely outside Java code.

Let's finish by considering message-driven beans (MDB). MDB implementation classes must also meet some requirements beyond implementing the javax.ejb.MessageDrivenBean interface: they must also implement an ejbCreate() method without arguments; and they must implement the javax.jms.MessageListener interface to handle JMS messages. We can extend AbstractEnterpriseBean to save the MessageDrivenContext and force subclasses to implement an ejbCreate() method as follows:

    package com. interface21.ejb.support;    import javax.ejb.MessageDrivenBean;    import javax.ejb.MessageDrivenContext;    public abstract class AbstractMessageDrivenBean extends AbstractEnterpriseBean        implements MessageDrivenBean {      private MessageDrivenContext messageDrivenContext;      protected final MessageDrivenContext getMessageDrivenContext() {        return messageDrivenContext;      }      public void setMessageDrivenContext(          MessageDrivenContext messageDrivenContext) {        logger. fine ("setMessageContext");        this.messageDrivenContext = messageDrivenContext;      }      public abstract void ejbCreate();      public void ejbRemove() {        logger. info("ejbRemove");      }    } 

Although EJB 2.0 supports only JMS messaging, EJB 2.1 supports JAXM messaging as well, meaning that the javax.jms.MessageListener is only one of three alternative messaging interfaces that an EJB 2.1 MDB might implement. Thus, rather than tie the AbstractMessageDrivenBean class to use of JMS, we ensure forward compatibility with EJB 2.1 by putting the requirement to implement javax.jms.MessageListener in a JMS-specific subclass:

    package com.interface21.ejb.support;    import javax.jms.MessageListener;    public abstract class AbstractJmsMessageDrivenBean        extends AbstractMessageDrivenBean implements MessageListener {      // Empty    } 

An application MDB that subclasses this class is guaranteed to meet the requirements of an EJB 2.0 JMS MDB. A trivial subclass automatically stubbed by an IDE will look like this:

    public class SimpleMDB extends AbstractJmsMessageDrivenBean {      public void ejbCreate() {      }      public void onMessage (Message message) {      }    } 

As with session beans, the ejbCreate() method can access the bean factory provided by the AbstractEnterpriseBean superclass.

Of course we don't need to use these (or similar) superclasses when implementing EJBs, but as they simplify application code and reduce the likelihood of errors resulting in wasted development-deploy cycles, they provide very good value. There's seldom any problem in the use of concrete inheritance depriving application EJBs of their own inheritance hierarchy. Due to the EJB programming restrictions, making an object an EJB by subclassing it to implement the required lifecycle methods is rarely a good idea. Our solution, however, makes it easy for a new EJB to use existing code, through using existing classes as helper objects instantiated through its bean factory. Custom application EJB superclasses can simply subclass one of the above framework superclasses to add additional application-specific behavior.

Accessing EJBs

It's important to achieve loose coupling between EJBs and code that uses them. There are many disadvantages in EJB client code accessing EJBs directly:

  • Client code must deal with JNDI lookups in multiple places. Obtaining and using EJB references is cumbersome. We need to do a JNDI lookup for the EJB home interface and invoke a create method on the home interface.

  • EJB JNDI names will be held in multiple places, making it difficult to modify client code.

  • Clients are too closely coupled to the EJB tier, making it difficult to change the interfaces it exposes.

  • It's impossible to introduce a consistent caching strategy for application data returned by the EJB tier or server resources such as naming contexts and EJB home interface references. Especially in distributed applications, caching the result of invoking EJBs may be essential to meet performance requirements.

  • Client code must deal with low-level checked exceptions from the EJB tier. For both local and remote EJBs there are JNDI NamingExceptions and EJBCreate exceptions. For remote EJBs every call on home or EJB may throw java.rmi.RemoteException. Except for remote exceptions (which we'll consider below) this is a lot of exception handling to achieve very little. In a local application, failure to look up an EJB home or create an stateless session EJB using a create() method that has no arguments that could be invalid almost certainly indicates serious application failure. Most likely we'll want to log this and gracefully inform the user that their action cannot be processed at this time due to an internal error.

Most importantly, if we allow code to work with EJB access APIs directly, we tie code unnecessarily to a particular implementation strategy.

Important 

Abstract inter-tier communication with Java interfaces, not on J2EE technologies such as EJB. Technologies are features of implementations.

Two well-known J2EE patterns — the Service Locator and Business Delegate patterns — address these problems. In this section, we'll discuss their benefits and how to implement them. As with implementing EJBs, generic infrastructure classes can be used to simplify application code. We can integrate EJB access with our overall application infrastructure by making service locators and business delegates JavaBeans, configured by an application bean factory.

Local versus Remote Access

There's a profound difference between accessing EJBs through local and remote interfaces. Invoking an EJB through a local interface isn't radically different from invoking an ordinary Java object. The EJB container will intercept each method call, but both caller and EJB are running in the same JVM, meaning that there is no possibility of distributed failures and no serialization issues.

Remote invocation is a completely different problem. We may encounter distributed failures, and we must consider efficiency: "chatty" calling, or exchanging an excessive amount of parameter data, will prove disastrous for performance.

We can choose between two fundamental strategies for remote invocation: we can try to achieve local-remote transparency, concealing from callers the issue of remote invocation; or we can use a client-side façade to provide a locally accessible point of contact for the EJB invocation.

I don't advocate local-remote transparency. As the designers of Java RMI didn't either, it's relatively hard to achieve when invoking EJBs. Secondly, it's an invitation to gross inefficiency.

It's much better to provide a local façade through which all communication with the EJB tier passes. This is an excellent application for the Business Delegate pattern, which is discussed below.

The Service Locator and Business Delegate J2EE Patterns

The first step in decoupling calling code from an EJB implementation is to ensure that all EJB references are obtained through a common factory. This avoids the duplication of JNDI lookup code, and enables the caching of the results of JNDI lookups to boost performance (although the overhead varies between servers, obtaining a naming context and looking up a home interface are potentially expensive operations). Such an object is called a service locator (Core J2EE Patterns).

The disadvantage is that clients will still need to work directly with the EJBs. They must still handle remote exceptions in a distributed application and may need to catch exceptions thrown when creating EJBs. However, a service locator can return references to SLSB EJB instances, rather than merely home interfaces (the service locator can invoke the no argument create() method as necessary). This frees clients of the need to handle the checked exceptions that may be thrown by attempting to create an EJB from a home interface.

When this is possible, clients needn't know that EJB is being used to implement business interfaces. This is clearly desirable; unless we use EJB to implement a distributed model, there's no reason to view EJB as more than a helpful framework for particular implementations of business interfaces. The Service Locator pattern works well with local EJB access.

Let's consider two approaches to service location where SLSBs are concerned.

Using a Typed Service Locator to Access EJBs

In the first approach, we use a dedicated, thread-safe service locator for each SLSB. Each service locator implements a factory interface that isn't EJB-specific: a method that can be implemented to return any implementation of the relevant business interface. When an EJB is used to implement this interface, the object returned will be an EJB reference, and the EJB's component interface will extend the business interface.

In my preferred implementation of this approach, the factory method throws a runtime exception, not a checked exception, as failure to create a business object is likely to be fatal. If it's possible to retry, the service locator can retry before throwing an exception to callers.

Each service locator implementation will need to perform a JNDI lookup to cache the home interface for the relevant EJB. This suggests the use of generic infrastructure classes to conceal the use of JNDI. Our framework includes convenient support for this; application service locators need merely to subclass the

com.interface21.ejb.access.AbstractLocalStatelessSessionServiceLocator class and implement the abstract setEjbHome() method as well as the relevant factory interface.

The com.interface21.ejb.access.AbstractLocalStatelessSessionServiceLocator class extends the com.interface21.jndi.AbstractJndiLocator class, which can be used to perform any JNDI lookup and cache the result. This allows the Service Locator pattern to be used not only for EJB access, but also to look up and cache any resource obtained through JNDI. The AbstractJndiLocator class is designed for use as a Java bean in our application framework. The following is a complete listing:

    package com.interface21.jndi;    import java14.java.util.logging.Logger;    import javax.naming.NamingException;    import com.interface21.beans.factory.InitializingBean;    public abstract class AbstractJndiLocator implements InitializingBean {      protected final Logger logger = Logger.getLogger(getClass().getName());      private static String PREFIX = "java:comp/env/";      private String jndiName;      public AbstractJndiLocator() {      } 

The JndiName property setter enables the JNDI name of the EJB or other resource to be held outside Java code:

    public final void setJndiName (String jndiName) {      if (!jndiName.startsWith(PREFIX))        jndiName = PREFIX + jndiName;      this.jndiName = jndiName;    }    public final String get JndiName() {      return jndiName;    } 

The implementation of the afterPropertiesSet() method, which is invoked by the bean factory after all properties have been set, performs the JNDI lookup. If the object is successfully located, the afterPropertiesSet() method invokes the abstract template method located() with the object. Subclasses must implement this to perform their own initialization, based on the type of object. If the lookup fails, the resulting javax.Naming.NamingException is thrown to be handled by the application framework (this will be considered a fatal initialization exception):

   public final void afterPropertiesSet() throws Exception {     if (this.jndiName == null || this.jndiName.equals(""))       throw new Exception ("Property 'jndiName' must be set on " +                          getClass().getName());     Object o = lookup(jndiName);     located (o);   }   protected abstract void located (Object o);   private Object lookup (String jndiName) throws NamingException {     logger.info("Looking up object with jndiName "' + jndiName + ""');     // This helper will close JNDI context     Object o = new JndiServices().lookup(jndiName);     logger.fine("Looked up objet with jndiName "' + jndiName +               "' OK: [" + o + "]");     return o;   } } 

The AbstractLocalStatelessSessionServiceLocator subclass implements the located()template method to check that that the object is actually an EJB local home, and to invoke an abstract setEjbHome() method that must be implemented by subclasses that know the application-specific type of the EJB home, enabling them to obtain EJB references from it:

    package com.interface21.ejb.access;    import javax.ejb.EJBLocalHome;    import com.interface21.beans.FatalBeanException;    import com.interface21.jndi.AbstractJndiLocator;    public abstract class AbstractLocalStatelessSessionServiceLocator        extends AbstractJndiLocator {      public AbstractLocalStatelessSessionServiceLocator() {      }      public AbstractLocalStatelessSessionServiceLocator (String jndiName) {        super(jndiName);      }      protected abstract void setEjbHome (EJBLocalHome home);      protected final void located(Object o) {        if (! (o instanceof EJBLocalHome))          throw new FatalBeanException("Located object with JNDI name "' +                    getJndiName() + "' must be an EJBLocalHome object", null);        setEjbHome((EJBLocalHome) o);      }    } 

It's always safe to cache a reference to a local EJB home interface. Although the EJB specification (§6.2.1) implies that it's safe to cache a remote home, there is a slight chance that a cached remote home reference may become "stale" in some servers (for example, if the remote server hosting the EJB was restarted during the client's lifetime).

The following UML class diagram illustrates the superclasses for both local and remote SLSB service locators, and how a LocalSLSBBoxOfficeFactory class might be used in the sample application to create BoxOffice objects (actually EJBs) as required:

click to expand

Let's look at how these superclasses can be used to simplify the implementation of a real service locator. The BoxOfficeFactory interface is the public interface of our application-specific typed service locator:

    public interface BoxOfficeFactory {      BoxOffice getBoxOffice() throws FatalException;    } 

Extending the com.interface21.ejb.access.AbstractionLocalStatelessSessionServiceLocator superclass makes it trivial to implement this interface:

    public class LocalSLSBBoxOfficeFactory        extends AbstractLocalStatelessSessionServiceLocator        implements BoxOfficeFactory { 

The implementation of the protected abstraction setEjbHome() method caches the home interface, after casting it to the appropriate type. This method will be invoked once, when the service locator is initialized:

    private BoxOfficeHomehome;    protected void setEjbHome(EJBLocalHome home) {      this.home = (BoxOfficeHome) home;    } 

The implementation of the getBoxOffice() method from the BoxOfficeFactory interface, which will be invoked by code using the EJB, creates a new SLSB reference as required. EJBCreate exceptions are caught and an unchecked exception, FatalException, is rethrown:

     public BoxOffice getBoxOffice() throws FatalException {       try {         return home.create();       }       catch (CreateException ex) {         throw new EjbConnectionFailure(           "Cannot create BoxOffice EJB from home"), ex);       }     }   } 

The following bean definition shows how such a service locator could be defined in the sample application's bean factory. The only property that must be set is jndiName, inherited from the AbstractJndiLocator superclass:

    <bean name="boxOfficeFactory"       >      <property name="jndiName">ejb/BoxOffice</property>    </bean> 

This is a simple approach that works very well with local EJBs. It hides EJB access from code that uses the business interface. One disadvantage is that it requires a factory interface and an implementing class for each EJB. This isn't usually a major problem, as with sensible design few systems need a vast number of EJBs. Slightly more seriously, all code that uses the EJB must work with the factory object: it's impossible to hold on to a reference to the business interface.

Transparent Dynamic Proxy: An Alternative to the Typed Service Locator for Local EJB Access

An alternative to the Service Locator pattern that achieves the same end is to conceal the factory in a custom bean definition, as described when we discussed our "bean factory" approach above. The custom bean definition looks up and caches the bean's home interface before returning an object of the required interface, which is actually a dynamic proxy wrapping a new instance of the SLSB. Before each method invocation, the dynamic proxy — which is threadsafe — transparently creates a new instance of the SLSB. Although the custom bean definition doesn't know the type of the EJB, it knows that an SLSB local interface must expose a create() method without arguments, which it can use a BeanWrapper object to invoke by name. Benchmarks show that this approach has only a small performance overhead.

This use of dynamic proxies and reflective method invocation sounds complex, but the complexity is hidden within framework code; it enables us to write less application code. Applications need only define a bean using the custom bean definition, as follows. Note the specification of the JNDI name and business interface:

    <bean name="boxOffice"          definitionClass=          "com.interface21.ejb.access.LocalStatelessSessionProxyBeanDefinition">      <customDefinition property="businessInterface">          com.wrox.expertj2ee.ticket.boxoffice.BoxOffice      </customDefinition>      <customDefinition property="jndiName">          ejb/BoxOffice      </customDefinition>    </bean> 

That's it: calling code can simply obtain a BoxOffice object like this, and cache it:

    BoxOffice boxOffice=(BoxOffice) beanFactory.getBean("boxOffice"); 

Most likely, this won't even be necessary: objects that need to use the BoxOffice interface can simply expose a bean property and have the application context set it to the boxOffice reference at run time.

This approach has the advantage of requiring no custom code to access an EJB. We need only define a bean using the custom definition.

This is the approach used in the sample application, although it would be simple to switch to the Typed Service Locator approach, code for which is included in the download.

Note 

See the code in the com.interface21.ejb.access.LocalStatelessSessionProxyBeanDefinition class for the implementation of this approach.

Using the Business Delegate Pattern for Remote EJB Access

A higher level of decoupling is achieved by the Business Delegate pattern, in which a client-side object exposes business methods that are implemented by calls to the EJB. This is more of a façade than a proxy approach, as the business delegate may change method signatures — for example, changing exception types or combining method calls, or even invoking multiple EJBs.

As the Business Delegate pattern requires more work to implement than the Service Locator, I'll focus on its use with remote interfaces, with which it delivers real value (its benefits are reduced with local EJB access). The benefits of a business delegate approach include:

  • The business delegate may be able to retry failed transactions if this is indicated, without calling code being complicated.

  • It's possible to handle exceptions from the server in a consistent way without every client needing to catchJ2EE infrastructure-related exceptions such as java.rmi.RemoteException and javax.naming.NamingException. The business delegate can catch such low-level exceptions, log them and throw an application-specific exception that makes more sense to client code. Or it may be appropriate to wrap EJB-tier exceptions as unchecked exceptions to simplify client code. This makes sense when exceptions are fatal — for example, if we've established that there's no point retrying after encountering a remote exception that prevents execution of a use case.

  • It may be possible to implement operations at a higher level of abstraction. For example, if a particular use case requires multiple EJB invocations, these may be able to be performed in the business delegate.

  • It may be possible to introduce caching by providing a caching implementation of the business delegate interface. This will benefit all clients.

The drawback of using a business delegate is the potential duplication of the business interface exposed by the EJB tier on the client-side. However, using a business delegate offers an opportunity to provide an interface that best fits the needs of clients. For example, we can simplify the interface exposed by the EJB tier by omitting irrelevant operations. We can simplify client code by only throwing checked exceptions if they're part of the API, and the client can be expected to handle them.

It's superficially attractive for the business delegate to implement the session bean's business methods interface. However, this fails to decouple EJB tier and client tier, and cannot deliver some of the benefits we've identified, such as concealing remote exceptions and simplifying the client API. Hence, I don't recommend copying method signatures in the business delegate (the approach recommended in EJB Design Patterns). If this is appropriate, the Service Locator pattern is simpler and should be preferred.

Our framework provides easy support for implementing business delegates. The same superclass we used for the typed service locator, AbstractLocalStatelessSessionServiceLocator, can be used as a superclass for a business delegate. Instead of merely returning an instance of the EJB on demand, a subclass would implement business operations, calling the EJB as necessary.

As the sample application uses EJBs with local interfaces, there is no need to use the Business Delegate pattern, which requires more work to implement than a service locator.

The following example illustrates the use of a business delegate to access a remote EJB. It uses a hypothetical EJB with a remote interface that exposes two int values: one of which can be cached, and one of which cannot. We saw this EJB's business methods interface and component interface under Implementing EJBs above. The remote interface exposes the following business methods:

    int getUncacheableValue() throws RemoteException;    int getCacheableValue() throws RemoteException; 

By using the Business Delegate pattern, we can simplify calling code by hiding remote exceptions and rethrowing unchecked fatal exceptions, and improve performance by caching the cacheable data value for a given period.

When using the Business Delegate pattern, it is good practice to define a business delegate interface, which application code will work with. This enables a caching implementation to be substituted without impact on client code, for example. The interface might look as follows for this EJB:

    public interface MyBusinessDelegate      int getUncacheableValue() throws FatalException;      int getCacheableValue() throws FatalException;    } 

The following implementation extends the AbstractRemoteStatelessSessionServiceLocator framework superclass, which performs the necessaryJNDI lookup and provides it with an EJB home it can cache in the setEjbHome() method:

    public class MyBusinessDelegateImpl        extends AbstractRemoteStatelessSessionServiceLocator        implements MyBusinessDelegate {      private static long MAX_DATA_AGE = 10000L;      private CalculatorHome home;      private long lastUpdate = OL;      protected void setEjbHome(EJBHome home) {        this. home = (MyHome) home;      }      private CalculatorRemote create() throws FatalException {        try {          return home . create();        }        catch (CreateException ex) {          throw new EjbConnectionFailure{                "Cannot create BoxOffice EJB from home", ex);        }        catch (CreateException ex) {          throw new EjbConnectionFailure (                "Cannot create BoxOffice EJB from home", ex);        }      }      public int getUncacheableValue() throws FatalException {        try {          return create() .getUncacheableValue();        }        catch (RemoteException ex) {          throw new EjbConnectionException("Can't connect to EJB", ex);        }      }      public synchronized int getCacheableValue() throws FatalException {        if (System.currentTimeMillis() - lastUpdate > MAX_DATA_AGE ) {          try {            this. cachedValue = create() .getCacheableValue();            lastUpdate = System. currentTimeMillis();          }          catch (RemoteException ex) {            throw new EjbConnectionException ("Can't connect to EJB", ex);          }        }        return this.cachedValue;      }    } 

Important 

It's important to abstract access to EJBs. With EJBs with local interfaces, use the Service Locator pattern, or the Business Delegate if there is a good reason that calling code shouldn't work with the EJB interface. With EJBs with remote interfaces, use the Business Delegate pattern, and conceal remote access details from clients.

Using JMS

LikeJDBC,JMS is a complex API. Working with it directly can obscure the purpose of application code. Thus an abstraction layer is appropriate. The com.interface21.jms.JmsTemplate class applies the same approach as the JdbcTemplate we examined in Chapter 9. We should try to conceal in framework code the complexity of the necessary JNDI lookups and the requirement to work with multiple JMS API objects.

Let's consider com.interface21.jms.JmsTemplate class's support for Pub/Sub messaging. On startup, a JmsTemplate instance does aJNDI lookup for the TopicConnectionFactory. TheJNDI name is passed into the constructor, as it may differ between application servers. The following method uses the cached TopicConnectionFactory to invoke a callback interface to create a message given a TopicSession, which it then publishes:

    private static String PREFIX = "java:comp/env/";    public void publish (String topicName, pubsubMessageCreator        pubSubMessageCreator) throws JmsException {      if (! topicName.startsWith(PREFIX))        topicName = PREFIX + topicName;      TopicConnection topicConnection = null;      try {        topicConnection = topicConnectionFactory.createTopicConnection();        TopicSession session = topicConnection.createTopicSession(          false, Session. AUTO_ACKNOWLEDGE);        Topic topic = (Topic) jndiServices. lookup ( topicName);        TopicPublisher publisher = session. createPublisher (topic);        Message message = pubSubMessageCreator. createMessage (session);        logger. info ("Message created was [" + message + "]");        publisher. publish (message);        logger. info ("Message published OK");      }      catch (NamingException ex) {        throw new JmsException ("Couldn't get topic name [" + topicName + "]", ex);      }      catch (JMSException ex) {        throw new JmsException ("Couldn't get connection factory or topic", ex);      }      finally {        if (topicConnection != null) {          try {            topicConnection. close();          }          catch (JMSException ex) {            logger.logp (Level.WARNING, "com.interface21.jms.JmsTemplate",              "publish", "Failed to close topic", ex);          }        }      }    } 

The com.interface21.jms.JmsException exception is unchecked, meaning that we have a choice as to whether to catch it. Calling code can implement the PubSubMessageCreator inner interface of JmsTemplate (shown below) without deep knowledge of JNDI:

    public interface PubSubMessageCreator {      Message createMessage (TopicSession topicSession) throws JMSException;    } 

Thus a message can be published with the following code:

    JmsTemplate jmsTemplate = new JmsTemplate("jms/TopicFactory") ;    jmsTemplate. publish ("jms/topic/referencedata",      new JmsTemplate. pubSubMessageCreator() {      public Message createMessage(TopicSession session) throws JMSException {        TextMessage msg = session.createTextMessage();        msg.setText ( "This is a test message");        return msg;      }    }); 

Message consumers can also benefit from a simplifying infrastructure. Note that it's often best to use MDBs as message consumers, as the EJB container provides valuable infrastructure for JMS message consumption; however, we can't always use EJB. We always need to implement the MessageListener interface. However, we don't want to have to deal with the complexity of registering a listener with a topic, which also involves multiple JNDI lookups. The JmsTemplate class provides a convenience method for this, with the following signature:

    public TopicConnection subscribeToTopicNonDurable ( String topicName,        MessageListener listener, String messageSelector)        throws JmsException; 

A custom bean definition of class com.interface21.jms.JmsListenerBeanDefinition can automatically register any JMS listener to a given topic. The following example is from the sample application, in which the com.wrox.expertj2ee.ticket.referencedata.support.DataUpdateJmsListener JMS listener notifies caching objects of a data update:

    <bean name="referenceDataListener"        definition>      <customDefinition property= "listenerBean">          com.wrox.expertj2ee.ticket.referencedata.support.DataUpdateJmsListener      </customDefinition>      <customDefinition property="topicConnectionFactoryName">          jms/TopicFactory      </customDefinition>      <customDefinition property="topicName">          jms/topic/referencedata      </customDefinition>      <property name="cachingCalendar" beanRef="true">          calendar      </property>    </bean> 

This enables us to implement a simple JMS listener that responds to events and refreshes data cached in another application component without the need to write any low-level JMS code, as shown in the following listing:

    public class DataUpdateJmsListener implements MessageListener {      protected final Logger logger = Logger.getLogger(getClass().getName() );      private CachingCalendar calendar; 

      public void setCachingCalendar (CachingCalendar calendar) {        this.calendar = calendar;      } 

      public void onMessage (Message m) {        try {          logger.info ("Refresing calendar in response to JMS message [" + m                       + "]" );          calendar.refresh();        }        catch (Exception ex) {          logger.logp (Level.SEVERE, getClass().getName(), "onMessage",                     "Failed to refresh calendar", ex);        }      }    } 

This simple abstraction doesn't take care of all JMS requirements — the JMS API includes many features such as transacted sessions, which pose more problems for abstraction. However, it provides a simple infrastructure for the commonest needs.



Expert One-on-One J2EE Design and Development
Microsoft Office PowerPoint 2007 On Demand
ISBN: B0085SG5O4
EAN: 2147483647
Year: 2005
Pages: 183

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net