Java 動態代理與AOP

 動態代理與AOP

代理模式 

代理模式給某一個目標對象(target)提供代理對象(proxy),並由代理對象控制對target對象的引用。程序員

模式圖:spring

代理模式中的角色有:編程

  • 抽象對象角色(AbstractObject):聲明瞭目標對象和代理對象的共同接口,這樣依賴在任何可使用目標對象的地方均可以使用代理對象。
  • 目標對象角色(RealObject):定義了代理對象所表明的目標對象。
  • 代理對象角色(ProxyObject):代理對象內部含有目標對象的引用,從而能夠在任什麼時候候操做目標對象;代理對象提供一個與目標對象相同的接口,以即可以在任什麼時候候替代目標對象。代理對象一般在客戶端調用傳遞給目標對象以前或者以後,執行某個操做,而不是單純的將調用傳遞給目標對象。
示例:
抽象對象角色
public abstract class AbstractObject {
    /**
     * 定義操做
     */
    public abstract void operation();
}

 

目標對象角色緩存

public class RealObject extends AbstractObject {
    public void operation() {
        System.out.println("Do Something!");
    }
}

 

代理對象角色安全

public class ProxyObject extends AbstractObject {
    RealObject realObject = new RealObject();
    public void operation() {
        //在調用目標對象以前,完成一些操做
        System.out.println("Before Do Something");
        realObject.operation();
        //在調用目標對象以後,完成一些操做
        System.out.println("After Do Something");
    }
}

 

客戶端
public class Client {
    public static void main(String[] args) {
        AbstractObject abstractObject = new ProxyObject();
        abstractObject.operation();
    }
}

 

 
 
 
 

按照代理類的建立時期,可分爲靜態代理和動態代理:框架

  • 靜態:由程序員建立代理類或特定工具自動生成源代碼再對其編譯。在程序運行前代理類的.class文件就已經存在了。
  • 動態:在程序運行時運用反射機制動態建立而成。

 

靜態代理

 以下面的例子:編輯器

 1 public interface Flyable {
 2     void fly(long ms);
 3 }
 4 
 5 
 6 public class Bird implements Flyable {
 7 
 8     @Override
 9     public void fly(long ms) {
10         System.out.println("bird is flying!");
11         try {
12             Thread.sleep(ms);
13         } catch (Exception e) {
14 
15         }
16     }
17 }
18 
19 public class Kite implements Flyable {
20 
21     @Override
22     public void fly(long ms) {
23         System.out.println("kite is flying!");
24         try {
25             Thread.sleep(ms);
26         } catch (Exception e) {
27 
28         }
29     }
30 }
31 
32 public class StaticProxy implements Flyable {
33     private Flyable flyable;
34 
35     public StaticProxy(Flyable flyable) {
36         this.flyable = flyable;
37     }
38 
39     @Override
40     public void fly(long ms) {
41         try {
42             System.out.println("before invoke ");
43             long begin = System.currentTimeMillis();
44             flyable.fly(ms);
45             long end = System.currentTimeMillis();
46             System.out.println("after invoke elpased " + (end - begin));
47         } catch (Exception e) {
48             e.printStackTrace();
49             System.out.println("invoke failed!");
50             throw e;
51         }
52     }
53 }
54 public static void main(String[] args) {
55     StaticProxy staticProxyBird = new StaticProxy(new Bird()); 
56     staticProxyBird.fly(100); 
57 
58     StaticProxy staticProxyKite = new StaticProxy(new Kite()); 
59     staticProxyKite.fly(200);
60 }

 

可見,靜態代理能夠作到在不修改目標對象的前提下,拓展目標對象的功能。但靜態代理有2個缺點:ide

1)代理類和委託類實現了相同的接口,代理類經過委託類實現了相同的方法。這樣就出現了大量的代碼重複。若是接口增長一個方法,除了全部實現類須要實現這個方法外,全部代理類也須要實現此方法。增長了代碼維護的複雜度。函數

2)代理對象只服務於一種類型的對象,若是要服務多類型的對象。勢必要爲每一種對象都進行代理,靜態代理在程序規模稍大時就沒法勝任了。如上的代碼是隻爲Flyable類的訪問提供了代理,可是若是還要爲其餘類提供代理的話,就須要咱們再次添加代理類。

工具

 

 

JDK代理

在動態代理中,Proxy代理類在編譯期是不存在的,而是在程序運行時被動態生成的,由於有了反射,能夠根據傳入的參數,生成你想要的代理(如你想代理A就代理A,想代理B就代理B),實現原理就是在生成Proxy的時候你須要傳入被代理類的全部接口(若是沒有接口是另外一種方式,下文會提),反射機制會根據你傳入的全部接口,幫你生成一個也實現這些接口的代理類出來。以後,代理對象每調用一個方法,都會把這個請求轉交給InvocationHandler來執行,而在InvocationHandler裏則經過反射機制,繼續轉發請求給真正的目標對象,最後由目標對象來返回結果。

動態代理與靜態代理相比較,最大的好處是接口中聲明的全部方法都被轉移到調用處理器一個集中的方法中處理(InvocationHandler.invoke)。這樣,在接口方法數量比較多的時候,咱們能夠進行靈活處理,而不須要像靜態代理那樣每個方法進行中轉。並且動態代理的應用使咱們的類職責更加單一,複用性更強。

 

 1 public class DynamicProxy implements InvocationHandler {
 2 
 3     private Object targetObject;
 4 
 5     public Object newProxyInstance(Object targetObject) {
 6         this.targetObject = targetObject;
 7         return Proxy.newProxyInstance(targetObject.getClass().getClassLoader(), targetObject.getClass().getInterfaces(), this);
 8     }
 9 
10     @Override
11     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
12         try {
13             System.out.println("before invoke ");
14             long begin = System.currentTimeMillis();
15             proxy = method.invoke(targetObject, args);
16             long end = System.currentTimeMillis();
17             System.out.println("after invoke elpased " + (end - begin));
18         } catch (Exception e) {
19             e.printStackTrace();
20             System.out.println("invoke failed!");
21             throw e;
22         }
23 
24         return proxy;
25     }
26 }
27 
28 public static void main() {
29 
30     DynamicProxy dynamicProxy = new DynamicProxy();
31     Flyable bird = (Flyable) dynamicProxy.newProxyInstance(new Bird());
32     bird.fly(100);
33 
34     Flyable kite = (Flyable) dynamicProxy.newProxyInstance(new Kite());
35     kite.fly(200);
36 }

上面的例子中,動態代理除了接受Flyable類型的目標對象,還能夠接受任何其餘類型的對象;也無論目標對象實現的接口有多少方法,均可以被代理。

從上面的代碼能夠看出,動態代理對象不須要實現目標對象接口,可是目標對象必定要實現接口,不然不能使用動態代理。 

 

 

 

CGLIB代理

上面的靜態代理和JDK代理模式都須要目標對象是一個實現了接口的目標對象,可是有的時候,目標對象可能只是一個單獨的對象,並無實現任何的接口,這個時候,咱們就可使用目標對象子類的方式實現代理,這種代理方式就是:Cglib代理,也叫作子類代理,它是在內存中構件一個子類對象,從而實現對目標對象的功能拓展。

Cglib是強大的高性能的代碼生成包,它能夠在運行期間拓展Java類與實現Java接口。它普遍的被許多AOP的框架使用,例如Spring AOP和synaop,爲他們提供方法的interception(攔截)。

Cglib包的底層是經過使用一個小而快的字節碼處理框架ASM來轉換字節碼並生成新的類,不鼓勵直接只使用ASM,由於它要求你必須對JVM內部結構,包括class文件的格式和指令集都很熟悉。

 

 1 public class Plane {
 2 
 3     public void fly(long ms) {
 4         System.out.println("plane is flying!");
 5         try {
 6             Thread.sleep(ms);
 7         } catch (Exception e) {
 8 
 9         }
10     }
11 }
12 
13 public class CglibProxy implements MethodInterceptor {
14     private Object target;
15 
16     public CglibProxy(Object target) {
17         this.target = target;
18     }
19 
20     public Object getProxyInstance() {
21         //1. 實例化工具類
22         Enhancer en = new Enhancer();
23         //2. 設置父類對象
24         en.setSuperclass(this.target.getClass());
25         //3. 設置回調函數
26         en.setCallback(this);
27         //4. 建立子類,也就是代理對象
28         return en.create();
29     }
30 
31     public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
32         System.out.println("before invoke ");
33         long begin = System.currentTimeMillis();
34 
35         //執行目標對象的方法
36         Object returnValue = method.invoke(target, objects);
37 
38         long end = System.currentTimeMillis();
39         System.out.println("after invoke elpased " + (end - begin));
40 
41         return returnValue;
42     }
43 
44 }
45 
46 public static void main() {
47     CglibProxy cglibProxy = new CglibProxy(new Plane()); 
48     Plane plane = (Plane) cglibProxy.getProxyInstance();             
49     plane.fly(150);
50 }

 

 

 

AOP

AOP(Aspect Oriented Programming,面向切面編程),像日誌、安全、緩存、事務 等與業務邏輯分離的功能,可能會散佈於各個業務bean,這樣的稱爲 橫切關注點(cross-cutting concern)。AOP有助於橫切關注點與它們所影響的對象之間解耦。

 

 

  • 切面(Aspect):通知+切點,即它是什麼,在什麼時候、何處完成其功能;
  • 切點(Pointcut):匹配通知所要織入的一個或多個鏈接點(where),一般使用明確的(或正則匹配的)類和方法名稱定義切點。
  1. 靜態方法切點,
  2. 動態方法切點,
  3. 註解切點,
  4. 表達式切點,
  5. 流程切點,
  6. 複合切點,
  • 通知(Advice):定義了切面的工做和時機,也就是要作什麼(what),何時作(when)。
  1. 前置通知(@Before):在目標方法被調用以前調用通知;
  2. 後置通知(@After):在目標方法被調用以後調用通知;
  3. 返回通知(@After-returning):在目標方法成功執行以後調用通知;
  4. 異常通知(@After-throwing):在目標方法拋出異常後調用通知;
  5. 環繞通知(@Around):目標方法調用以前、以後執行自定義的行爲;
 
  • 鏈接點(joint point):容許使用通知的地方,這個點能夠是調用方法時、拋出異常時、甚至修改一個字段時。
  • 引入(Introduction):向現有的類添加新方法或屬性;
  • 織入(Weaving):把切面應用到目標對象,並建立新的代理對象的過程。

 

AOP的實現原理是基於動態代理。在Spring的AOP編程中:

  • 若是加入容器的目標對象有實現接口,就使用JDK代理
  • 若是目標對象沒有實現接口,就使用Cglib代理。

 

AOP除了有Spring AOP實現外,還有著名的AOP實現者:AspectJ。

  • AspectJ是語言級別的AOP實現,擴展了Java語言,定義了AOP語法,可以在編譯期提供橫切代碼的織入,因此它有專門的編譯器用來生成遵照Java字節碼規範的Class文件;
  • Spring AOP本質上底層仍是動態代理,因此Spring AOP是不須要有專門的編輯器的;

 

 

Spring AOP

Spring在新版本中對AOP功能進行了加強,體如今這麼幾個方面:

  • 在XML配置文件中爲AOP提供了aop命名空間
  • 增長了AspectJ切點表達式語言的支持
  • 能夠無縫地集成AspectJ

 

先看一個例子, 如何使用 引介切面(Introduction Advisor)爲一個現有對象添加任何接口的實現:

public interface Waiter {

    // 向客人打招呼
    void greetTo(String clientName);

    // 服務
    void serveTo(String clientName);
}

public class NaiveWaiter implements Waiter {

    public void greetTo(String clientName) {
        System.out.println("NaiveWaiter:greet to " + clientName + "...");
    }

    public void serveTo(String clientName) {
        System.out.println("NaiveWaiter:serving " + clientName + "...");
    }
}
public interface Seller { // 賣東西 int sell(String goods, String clientName); } public class SmartSeller implements Seller { // 賣東西 public int sell(String goods, String clientName) { System.out.println("SmartSeller: sell " + goods + " to " + clientName + "..."); return 100; } }

如上示例代碼,有一個服務員的接口,還有一個售貨員的接口,如今想作的就是:想這個服務員能夠充當售貨員的角色,能夠賣東西!

 

 

 

咱們的引介切面具體是這樣乾的:

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.DeclareParents;

@Aspect
public class EnableSellerAspect {

    @DeclareParents(value = "com.example.demo.NaiveWaiter",  // 切點(目標類)
            defaultImpl = SmartSeller.class)                 // 加強類

    public Seller seller;                                   // 加強類接口
}

切面技術將SmartSeller融合到NaiveWaiter中,這樣NaiveWaiter就實現了Seller接口!!!

PS:上面使用@Aspect註解須要引入以下依賴

     <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjrt</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.4</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.3.0</version>
        </dependency>

 

beans.xml 也比較簡單

<?xml version="1.0" encoding="UTF-8" ?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans 
       http://www.springframework.org/schema/beans/spring-beans-4.0.xsd http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop-4.0.xsd"
> <aop:aspectj-autoproxy/> <bean id="waiter" class="com.example.demo.NaiveWaiter"/> <bean class="com.example.demo.EnableSellerAspect"/> </beans>

 

測試一下:

public class Test {
    public static void main(String[] args) {

        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("beans.xml");
        Waiter waiter = (Waiter) ctx.getBean("waiter");

        // 調用服務員原有的方法
        waiter.greetTo("Java3y");
        waiter.serveTo("Java3y");

        // 經過引介/引入切面已經將waiter服務員實現了Seller接口,因此能夠強制轉換
        Seller seller = (Seller) waiter;
        seller.sell("水軍", "Java3y");

    }
}

當引入接口方法被調用時,代理對象會把此調用委託給實現了新接口的某個其餘對象。實際上,一個Bean的實現被拆分到多個類中

 

 

 引介切面用代理的方式爲某個對象實現接口,從而可以使用該接口下的方法。這種方式是非侵入式的。

 

上面是使用註解方式,再看看下使用XML配置的方式,例如:

   <bean id="testBeforeAdvice" class="com.example.demo.TestBeforeAdvice"/>
    <bean id="waiter" class="com.example.demo.NaiveWaiter"/>
    <bean class="com.example.demo.EnableSellerAspect"/>
    <aop:config proxy-target-class="true">
        <aop:advisor advice-ref="testBeforeAdvice" pointcut="execution(* com..*.Waiter.greetTo(..))"/>
    </aop:config>

 

前置加強方法實現

public class TestBeforeAdvice implements MethodBeforeAdvice {

    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("before prepared...");
        System.out.println("args[0]: " + args[0]);
        System.out.println("before finish.");
    }
}

如此,當調用 waiter.greetTo("Java3y") 方法時,會先調用before通知。

 

更多AOP配置元素:

配置元素   用途
<aop:advisor> 定義AOP通知器
<aop:after> 定義AOP後置通知
<aop:after-returning> 定義AOP返回通知
<aop:after-throwing> 定義AOP異常通知
<aop:around> 定義AOP環繞通知
<aop:aspect> 定義一個切面
<aop:before> 定義AOP前置通知
<aop:config> 頂層AOP配置元素,大多數的<aop:*>元素必須包含在<aop:config>元素內
<aop:aspectj-autoproxy> 啓用@AspectJ註解驅動的切面
<aop:declare-parents> 以透明的方式爲被通知的對象引入額外的接口
<aop:pointcut> 定義一個切點

 

相關文章
相關標籤/搜索