Can @Resource be used to inject primitives in EJB3.0?

Jonathan S. Fisher picture Jonathan S. Fisher · Jun 16, 2011 · Viewed 9.5k times · Source

Using Glassfish, I can setup a string jndi entry:

JNDI name: "com/xyzcompany/echo/EchoServiceBean/viewName"
Factory Class: org.glassfish.resources.custom.factory.PrimitivesAndStringFactory
Properties: value="Testing123"

I can then inject this container configured string into my EJB:

    @Resource(lookup = "com/xyzcompany/echo/EchoServiceBean/viewName")
    String viewName;

The lookup= appears to internally do an InitialContext.lookup(...). However, this uses ejb3.1, but unfortunately my prod environment is only ejb3.0.

I guess i'm trying to figure out is there a way to use @Resource(name=) or @Resource(mappedName=) to do something similar? name= appears to be application specific, so I should be able to somehow map a relative name to a global JNDI name, but I can't figure out what annotation does the mapping.

Thanks!

Answer

David Blevins picture David Blevins · Jun 17, 2011

All 8 primitive wrappers and String are supported @Resource types and are available for lookup or injection via declaring them in the standard ejb-jar.xml file.

Declaring the name value (and type) pairs

This is done with the <env-entry> xml element in deployment descriptor.

In EJB 3.0 you have to do this for each bean that wishes to reference the same name/value pairs. This is because EJB was originally designed different than Servlets and each EJB literally gets its own private JNDI namespace, java:comp/env, whereas all Servlets in the same module share the same java:comp/env.

<ejb-jar>
  <enterprise-beans>
    <session>
      <ejb-name>MySessionBean</ejb-name>
      <env-entry>
        <env-entry-name>myBoolean</env-entry-name>
        <env-entry-type>java.lang.Boolean</env-entry-type>
        <env-entry-value>true</env-entry-value>
      </env-entry>
      <env-entry>
        <env-entry-name>myString</env-entry-name>
        <env-entry-type>java.lang.String</env-entry-type>
        <env-entry-value>hello world</env-entry-value>
      </env-entry>
      <env-entry>
        <env-entry-name>myDouble</env-entry-name>
        <env-entry-type>java.lang.Double</env-entry-type>
        <env-entry-value>1.1</env-entry-value>
      </env-entry>
      <env-entry>
        <env-entry-name>myLong</env-entry-name>
        <env-entry-type>java.lang.Long</env-entry-type>
        <env-entry-value>12345678</env-entry-value>
      </env-entry>
      <env-entry>
        <env-entry-name>myFloat</env-entry-name>
        <env-entry-type>java.lang.Float</env-entry-type>
        <env-entry-value>1.3</env-entry-value>
      </env-entry>
      <env-entry>
        <env-entry-name>myInteger</env-entry-name>
        <env-entry-type>java.lang.Integer</env-entry-type>
        <env-entry-value>1024</env-entry-value>
      </env-entry>
      <env-entry>
        <env-entry-name>myShort</env-entry-name>
        <env-entry-type>java.lang.Short</env-entry-type>
        <env-entry-value>42</env-entry-value>
      </env-entry>
      <env-entry>
        <env-entry-name>myByte</env-entry-name>
        <env-entry-type>java.lang.Byte</env-entry-type>
        <env-entry-value>128</env-entry-value>
      </env-entry>
      <env-entry>
        <env-entry-name>myCharacter</env-entry-name>
        <env-entry-type>java.lang.Character</env-entry-type>
        <env-entry-value>D</env-entry-value>
      </env-entry>
    </session>
  </enterprise-beans>
</ejb-jar>

For readers lucky enough to be using EJB 3.1, you can use global JNDI and declare them in the application.xml and look them up from anywhere via java:app/myString. A feature most vendors have had for years which is now finally standard as of Java EE 6. Injection of those entries is also possible via @Resource(lookup="java:app/myString")

Also new in Java EE 6 is support for two extra env-entry-type types, java.lang.Class and any enum. For example:

<env-entry>
  <env-entry-name>myPreferredListImpl</env-entry-name>
  <env-entry-type>java.lang.Class</env-entry-type>
  <env-entry-value>java.util.ArrayList</env-entry-value>
</env-entry>
<env-entry>
  <env-entry-name>myBillingStragety</env-entry-name>
  <env-entry-type>java.lang.Class</env-entry-type>
  <env-entry-value>org.superbiz.BiMonthly</env-entry-value>
</env-entry>
<env-entry>
  <env-entry-name>displayElapsedTimeAs</env-entry-name>
  <env-entry-type>java.util.concurrent.TimeUnit</env-entry-type>
  <env-entry-value>MINUTES</env-entry-value>
</env-entry>
<env-entry>
  <env-entry-name>myFavoriteColor</env-entry-name>
  <env-entry-type>org.superbiz.ColorEnum</env-entry-type>
  <env-entry-value>ORANGE</env-entry-value>
</env-entry>

Referencing them with Injection

Any of the above can be injected via @Resource. Just don't forget to fill in the name attribute to match the <env-entry-name>

@Stateless
public class MySessionBean implements MySessionLocal {

    @Resource(name="myString")
    private String striing;

    @Resource(name = "myDouble")
    private Double doouble;

    @Resource(name = "myLong")
    private Long loong;

    @Resource(name = "myName")
    private Float flooat;

    @Resource(name = "myInteger")
    private Integer inteeger;

    @Resource(name = "myShort")
    private Short shoort;

    @Resource(name = "myBoolean")
    private Boolean booolean;

    @Resource(name = "myByte")
    private Byte byyte;

    @Resource(name = "myCharacter")
    private Character chaaracter;

}

Referencing them with JNDI

These names can also be standardly looked up via the javax.naming.InitialContext in the EJBs private and portable java:comp/env namespace.

@Stateless
public class MySessionBean implements MySessionLocal {

    @PostConstruct
    private void init() {

        try {
            final InitialContext initialContext = new InitialContext();// must use the no-arg constructor

            final String myString = (String) initialContext.lookup("java:comp/env/myString");
            final Boolean myBoolean = (Boolean) initialContext.lookup("java:comp/env/myBoolean");
            final Double myDouble = (Double) initialContext.lookup("java:comp/env/myDouble");
            final Long myLong = (Long) initialContext.lookup("java:comp/env/myLong");
            final Float myFloat = (Float) initialContext.lookup("java:comp/env/myFloat");
            final Integer myInteger = (Integer) initialContext.lookup("java:comp/env/myInteger");
            final Short myShort = (Short) initialContext.lookup("java:comp/env/myShort");
            final Byte myByte = (Byte) initialContext.lookup("java:comp/env/myByte");
            final Character myCharacter = (Character) initialContext.lookup("java:comp/env/myCharacter");
        } catch (NamingException e) {
            throw new EJBException(e);
        }
    }
}

Referencing them with the SessionContext

In EJB 3.0 as part of the simplification effort we added the ability to use the javax.ejb.SessionContext to do lookups. It is essentially the same, but has a little bit of sugar on it.

  • the java:comp/env prefix is not required
  • does not throw a checked exception (will instead throw EJBException for missing names)

Service Locator patterns were all the buzz in 2003 so we decided to build a little bit of convenience into the EJB API.

@Stateless
public class MySessionBean implements MySessionLocal {

    @Resource
    private SessionContext sessionContext;

    @PostConstruct
    private void init() {

        final String myString = (String) sessionContext.lookup("myString");
        final Boolean myBoolean = (Boolean) sessionContext.lookup("myBoolean");
        final Double myDouble = (Double) sessionContext.lookup("myDouble");
        final Long myLong = (Long) sessionContext.lookup("myLong");
        final Float myFloat = (Float) sessionContext.lookup("myFloat");
        final Integer myInteger = (Integer) sessionContext.lookup("myInteger");
        final Short myShort = (Short) sessionContext.lookup("myShort");
        final Byte myByte = (Byte) sessionContext.lookup("myByte");
        final Character myCharacter = (Character) sessionContext.lookup("myCharacter");
    }
}

Side note on IntialContext evilness

Also, with my vendor hat on, I can tell you that there's a fair bit of slow plumbing that can be avoided under the hood with the SessionContext lookup.

When you do 'java:' lookups on an InitialContext, the call goes to the VM, through a bunch of hoops to find who can resolve that name, then eventually to the vendor who will have to lookup state from the thread to figure out who asked and what namespace they're supposed to get. It does this on each and every call no matter what properties you pass into the InitialContext and what context the vendor initialized in its construction. The 'java:' simply jumps over all that. It's a rather frustrating part of being a vendor. It's also why the new javax.ejb.embedded.EJBContainer API does not use InitialContext anywhere at all and just references javax.naming.Context which is an actual interface rather than a concrete "factory" class with intense and obtuse plumbing.

Doing the call on SessionContext should be much faster if the vendor did it right. In OpenEJB at least, all the above including the ThreadLocal is skipped and the call goes right into that bean's JNDI namespace which is already attached to the SessionContext.

Another way to avoid the InitialContext overhead is to simply lookup java:comp/env once in the @PostConstruct and keep that resulting Context object and only use that. Then don't prefix lookups with java:comp/env/ and just lookup the names directly such as myString and myInteger. It will be faster, guaranteed.