Sunday, April 22, 2012

Custom CDI Scopes

Sometimes when working with Dependency Inject (DI) there arises a reason to develop a custom DI scope. For example, if an application has a graph of objects that it will build out, utilize, and then dispose of during the lifetime of the application a custom scope would make sense. Or, if an application is context aware and DI should be performed based on the context a segment of code is running in then a custom scope also makes sense.
For example, when developing the ODE-X project I observed several different code contexts I wanted to support to realize a virtual machine metaphor:
  • Executable - XML is parsed building an object graph consisting of Blocks containing a sequence of Instructions. The executable objects themselves are stateless so they can be utilized in more than one program but they can have DI injection at runtime of the current context. Executables are loaded/unloaded at runtime and if an executable is no longer references by an active program it can be unloaded removing it from memory. Executable PostCreate and PreDestory methods should be invoked when execuables are loaded and unloaded respectively
  • Program - A runtime specific configuration for a set of executables. Programs may be installed and uninstalled and provides a scope of global memory and runtime state for all included executables. Business Process Monitor (BPM) configurations should be specified in this scope.
  • Process - A distinct instance of a business process. Provides process level global memory for a business process that may be shared among member threads
  • Thread - A path of execution maintaining a stack of state. The core premise of ODE-X relies on the ability of removing a virtual thread state from physical memory and persisting it to XML in a database and then rehydrating and resuming execution at some distant point in the future.
  • Instructional - per instruction DI based on the current execution state. At the beginning of instruction execution instructional objects are created and provided t to the executable and then afterwards all objects in the instructional scope are discarded


In this architecture a virtual processor would execute each Executable Instruction with the following scope hierarchy : program->process->thread->instructional->executable.

To summarize, this application will maintain the lifecycle of a set of objects participating in DI and it needs a way of notifying the DI container when a set of DI obtained objects are no longer needed. Additionally, to simplify the architecture and take full advantage of DI, the application would like to configure DI such that objects defined in a higher level scope can be injected into objects defined in a lower level scope.
Fortunately the talented specification leads of JSR 330 (DI for Java) and JSR 299 (CDI) had the prescience to allow custom scopes. While some of my posts about perceived defects in CDI and Weld may be pointed I sincerely am impressed with CDI extensions and it truly does unleash JavaEE. I have written a simple example illustrating a custom CDI scope with the bonus of a scope qualifier.
For this example I disabled CDI auto discovery in favor of manually declaring CDI bean classes which is my personal preference. The key points of the sample is the CDI extension declaration of the sample DI actors

public void beforeBeanDiscovery(@Observes BeforeBeanDiscovery bbd, BeanManager bm) {
  bbd.addAnnotatedType(bm.createAnnotatedType(FooCDIInstanceProducer.class));
  bbd.addAnnotatedType(bm.createAnnotatedType(FooScopeContext.class));
  bbd.addAnnotatedType(bm.createAnnotatedType(Foo.class));
  bbd.addAnnotatedType(bm.createAnnotatedType(Bar.class));
  bbd.addQualifier(FooInstance.class);
  bbd.addScope(FooScope.class, false, false);
}

public void afterBeanDiscovery(@Observes AfterBeanDiscovery abd, BeanManager bm) {
  abd.addContext(new FooCDIContextImpl());
}

The use of the custom scopein the code:

FooScopeContext fooScope = ..
fooScope.create();
try {
fooScope.begin();
...
} finally {
fooScope.end();
}
fooScope.destroy();

and the finally the integration of the application scope with the CDI scope Context. Notice the use of Java ThreadLocals so that the application representation of the scope maintains the actual instances of the Objects and then passes those references to the CDI Context implementation so that they in turn can be passed back to the DI container for injection. While I am not a fan of ThreadLocals they are the ideal solution for custom scopes and I don't think one could make a CDI scope without them.

I came across an additional challenge with CDI custom scopes in that I wanted to add qualifiers to custom scoped objects so that injection point information could be passed into the custom scope Context and based upon the qualifier information the appropriate scoped object could be returned and injected. The solution I ended up at involved creating a custom qualifier producer and then passing the qualifier information on to the scope Context using another ThreadLocal variable. While I think the most ideal solution would be for the scopeContext provider to be presented with the InjectionTarget information if present this approach does have the benefit of separating scoped object creation from qualifier instance modification. For example, the scopeContext simply creates and tracks the lifecycle of the object whereas the Qualifier Provider can mutate the returned object based on the qualifier information.

One final thing to keep in mind with CDI custom scopes is that it adds more utility to the JSR 330 Provider interface. Before I though the Provider interface was only useful for obtaining references to brand new instances of DI objects on demand. In the context of a custom scope using the Provider interface may mean that the returned instance is not a new instance but a handle to an existing scoped object. For example, I will use the Provider interface in Executable and Instructional scoped objects so that contextual instances of objects in the higher Program, Process, and Thread scopes can be retrieved and utilized for contextual execution of the instruction.

No comments:

Post a Comment