Wednesday, August 17, 2011

JSR-299 CDI Interceptors for Spring Beans

Another early release of a Spring-CDI module, this time it's the interceptor pattern implementation. It implements a JSR-299 compliant interceptor binding model for Spring managed beans. We will use that in our business applications for cross-cutting-concerns.


Please notice: The intention of my blog is to share and discuss ideas. If you use any of this in your applications you're acting at your own risk.

JSR-299 interceptor pattern implementation

Features

The interceptor module provides the following features:

- Use JSR-299 @Interceptor, @InterceptorBinding, @AroundInvoke and @Nonbinding annotations and their defined semantics in Spring managed beans
- Support interceptors on the class level (applied to all declared methods) as well as on specific methods (method-level interceptors)
- Support chains of multiple interceptors for the same target method
- Support scoped target beans
- Integrate with Spring AOP (CGLIB proxies only)

Interceptors for lifecycle event callbacks are supported since Spring 2.5 and are therefore not subject of this Sping-CDI module (@PostConstruct and @PreDestroy annotations).

This release of the Spring-CDI interceptor module does not support JSR318 @Interceptors annotation. The CDI spec states: "If an interceptor does not declare an @Interceptor annotation, it must be bound to beans using @Interceptors or ejbjar.xml." Because JSR299 specifies @InterceptorBinding as the binding mechanism I would not recommend to use @Interceptors annotation anymore.

Timeout interceptors and default interceptors are not yet supported.

Download Link

The Spring-CDI interceptor module is an usual Spring IoC-container extension delivered as JAR archive. You can download the module JAR and put that on the classpath of your Spring application.

Compiled Spring-CDI interceptor module JAR: Version 0.5.2
Sources: Version 0.5.2
API-Doc: Version 0.5.2

Everything is hosted on a git repository on Github.com.

Dependencies

Here are my runtime dependencies:


	org.springframework
	spring-context
	3.0.5.RELEASE



	org.springframework
	org.springframework.aop
	3.0.5.RELEASE



	javax.enterprise
	cdi-api
	1.0



	org.jboss.spec.javax.interceptor
	jboss-interceptors-api_1.1_spec
	1.0.0.Final



	cglib
	cglib
	2.2.2


Configuration

If the Spring-CDI interceptor module JAR and its dependencies are on your classpath, all you need to do is:

(1) register InterceptorAwareBeanFactoryPostProcessor in your application context
(2) define an include-filter to include javax.interceptor.Interceptor as component annotation in your context:component-scan tag



If you like to order your interceptors you need to configure the interceptorOrder property of InterceptorAwareBeanFactoryPostProcessor:



Use Case

The following code snippets show how you can use the interceptor pattern ones you have configured your Spring application as described above. For more complex scenarios see my unit test cases.

Let's assume you have a business interface called: Simple_MyServiceInterface

public interface Simple_MyServiceInterface {

	String sayHello();
	String sayHello(String what);
	String sayGoodBye();
	
}

This is your implementation of the service.

@Component
@ReturnValueAdopted
public class Simple_MyServiceInterface_Impl implements Simple_MyServiceInterface {

	@Override
	public String sayHello() {
		return "Hello";
	}

	@Override
	public String sayHello(String what) {
		return what;
	}

	@Override
	public String sayGoodBye() {
		return "Good bye";
	}

}

Notice the @ReturnValueAdopted annotation on the type level. It is an interceptor binding declaration:

@Inherited
@InterceptorBinding
@Target({TYPE, METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReturnValueAdopted { }

This interceptor also declares the @ReturnValueAdopted interceptor binding which means the interceptor is applied to the Simple_MyServiceInterface_Impl.

@Interceptor @ReturnValueAdopted
public class Simple_MyInterceptor {

	@AroundInvoke
	public String extendReturnValueWithSomeNonsense(InvocationContext ctx) throws Exception {
		String result = null;
		ctx.getContextData().put("Some", "Nonsense");
		try {
			result = (String)ctx.proceed();
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return result + "_hello_world";
	}
	
}

The @Interceptor annotation declares the class as an interceptor. It will intercept all method calls on the Simple_MyServiceInterface_Impl bean. More specifically it will extend the return values with "_hello_world". Also notice the context data written to the InvocationContext. This allows chains of interceptors to exchange context data.

You can now autowire the Simple_MyServiceInterface_Impl bean into arbitrary beans:

@ContextConfiguration("/test-context-interceptor-simple.xml")
@RunWith(SpringJUnit4ClassRunner.class)
@DirtiesContext
public class SimpleInterceptorTestCase {

	@Autowired // will be intercepted
	private Simple_MyServiceInterface someInterceptedBean;
	
	@Test
	public void testHelloWorldExtension() {
		Assert.assertTrue(someInterceptedBean.sayHello().equals("Hello_hello_world"));
	}
	
}

The injected bean will have the defined Simple_MyInterceptor applied. When the sayHello() method is called the interceptor will extend the return value with the "_hello_world" message.

How it works

The core is the InterceptorAwareBeanFactoryPostProcessor. It uses two strategies to implement the logic. The InterceptorResolutionLogic scans the application context for registered interceptors. The InterceptorOrderingStrategy implements the ordering of chained interceptors for a target method call. The result of the data collection within these two strategies is stored in the InterceptorMetaDataBean. The bean factory post processor also registers the InterceptorAwareBeanPostProcessor with creates a proxy for each intercepted bean. This proxy has the intercepted bean as target and the InterceptedBeanProxyAdvice as interceptor advice applied. This advice creates (and caches) the chains of user defined JSR299 interceptors for each unique method call. It retrieves the required meta data for chaining interceptors from the InterceptorMetaDataBean.


Try everything if you have some time left!

No comments:

Post a Comment