Inversion of Control and Dependency Injection
Let’s take a look at an EJB 2.1 client. We have a stateless session bean that performs simple calculations.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
public class Client { public static final String JNDI_CALC_HOME = "calculateHome"; public static void main(String[] args) { Client c = new Client(); c.execute(); } public void execute() { CalculateHome chome = null; Properties props = new Properties(); props.put(Context.PROVIDER_URL, "iiop://eudyptes:1050"); props.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.cosnaming.CNCtxFactory"); try { Context root = new InitialContext(); Object stub = root.lookup(JNDI_CALC_HOME); chome = (CalculateHome) PortableRemoteObject.narrow(stub, CalculateHome.class); root.close(); } catch (NamingException ce) { System.err.println("Could not find calculate"); ce.printStackTrace(); System.exit(1); } Calculate calculateEjb = null; try { System.out.println("GotHome. Creating EJB."); calculateEjb = chome.create(); System.out.println("Got Calculate bean."); } catch (CreateException ce) { System.err.println("Could not create the EJB"); ce.printStackTrace(); System.exit(1); } catch (RemoteException ce) { System.err.println("Could not create the EJB"); ce.printStackTrace(); System.exit(1); } // now try out some business methods try { System.out.println("adding 42 and 13 ..."); System.out.println(calculateEjb.add(42, 13)); System.out.println("subtracting 13 from 42 ..."); System.out.println(calculateEjb.subtract(42, 13)); } catch (RemoteException ce) { System.err.println("Could not create the EJB"); ce.printStackTrace(); } // be a good citizen and clean up after yourself. try { System.out.println("Removing the EJB"); calculateEjb.remove(); } catch (RemoveException e) { System.err.println("Could not remove the EJB"); e.printStackTrace(); } catch (RemoteException ce) { System.err.println("Could not remove the EJB"); ce.printStackTrace(); } } } |
The client needs to know the following items.
- the JNDI name for the EJB home
- Where the JNDI naming server is running
- The protocol used by the JNDI naming server
- The application server specific JNDI context factory implementation
Then the client needs to do some bookkeeping. The EJB spec uses the Factory Design Pattern to obtain an EJB (actually an EJBObject proxy). In the .85 version of the spec they actually called it the factory but was later changed to the EJB “home”. So, we need to get the home via JNDI and then use the Factory Method to get an instance. Then when we are done with the EJB we need to say so by “removing” it.
Using PortableRemoteObject.narrow
to downcast the stubs was a bit of a pain
but it was worse before that method existed. The client needed to know if the stub was
from a CORBA based or RMI based application server. Imagine maintaining that!
The JNDI properties were different for each application server. Here are a few.
For Sun reference implementation
java.naming.factory.initial=com.sun.jndi.cosnaming.CNCtxFactory
java.naming.provider.url=iiop://localhost:105
For Weblogic
java.naming.factory.initial=weblogic.jndi.WLInitialContextFactory
java.naming.provider.url=t3://localhost:7001
For Websphere 5
java.naming.factory.initial=com.ibm.websphere.naming.WsnInitialContextFactory
java.naming.provider.url=iiop://localhost:2809
java.naming.factory.url.pkgs=com.ibm.ws.naming
This was a bit simplified later when you were able to place a jndi.properties file in your classpath.
Or, you could write an ANT target that reads a properties file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<target name="runclient" depends="compile" > <fail unless="java.naming.factory.initial" message="java.naming.factory.initial is not set" /> <fail unless="java.naming.provider.url" message="java.naming.provider.url is not set" /> <echo> java.naming.factory.initial is ${java.naming.factory.initial} java.naming.provider.url is ${java.naming.provider.url} </echo> <java classname="${clientclass}" fork="yes"> <classpath refid="run.cmdlineclient.classpath"/> <sysproperty key="java.naming.factory.initial" value="${java.naming.factory.initial}"/> <sysproperty key="java.naming.provider.url" value="${java.naming.provider.url}"/> </java> </target> |
But that is still a pain. What if we could just hand the client the EJB
through a setter? Would that simplify the client?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public class Client { private Calculate calculateEjb; public void setCalculateEjb(Calculate calculateEjb) { this.calculateEjb = calculateEjb; } public void execute() { try { System.out.println("adding 42 and 13 ..."); System.out.println(calculateEjb.add(42, 13)); System.out.println("subtracting 13 from 42 ..."); System.out.println(calculateEjb.subtract(42, 13)); } catch (RemoteException ce) { System.err.println("Could not create the EJB"); ce.printStackTrace(); } } } |
What do you think? Looks simpler to me!
So now the client is not controlling how it gets the EJB. Something else is controlling that.
You can say that the configuration control has been inverted from the EJB client to another component.
The client is simply injected with it’s EJB dependency.
What component is responsible for this Inversion of Control (IoC)?
That depends.
EJB 3 will have this capability but there are other open source IoC containers available now. In another post I will show you how to do this sort of Dependency Injection (DI) using the Spring Framework which is currently the most popular IoC container available.