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
tagIf 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