SeamFramework.orgCommunity Documentation

第 9 章 拦截器

9.1. 拦截器绑定
9.2. 实现拦截器
9.3. 激活拦截器
9.4. 成员拦截器绑定
9.5. 多个拦截器绑定注释
9.6. 拦截器绑定类型的继承
9.7. @Interceptors 的使用

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() { ... }
}

然而,这种方法具有下列缺陷:

Therefore, we recommend the use of CDI-style interceptor bindings.