3.靜態代理&動態代理&CGlib

code1997發表於2020-10-28

代理模式

1 代理的概念

代理的作用:為了實現在不更改被代理類的基礎上,對被代理類的功能進行擴充套件。

代理物件:對被代理物件的功能進行擴充套件(中介)

目標物件:被代理物件。

原則:代理物件,代理的是目標物件,不能有更多的功能。

2 實現方式

2.1 靜態代理

2.1.1 靜態代理的要求
  1. 代理類和被代理類必須實現相同的介面。
  2. 代理類中存在被代理類的物件的引用。
2.1.2 程式碼實現
package com.atguigu.proxytest;

/**
 * 靜態代理舉例
 * 特點:代理類和被代理類在編譯期間就已經確定了。
 *
 * @author 龍
 */
interface ClothFactory {
    /**
     * 生產衣服的方法
     */
    void produceCloth();
}

/**
 * 代理類
 */
class ProxyClothFactory implements ClothFactory {
    /**
     * 使用被代理類物件進行例項化
     */
    private ClothFactory factory;

    public ProxyClothFactory(ClothFactory factory) {
        this.factory = factory;
    }

    @Override
    public void produceCloth() {
        System.out.println("代理工廠進行一些準備工作");
        factory.produceCloth();
        System.out.println("代理工廠進行一些後續的收尾工作");
    }
}
/**
 * 被代理類
 */
class NikeClothFactory implements ClothFactory {

    @Override
    public void produceCloth() {
        System.out.println("Nike生產了一批運動服裝");
    }
}

/**
 * @author 龍
 */
public class StaticProxyTest {
    public static void main(String[] args) {
        NikeClothFactory nike = new NikeClothFactory();
        ProxyClothFactory proxyClothFactory = new ProxyClothFactory(nike);
        proxyClothFactory.produceCloth();
    }
}

2.1.3 存在的問題
  1. 代理類和被代理類必須實現相同的介面。
  2. 代理類和被代理類在編譯時期,就已經確定。
  3. 只可以擴充套件介面中的方法。
  4. 一旦介面增加方法,就需要對目標物件和代理類進行功能擴充套件。

2.2 動態代理

2.2.1 要求
  • 必須實現一個介面
  • 代理類存在一個
2.2.2 程式碼實現
package com.atguigu.proxytest;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @author 龍
 */
interface Human {
    /**
     * 能力
     *
     * @return :能力
     */
    String getBelief();

    /**
     * 吃食物
     *
     * @param food :食物
     */
    void eat(String food);
}

/**
 * 超人類
 */
class SuperMan implements Human {

    @Override
    public String getBelief() {
        return "I can Fly";
    }

    @Override
    public void eat(String food) {
        System.out.println("我喜歡吃" + food);
    }
}

/**
 * 根據被代理類建立代理類物件
 */
class ProxyFactory {
    public static Object getProxyInstance(Object obj) {
        //呼叫此方法,返回一個代理類物件
        MyInvocationHandler handler = new MyInvocationHandler();
        handler.bind(obj);
        Object o = Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), handler);
        return o;
    }
}

class MyInvocationHandler implements InvocationHandler {
    //被代理類物件
    private Object object;

    public void bind(Object object) {
        this.object = object;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //當我們通過代理類物件呼叫方法A的時候就會呼叫該方法,將被代理類要求執行的方法a的功能就生命在invoke()中
        HumanUtil util = new HumanUtil();
        util.method1();
        //類似於AOP:面向切面程式設計。
        Object invoke = method.invoke(object, args);
        util.method2();
        return invoke;
    }
}

class HumanUtil {
    public void method1() {
        System.out.println("通用方法1");
    }

    public void method2() {
        System.out.println("通用方法2");
    }
}

/**
 * 存在的問題:
 * 一:如何根據載入到記憶體中的被代理類,動態的建立一個代理類及其物件。
 * 二:當通過代理類的物件呼叫方法時,如何動態的去呼叫被代理的同名方法
 *
 * @author 龍
 */
public class ProxyTest {
    public static void main(String[] args) {
        SuperMan superMan = new SuperMan();
        //此時為代理類物件
        Human proxyInstance = (Human) ProxyFactory.getProxyInstance(superMan);
        //會自動的呼叫被代理類中同名的方法。
        proxyInstance.eat("四川麻辣燙");
        String belief = proxyInstance.getBelief();
        System.out.println(belief);
        NikeClothFactory nikeClothFactory = new NikeClothFactory();
        ClothFactory proxyInstance1 = (ClothFactory) ProxyFactory.getProxyInstance(nikeClothFactory);
        proxyInstance1.produceCloth();
    }
}
2.2.3 動態代理

優點:

  1. 不需要我們手動的建立代理類,只需要編寫一個動態處理器,真正的代理物件由JDK在執行的時候為我們動態的建立。
  2. 減少了對業務介面的依賴,降低了耦合度。

缺點:

  1. 無法擺脫對介面的依賴,必須要實現一個介面,這是由java的繼承機制註定了這些被動態代理類必須實現一個介面。

2.3 cglib

2.3.1 出現背景

​ 因為JDK要求動態代理必須實現通過介面定義業務方法,對於沒有介面的類,如何實現動態代理,這就是CGlib要實現的。CGLib採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。但因為採用的是繼承,所以不能對final修飾的類進行代理。JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。Cglib又被稱為:子類代理。

2.3.2 程式碼實現
package com.atguigu.proxytest;

import org.springframework.cglib.proxy.Enhancer;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;

import java.lang.reflect.Method;

public class CGLibTest {
    /*cglib代理:子類代理
     * 特點建立目標類的子類  通過重寫父類方法來實現目標類功能的控制
     * */
    public static void main(String[] args) {
        Teacher t=new Teacher(31, "韓寒");//定義目標類物件
        ZiProxyFactroy factroy=new ZiProxyFactroy(t);//建立工廠類物件並關聯目標類物件
        //呼叫工廠類物件的getInstance方法動態獲取目標類的子類物件
        Teacher proxy=(Teacher)factroy.getInstance();
        proxy.hehe();
        proxy.teach();
    }

}
//1 建立子類物件工廠類    實現MethodInterceptor
class ZiProxyFactroy implements MethodInterceptor{
    //2 定義成員變數記錄目的物件
    private Object target;
    public ZiProxyFactroy(Object target) {//通過構造方法關聯目標物件
        this.target = target;
    }
    //3 目標物件的方法被呼叫時  intercept方法就會執行
    public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        System.out.println(method.getName()+"+++++方法被呼叫前的域處理程式碼");
        Object result=method.invoke(target, args);
        System.out.println(method.getName()+"-----方法被呼叫後的後處理程式碼");
        return result;
    }
    //4 定義一個動態生成目標類的子類物件
    public Object getInstance() {
        Enhancer er=new Enhancer(); //4.1 建立加強工具類
        er.setSuperclass(target.getClass()); //4.2 指定要加強的父類(目標類)
        er.setCallback(this); //4.3  設定回撥函式
        return er.create(); //4.4 返回一個子類物件
    }
}
//5 定義目標類
class Teacher{
    int age; String name;
    public Teacher(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public Teacher() {
        super();
    }

    void hehe() {
        System.out.println("老師::"+age+","+name);
    }
    void teach() {
        System.out.println(name+"正在教書!!!");
    }
}

注:需要匯入jar包 spring-core-4.3.18.RELEASE.jar

相關文章