拦截器是一种强大的方法在应用程序捕捉运行方法和解耦。拦截器能够拦截任何java类型的调用.
这使得拦截器适合解决事务管理,安全性,以及日记记录.
本质上说,拦截器并不知道他们截获的实际语义事件.所以,拦截器并非很适合和系统的业务挂钩.
而本章的装饰器,则又不同.
装饰器只截取调用某个Java接口,所以获知这个接口的全部语义链接。
decorator直接实现与业务语义操做,这也意味着装饰没有拦截器的通用性。
拦截器和修饰符,尽管在不少方面类似,是互补的。但decorator没法解决技术问题,横跨许多不一样的类型。
假设咱们有一个接口,表明帐户:php
public interface Account { public BigDecimal getBalance(); public User getOwner(); public void withdraw(BigDecimal amount); public void deposit(BigDecimal amount); }
几种不一样的Bean在咱们系统实现帐户接口。css
然而,咱们有一个强制要求:任何类型的帐户,交易必须由系统日志进行记录.
这就是装饰器的一个工做.
用@Decorator标注一个bean(甚至多是一个抽象类),这样就代表此类是装饰器.java
@Decorator public abstract class LargeTransactionDecorator implements Account { ... }
装饰器的装修类型实现方法,可让他拦截他想要拦截的.spring
@Decorator public abstract class LargeTransactionDecorator implements Account { @Inject @Delegate @Any Account account; @PersistenceContext EntityManager em; public void withdraw(BigDecimal amount) { ... } public void deposit(BigDecimal amount); ... } }
须要注意的是,一个装饰器多是一个抽象类. 所以,某些状况下你可能不须要去实现方法.安全
decorator有特殊的注射点,称为委托注入点(delegate injection point),
其必须有一个delegate injection point,能够是一个构造函数参数,初始化方法参数或injected field.
服务器
@Decorator public abstract class LargeTransactionDecorator implements Account { @Inject @Delegate @Any Account account; ... }
像上面这段代码,装饰器将绑定到全部实现了Account的Bean上.app
若是是下面这段代码,@Foreign是咱们自定义.
那么装饰器将绑定到实现了Account的Bean而且qualifiers是@Foreign的Bean上.
框架
@Decorator public abstract class LargeTransactionDecorator implements Account { @Inject @Delegate @Foreign Account account; ... }
decorator可能调用委托对象,和拦截器调用InvocationContext.proceed() 有大体有相同的结果.但主要的区别在于装饰能够委托对象上调用任何业务方法。async
@Decorator public abstract class LargeTransactionDecorator implements Account { @Inject @Delegate @Any Account account; @PersistenceContext EntityManager em; public void withdraw(BigDecimal amount) { account.withdraw(amount); if ( amount.compareTo(LARGE_AMOUNT)>0 ) { em.persist( new LoggedWithdrawl(amount) ); } } public void deposit(BigDecimal amount); account.deposit(amount); if ( amount.compareTo(LARGE_AMOUNT)>0 ) { em.persist( new LoggedDeposit(amount) ); } } }
默认状况下,全部装饰器都是禁用的.推荐用bean.xml进行开启.bean.xml是第一优先的.其次才是@Priority.
CDI 1.1之后的decorator可使用@Priority开启。@Priority定义了装饰器和拦截器的优先顺序,但仍是没bean.xml里直观.
ide
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"> <decorators> <class>org.mycompany.myapp.LargeTransactionDecorator</class> </decorators> </beans>
注意:不要即在bean.xml配置又写@Priority.可能会出一些奇怪的问题.根本上,同时用这两种方式就是错误的.
拦截器的功能是定义在Java拦截器规范。
拦截器规范定义了三种拦截点:
在容器的生命周期中进行拦截
public class DependencyInjectionInterceptor { @PostConstruct public void injectDependencies(InvocationContext ctx) { ... } }
EJB超时时使用的拦截器
public class TimeoutInterceptor { @AroundTimeout public Object manageTransaction(InvocationContext ctx) throws Exception { ... } }
在业务上,对某一个Bean的方法进行拦截
public class TransactionInterceptor { @AroundInvoke public Object manageTransaction(InvocationContext ctx) throws Exception { ... } }
@AroundInvoke注释指定了要用做拦截器的方法,拦截器方法与被拦截的业务方法执行同一个java调用堆栈、同一个事务和安全上下文中。用@AroundInvoke注释指定的方法必须遵照如下格式:public Object XXX(javax.interceptor.InvocationContext ctx) throws Exception
下面是javax.interceptor.InvocationContext封装了客户端所调用业务方法的一些信息。
package javax.interceptor; public interface InvocationContext{ public Object getTarget(); public Method getMethod(); public Ojbect[] getParameters(); public void setParameters(Object[] newArgs); public java.util.Map<String, Ojbect> getContextData(); public Object proceed() throws Exception; }
() 获取被拦截业务方法的参数
示例:
//被拦截的方法 @Interceptors(HelloInterceptor.class) public class HelloChinaBean { public String SayHello(String name) { return name +"Hello World."; } } //拦截器定义 public class HelloInterceptor { @AroundInvoke public Object log(InvocationContext ctx) throws Exception { try{ if (ctx.getMethod().getName().equals("SayHello")){ System.out.println("Holle World!!!" ); } return ctx.proceed(); }catch (Exception e) { throw e; } } }
@InterceptorBinding @Target({METHOD, TYPE}) @Retention(RUNTIME) public @interface Transactional {}
假设咱们想要申明一些bean的事务。咱们先要的是一个拦截器绑定类型来指定哪些bean咱们要申明.
首先定义一个注解
如今咱们能够很容易地指定类ShoppingCart是事务性对象:
@Transactional public class ShoppingCart { ... }
或者咱们能够指定一个
@Transactional @Interceptor public class TransactionInterceptor { @AroundInvoke public Object manageTransaction(InvocationContext ctx) throws Exception { ... } }
器能够利用依赖注入:
@Transactional @Interceptor public class TransactionInterceptor { @Resource UserTransaction transaction; @AroundInvoke public Object manageTransaction(InvocationContext ctx) throws Exception { ... } }
多个拦截器可使用相同的拦截器绑定类型。
3.启用拦截器(Enabling interceptors) 默认状况下,全部拦截器被禁用.要使用拦截器.须要在bean.xml中进行配置,以启用.从CDI 1.1起拦截器可使用@Priority注释为整个应用程序启用。
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"> <interceptors> <class>org.mycompany.myapp.TransactionInterceptor</class> </interceptors> </beans>
这样有2个好处:
固然也能够配置启用多个拦截器
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation=" http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_1_1.xsd"> <interceptors> <class>org.mycompany.myapp.SecurityInterceptor</class> <class>org.mycompany.myapp.TransactionInterceptor</class> </interceptors> </beans>
拦截器毕竟比较重要,不推荐使用@Priority启用.
在CDI中,XML配置的优先级高于@Priority.
关于@Priority能够参考下列:
public static class Interceptor.Priority
extends Object
Priorities that define the order in which interceptors are invoked. These values should be used with the Priority annotation.
Interceptors defined by platform specifications should have priority values in the range PLATFORM_BEFORE up until LIBRARY_BEFORE, or starting at PLATFORM_AFTER.
Interceptors defined by extension libraries should have priority values in the range LIBRARY_BEFORE up until APPLICATION, or LIBRARY_AFTER up until PLATFORM_AFTER.
Interceptors defined by applications should have priority values in the range APPLICATION up until LIBRARY_AFTER.
An interceptor that must be invoked before or after another defined interceptor can choose any appropriate value.
Interceptors with smaller priority values are called first. If more than one interceptor has the same priority, the relative order of these interceptor is undefined.
For example, an extension library might define an interceptor like this:
@Priority(Interceptor.Priority.LIBRARY_BEFORE+10) @Interceptor public class ValidationInterceptor { ... }
假设咱们想要添加一些额外的信息给咱们的@transactional注解:
@InterceptorBinding @Target({METHOD, TYPE}) @Retention(RUNTIME) public @interface Transactional { boolean requiresNew() default false; }
CDI将使用requiresNew的值选择两个不一样的拦截器,TransactionInterceptor和RequiresNewTransactionInterceptor
下面是requiresNew为true的拦截器
@Transactional(requiresNew = true) @Interceptor public class RequiresNewTransactionInterceptor { @AroundInvoke public Object manageTransaction(InvocationContext ctx) throws Exception { ... } }
以下使用:
@Transactional(requiresNew = true) public class ShoppingCart { ... }
可是若是咱们只有一个拦截器,咱们但愿容器拦截器绑定时忽略requiresNew的值,也许这些信息只用于拦截器实现。咱们可使用@Nonbinding注释:
@InterceptorBinding @Target({METHOD, TYPE}) @Retention(RUNTIME) public @interface Secure { @Nonbinding String[] rolesAllowed() default {}; }
一般咱们使用拦截器绑定的组合类型绑定多个拦截器bean。例如,下面的声明将用于绑定TransactionInterceptor和SecurityInterceptor这2个拦截器到ShoppingCart.
@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 { ... }
幸运的是,CDI围绕Java没有的这个特性开展了一些工做.
咱们会标注一个拦截器绑定类型,其有其余拦截器的绑定类型,(称为元注解)
表述起来有点费劲,就如同下面代码这样.
@Transactional @Secure @InterceptorBinding @Target(TYPE) @Retention(RUNTIME) public @interface Action { ... }
如今任何Bean绑定 Action这个注解 ,其实就是绑定到了@Transactional @Secure.(就是拦截器TransactionInterceptor和拦截器SecurityInterceptor). (甚至TransactionalSecureInterceptor,若是它存在.)
这个注解@Interceptors是拦截器规范定义的,cdi是支持的<使用托管bean和EJB规范>.以下:
@Interceptors({TransactionInterceptor.class, SecurityInterceptor.class}) public class ShoppingCart { public void checkout() { ... } }
但缺点也很明显,不推荐使用.缺点以下:
所以仍是使用上面CDI的使用方式比较好.
CDI高级说明以及Producer methods
先贴一段代码,下面都用到
import javax.enterprise.inject.Produces; @SessionScoped public class Preferences implements Serializable { private PaymentStrategyType paymentStrategy; ... @Produces @Preferred public PaymentStrategy getPaymentStrategy() { switch (paymentStrategy) { case CREDIT_CARD: return new CreditCardPaymentStrategy(); case CHECK: return new CheckPaymentStrategy(); case PAYPAL: return new PayPalPaymentStrategy(); default: return null; } } } //注入一个Producer methods @Inject @Preferred PaymentStrategy paymentStrategy;
@Produces @Preferred @SessionScoped public PaymentStrategy getPaymentStrategy() { ... }
注意:Producer methods不继承声明此Producer methods的Bean的Scope.
在Producer methods一开始的实例有一个潜在的问题
CreditCardPaymentStrategy 的实现使用 Java new 运算符来实例化。
private PaymentStrategyType paymentStrategy;
而producer methods应该理解为一个独立的Bean,而paymentStrategy是从Preferences 中用new实例化的.因此咱们应该使用下面这种方式来使用producer methods方法.
@Produces @Preferred @SessionScoped public PaymentStrategy getPaymentStrategy(CreditCardPaymentStrategy ccps, CheckPaymentStrategy cps, PayPalPaymentStrategy ppps) { switch (paymentStrategy) { case CREDIT_CARD: return ccps; case CHEQUE: return cps; case PAYPAL: return ppps; default: return null; } }
这里会有问题,若是CreditCardPaymentStrategy 是一个@RequestScope,那这里必然是要发生错误的.由于注入的CreditCardPaymentStrategy Bean实例是request,在@SessionScoped使用前容器就会进行销毁.那么就出错了.
@Produces @Preferred @SessionScoped public PaymentStrategy getPaymentStrategy(@New CreditCardPaymentStrategy ccps, @New CheckPaymentStrategy cps, @New PayPalPaymentStrategy ppps) { switch (paymentStrategy) { case CREDIT_CARD: return ccps; case CHEQUE: return cps; case PAYPAL: return ppps; default: return null; } }
在CDI 1.1 @New限定符被弃用。CDI鼓励应用程序注入@Dependent范围bean。
@Produces @RequestScoped Connection connect(User user) { return createConnection(user.getId(), user.getPassword()); }
而在一个相同的类中,disposer method能够进行匹配.
void close(@Disposes Connection connection) { connection.close(); }
说明:在同一个类中,disposer method能够进行匹配类型为Connection 的Procucer methods,从而在 Procucer methods周期结束后进行jdbc的连接关闭.
import javax.enterprise.context.RequestScoped; import javax.enterprise.inject.Disposes; import javax.enterprise.inject.Produces; import javax.enterprise.inject.spi.InjectionPoint; import javax.persistence.EntityManager; import javax.persistence.EntityManagerFactory; import javax.persistence.PersistenceUnit; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class Resources { @PersistenceUnit private EntityManagerFactory entityManagerFactory; @Produces @RequestScoped protected EntityManager createEntityManager() { return entityManagerFactory.createEntityManager(); } //参数必须对应上面方法的返回值 protected void closeEntityManager(@Disposes EntityManager entityManager) { if ( entityManager.isOpen() ) { entityManager.close(); } } }
参考:https://my.oschina.net/zhaoqian/blog/264604
https://www.jianshu.com/p/5d2bd9369134