The fact you can only inject other EJB's or something you can annotate with @Resource (JMS Queue, JCA and so on) is not enough in the case I want to externalize some of the logic of the EJB in a collaborator and unit testing the isolated EJB by mocking the collaborator.
Also properties files are quite tricky to use with EJB3, it would not be nice to have to access the file directly from the EJB in each EJB!
Since what I needed was just injecting something I thought it was a nice opportunity to give a try to Google Guice and I must say I am really satisfied with the final result :)
Since EJB are instantiated by the application server the only way to inject something is through an interceptor.
Therefore we will have a singleton EJB holding the Guice injector and an interceptor setting the dependencies on the EJB before the method invocation.
Let's first define the EJB interface
import com.google.inject.Injector;
@Local
public interface GuiceInjectorHolder {
public Injector getInjector();
}
Next let's implement the EJB
@Singleton
public class GuiceInjectorHolderBean implements GuiceInjectorHolder{
private final Injector injector = Guice.createInjector(new PropertyModule());
@Override
public Injector getInjector(){
return injector;
}
/**
* Convenient Guice module for loading configuration values from property files
*
* @author diego
*
*/
private static class PropertyModule extends AbstractModule{
@Override
protected void configure() {
Properties prop = loadProperties();
Names.bindProperties(binder(), prop);
}
private Properties loadProperties() {
Properties result = new Properties();
try {
result.load(getClass().getResourceAsStream("/application.properties"));
} catch (IOException e) {
throw new RuntimeException(e);
}
return result;
}
}
}
As you can see an extension of the AbstractModule is provided to the Guice constructor. What it mainly does is simply loading properties from application.properties, which has to be found in the classpath. With few lines of code you can now make your application fully configurable :)It is time now to implement the Interceptor:
public class GuiceInterceptor {
@EJB
private GuiceInjectorHolder injectorHolder;
@AroundInvoke
public Object injectByGuice(InvocationContext context) throws Exception{
injectorHolder.getInjector().injectMembers(context.getTarget());
return context.proceed();
}
}
And finally our EJB
@Stateless
@Interceptors(value = { GuiceInterceptor.class })
public class InjectedEJB implement SomeLocalInterface{
@Inject
private Collaborator collaborator;
@Inject
private String configurableValue;
public void doSomething() {
//something before
collaborator.doSomething(configurableValue);
//something after
}
}
Now you can mock your collaborator and unit test the EJB.
Next time I will talk about mocking an EntityManager.
Bellu Blog, mi piacisti 'mbare!
ReplyDeleteYour solution worked for me. Really awesome. Thanks a lot
ReplyDelete