攔截器是一種強大的方法在應用程序捕捉運行方法和解耦。攔截器能夠攔截任何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