Circular Injection of Util Classes in EJB 3.0

In my last post Circular Dependencies of Session Beans I presented a method to use interceptors in session beans in order to inject beans. This works great until you want to add circular dependencies. Then you have to look up the beans by name and inject them into the bean. But this is kind of cumbersome. So, if it is possible, have a bean structure, which is topologically sortable and inject util classes having circular dependencies between each other. This post shows how to achieve the latter.

Again, we have an interceptor which intercepts the callbacks post construct and pre destroy as well as invocations of business methods of a bean. Recall, that an interceptor is created along with the corresponding bean and that it has the same lifecycle (see documentation):

Just like a bean class, an interceptor can be the target of Dependency injection. The format for how this works is the same, and the injection works off the same ENC [Entity Naming Context] as the bean to which the interceptor is bound. […] The interceptors are created at the same time as the bean instance is created, and dependecy injection occurs before the first business method is called.

In an external interceptor you may annotate exactly one method with @PostConstruct. This method intercepts the method annotated with @PostConstruct in the bean (analogous for @PreDestroy) (see documentation):

Callbacks for session beans can also be put into a separate class, configured as an interceptor. This means that your interceptors can initialise themselves when constructed.

Likely, exactly one method can be annotated with @AroundInvoke and intercepts the business method of the bean (if it is defined on class level). So, in the post construct callback the interceptor walks over all fields annotated with @Inject and injects the corresponding beans or util classes. The util classes are stored in a map from class type to the corresponding instance, which is set to null in the first place. As soon as a util has to be injected the first time it is instantiated and recursively walks over the fields annotated with @Inject. Circles are no problems since the instantiated util instance is inserted into the formerly mentioned map cache instantly (see the comment in the listing). The pre destroy callback is used to clear all injected fields. The following code snippet shows the abstract super class of the interceptor:

public abstract class InjectionInterceptor {
	private static final Logger log = Logger.getLogger(InjectionInterceptor.class.getName());
	private List<Object> beans;
	private Map<Class<?>, Object> utils;
	private Set<InjectionWalker> injectionWalkers;
	
	protected void setBeans(Object...beans) {
		this.beans = Arrays.asList(beans);
	}
	
	protected void setUtils(Class<?>...utilClasses) {
		this.injectionWalkers = new HashSet<InjectionWalker>((int)(1.5*(utilClasses.length+1)));
		this.utils = new HashMap<Class<?>, Object>((int)(1.5*utilClasses.length));
		for(Class<?> utilClass : utilClasses) {
			this.utils.put(utilClass, null);
		}
	}
	
	@PostConstruct
	protected void postConstruct(final InvocationContext ctx) throws Exception {
		if(this.beans == null || this.beans.isEmpty()) {
			return;
		}
		final InjectionWalker injectionWalker = new InjectionWalker(ctx.getTarget());
		this.injectionWalkers.add(injectionWalker);
		injectionWalker.inject();
		ctx.proceed();
	}

	@AroundInvoke
	public Object intercept(final InvocationContext ctx) throws Exception {
		return ctx.proceed();
	}

	@PreDestroy
	protected void preDestroy(final InvocationContext ctx) throws Exception {
		for(final InjectionWalker injectionWalker : this.injectionWalkers) {
			injectionWalker.clear();
		}
		this.injectionWalkers = null;
		this.beans = null;
		this.utils = null;
		ctx.proceed();
	}
	
	private class InjectionWalker {
		private final Set<Field> fields;
		private final Set<Field> injectedFields;
		private final Object target;
		
		public InjectionWalker(final Object target) {
			this.target = target;
			this.fields = new HashSet<Field>(Arrays.asList(target.getClass().getFields()));
			this.injectedFields = new HashSet<Field>((int)(1.5*fields.size()));
		}
		
		public void inject() {
			for(final Field field : this.fields) {
				if(field.getAnnotation(Inject.class) != null) {
					for(final Object bean : InjectionInterceptor.this.beans) {
						if(field.getType().isAssignableFrom(bean.getClass())) {
							try {
								field.set(this.target, bean);
								this.injectedFields.add(field);
							}
							catch (Throwable t) {
								log.log(Level.SEVERE, "InjectionWalker#inject(): target: " + 
										this.target + ", field: " + field.getName(), t);
							}
						}
					}
					for(final Entry<Class<?>, Object> utilEntry : InjectionInterceptor.this.utils.entrySet()) {
						if(field.getType().isAssignableFrom(utilEntry.getKey())) {
							try {
								if(utilEntry.getValue() == null) {
									final Object utilInstance = utilEntry.getKey().newInstance();
									// prevents endless loops, because of circular dependencies between util classes.
									utilEntry.setValue(utilInstance);
									final InjectionWalker injectionWalker = new InjectionWalker(utilInstance);
									InjectionInterceptor.this.injectionWalkers.add(injectionWalker);
									injectionWalker.inject();
								}
								field.set(this.target, utilEntry.getValue());
								this.injectedFields.add(field);
								
							}
							catch (Throwable t) {
								log.log(Level.SEVERE, "InjectionWalker#inject(): target: " + 
										this.target + ", field: " + field.getName(), t);
							}
						}
					}
				}
			}
		}
		
		public void clear() {
			if(this.injectedFields != null) {
				for(final Field field : this.injectedFields) {
					try {
						field.set(target, null);
					}
					catch(Throwable t) {
						log.log(Level.SEVERE, "InjectionWalker#clear(): target: " + 
								this.target + ", field: " + field.getName(), t);
					}
				}
			}
		}
	}
}

The interceptor itself is rather short:

public class UtilInterceptor extends InjectionInterceptor {
	@EJB
	private Bar bar;
	
	@PostConstruct
	@Override
	protected void postConstruct(final InvocationContext ctx) throws Exception {
		this.setBeans(bar);
		this.setUtils(FooUtil.class, BarUtil.class);
		super.postConstruct(ctx);
	}
}

Here are the util classes with circular dependency:

public class FooUtil {
	private final static Logger log = Logger.getLogger(FooUtil.class.getName());
	@Inject
	public Bar bar;
	@Inject
	public BarUtil barUtil;
	
	public void foo(String msg) {
		log.severe("FooUtil#foo(String): " + this + " msg: " + msg);
		this.barUtil.bar("this.barUtil#bar(String) called from " + this);
	}
	
	public void bar(String msg) {
		log.severe("FooUtil#bar(String): " + this + " msg: " + msg);
		this.bar.bar("this.bar#bar(String) called from " + this);
	}
}
public class BarUtil {
	private final static Logger log = Logger.getLogger(BarUtil.class.getName());
	@Inject
	public FooUtil fooUtil;
	public void bar(String msg) {
		log.severe("BarUtil#bar(): " + this + " msg: " + msg);
		this.fooUtil.bar("this.fooUtil#bar(String) called from " + this);
	}
}

An example setup can be found in the project package inject-dependencies-0.0.2-SNAPSHOT.

The example can be started via the mbean like this:
how to invoke the foo method of the mbean

The output should look like this:

16:23:36,809 SEVERE [FooBean] FooBean#foo(): de.jn.sandbox.ejb.injection.FooBean@183ad8dd
16:23:36,809 SEVERE [FooUtil] FooUtil#foo(String): de.jn.sandbox.ejb.injection.FooUtil@3f73a198 msg: this.fooUtil#foo(String) called from de.jn.sandbox.ejb.injection.FooBean@183ad8dd
16:23:36,809 SEVERE [BarUtil] BarUtil#bar(): de.jn.sandbox.ejb.injection.BarUtil@407622b3 msg: this.barUtil#bar(String) called from de.jn.sandbox.ejb.injection.FooUtil@3f73a198
16:23:36,809 SEVERE [FooUtil] FooUtil#bar(String): de.jn.sandbox.ejb.injection.FooUtil@3f73a198 msg: this.fooUtil#bar(String) called from de.jn.sandbox.ejb.injection.BarUtil@407622b3
16:23:36,818 SEVERE [BarBean] BarBean#bar(): de.jn.sandbox.ejb.injection.BarBean@24d25eff, msg: this.bar#bar(String) called from de.jn.sandbox.ejb.injection.FooUtil@3f73a198

Yeehaa.

Leave a Reply

Your email address will not be published. Required fields are marked *

Captcha * Time limit is exhausted. Please reload CAPTCHA.