X

Do's and Don'ts for JavaEE Resource Injection

Guest Author

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 href="http://weblogs.java.net/blog/ss141213/archive/2005/12/dont_use_persis_1.html">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 {
face="Cumberland, monospace">public void begin(String userName) {...}
face="Cumberland, monospace">@Remove
face="Cumberland, monospace">public void end(String userName) {
face="Cumberland, monospace">//remove this bean when we finished shopping...
face="Cumberland, monospace">}
}

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.  href="http://www.jcp.org/en/jsr/detail?id=244">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.








Join the discussion

Comments ( 2 )
  • Luc Duponcheel Wednesday, November 8, 2006
    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
  • guest Wednesday, November 8, 2006
    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

Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.