SeamFramework.orgCommunity Documentation
Interceptor functionality is defined in the Java Interceptors specification. CDI enhances this functionality with a more sophisticated, semantic, annotation-based approach to binding interceptors to beans.
The Interceptors specification defines two kinds of interception points:
业务方法拦截,和
生命周期回调方法拦截。
In addition, the EJB specification defines timeout method interception.
A business method interceptor applies to invocations of methods of the bean by clients of the bean:
public class TransactionInterceptor {
@AroundInvoke
public Object manageTransaction(InvocationContext ctx) throws Exception { ... }
}
一个 生命周期回调拦截器 是容器应用在生命周期回调方法的调用上:
public class DependencyInjectionInterceptor {
@PostConstruct
public void injectDependencies(InvocationContext ctx) { ... }
}
一个拦截器类既可以拦截生命周期回调方法,也可以拦截业务方法。
A timeout method interceptor applies to invocations of EJB timeout methods by the container:
public class TimeoutInterceptor {
@AroundTimeout
public Object manageTransaction(InvocationContext ctx) throws Exception { ... }
}
Suppose we want to declare that some of our beans are transactional. The first thing we need is an interceptor binding type to specify exactly which beans we're interested in:
@InterceptorBinding
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Transactional {}
现在我们可以很容易地指定我们的 ShoppingCart
是一个事务性的对象:
@Transactional
public class ShoppingCart { ... }
或者,如果我们愿意的话,我们可以仅仅指定一个方法是事务性的:
public class ShoppingCart {
@Transactional public void checkout() { ... }
}
That's great, but somewhere along the line we're going to have to actually implement the interceptor that provides this transaction management aspect. All we need to do is create a standard interceptor, and annotate it @Interceptor
and @Transactional
.
@Transactional @Interceptor
public class TransactionInterceptor {
@AroundInvoke
public Object manageTransaction(InvocationContext ctx) throws Exception { ... }
}
Interceptors can take advantage of dependency injection:
@Transactional @Interceptor
public class TransactionInterceptor {
@Resource UserTransaction transaction;
@AroundInvoke
public Object manageTransaction(InvocationContext ctx) throws Exception { ... }
}
多个拦截器可以使用相同的拦截器绑定类型
By default, all interceptors are disabled. We need to enable our interceptor in the beans.xml
descriptor of a bean archive. This activation only applies to the beans in that archive.
<beans
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<interceptors>
<class
>org.mycompany.myapp.TransactionInterceptor</class>
</interceptors>
</beans
>
哇塞!为何使用尖括号?
Well, having the XML declaration is actually a good thing. It solves two problems:
它可以让我们在我们的系统中指定所有的拦截器顺序,确保这些行为具有确定性,并且
它能够让我们在部署期间激活或者关闭拦截器类。
For example, we could specify that our security interceptor runs before our transaction interceptor.
<beans
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/beans_1_0.xsd">
<interceptors>
<class
>org.mycompany.myapp.SecurityInterceptor</class>
<class
>org.mycompany.myapp.TransactionInterceptor</class>
</interceptors>
</beans
>
Or we could turn them both off in our test environment by simply not mentioning them in beans.xml
! Ah, so simple.
假定我们想要往 @Transactional
注释中添加一些额外信息:
@InterceptorBinding
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Transactional {
boolean requiresNew() default false;
}
CDI will use the value of requiresNew
to choose between two different interceptors, TransactionInterceptor
and RequiresNewTransactionInterceptor
.
@Transactional(requiresNew = true) @Interceptor
public class RequiresNewTransactionInterceptor {
@AroundInvoke
public Object manageTransaction(InvocationContext ctx) throws Exception { ... }
}
现在我们可以像这样使用 RequiresNewTransactionInterceptor
:
@Transactional(requiresNew = true)
public class ShoppingCart { ... }
But what if we only have one interceptor and we want the container to ignore the value of requiresNew
when binding interceptors? Perhaps this information is only useful for the interceptor implementation. We can use the @Nonbinding
annotation:
@InterceptorBinding
@Target({METHOD, TYPE})
@Retention(RUNTIME)
public @interface Secure {
@Nonbinding String[] rolesAllowed() default {};
}
Usually we use combinations of interceptor bindings types to bind multiple interceptors to a bean. For example, the following declaration would be used to bind TransactionInterceptor
and SecurityInterceptor
to the same bean:
@Secure(rolesAllowed="admin") @Transactional
public class ShoppingCart { ... }
然而,在非常复杂的情况下,一个拦截器本身可以指定一些拦截器绑定类型的组合:
@Transactional @Secure @Interceptor
public class TransactionalSecureInterceptor { ... }
然后,这个拦截器可以使用下面的任何一个组合来绑定到 checkout()
方法上:
public class ShoppingCart {
@Transactional @Secure public void checkout() { ... }
}
@Secure
public class ShoppingCart {
@Transactional public void checkout() { ... }
}
@Transactional
public class ShoppingCart {
@Secure public void checkout() { ... }
}
@Transactional @Secure
public class ShoppingCart {
public void checkout() { ... }
}
Java语言对注释的支持有一个限制,那就是缺少注释的继承机制。实际上,注释应该可以重用内置,应该支持下列工作:
public @interface Action extends Transactional, Secure { ... }
Well, fortunately, CDI works around this missing feature of Java. We may annotate one interceptor binding type with other interceptor binding types (termed a meta-annotation). The interceptor bindings are transitive — any bean with the first interceptor binding inherits the interceptor bindings declared as meta-annotations.
@Transactional @Secure
@InterceptorBinding
@Target(TYPE)
@Retention(RUNTIME)
public @interface Action { ... }
Now, any bean annotated @Action
will be bound to both TransactionInterceptor
and SecurityInterceptor
. (And even TransactionalSecureInterceptor
, if it exists.)
The @Interceptors
annotation defined by the interceptor specification (and used by the managed bean and EJB specifications) is still supported in CDI.
@Interceptors({TransactionInterceptor.class, SecurityInterceptor.class})
public class ShoppingCart {
public void checkout() { ... }
}
然而,这种方法具有下列缺陷:
拦截器实现被硬编码到在业务代码中,
拦截器可能无法轻松地在部署期间关闭,并且
the interceptor ordering is non-global — it is determined by the order in which interceptors are listed at the class level.
Therefore, we recommend the use of CDI-style interceptor bindings.