Tuesday, September 22, 2009

Various Springframework contexts in the J2EE application

A J2EE application can have EJB modules and web modules. These modules can integrate with the Springframework.

Integration of EJB and Springframework

Spring uses injection. The first obvious question is: How do the EJBs use the beans configured in Spring?
The answer seems to be that each Spring EJB bean has a private
instance variable beanFactoryLocator. This variable will store all the bean definitions from the various Spring xml files. (Note: It is a waste if there are many Spring EJBs. The javadoc below of AbstractEnterpriseBean says there is an option to use of a shared ApplicationContext between multiple EJBs.)
As a result of this bean configuration, the ejb-jar.xml will typically have the following for the EJB to indicate the path to the xml files to load the spring beans from:
<message-driven>
 <ejb-name>mdb1</ejb-name>
 <ejb-class>com.foo.Mdbean1</ejb-class>
 ......
 <env-entry>
  <env-entry-name>ejb/BeanFactoryPath</env-entry-name>
  <env-entry-type>java.lang.String</env-entry-type>
  <env-entry-value>
   classpath*:spring/fooContext.xml
  </env-entry-value>
 </env-entry>
 ......
</message-driven>
In the above, the value "ejb/BeanFactoryPath" should be written as is. It is hard-coded in the Spring class AbstractEnterpriseBean:
public static final String BEAN_FACTORY_PATH_ENVIRONMENT_KEY = "java:comp/env/ejb/BeanFactoryPath";
But this key might be able to be customized.

Now there is a house to store those beans. The outside code will need to interact with this house. So the next question is: How will the business logic use the beans in this house?
The answer seems to be that a bean in the house will be looked up using the bean factory and the bean name. The bean factory here refers to that bean "house". The following can be some code in some method of Mdbean1:
this.proxy = this.getBeanFactory().getBean(String
.valueOf(this.fooObjectLocator.getObject())));
The locator is a JndiObjectFactoryBean and is created in the constructor of the EJB. When the locator is created, you also set a jndi name. The following is some code from the constructor of Mdbean1.
if (this.fooObjectLocator == null)
    {
      this.fooObjectLocator = new JndiObjectFactoryBean();
      this.fooObjectLocator.setJndiName(FOO_OBJECT_PATH_ENVIRONMENT_KEY);
      try
      {
        this.fooObjectLocator.afterPropertiesSet();
      }
      catch (NamingException e)
      {
        LOGGER.fatal("OBJECT NOT FOUND[" + e.getMessage() + "]", e);
      }
    }
The spring EJBs inherit the following class:
public abstract class AbstractEnterpriseBean implements EnterpriseBean
{
  public static final String BEAN_FACTORY_PATH_ENVIRONMENT_KEY = "java:comp/env/ejb/BeanFactoryPath";

  /**
   * Helper strategy that knows how to locate a Spring BeanFactory (or
   * ApplicationContext).
   */
  private BeanFactoryLocator beanFactoryLocator;

  /** factoryKey to be used with BeanFactoryLocator */
  private String beanFactoryLocatorKey;

   ......

  void loadBeanFactory() throws BeansException
  {
    if (this.beanFactoryLocator == null)
    {
      this.beanFactoryLocator = new ContextJndiBeanFactoryLocator();
    }
    if (this.beanFactoryLocatorKey == null)
    {
      this.beanFactoryLocatorKey = BEAN_FACTORY_PATH_ENVIRONMENT_KEY;
    }

    this.beanFactoryReference = this.beanFactoryLocator
        .useBeanFactory(this.beanFactoryLocatorKey);

    // We cannot rely on the container to call ejbRemove() (it's skipped in
    // the case of system exceptions), so ensure the the bean factory
    // reference is eventually released.
    WeakReferenceMonitor.monitor(this,
        new BeanFactoryReferenceReleaseListener(this.beanFactoryReference));
  }
   .....
}
The javadoc of this class has the following:

* Provides a standard way of loading a Spring BeanFactory. Subclasses act as a
* facade, with the business logic deferred to beans in the BeanFactory. Default
* is to use a {@link org.springframework.context.access.ContextJndiBeanFactoryLocator},
* which will initialize an XML ApplicationContext from the class path (based on a JNDI
* name specified). For a different locator strategy, setBeanFactoryLocator
* may be called (before your EJB's ejbCreate method is invoked,
* e.g. in setSessionContext). For use of a shared ApplicationContext between
* multiple EJBs, where the container class loader setup supports this visibility, you may
* instead use a {@link org.springframework.context.access.ContextSingletonBeanFactoryLocator}.
* Alternatively, {@link #setBeanFactoryLocator} may be called with a custom implementation
* of the {@link org.springframework.beans.factory.access.BeanFactoryLocator} interface.


Various Spring contexts for EJB module, Web module, and Servlet, and their relationship

1. The ejb Mdbean1 has its Spring Context(the bean factory). For Spring message driven bean and stateless session bean, this bean factory is created in the ejb life cycle method ejbCreate(). Both the Spring class AbstractMessageDrivenBean and AbstractStatelessSessionBean have the following method:
public void ejbCreate() throws CreateException {
  loadBeanFactory();
  onEjbCreate();
}
The xml files that contain the Spring beans are defined in the ejb-jar.xml file:
<env-entry>
<env-entry-name>ejb/BeanFactoryPath</env-entry-name>
<env-entry-type>java.lang.String</env-entry-type>
<env-entry-value>
classpath*:spring/fooContext.xml
</env-entry-value>
</env-entry>
This Spring context is attached to Mdbean1 as an instance variable of type BeanFactoryLocator.

2. The web module has three Spring contexts. These contexts have nothing to do with the Spring context in the ejb module. The three contexts are:

(1) the parent context of the root web application context. It is created via the Spring ContextLoaderListener and the context parameters in the web.xml:
<context-param>
<param-name>locatorFactorySelector</param-name>
<param-value>classpath*:spring/beanRefContext.xml</param-value>
</context-param>
<context-param>
<param-name>parentContextKey</param-name>
<param-value>fooID</param-value>
</context-param>
......
......
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
Note that the param-name "locatorFactorySelector" is exactly the hard-coded value
LOCATOR_FACTORY_SELECTOR_PARAM in the code
servletContext.getInitParameter(LOCATOR_FACTORY_SELECTOR_PARAM);
And the param-name "parentContextkey" is exactly the hard-coded value LOCATOR_FACTORY_KEY_PARAM
in the code
servletContext.getInitParameter(LOCATOR_FACTORY_KEY_PARAM);
The two hard-coded values are defined in the class org.springframework.web.context.ContextLoader. There might be customized way to specify the key and the selector.
This context is created at the same time when the root web application context is created. And it is saved to the instance variable of the root web application context as its parent conetxt.
ApplicationContext parent = loadParentContext(servletContext);
this.context = createWebApplicationContext(servletContext, parent);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
Note: if the param "parentContextKey" is not specified in web.xml, the context will not be created. It will be null.
The actual method that creates the context is the API method from the javax interface ServletContextListener that the Spring class ContextLoaderListener implements:
public void contextInitialized(ServletContextEvent event) {
  this.contextLoader = createContextLoader(); 
  this.contextLoader.initWebApplicationContext(event.getServletContext());
}
(2) the root web application context. It is created via the same ContextLoaderListener that creates the parent context in (1) and the following parameter in the web.xml:
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/foo1.xml /WEB-INF/foo-2.xml</param-value>
</context-param>
As shown by the code of ContextLoader in (1), it uses the context in (1) as its parent context. The context is attached to the ServletContext using a specific attribute.

(3) the context created by the Spring DispatcherServlet. It is created via the following in the web.xml file:
<servlet>
<servlet-name>xyz</servlet-name>
<servlet-class>
org.springframework.web.servlet.DispatcherServlet
</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
The DispatcherServlet uses the servlet life cycle method init() to create the context and saves it to the private variable of type WebApplicationContext. This context uses the context created in (2) as its parent context. It gets the parent context using the ServletContext and the specific attribute. It uses the namespace to load the bean xml files. The servlet name in web.xml is "xyz". So it sets the namespace to "xyz-servlet". And by default, it will load the bean xml files from WEB-INF/xyz-servlet.xml.

How is the parent context used?

In the cases of the root web application context and the servlet Spring context, they have parent context and store it in the instance variable. What is the use of this parent context? I have not done any research on this. But I guess that when they look up a bean, they will first try to find it from the beans loaded through their own xml files. If they can not find the bean, then they will use the parent context to get it.

Some Important Beans in the Spring XML Configuration File

Beans for Transaction Management
Spring has its own transaction management mechanism. The following is a sample configuration in the bean xml file.
<bean id="myTransactionManager"
 class="org.springframework.transaction.jta.JtaTransactionManager">
</bean>
<bean id="matchAllWithPropReq"
 class="org.springframework.transaction.interceptor.MatchAlwaysTransactionAttributeSource">
 <property name="transactionAttribute">
  <value>PROPAGATION_REQUIRED,-MyCheckedException</value>
 </property>
</bean>

<bean id="matchAllTxInterceptor"
 class="org.springframework.transaction.interceptor.TransactionInterceptor">
 <property name="transactionManager">
  <ref bean="myTransactionManager" />
 </property>
 <property name="transactionAttributeSource">
  <ref bean="matchAllWithPropReq" />
 </property>
</bean>
The xml bean file that contains the business objects can have the following:
<bean id="autoProxyCreator"
 class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
 <property name="interceptorNames">
  <list>
   <idref bean="matchAllTxInterceptor" />
   <idref bean="other interceptors, can be user defined" />
  </list>
 </property>
 <property name="beanNames">
  <list>
   <idref local="foo1Bo" />
   <idref local="foo2Bo" />
   ......
   <idref local="foonBo" />
  </list>
 </property>
</bean>
Beans for View
The following bean is an example bean that maps the URLs to spring controllers in the Spring MVC framework.
<bean id="myUrlMapping"
 class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
 <property name="mappings">
  <props>
   <prop key="/foo1.htm">bar1Controller</prop>
   <prop key="/foo2.htm">bar2Controller</prop>
   ......
  </props>
 </property>
</bean>
The following is an example bean for message bundle.
<bean id="messageSource" class="org.springframework.context.support.ResourceBundleMessageSource">
<property name="basename">
<value>messages</value>
</property>
</bean>
The following is an example bean used for view resolver.
<bean id="viewResolver" class="org.springframework.web.servlet.view.ResourceBundleViewResolver">
<property name="basename">
<value>views</value>
</property>
</bean>
In the WEB-INF/classes directory, you put two files messages.properties and views.properties. They can look like the following.
messages.properties:
messageId1=default message for Id1.
messageWithArguments=this message has arg1 {0} and arg2 {1}.
views.properties:
viewName1.(class)=org.springframework.web.servlet.view.InternalResourceView
viewName1.url=/jsp/view1.jsp
viewName2.(class)=org.springframework.web.servlet.view.InternalResourceView
viewName2.url=/jsp/view2.jsp

No comments:

Post a Comment