1. 程式人生 > >spring AOP 代理(靜態與動態+使用cglib實現)

spring AOP 代理(靜態與動態+使用cglib實現)

一、沒有代理模式

缺點:

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

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

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

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

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

Math類:

public
class Math { // 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; } }
View Code

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

public class Math {
    //
    public int add(int n1,int n2){
        //開始時間
        long start = getTime();
        delay();
        int result=n1+n2;
        System.out.println(n1+"+"+n2+"="+result);
        System.out.println("共用時:" + (getTime() - start));
        return result;
    }
    //
    public int sub(int n1,int n2){
        //開始時間
        long start = getTime();
        delay();
        int result=n1*n2;
        System.out.println(n1+"-"+n2+"="+result);
        System.out.println("共用時:" + (getTime() - start));
        return result;
    }

    //
    public int mut(int n1,int n2){
        //開始時間
        long start = getTime();
        delay();
        int result=n1*n2;
        System.out.println(n1+"X"+n2+"="+result);
        System.out.println("共用時:" + (getTime() - start));
        return result;
    }

    //
    public int div(int n1,int n2){
        //開始時間
        long start = getTime();
        delay();
        int result=n1/n2;
        System.out.println(n1+"/"+n2+"="+result);
        System.out.println("共用時:" + (getTime() - start));
        return result;
    }
    public static long getTime() {
        return System.currentTimeMillis();
    }
    public static void delay(){
        try {
            int n = (int) new Random().nextInt(500);
            Thread.sleep(n);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
View Code

測試執行:

 Math math = new Math();
        int n=100;
        int m=5;
        math.add(n,m);
        math.sub(n,m);
        math.mut(n,m);
        math.div(n,m);

 

二、靜態代理

 1、定義抽象主題介面。

IMath:
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、實現介面:

MathImpl
public class MathImpl 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;
    }
}
View Code

3、代理類

MathProxy
public class MathProxy implements IMath {
    IMath math = new MathImpl();

    @Override
    public int add(int n1, int n2) {
        //開始時間
        long start = getTime();
        delay();
        int result = math.add(n1, n2);
        System.out.println("共用時:" + (getTime() - start));
        return result;
    }

    @Override
    public int sub(int n1, int n2) {
        //開始時間
        long start = getTime();
        delay();
        int result = math.sub(n1, n2);
        System.out.println("共用時:" + (getTime() - start));
        return result;
    }

    @Override
    public int mut(int n1, int n2) {
        //開始時間
        long start = getTime();
        delay();
        int result = math.mut(n1, n2);
        System.out.println("共用時:" + (getTime() - start));
        return result;
    }

    @Override
    public int div(int n1, int n2) {
        //開始時間
        long start = getTime();
        delay();
        int result = math.div(n1, n2);
        System.out.println("共用時:" + (getTime() - start));
        return result;
    }

    public static long getTime() {
        return System.currentTimeMillis();
    }
    public static void delay(){
        try {
            int n = (int) new Random().nextInt(500);
            Thread.sleep(n);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
View Code

測試執行:

  IMath math = new MathProxy();
        int n1=100,n2=5;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        math.div(n1, n2);
View Code

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

已解決:

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

2、解決了“依賴倒轉(DIP)”的問題,通過引入介面。

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

未解決:

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

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

三、動態代理

1、介面不變:

2、實現類介面不變

3、修改代理類,實現介面InvocationHandler

DynamicProxy

 

package com.wbg.springAOP.springdynamic;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Random;
/**
 * 動態代理類
 */
public class DynamicProxy implements InvocationHandler {
    IMath math = new MathImpl();
    //目標代理物件
    Object targetObject;
    public DynamicProxy(Object target){
        this.targetObject=target;
    }
    public DynamicProxy(){

    }
    /**
     * 獲得被代理後的物件
     * @param object 被代理的物件
     * @return 代理後的物件
     */
    public Object getProxyObject(Object object){
        this.targetObject=object;
        return Proxy.newProxyInstance(
                targetObject.getClass().getClassLoader(), //類載入器
                targetObject.getClass().getInterfaces(),  //獲得被代理物件的所有介面
                this);  //InvocationHandler物件
        //loader:一個ClassLoader物件,定義了由哪個ClassLoader物件來生成代理物件進行載入
        //interfaces:一個Interface物件的陣列,表示的是我將要給我需要代理的物件提供一組什麼介面,如果我提供了一組介面給它,那麼這個代理物件就宣稱實現了該介面(多型),這樣我就能呼叫這組介面中的方法了
        //h:一個InvocationHandler物件,表示的是當我這個動態代理物件在呼叫方法的時候,會關聯到哪一個InvocationHandler物件上,間接通過invoke來執行
    }


    /**
     * 獲取時間
     * @return
     */
    public static long getTime() {
        return System.currentTimeMillis();
    }

    /**
     * 延遲
     */
    public static void delay(){
        try {
            int n = (int) new Random().nextInt(500);
            Thread.sleep(n);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 當用戶呼叫物件中的每個方法時都通過下面的方法執行,方法必須在介面
     * proxy 被代理後的物件
     * method 將要被執行的方法資訊(反射)
     * args 執行方法時需要的引數
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        long start=getTime();
        delay();
        Object result=method.invoke(targetObject,args);
        System.out.println("共用時:" + (getTime() - start));
        return result;
    }
}

 

4、測試:

方式一:

 DynamicProxy dynamicProx = new DynamicProxy(new MathImpl());
        IMath math = (IMath) Proxy.newProxyInstance(Test.class.getClassLoader(),new Class[]{IMath.class},dynamicProx);
        int n1=100,n2=5;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        math.div(n1, n2);

方式二:

  //例項化一個MathProxy代理物件
      //通過getProxyObject方法獲得被代理後的物件
      IMath math=(IMath)new DynamicProxy().getProxyObject(new MathImpl());
        int n1=100,n2=5;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        math.div(n1, n2);

執行結果

JDK內建的Proxy動態代理可以在執行時動態生成位元組碼,而沒必要針對每個類編寫代理類。中間主要使用到了一個介面InvocationHandler與Proxy.newProxyInstance靜態方法,引數說明如下:

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

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

、動態代理,使用cglib實現

CGLIB(Code Generation Library)是一個開源專案,是一個強大的,高效能,高質量的Code生成類庫,它可以在執行期擴充套件Java類與實現Java介面,通俗說cglib可以在執行時動態生成位元組碼。

1、引用cglib,通過maven

 <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>3.2.9</version>
        </dependency>

 2、實現DynamicProxyCgilb類 介面MethodInterceptor

package com.wbg.springAOP.springdynamic;

import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;
import java.util.Random;

/*
 * 動態代理類
 * 實現了一個方法攔截器介面
 */
public class DynamicProxyCgilb implements MethodInterceptor {

    //被代理物件
    Object targetObject;
    //動態生成一個新的類,使用父類的無參構造方法建立一個指定了特定回撥的代理例項
    public Object getProxyObject(Object object){
        this.targetObject = object;
        //增強器,動態程式碼生成器
        Enhancer enhancer=new Enhancer();
        //回撥方法
        enhancer.setCallback(this);
        //設定生成類的父類型別
        enhancer.setSuperclass(targetObject.getClass());
        //動態生成位元組碼並返回代理物件
        return enhancer.create();
    }
    // 攔截方法
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        long start=getTime();
        delay();
        Object result=methodProxy.invoke(targetObject,objects);
        System.out.println("共用時:" + (getTime() - start));
        return result;
    }


    /**
     * 獲取時間
     * @return
     */
    public static long getTime() {
        return System.currentTimeMillis();
    }

    /**
     * 延遲
     */
    public static void delay(){
        try {
            int n = (int) new Random().nextInt(500);
            Thread.sleep(n);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
View Code

3、測試

  MathImpl math = (MathImpl) new DynamicProxyCgilb().getProxyObject(new MathImpl());
        int n1=100,n2=5;
        math.add(n1, n2);
        math.sub(n1, n2);
        math.mut(n1, n2);
        math.div(n1, n2);
View Code

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