A Sunny Commune - Cheng's Blog

« 资源注射,一生一次: EJB 3... | Main | NullPointerException... »

http://blogs.sun.com/chengfang/date/20060316 Thursday March 16, 2006

Do's and Don'ts for JavaEE Resource Injection

Resource injection has made writing JavaEE applications very easy, but it has also made it easy to make mistakes that are hard to debug. Compared to the old-style lookup, resource injection requires a class-level variables as a place holder for the injected resource.  This variable can then be shared, read and updated by the entire class, all subclasses, and all other classes that have a reference to it.  As a result, the value of the holding variable may be out of sync with that of the resource in the naming context.  With on-demand lookup, it is usually not a concern since the resource is not cached and never modified.

The concept of shared resources always brings up the issue of thread-safety.  Since EJB components are not allowed to manage threads, it is not a problem there.  But it is a big deal for web applications, where one servlet instance can concurrently process multiple HTTP requests.

Here are a list of points I would keep in mind when using JavaEE resource injection, in no particular order.  I use term "resource injection" for both EJB and resources, and so it really means "dependency injection."

1. Do not reassign value to injected resources

Injection happens only once when a component instance is created, and it lasts until the instance is destroyed. Applications should not attempt to reassign the value of a  injected resource. Consider the following example:

public class FooServlet extends HttpServlet {
    @Resource(name="jdbc/defaultDataSource", mappedName="jdbc/__default")
    private DataSource defaultDataSource;

    public void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        Connection conn = null;
        try {
            conn = defaultDataSource.getConnection();
            //more operations
        } catch (SQLException e) {
            throw new ServletException(e);
        } finally {
            if(conn != null) {
                try {
                    conn.close();
                } catch (SQLException ignoreIt) {
                }
            }
            defaultDataSource = null;
        }
    }
}

Depending on the size of the FooServlet instance pool, the first a few HTTP requests will be handled correctly.  Once any instance gets reused, the client will get NullPointerException since the instance variable defaultDataSource has been set to null from previous invocation.

Can we declare defaultDataSource to be final, to prevent resource overwriting?  No, that would make it impossible for the container to initialize it.  Actually, JavaEE specification clearly states that injections fields and methods cannot be final.  See the next item for what we can do.

2. Restrict access to injection fields and methods, even for setter methods

When we inject a resource, we trust the container to manage the lifecycle of the resource, e.g., create, destroy, initialize, cleanup, open, and close.  To prevent application components from interfering with the container service, we should make these injection fields and methods as restricted as possible.  JavaEE platform requires that injection fields and methods can have any type of access control level: private, package default, protected, and public.

One may argue that, since injection fields and methods are not exposed via bean's business interfaces, and a bean class cannot be subclassed, therefore they are only accessible within the bean class itself.  Well, there are exceptions to this, with the introduction of interceptors.  This example shows how other classes can access and update injected resource of the bean that has a private resource field and a public setter method.

@Stateless
@Interceptors({InterceptorA.class})
public class FooBean implements FooIF {
    private String contactEmailAddress;

    //This method should be private
    @Resource(name="contact-email-address" description="declared as an env-entry in ejb-jar.xml")
    public void setContactEmailAddress(String contactEmailAddress) {
        this.contactEmailAddress = contactEmailAddress;
    }
    ...
}

public class InterceptorA {
    @PostConstruct
    public void postConstruct(InvocationContext inv) {
        FooBean fooBean = (FooBean) inv.getTarget();
        fooBean.setContactEmailAddress(null);
        try {
            inv.proceed();
        } catch (RuntimeException e) {
            throw e;
        } catch (Exception e) {
            throw new IllegalStateException(e);
        }
    }
}

Interceptors and their superclasses all have a reference to the bean instance, and can potentially modify any visible injected resource.  To make applications more maintainable, I would make all injection fields and methods private, and expose the resource via a public or protected getter method.

3. Make injected resource thread-safe in Servlet

This is not a new issue, nor is it specific to resource injection.  Any mutable class-level variables in a Servlet may be modified by multiple threads concurrently.  So we should either remove them, or manage them correctly.  Since resource injection makes it so easy to obtain resources, we are tempted to use it more than necessary.  Sahoo wrote a blog here about using @PersistenceContext in web applications.

4. Do not inject a stateful object (e.g., Stateful Session bean) into a stateless component (e.g., Servlet, Stateless Session bean)

Stateless components are usually pooled and reused.  Resources are injected when these instances are created, and stay there permanently even after a business invocation.  The next business invocation on the same instance will use the same resources.  It is fine if the resource is stateless or read-only; but for a stateful object, its state is mixed with previous users' state and no longer accurate. 

public class HelloBean implements HelloIF {
    @EJB(name="ejb/shoppingCartBean")
    private ShoppingCartIF shoppingCartBean;

    public void doShopping(String userName) {
        shoppingCartBean.begin(userName);
        //more operations
        shoppingCartBean.end(userName);
    }
}

@Stateful
public class ShoppingCartBean implements ShoppingCartIF {
public void begin(String userName) {...}
@Remove
public void end(String userName) {
//remove this bean when we finished shopping...
}
}

In this example, for any instance of HelloBean, shoppingCartBean is only initialized once at instantiation time, and used by all business invocations.  Apparently, the above HelloBean is not well designed.  Moreover, if the stateful bean business method end(String userName) has been annotated with @Remove(), the stateful session bean will be removed after end(String userName) completes.  Next time when HelloIF.doShopping(String userName) business method is invoked, the container will throw java.rmi.NoSuchObjectException for a remote invocation, or javax.ejb.NoSuchEJBException for a local invocation.

5. Declare injection fields and methods static in application client main class

6. Do not declare injection fields and methods static for other component types

This is required by JavaEE specification EE.5.2.3:

For all classes except application client main classes, the fields or methods must not be static. Because application clients use the same lifecycle as J2SE applications, no instance of the application client main class is created by the application client container. Instead, the static main method is invoked. To support injection for the application client main class, the fields or methods annotated for injection must be static.

In the following example, HelloIF and EchoIF are business interfaces, and HelloBean and EchoBean are bean classes that implement the two business interfaces, respectively.  We also assume there are no other beans in the EAR that implement the same business interfaces.


correct

wrong

field injection in a servlet or EJB 3 bean

@EJB private EchoIF echoBean;

@EJB static private EchoIF echoBean;



method injection in a servlet or EJB 3 bean

private HelloIF helloBean;

@EJB
private void setHelloBean(HelloIF helloBean) {
    this.helloBean = helloBean;
}

private static HelloIF helloBean;

@EJB
private static void setHelloBean(HelloIF helloBean) {
    this.helloBean = helloBean;
}

field injection in an application client main class

@EJB static private EchoIF echoBean;

@EJB private EchoIF echoBean;



method injection in an application client main class

private static HelloIF helloBean;

@EJB
private static void setHelloBean(HelloIF helloBean) {
    MainClass.helloBean = helloBean;
}

private HelloIF helloBean;

@EJB
private void setHelloBean(HelloIF helloBean) {
    this.helloBean = helloBean;
}


7. Do not use EJB bean class as injection type

Use EJB business interface instead.  EJB and its client should interact via remote/local business interfaces, or remote/local component interfaces.  A client should not even know what the bean class is. 


correct

wrong

field injection in a servlet

@EJB private EchoIF echoBean;

@EJB private EchoBean echoBean;



method injection in a servlet

private HelloIF helloBean;

@EJB
private void setHelloBean(HelloIF helloBean) {
    this.helloBean = helloBean;
}

private HelloBean helloBean;

@EJB
private void setHelloBean(HelloBean helloBean) {
    this.helloBean = helloBean;
}


8. For the same resource, do not use both field injection and method injection

It is confusing and not necessary.  JavaEE specification EE.5.2.3 disallows it:

Each resource may only be injected into a single field or method of a given name in a given class. Requesting injection of the java:comp/env/com.example.MyApp/myDatabase resource into both the setMyDatabase method and the myDatabase field is an error. Note, however, that either the field or the method could request injection of a resource of a different (non-default) name. By explicitly specifying the JNDI name of a resource, a single resource may be injected into multiple fields or methods of multiple classes.

9. Use mappedName() with caution

mappedName field in @Resource and @EJB annotations specifies a "A product specific name that this resource should be mapped to."  For example,

@Stateless
public class EchoBean implements EchoIF {

    @EJB(name="ejb/HelloBean", mappedName="this.is.its.physical.name.in.runtime.environment")
    private HelloIF helloBean;

    @Resource(mappedName="physical.name.for.this.datasource.as.configured.in.appserver")
    private DataSource defaultDataSource;
    ...
}

With this technique, deployers no longer have to map ejb-ref, ejb-local-ref, resource-ref, resource-env-ref, or message-destination-ref to their physical name in target runtime environment.  However, this is the caution, as quoted from javaee_5.xsd:

Many application servers provide a way to map these local names to names of resources known to the application server.  This mapped name is often a global JNDI name, but may be a name of any form.

Application servers are not required to support any particular form or type of mapped name, nor the ability to use mapped names.  The mapped name is product-dependent and often installation-dependent.  No use of a mapped name is portable.

10. Provide sufficient amount of information for injections

All fields of @EJB and @Resource annotations are optional, but developers are expected to provide sufficient amount of information in order for injection to work.  The amount of information needed depends on the usage context.  For instance, a stateless session bean has the following injections:

package example.pkg;

@Stateless
public class EchoBean implements EchoIF {

    @EJB private HelloIF helloBean;

}

This injection is the same as:

@EJB(name="example.pkg.EchoBean/helloBean", beanName="HelloBean", beanInterface=HelloIF.class)
private HelloIF helloBean;

It assumes that there is only one EJB in the EAR that implements HelloIF, and therefore we do not need to specify beanName(); and that the beanInterface() is the same as the field type (HelloIF), and that the relative logical jndi name for this ejb reference will be example.pkg.EchoBean/helloBean.  If any one of the above is not what you expect it to be, then you need to provide more information.




Comments:

Hello, how can an application client use a business delegate in ejb3.0 (in order to hide clients from ejb technology). All my attemps failed with nullpointerexceptions [ the stateless sessionbean that is now injected in the business delegate is null ] is it not a pity that we have to give up this design pattern

Posted by Luc Duponcheel on November 08, 2006 at 03:17 PM EST #

Your business delegates are not JavaEE components and thus are not required by JavaEE platform to support injections. Nevertheless, you can still do:

1. define <ejb-ref> and <ejb-local-ref> in deployment descriptors like web.xml or application-client.xml, and do jndi lookup inside these business delegates.

2. inject target ejb and resources into component classes that share the same naming context as business delegates, which can then do jndi lookup. Compared to 1, you don't have to declare <ejb-ref> or <ejb-local-ref>

See Glassfish EJB FAQ: https://glassfish.dev.java.net/javaee5/ejb/EJB_FAQ.html#POJOLocalEJB

Posted by 192.18.128.5 on November 08, 2006 at 03:46 PM EST #

Post a Comment:
  • HTML Syntax: NOT allowed