Dependency Injection and replacing dependencies in CDI/Weld

2 Flares 2 Flares ×

From time to time, in a system I develop, which uses EJB3 beans as the basic “component model”, I have a need to do some operations using a new entity manager (but still in the same transaction). The problem here is that if I invoke a method on another bean, which has an entity manager injected (using @PersistenceContext EntityManager em), the entity manager will be the original one. There is no way to temporarily replace it or any other dependency, just for the time of one invocation.

That’s why I wanted to see if that would be possible to achieve using the recently released Weld framework, reference implementation of the CDI specification (part of JEE 6). Unlike in Seam, where injection is done whenever a method on a managed bean is invoked (which would theoretically make it easier to do the temporary replacement), in CDI injection happens once, when the objects are constructed. So the only way (at least the only way I see) to implement temporary replacement is to wrap the injected beans in some kind of an interceptor.

In CDI, injected beans can either be “stand-alone”, or come from a producer (factory) method (annotated with @Producer). As EntityManagers are typically created by producer methods, in the example below I assume this scenario. It is possible to slightly change the code so that it works for normal beans, however I didn’t manage to create a nice universal solution (which wouldn’t simply do an “if” to check if the bean is created by a producer method).

To test, we define a simple bean which will have a dependency injected via constructor injection:

1
2
3
4
5
6
7
8
9
10
11
12
@ApplicationScoped
public class FirstBean implements Serializable {
    private ISecondBean second;
    public FirstBean() { }
 
    @Inject
    public FirstBean(ISecondBean second) { this.second = second; }
 
    public void start(int c) {
        System.out.println("Type of second: " + second.getMessage(c));
    }
}

The interface ISecondBean has two implementations:

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface ISecondBean extends Serializable {
    String getMessage(int c);
}
 
@Alternative
public class SecondBeanA implements ISecondBean {
    public String getMessage(int c) { return "variant A; " + c; }
}
 
@Alternative
public class SecondBeanB implements ISecondBean {
    public String getMessage(int c) { return "variant B; " + c; }
}

The @Alternative annotation makes the bean disabled by default, so that Weld doesn’t try to inject an instance of it when there’s a matching injection point. That’s because we want to produce implementations of ISecondBean using a producer method:

1
2
3
4
5
6
7
@ApplicationScoped
public class SecondBeanProducer {
    @Produces
    public ISecondBean getSecondBean() {
        return new SecondBeanA();  // by default we use the A variant
    }
}

A very nice feature of Weld/CDI, especially for evaluation purposes, is that it’s possible to use it very easily in Java SE. To test our beans, all we need to do is run the org.jboss.weld.environment.se.StartMain, putting a jar with our classes in the classpath. One important thing is that the jar must contain a beans.xml file (it can be empty, but it must exist). We can observe the container startup-event to do some work:

1
2
3
4
5
6
7
8
9
10
11
12
@ApplicationScoped
public class Main {
    @Inject
    private FirstBean first;
 
    public void start(@Observes ContainerInitialized event) throws Exception {
        System.out.println("Welcome to Main!");
        first.start(1);
        first.start(2);
        first.start(3);
    }
}

The result will be:

1
2
3
4
Welcome to Main!
Type of second: variant A; 1
Type of second: variant A; 2
Type of second: variant A; 3

Now we finally come to the point of implementing the replaceable dependencies. As you may have guessed, ISecondBean corresponds to EntityManager, and that’s the dependency that we will try to replace.

To do that, we first have to define an interceptor. That’s quite easy and intuitive in CDI/Weld (documentation here). There are three steps:

  1. write an annotation, which will be an interceptor binding: all methods annotated with that annotation will be intercepted
  2. write the interceptor, annotating it with the annotation created earlier: that way Weld will know that the annotation binds the given interceptor
  3. enable the interceptor in beans.xml

Here’s the code (full interceptor code below):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@InterceptorBinding
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
public @interface ReplaceableResult {
}
 
@Interceptor
@ReplaceableResult
public class ReplaceableResultInterceptor {
    @AroundInvoke
    public Object replace(InvocationContext ctx) throws Exception {
        ...
    }
}
1
2
3
4
5
<beans>
    <interceptors>
        <class>test.ReplaceableResultInterceptor</class>
    </interceptors>
</beans>

Adding an interceptor for a method is easy: if we want to be able to temporarily replace implementations of ISecondBean, all we need to do is annotate the producer method with the new annotation. SecondBeanProducer now takes the form:

1
2
3
4
5
6
7
@ApplicationScoped
public class SecondBeanProducer {
    @Produces @ReplaceableResult
    public ISecondBean getSecondBean() {
        return new SecondBeanA();  // by default we use the A variant
    }
}

We can now test the temporary replacement by modifying the Main bean that we used before:

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
@ApplicationScoped
public class Main {
    @Inject
    private FirstBean first;
 
    public void start(@Observes ContainerInitialized event) throws Exception {
        System.out.println("Welcome to Main!");
        first.start(1);
 
        // Here we replace the implementation of ISecondBean just for 
        // the duration of one method call.
        ReplaceableResultInterceptor.withReplacement(
                ISecondBean.class,
                new SecondBeanB(), // we are using the B variant
                new Callable<Void>() {
                    @Override
                    public Void call() throws Exception {
                        first.start(2);
                        return null;
                    }
                });
 
        first.start(3);
    }
}

The result will now be:

1
2
3
4
Welcome to Main!
Type of second: variant A; 1
Type of second: variant B; 2
Type of second: variant A; 3

So the dependency in FirstBean was swapped! Which was our goal: for the duration of the bean method invocation, whenever a ISecondBean dependency is injected, the temporary implementation will be used.
Full code of the interceptor:

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
67
68
69
70
71
@Interceptor
@ReplaceableResult
public class ReplaceableResultInterceptor {
    private static ThreadLocal<Map<Class<?>, Object>> replacements 
        = new ThreadLocal<Map<Class<?>, Object>>() {
        @Override
        protected Map<Class<?>, Object> initialValue() {
            return new HashMap<Class<?>, Object>();
        }
    };
 
    @AroundInvoke
    public Object replace(InvocationContext ctx) throws Exception {
        Class<?> returnType = ctx.getMethod().getReturnType();
        if (!returnType.isInterface()) {
            throw new UnsupportedOperationException("Only results of methods " + 
               "which produce implementations of an interface can be replaced!");
        }
 
        // Getting the result of the producer method
        Object result = ctx.proceed();
 
        // Wrapping it in an interceptor, which on an invocation will check if 
        // there's a replacement
        result = Proxy.newProxyInstance(
                returnType.getClassLoader(),
                new Class[] { returnType },
                new ReplaceableInterceptor(returnType, result));
 
        return result;
    }
 
    public static <V> V withReplacement(Class<?> replacing, Object replacement, 
        Callable<V> toCall) throws Exception {
        boolean hasOld = false;
        Object old = null;
        try {
            hasOld = replacements.get().containsKey(replacing);
            old = replacements.get().put(replacing, replacement);
            return toCall.call();
        } finally {
            if (hasOld) {
                replacements.get().put(replacing, old);
            } else {
                replacements.get().remove(replacing);
            }
        }
    }
 
    private class ReplaceableInterceptor implements InvocationHandler {
        private final Class<?> replacementsKey;
        private final Object delegate;
 
        private ReplaceableInterceptor(Class<?> replacementsKey, 
            Object delegate) {
            this.replacementsKey = replacementsKey;
            this.delegate = delegate;
        }
 
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) 
            throws Throwable {
            if (replacements.get().containsKey(replacementsKey)) {
                return method.invoke(replacements.get().get(replacementsKey), 
                    args);                                                     
            } else {
                return method.invoke(delegate, args);
            }
        }
    }
}

Overall, CDI/Weld works very well and I’m very satisfied with it. One thing I’m missing is being to able to do programmatic configuration (Guice-style), but it’s already in the list of ideas for future portable extensions. I think that programmatic configuration and the ability to run Weld in JavaSE will create a great mix for “semi-integration” testing.

Adam

2 Flares Twitter 1 Facebook 0 Google+ 0 LinkedIn 1 Email -- 2 Flares ×