week 7 CDI services

  • .Decorators裝飾器綜述

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

須要注意的是,一個裝飾器多是一個抽象類. 所以,某些狀況下你可能不須要去實現方法.安全

2.Delegate object(委託對象)

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) );
      }
   }
}

 

3.Enabling decorators(啓用裝飾器)

默認狀況下,全部裝飾器都是禁用的.推薦用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.可能會出一些奇怪的問題.根本上,同時用這兩種方式就是錯誤的.

  • Interceptors(攔截器)

1.攔截器綜述

攔截器的功能是定義在Java攔截器規範。

攔截器規範定義了三種攔截點:

  1. 業務方法攔截,
  2. 生命週期回調偵聽,
  3. 超時攔截(EJB)方法。

在容器的生命週期中進行攔截

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封裝了客戶端所調用業務方法的一些信息。

 
  • getTarget() 指向被調用的bean實例
  • getMethod() 指向被攔截的業務方法getParameters
  • 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;
    }

     

    () 獲取被攔截業務方法的參數
  • setParameters() 設置被攔截業務方法的參數
  • getContextData() 返回一個Map對象,它在整個方法調用期間均可以被訪問到。位於同一個方法調用內的不一樣攔截器之間能夠利用它來傳遞上下文相關的數據。

示例:

//被攔截的方法
@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 {}
 
 

 

 

 

2.攔截器綁定(Interceptor bindings)

假設咱們想要申明一些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個好處:

  • 攔截器比較重要,在XML中確保其肯定性行爲
  • 它讓咱們在部署時啓用或禁用攔截器類。

固然也能夠配置啓用多個攔截器

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

4.Interceptor bindings with members(攔截器註解屬性)

假設咱們想要添加一些額外的信息給咱們的@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 {};
}

 

5.Multiple interceptor binding annotations(多重攔截器綁定註解)

一般咱們使用攔截器綁定的組合類型綁定多個攔截器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() { ... }
}

 

6. Interceptor binding type inheritance(攔截器綁定類型繼承)

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,若是它存在.)

7.Use of @Interceptors(同時用多個攔截器)

這個註解@Interceptors是攔截器規範定義的,cdi是支持的<使用託管bean和EJB規範>.以下:

@Interceptors({TransactionInterceptor.class, SecurityInterceptor.class})
public class ShoppingCart {

   public void checkout() { ... }
}

但缺點也很明顯,不推薦使用.缺點以下:

  1. 攔截器在代碼中是硬編碼.
  2. 攔截器在部署時很差更改.
  3. 攔截器命令是非全局的——它是在類級別由攔截器的順序列出.

所以仍是使用上面CDI的使用方式比較好. 

CDI高級說明以及Producer methods

CDI是爲解耦而生.如Spring主要用途是AOP.IOC(DI),而CDI除了DI外,AOP功能也是有的.從實際使用上來看,CDI比Spring功能更豐富,更靈活,其代價也是有的,學習成本相對spring較高.

1.CDI致力於鬆耦合,強類型.

實現鬆散耦合的三種方式:
  1. 部署時候的多態選擇,@alternatives 
  2. producer methods在運行時的多態.
  3. 上下文相關的生命週期管理與bean生命週期解耦。
這些技術使客戶端和服務器的鬆散耦合服務。客戶端再也不是緊密地綁定到一個接口的一個實現,也不須要管理生命週期實現。這種方法容許有狀態的對象看成服務交互。鬆散耦合使系統更具活力。在之前,框架老是犧牲了類型安全(尤爲是經過使用XML描述符,Spring2.5)。

CDI提供了三個額外的重要措施,進一步鬆耦合:
  1. 在業務邏輯層用攔截器技術解耦.
  2. 修飾符(註解)能夠用來分離一些業務問題
  3. 用CDI EVENT技術進行解耦事件生產者與消費者.
第二個CDI是強類型的.不管是依賴關係的信息,攔截器,修飾符的Bean,以及CDI event的生產者,消費者等的信息所有都是類型安全的.由編譯器進行驗證.
CDI是確確實實沒String標識符,如xml配置什麼的.好比Spring2.5用XML配置,其實都是字符串,以及"約定大於配置"的概念.在CDI裏是沒有的.CDI框架不是隱藏,而是沒有.
這種方法的最明顯好處就是任何IDE均可以提供自動完成,驗證以及最重要的重構!(瞭解JPA的,能夠對比安全類型的查詢和JPQL.若是重構代碼JPQL是很是麻煩的).
還有個好處就是你在識別不一樣的對象,事件,攔截器能夠經過註解而不是字符串名字,這樣你能夠提高代碼質量.

CDI鼓勵開發使用註解.如
@Asynchronous,  
@Secure,
@Updated,
而不是使用複合名稱,
asyncPaymentProcessor,
SecurityInterceptor
DocumentUpdatedEvent.
這也符合代碼大全裏的一些概念.只不過不用費盡心思考慮命名了,這樣更簡潔高效.
註釋是可重用的。他們幫助描述系統的不一樣部分的共同特質。他們幫助咱們分類和理解代碼。他們幫助咱們應對常見問題的經常使用方法。他們使咱們的代碼更簡潔高效.

2.高級功能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;

 

A:Producer methods的Scope

Producer methods的默認範圍是@Dependent.
從上面代碼咱們能夠思考一種場景,那就是一個用戶會話中有多個PaymentStrategy對象的實例.若是想改變,咱們能夠在Producer方法上添加一個@SessionSciped註解.
如今,若是一個用戶調用了這個Producer methods,那麼返回的這個PaymentStrategy對象的實例將綁定到會話的上下文.Producer methods不會再實例化另外一個出來.
@Produces @Preferred @SessionScoped
public PaymentStrategy getPaymentStrategy() {
   ...
}

 

注意:Producer methods不繼承聲明此Producer methods的Bean的Scope.
其實這裏有2個不一樣的Bean:Producer methods(至關於一個Bean)以及聲明這個生產方法的Bean.

B: Injection into producer methods

在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使用前容器就會進行銷燬.那麼就出錯了.

這是個問題,因此咱們有3種處理方案.

  1.  producer method to @Dependent or @RequestScoped.<最好的方式>
  2. CreditCardPaymentStrategy 更改Scope,但這可能會影響其餘的地方,不是很好.
  3. 使用@New限定符,但在CDI 1.1 @New限定符被棄用。CDI鼓勵應用程序注入@Dependent範圍bean。

C:Use of @New with producer methods<不推薦>

Consider the following producer method:
@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; } }
這將會建立一個新的CreditCardPaymentStrategy依賴實例,傳遞到生產方法,依賴對象不會被摧毀,直到會話結束。

在CDI 1.1 @New限定符被棄用。CDI鼓勵應用程序注入@Dependent範圍bean。

D:Disposer methods

一些Procucer methods返回的對象須要顯式的破壞。例如,有人須要關閉這個JDBC鏈接:
@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

相關文章
相關標籤/搜索