程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA綜合教程 >> 靜態代理、JDK動態代理、CGLIB動態代理、Spring實現AOP、IOC+AOP,cglibaop

靜態代理、JDK動態代理、CGLIB動態代理、Spring實現AOP、IOC+AOP,cglibaop

編輯:JAVA綜合教程

靜態代理、JDK動態代理、CGLIB動態代理、Spring實現AOP、IOC+AOP,cglibaop


一、為什麼需要代理模式

假設需實現一個計算的類Math、完成加、減、乘、除功能,如下所示:

 1 package com.zhangguo.Spring041.aop01;
 2 
 3 public class Math {
 4     //加
 5     public int add(int n1,int n2){
 6         int result=n1+n2;
 7         System.out.println(n1+"+"+n2+"="+result);
 8         return result;
 9     }
10     
11     
12     //減
13     public int sub(int n1,int n2){
14         int result=n1-n2;
15         System.out.println(n1+"-"+n2+"="+result);
16         return result;
17     }
18     
19     //乘
20     public int mut(int n1,int n2){
21         int result=n1*n2;
22         System.out.println(n1+"X"+n2+"="+result);
23         return result;
24     }
25     
26     //除
27     public int div(int n1,int n2){
28         int result=n1/n2;
29         System.out.println(n1+"/"+n2+"="+result);
30         return result;
31     }
32 }

現在需求發生了變化,要求項目中所有的類在執行方法時輸出執行耗時。最直接的辦法是修改源代碼,如下所示:

 1 package com.zhangguo.Spring041.aop01;
 2 
 3 import java.util.Random;
 4 
 5 public class Math {
 6     //加
 7     public int add(int n1,int n2){
 8         //開始時間
 9         long start=System.currentTimeMillis();
10         lazy();
11         int result=n1+n2;
12         System.out.println(n1+"+"+n2+"="+result);
13         Long span= System.currentTimeMillis()-start;
14         System.out.println("共用時:"+span);
15         return result;
16     }
17     
18     //減
19     public int sub(int n1,int n2){
20         //開始時間
21         long start=System.currentTimeMillis();
22         lazy();
23         int result=n1-n2;
24         System.out.println(n1+"-"+n2+"="+result);
25         Long span= System.currentTimeMillis()-start;
26         System.out.println("共用時:"+span);
27         return result;
28     }
29     
30     //乘
31     public int mut(int n1,int n2){
32         //開始時間
33         long start=System.currentTimeMillis();
34         lazy();
35         int result=n1*n2;
36         System.out.println(n1+"X"+n2+"="+result);
37         Long span= System.currentTimeMillis()-start;
38         System.out.println("共用時:"+span);
39         return result;
40     }
41     
42     //除
43     public int div(int n1,int n2){
44         //開始時間
45         long start=System.currentTimeMillis();
46         lazy();
47         int result=n1/n2;
48         System.out.println(n1+"/"+n2+"="+result);
49         Long span= System.currentTimeMillis()-start;
50         System.out.println("共用時:"+span);
51         return result;
52     }
53     
54     //模擬延時
55     public void lazy()
56     {
57         try {
58             int n=(int)new Random().nextInt(500);
59             Thread.sleep(n);
60         } catch (InterruptedException e) {
61             e.printStackTrace();
62         }
63     }
64 }

測試運行:

package com.zhangguo.Spring041.aop01;

public class Test {
    
    @org.junit.Test
    public void test01()
    {
        Math math=new Math();
        int n1=100,n2=5;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        math.div(n1, n2);
    }
}

運行結果:

缺點:

1、工作量特別大,如果項目中有多個類,多個方法,則要修改多次。

2、違背了設計原則:開閉原則(OCP),對擴展開放,對修改關閉,而為了增加功能把每個方法都修改了,也不便於維護。

3、違背了設計原則:單一職責(SRP),每個方法除了要完成自己本身的功能,還要計算耗時、延時;每一個方法引起它變化的原因就有多種。

4、違背了設計原則:依賴倒轉(DIP),抽象不應該依賴細節,兩者都應該依賴抽象。而在Test類中,Test與Math都是細節。

使用靜態代理可以解決部分問題。

二、靜態代理

 1、定義抽象主題接口。

package com.zhangguo.Spring041.aop02;

/**
 * 接口
 * 抽象主題
 */
public interface IMath {
    //加
    int add(int n1, int n2);

    //減
    int sub(int n1, int n2);

    //乘
    int mut(int n1, int n2);

    //除
    int div(int n1, int n2);

}

2、主題類,算術類,實現抽象接口。

package com.zhangguo.Spring041.aop02;

/**
 * 被代理的目標對象
 *真實主題
 */
public class Math implements IMath {
    //加
    public int add(int n1,int n2){
        int result=n1+n2;
        System.out.println(n1+"+"+n2+"="+result);
        return result;
    }
    
    //減
    public int sub(int n1,int n2){
        int result=n1-n2;
        System.out.println(n1+"-"+n2+"="+result);
        return result;
    }
    
    //乘
    public int mut(int n1,int n2){
        int result=n1*n2;
        System.out.println(n1+"X"+n2+"="+result);
        return result;
    }
    
    //除
    public int div(int n1,int n2){
        int result=n1/n2;
        System.out.println(n1+"/"+n2+"="+result);
        return result;
    }
}

3、代理類

 1 package com.zhangguo.Spring041.aop02;
 2 
 3 import java.util.Random;
 4 
 5 /**
 6  * 靜態代理類
 7  */
 8 public class MathProxy implements IMath {
 9 
10     //被代理的對象
11     IMath math=new Math();
12     
13     //加
14     public int add(int n1, int n2) {
15         //開始時間
16         long start=System.currentTimeMillis();
17         lazy();
18         int result=math.add(n1, n2);
19         Long span= System.currentTimeMillis()-start;
20         System.out.println("共用時:"+span);
21         return result;
22     }
23 
24     //減法
25     public int sub(int n1, int n2) {
26         //開始時間
27         long start=System.currentTimeMillis();
28         lazy();
29         int result=math.sub(n1, n2);
30         Long span= System.currentTimeMillis()-start;
31         System.out.println("共用時:"+span);
32         return result;
33     }
34 
35     //乘
36     public int mut(int n1, int n2) {
37         //開始時間
38         long start=System.currentTimeMillis();
39         lazy();
40         int result=math.mut(n1, n2);
41         Long span= System.currentTimeMillis()-start;
42         System.out.println("共用時:"+span);
43         return result;
44     }
45     
46     //除
47     public int div(int n1, int n2) {
48         //開始時間
49         long start=System.currentTimeMillis();
50         lazy();
51         int result=math.div(n1, n2);
52         Long span= System.currentTimeMillis()-start;
53         System.out.println("共用時:"+span);
54         return result;
55     }
56 
57     //模擬延時
58     public void lazy()
59     {
60         try {
61             int n=(int)new Random().nextInt(500);
62             Thread.sleep(n);
63         } catch (InterruptedException e) {
64             e.printStackTrace();
65         }
66     }
67 }

4、測試運行

 1 package com.zhangguo.Spring041.aop02;
 2 
 3 public class Test {
 4     
 5     IMath math=new MathProxy();
 6     @org.junit.Test
 7     public void test01()
 8     {
 9         int n1=100,n2=5;
10         math.add(n1, n2);
11         math.sub(n1, n2);
12         math.mut(n1, n2);
13         math.div(n1, n2);
14     }
15 }

 

5、小結

通過靜態代理,是否完全解決了上述的4個問題:

已解決:

5.1、解決了“開閉原則(OCP)”的問題,因為並沒有修改Math類,而擴展出了MathProxy類。

5.2、解決了“依賴倒轉(DIP)”的問題,通過引入接口。

5.3、解決了“單一職責(SRP)”的問題,Math類不再需要去計算耗時與延時操作,但從某些方面講MathProxy還是存在該問題。

未解決:

5.4、如果項目中有多個類,則需要編寫多個代理類,工作量大,不好修改,不好維護,不能應對變化。

如果要解決上面的問題,可以使用動態代理。

三、動態代理,使用JDK內置的Proxy實現

只需要一個代理類,而不是針對每個類編寫代理類。

在上一個示例中修改代理類MathProxy如下:

 1 package com.zhangguo.Spring041.aop03;
 2 
 3 import java.lang.reflect.InvocationHandler;
 4 import java.lang.reflect.Method;
 5 import java.lang.reflect.Proxy;
 6 import java.util.Random;
 7 
 8 /**
 9  * 動態代理類
10  */
11 public class DynamicProxy implements InvocationHandler {
12 
13     //被代理的對象
14     Object targetObject;
15     
16     /**
17      * 獲得被代理後的對象
18      * @param object 被代理的對象
19      * @return 代理後的對象
20      */
21     public Object getProxyObject(Object object){
22         this.targetObject=object;
23         return Proxy.newProxyInstance(
24                 targetObject.getClass().getClassLoader(), //類加載器
25                 targetObject.getClass().getInterfaces(),  //獲得被代理對象的所有接口
26                 this);  //InvocationHandler對象
27         //loader:一個ClassLoader對象,定義了由哪個ClassLoader對象來生成代理對象進行加載
28         //interfaces:一個Interface對象的數組,表示的是我將要給我需要代理的對象提供一組什麼接口,如果我提供了一組接口給它,那麼這個代理對象就宣稱實現了該接口(多態),這樣我就能調用這組接口中的方法了
29         //h:一個InvocationHandler對象,表示的是當我這個動態代理對象在調用方法的時候,會關聯到哪一個InvocationHandler對象上,間接通過invoke來執行
30     }
31     
32     
33     /**
34      * 當用戶調用對象中的每個方法時都通過下面的方法執行,方法必須在接口
35      * proxy 被代理後的對象
36      * method 將要被執行的方法信息(反射)
37      * args 執行方法時需要的參數
38      */
39     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
40         //被織入的內容,開始時間
41         long start=System.currentTimeMillis();
42         lazy();
43         
44         //使用反射在目標對象上調用方法並傳入參數
45         Object result=method.invoke(targetObject, args);
46         
47         //被織入的內容,結束時間
48         Long span= System.currentTimeMillis()-start;
49         System.out.println("共用時:"+span);
50         
51         return result;
52     }
53     
54     //模擬延時
55     public void lazy()
56     {
57         try {
58             int n=(int)new Random().nextInt(500);
59             Thread.sleep(n);
60         } catch (InterruptedException e) {
61             e.printStackTrace();
62         }
63     }
64 
65 }

測試運行:

 1 package com.zhangguo.Spring041.aop03;
 2 
 3 public class Test {
 4     
 5     //實例化一個MathProxy代理對象
 6     //通過getProxyObject方法獲得被代理後的對象
 7     IMath math=(IMath)new DynamicProxy().getProxyObject(new Math());
 8     @org.junit.Test
 9     public void test01()
10     {
11         int n1=100,n2=5;
12         math.add(n1, n2);
13         math.sub(n1, n2);
14         math.mut(n1, n2);
15         math.div(n1, n2);
16     }
17     
18     IMessage message=(IMessage) new DynamicProxy().getProxyObject(new Message());
19     @org.junit.Test
20     public void test02()
21     {
22         message.message();
23     }
24 }

 

 小結:

 JDK內置的Proxy動態代理可以在運行時動態生成字節碼,而沒必要針對每個類編寫代理類。中間主要使用到了一個接口InvocationHandler與Proxy.newProxyInstance靜態方法,參數說明如下:

 使用內置的Proxy實現動態代理有一個問題:被代理的類必須實現接口,未實現接口則沒辦法完成動態代理。

如果項目中有些類沒有實現接口,則不應該為了實現動態代理而刻意去抽出一些沒有實例意義的接口,通過cglib可以解決該問題。

、動態代理,使用cglib實現

CGLIB(Code Generation Library)是一個開源項目,是一個強大的,高性能,高質量的Code生成類庫,它可以在運行期擴展Java類與實現Java接口,通俗說cglib可以在運行時動態生成字節碼。

 4.1、引用cglib,通過maven

 修改pom.xml文件,添加依賴

保存pom.xml配置文件,將自動從共享資源庫下載cglib所依賴的jar包,主要有如下幾個:

4.2、使用cglib完成動態代理,大概的原理是:cglib繼承被代理的類,重寫方法,織入通知,動態生成字節碼並運行,因為是繼承所以final類是沒有辦法動態代理的。具體實現如下:

 1 package com.zhangguo.Spring041.aop04;
 2 
 3 import java.lang.reflect.Method;
 4 import java.util.Random;
 5 
 6 import net.sf.cglib.proxy.Enhancer;
 7 import net.sf.cglib.proxy.MethodInterceptor;
 8 import net.sf.cglib.proxy.MethodProxy;
 9 
10 /*
11  * 動態代理類
12  * 實現了一個方法攔截器接口
13  */
14 public class DynamicProxy implements MethodInterceptor {
15 
16     // 被代理對象
17     Object targetObject;
18 
19     //Generate a new class if necessary and uses the specified callbacks (if any) to create a new object instance. 
20     //Uses the no-arg constructor of the superclass.
21     //動態生成一個新的類,使用父類的無參構造方法創建一個指定了特定回調的代理實例
22     public Object getProxyObject(Object object) {
23         this.targetObject = object;
24         //增強器,動態代碼生成器
25         Enhancer enhancer=new Enhancer();
26         //回調方法
27         enhancer.setCallback(this);
28         //設置生成類的父類類型
29         enhancer.setSuperclass(targetObject.getClass());
30         //動態生成字節碼並返回代理對象
31         return enhancer.create();
32     }
33 
34     // 攔截方法
35     public Object intercept(Object object, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
36         // 被織入的橫切內容,開始時間 before
37         long start = System.currentTimeMillis();
38         lazy();
39 
40         // 調用方法
41         Object result = methodProxy.invoke(targetObject, args);
42 
43         // 被織入的橫切內容,結束時間
44         Long span = System.currentTimeMillis() - start;
45         System.out.println("共用時:" + span);
46         
47         return result;
48     }
49 
50     // 模擬延時
51     public void lazy() {
52         try {
53             int n = (int) new Random().nextInt(500);
54             Thread.sleep(n);
55         } catch (InterruptedException e) {
56             e.printStackTrace();
57         }
58     }
59 
60 }

測試運行:

package com.zhangguo.Spring041.aop04;

public class Test {
    //實例化一個DynamicProxy代理對象
    //通過getProxyObject方法獲得被代理後的對象
    Math math=(Math)new DynamicProxy().getProxyObject(new Math());
    @org.junit.Test
    public void test01()
    {
        int n1=100,n2=5;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        math.div(n1, n2);
    }
    //另一個被代理的對象,不再需要重新編輯代理代碼
    Message message=(Message) new DynamicProxy().getProxyObject(new Message());
    @org.junit.Test
    public void test02()
    {
        message.message();
    }
}

 

運行結果:

4.3、小結

使用cglib可以實現動態代理,即使被代理的類沒有實現接口,但被代理的類必須不是final類。

五、使用Spring實現AOP

SpringAOP中,通過Advice定義橫切邏輯,Spring中支持5種類型的Advice:

5.1、新建 一個Maven項目,在項目中引入Spring核心庫與AOP,修改pom.xml文件,在dependencies中增加如下節點:

    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>4.3.0.RELEASE</version>
    </dependency>

 

當保存pom.xml文件時會從遠程共享庫自動將需要引入的jar包下載到本地並引入項目中:

5.2、定義通知(Advice)

 前置通知

 1 package com.zhangguo.Spring041.aop05;
 2 
 3 import java.lang.reflect.Method;
 4 
 5 import org.springframework.aop.MethodBeforeAdvice;
 6 
 7 /**
 8  * 前置通知
 9  */
10 public class BeforeAdvice implements MethodBeforeAdvice {
11 
12     /**
13      * method 方法信息
14      * args 參數
15      * target 被代理的目標對象
16      */
17     public void before(Method method, Object[] args, Object target) throws Throwable {
18         System.out.println("-----------------前置通知-----------------");
19     }
20 }

後置通知

 1 package com.zhangguo.Spring041.aop05;
 2 
 3 import java.lang.reflect.Method;
 4 
 5 import org.springframework.aop.AfterReturningAdvice;
 6 
 7 /**
 8  * 後置通知
 9  *
10  */
11 public class AfterAdvice implements AfterReturningAdvice {
12 
13     /*
14      * returnValue 返回值
15      * method 被調用的方法
16      * args 方法參數
17      * target 被代理對象
18      */
19     public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
20         System.out.println("-----------------後置通知-----------------");
21     }
22 
23 }

環繞通知

 1 package com.zhangguo.Spring041.aop05;
 2 
 3 import org.aopalliance.intercept.MethodInterceptor;
 4 import org.aopalliance.intercept.MethodInvocation;
 5 
 6 /**
 7  * 環繞通知
 8  * 方法攔截器
 9  *
10  */
11 public class SurroundAdvice implements MethodInterceptor {
12 
13     public Object invoke(MethodInvocation i) throws Throwable {
14         //前置橫切邏輯
15         System.out.println("方法" + i.getMethod() + " 被調用在對象" + i.getThis() + "上,參數 " + i.getArguments());
16         //方法調用
17         Object ret = i.proceed();
18         //後置橫切邏輯
19         System.out.println("返回值:"+ ret);
20         return ret;
21     }
22 }

5.3、創建代理工廠、設置被代理對象、添加通知。

 1 package com.zhangguo.Spring041.aop05;
 2 
 3 import org.springframework.aop.framework.ProxyFactory;
 4 
 5 public class Test {
 6 
 7     @org.junit.Test
 8     public void test01()
 9     {
10         //實例化Spring代理工廠
11         ProxyFactory factory=new ProxyFactory();
12         //設置被代理的對象
13         factory.setTarget(new Math());
14         //添加通知,橫切邏輯
15         factory.addAdvice(new BeforeAdvice());
16         factory.addAdvice(new AfterAdvice());
17         factory.addAdvice(new SurroundAdvice());
18         //從代理工廠中獲得代理對象
19         IMath math=(IMath) factory.getProxy();
20         int n1=100,n2=5;
21         math.add(n1, n2);
22         math.sub(n1, n2);
23         math.mut(n1, n2);
24         math.div(n1, n2);
25     }
26     @org.junit.Test
27     public void test02()
28     {
29         //message.message();
30     }
31 }

運行結果:

5.4、封裝代理創建邏輯

 在上面的示例中如果要代理不同的對象需要反復創建ProxyFactory對象,代碼會冗余。同樣以實現方法耗時為示例代碼如下:

5.4.1、創建一個環繞通知:

 1 package com.zhangguo.Spring041.aop05;
 2 
 3 import java.util.Random;
 4 
 5 import org.aopalliance.intercept.MethodInterceptor;
 6 import org.aopalliance.intercept.MethodInvocation;
 7 
 8 /**
 9  * 用於完成計算方法執行時長的環繞通知
10  */
11 public class TimeSpanAdvice implements MethodInterceptor {
12 
13     public Object invoke(MethodInvocation invocation) throws Throwable {
14         // 被織入的橫切內容,開始時間 before
15         long start = System.currentTimeMillis();
16         lazy();
17         
18         //方法調用
19         Object result = invocation.proceed();
20         
21         // 被織入的橫切內容,結束時間
22         Long span = System.currentTimeMillis() - start;
23         System.out.println("共用時:" + span);
24         
25         return result;
26     }
27 
28     // 模擬延時
29     public void lazy() {
30         try {
31             int n = (int) new Random().nextInt(500);
32             Thread.sleep(n);
33         } catch (InterruptedException e) {
34             e.printStackTrace();
35         }
36     }
37 }

 5.4.2、封裝動態代理類

 1 package com.zhangguo.Spring041.aop05;
 2 
 3 import org.springframework.aop.framework.ProxyFactory;
 4 
 5 /**
 6  * 動態代理類
 7  *
 8  */
 9 public abstract class DynamicProxy {
10     /**
11      * 獲得代理對象
12      * @param object 被代理的對象
13      * @return 代理對象
14      */
15     public static Object getProxy(Object object){
16         //實例化Spring代理工廠
17         ProxyFactory factory=new ProxyFactory();
18         //設置被代理的對象
19         factory.setTarget(object);
20         //添加通知,橫切邏輯
21         factory.addAdvice(new TimeSpanAdvice());
22         return factory.getProxy();
23     }
24 }

5.4.3、測試運行

 1 package com.zhangguo.Spring041.aop05;
 2 
 3 import org.springframework.aop.framework.ProxyFactory;
 4 
 5 public class Test {
 6 
 7     @org.junit.Test
 8     public void test01()
 9     {
10         //從代理工廠中獲得代理對象
11         IMath math=(IMath) DynamicProxy.getProxy(new Math());
12         int n1=100,n2=5;
13         math.add(n1, n2);
14         math.sub(n1, n2);
15         math.mut(n1, n2);
16         math.div(n1, n2);
17     }
18     @org.junit.Test
19     public void test02()
20     {
21         IMessage message=(IMessage) DynamicProxy.getProxy(new Message());
22         message.message();
23     }
24 }

運行結果:

六、使用IOC配置的方式實現AOP

6.1、引入Spring IOC的核心jar包,方法與前面相同。

6.2、創建IOC的配置文件beans.xml,內容如下:

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p"
 4     xsi:schemaLocation="http://www.springframework.org/schema/beans
 5         http://www.springframework.org/schema/beans/spring-beans.xsd">
 6     <!-- 被代理的目標對象 -->
 7     <bean id="target" class="com.zhangguo.Spring041.aop06.Math"></bean>
 8     <!--通知、橫切邏輯-->
 9     <bean id="advice" class="com.zhangguo.Spring041.aop06.AfterAdvice"></bean>
10     <!--代理對象 -->
11     <!--interceptorNames 通知數組 -->
12     <!--p:target-ref 被代理的對象-->
13     <!--p:proxyTargetClass 被代理對象是否為一個類,如果是則使用cglib,否則使用jdk動態代理  -->
14     <bean id="proxy" class="org.springframework.aop.framework.ProxyFactoryBean"
15         p:interceptorNames="advice"
16         p:target-ref="target"
17         p:proxyTargetClass="true"></bean>
18 </beans>

6.3、獲得代理類的實例並測試運行

 1 package com.zhangguo.Spring041.aop06;
 2 
 3 import org.springframework.context.ApplicationContext;
 4 import org.springframework.context.support.ClassPathXmlApplicationContext;
 5 
 6 public class Test {
 7 
 8     @org.junit.Test
 9     public void test01()
10     {
11         //容器
12         ApplicationContext ctx=new ClassPathXmlApplicationContext("beans.xml");
13         //從代理工廠中獲得代理對象
14         IMath math=(IMath)ctx.getBean("proxy");
15         int n1=100,n2=5;
16         math.add(n1, n2);
17         math.sub(n1, n2);
18         math.mut(n1, n2);
19         math.div(n1, n2);
20     }
21 }

 6.4、小結

這裡有個值得注意的問題:從容器中獲得proxy對象時應該是org.springframework.aop.framework.ProxyFactoryBean類型的對象(如下代碼所示),但這裡直接就轉換成IMath類型了,這是因為:ProxyFactoryBean本質上是一個用來生產Proxy的FactoryBean。如果容器中的某個對象持有某個FactoryBean的引用它取得的不是FactoryBean本身而是 FactoryBean的getObject()方法所返回的對象。所以如果容器中某個對象依賴於ProxyFactoryBean那麼它將會使用到 ProxyFactoryBean的getObject()方法所返回的代理對象這就是ProxyFactryBean得以在容器中使用的原因。

1         ProxyFactoryBean message=new ProxyFactoryBean();
2         message.setTarget(new Message());
3         message.addAdvice(new SurroundAdvice());
4         ((IMessage)message.getObject()).message();

七、使用XML配置Spring AOP切面

7.1、添加引用,需要引用一個新的jar包:aspectjweaver,該包是AspectJ的組成部分。可以去http://search.maven.org搜索後下載或直接在maven項目中添加依賴。

示例中使用pom.xml文件如下所示:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>com.zhangguo</groupId>
    <artifactId>Spring041</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>
    <name>Spring041</name>
    <url>http://maven.apache.org</url>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring.version>4.3.0.RELEASE</spring.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>test</scope>
            <version>4.10</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>${spring.version}</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.9</version>
        </dependency>
        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.4</version>
        </dependency>
    </dependencies>
</project>

7.2、定義通知

該通知不再需要實現任何接口或繼承抽象類,一個普通的bean即可,方法可以帶一個JoinPoint連接點參數,用於獲得連接點信息,如方法名,參數,代理對象等。

 1 package com.zhangguo.Spring041.aop08;
 2 
 3 import org.aspectj.lang.JoinPoint;
 4 
 5 /**
 6  * 通知
 7  */
 8 public class Advices {
 9     //前置通知
10     public void before(JoinPoint jp)
11     {
12         System.out.println("--------------------bofore--------------------");
13         System.out.println("方法名:"+jp.getSignature()+",參數:"+jp.getArgs().length+",代理對象:"+jp.getTarget());
14     }
15     //後置通知
16     public void after(JoinPoint jp){
17         System.out.println("--------------------after--------------------");
18     }
19 }

通知的類型有多種,有些參數會不一樣,特別是環繞通知,通知類型如下:

 1 //前置通知
 2 public void beforeMethod(JoinPoint joinPoint)
 3 
 4 //後置通知
 5 public void afterMethod(JoinPoint joinPoint)
 6 
 7 //返回值通知
 8 public void afterReturning(JoinPoint joinPoint, Object result)
 9 
10 //拋出異常通知
11 //在方法出現異常時會執行的代碼可以訪問到異常對象,可以指定在出現特定異常時在執行通知代碼
12 public void afterThrowing(JoinPoint joinPoint, Exception ex)
13 
14 //環繞通知
15 //環繞通知需要攜帶ProceedingJoinPoint類型的參數
16 //環繞通知類似於動態代理的全過程:ProceedingJoinPoint類型的參數可以決定是否執行目標方法。
17 //而且環繞通知必須有返回值,返回值即為目標方法的返回值
18 public Object aroundMethod(ProceedingJoinPoint pjd)

7.3、配置IOC容器依賴的XML文件beansOfAOP.xml

 1 <?xml version="1.0" encoding="UTF-8"?>
 2 <beans xmlns="http://www.springframework.org/schema/beans"
 3     xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
 4     xmlns:p="http://www.springframework.org/schema/p"
 5     xmlns:aop="http://www.springframework.org/schema/aop"
 6     xsi:schemaLocation="http://www.springframework.org/schema/beans
 7         http://www.springframework.org/schema/beans/spring-beans.xsd
 8         http://www.springframework.org/schema/aop
 9         http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
10      
11     <!--被代理的目標對象 -->
12     <bean id="math" class="com.zhangguo.Spring041.aop08.Math"></bean>
13     <!-- 通知 -->
14     <bean id="advice" class="com.zhangguo.Spring041.aop08.Advices"></bean>
15     <!-- AOP配置 -->
16     <!-- proxy-target-class屬性表示被代理的類是否為一個沒有實現接口的類,Spring會依據實現了接口則使用JDK內置的動態代理,如果未實現接口則使用cblib -->
17     <aop:config proxy-target-class="true">
18         <!-- 切面配置 -->
19         <!--ref表示通知對象的引用 -->
20         <aop:aspect ref="advice">
21             <!-- 配置切入點(橫切邏輯將注入的精確位置) -->
22             <aop:pointcut expression="execution(* com.zhangguo.Spring041.aop08.Math.*(..))" id="pointcut1"/>
23             <!--聲明通知,method指定通知類型,pointcut指定切點,就是該通知應該注入那些方法中 -->
24             <aop:before method="before" pointcut-ref="pointcut1"/>
25             <aop:after method="after" pointcut-ref="pointcut1"/>
26         </aop:aspect>
27     </aop:config>
28 </beans>

 加粗部分的內容是在原IOC內容中新增的,主要是為AOP服務,如果引入失敗則沒有智能提示。xmlns:是xml namespace的簡寫。xmlns:xsi:其xsd文件是xml需要遵守的規范,通過URL可以看到,是w3的統一規范,後面通過xsi:schemaLocation來定位所有的解析文件,這裡只能成偶數對出現。

<bean id="advice" class="com.zhangguo.Spring041.aop08.Advices"></bean>表示通知bean,也就是橫切邏輯bean。<aop:config proxy-target-class="true">用於AOP配置,proxy-target-class屬性表示被代理的類是否為一個沒有實現接口的類,Spring會依據實現了接口則使用JDK內置的動態代理,如果未實現接口則使用cblib;在Bean配置文件中,所有的Spring AOP配置都必須定義在<aop:config>元素內部。對於每個切面而言,都要創建一個<aop:aspect>元素來為具體的切面實現引用後端Bean實例。因此,切面Bean必須有一個標識符,供<aop:aspect>元素引用。

aop:aspect表示切面配置, ref表示通知對象的引用;aop:pointcut是配置切入點,就是橫切邏輯將注入的精確位置,那些包,類,方法需要攔截注入橫切邏輯。

aop:before用於聲明通知,method指定通知類型,pointcut指定切點,就是該通知應該注入那些方法中。在aop Schema中,每種通知類型都對應一個特定地XML元素。通知元素需要pointcut-ref屬性來引用切入點,或者用pointcut屬性直接嵌入切入點表達式。method屬性指定切面類中通知方法的名稱。有如下幾種:

<!-- 前置通知 -->
<aop:before method="before" pointcut-ref="pointcut1"/>
<!-- 後置通知 -->
<aop:after method="after" pointcut-ref="pointcut1"/>
<!--環繞通知 -->
<aop:around method="around" pointcut="execution(* com.zhangguo.Spring041.aop08.Math.s*(..))"/>
<!--異常通知 -->
<aop:after-throwing method="afterThrowing" pointcut="execution(* com.zhangguo.Spring041.aop08.Math.d*(..))"  throwing="exp"/>
<!-- 返回值通知 -->
<aop:after-returning method="afterReturning" pointcut="execution(* com.zhangguo.Spring041.aop08.Math.m*(..))" returning="result"/>

關於execution請查看另一篇文章:

7.4、獲得代理對象

 1 package com.zhangguo.Spring041.aop08;
 2 
 3 import org.springframework.aop.framework.ProxyFactoryBean;
 4 import org.springframework.context.ApplicationContext;
 5 import org.springframework.context.support.ClassPathXmlApplicationContext;
 6 
 7 public class Test {
 8 
 9     @org.junit.Test
10     public void test01()
11     {
12         //容器
13         ApplicationContext ctx=new ClassPathXmlApplicationContext("beansOfAOP.xml");
14         //從代理工廠中獲得代理對象
15         IMath math=(IMath)ctx.getBean("math");
16         int n1=100,n2=5;
17         math.add(n1, n2);
18         math.sub(n1, n2);
19         math.mut(n1, n2);
20         math.div(n1, n2);
21     }
22 }

7.5、測試運行

7.6、環繞通知、異常後通知、返回結果後通知

在配置中我們發現共有5種類型的通知,前面我們試過了前置通知與後置通知,另外幾種類型的通知如下代碼所示:

package com.zhangguo.Spring041.aop08;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

/**
 * 通知
 */
public class Advices {
    //前置通知
    public void before(JoinPoint jp)
    {
        System.out.println("--------------------前置通知--------------------");
        System.out.println("方法名:"+jp.getSignature().getName()+",參數:"+jp.getArgs().length+",被代理對象:"+jp.getTarget().getClass().getName());
    }
    //後置通知
    public void after(JoinPoint jp){
        System.out.println("--------------------後置通知--------------------");
    }
    //環繞通知
    public Object around(ProceedingJoinPoint pjd) throws Throwable{
        System.out.println("--------------------環繞開始--------------------");
        Object object=pjd.proceed();
        System.out.println("--------------------環繞結束--------------------");
        return object;
    }
    //異常後通知
    public void afterThrowing(JoinPoint jp,Exception exp)
    {
        System.out.println("--------------------異常後通知,發生了異常:"+exp.getMessage()+"--------------------");
    }
    //返回結果後通知
    public void afterReturning(JoinPoint joinPoint, Object result)
    {
        System.out.println("--------------------返回結果後通知--------------------");
        System.out.println("結果是:"+result);
    }
}

 容器配置文件beansOfAOP.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:p="http://www.springframework.org/schema/p"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop-4.3.xsd">
     
    <!--被代理的目標對象 -->
    <bean id="math" class="com.zhangguo.Spring041.aop08.Math"></bean>
    <!-- 通知 -->
    <bean id="advice" class="com.zhangguo.Spring041.aop08.Advices"></bean>
    <!-- AOP配置 -->
    <!-- proxy-target-class屬性表示被代理的類是否為一個沒有實現接口的類,Spring會依據實現了接口則使用JDK內置的動態代理,如果未實現接口則使用cblib -->
    <aop:config proxy-target-class="true">
        <!-- 切面配置 -->
        <!--ref表示通知對象的引用 -->
        <aop:aspect ref="advice">
            <!-- 配置切入點(橫切邏輯將注入的精確位置) -->
            <aop:pointcut expression="execution(* com.zhangguo.Spring041.aop08.Math.a*(..))" id="pointcut1"/>
            <!--聲明通知,method指定通知類型,pointcut指定切點,就是該通知應該注入那些方法中 -->
            <aop:before method="before" pointcut-ref="pointcut1"/>
            <aop:after method="after" pointcut-ref="pointcut1"/>
            <aop:around method="around" pointcut="execution(* com.zhangguo.Spring041.aop08.Math.s*(..))"/>
            <aop:after-throwing method="afterThrowing" pointcut="execution(* com.zhangguo.Spring041.aop08.Math.d*(..))"  throwing="exp"/>
            <aop:after-returning method="afterReturning" pointcut="execution(* com.zhangguo.Spring041.aop08.Math.m*(..))" returning="result"/>
        </aop:aspect>
    </aop:config>
</beans>

測試代碼:

package com.zhangguo.Spring041.aop08;

import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class Test {

    @org.junit.Test
    public void test01()
    {
        //容器
        ApplicationContext ctx=new ClassPathXmlApplicationContext("beansOfAOP.xml");
        //從代理工廠中獲得代理對象
        IMath math=(IMath)ctx.getBean("math");
        int n1=100,n2=0;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        math.div(n1, n2);
    }
}

運行結果:

小結:不同類型的通知參數可能不相同;aop:after-throwing需要指定通知中參數的名稱throwing="exp",則方法中定義應該是這樣:afterThrowing(JoinPoint jp,Exception exp);aop:after-returning同樣需要設置returning指定方法參數的名稱。通過配置切面的方法使AOP變得更加靈活。

八、示例下載

點擊下載

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved