1. 程式人生 > 其它 >動態代理兩種方式

動態代理兩種方式

1.動態代理兩種方式簡述

JDK動態代理:利用反射機制生成一個實現代理介面匿名類,在呼叫具體方法前呼叫InvokeHandler來處理。

CGLib動態代理:利用ASM(開源的Java位元組碼編輯庫,操作位元組碼)開源包,將代理物件類的class檔案載入進來,通過修改其位元組碼生成子類來處理。

區別:JDK代理只能對實現介面的類生成代理;CGlib是針對類實現代理,對指定的類生成一個子類,並覆蓋其中的方法,這種通過繼承類的實現方式,不能代理final修飾的類。

2.動態代理的特點

1.位元組碼隨用隨建立,隨用隨載入。 2.它與靜態代理的區別也在於此。因為靜態代理是位元組碼一上來就建立好,並完成載入。
3.裝飾者模式就是靜態代理的一種體現。

3.動態代理常用的有兩種方式

3.1 基於介面的動態代理

提供者:JDK 官方的 Proxy 類。 要求:被代理類最少實現一個介面。

3.2 基於子類的動態代理

提供者:第三方的 CGLib,如果報 asmxxxx 異常,需要匯入 asm.jar。 要求:被代理類不能用 final 修飾的類(最終類)。

4.使用JDK官方的Porxy類建立物件

實體類:

package com.jh.spring13jdk動態代理;

import lombok.Data;

@Data
public class Game implements Open{
    
//遊戲的網速 private int ms = 460; @Override public int openApp() { System.out.println("開啟遊戲後的網速是:" + this.getMs()); return this.getMs(); } }

介面:

package com.jh.spring13jdk動態代理;

public interface Open {
    int openApp();
}

測試類:

package com.jh.spring13jdk動態代理;

import org.junit.Test;

import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; public class Spring13Test { //JDK動態代理 省略了代理物件!!! 直接有jdk中的代理類來實現,但是目標類必須有介面 @Test public void test01() { final Game game = new Game(); /** * 代理: * 間接。 * 獲取代理物件: * 要求: * 被代理類最少實現一個介面 * 建立的方式 * Proxy.newProxyInstance(三個引數) * 引數含義: * ClassLoader:和被代理物件使用相同的類載入器。 * Interfaces:和被代理物件具有相同的行為。實現相同的介面。 * InvocationHandler:如何代理。 * 策略模式:使用場景是: * 資料有了,目的明確。 * 如何達成目標,就是策略。 * */ //使用JDK動態類物件,當作迅遊加速器的類,代替了靜態的代理類 //Proxy:代理的意思。 newProxyInstancece建立代理物件 Open jdkProxy = (Open) Proxy.newProxyInstance( game.getClass().getClassLoader(), //類的載入器 game.getClass().getInterfaces(), //類的所有介面 new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Integer ms = (Integer) method.invoke(game, args); if (ms != null) { ms = ms - 400; } return ms; } } ); int i = jdkProxy.openApp(); System.out.println("i=" + i); } }

5.使用CGLib的Enhancer類建立代理物件

對於沒有介面的類,如何實現動態代理呢,這就需要CGLib了,CGLib採用了非常底層的位元組碼技術,其原理是通過位元組碼技術為一個類建立子類,並在子類中採用方法攔截的技術攔截所有父類方法的呼叫,順勢織入橫切邏輯。但因為採用的是繼承。所以不能對final修飾的類進行代理。

實體類:

package com.jh.spring14cjlib動態代理;

import lombok.Data;

/**
 * 目標類
 * 父母
 */
@Data
public class Parents {
    //成績
    private int score = 599;

    //高考
    public int gaoKao(){
        System.out.println("父母參加高考,分數是:"+this.getScore());
        return this.getScore();
    }
}

工廠類:

package com.jh.spring14cjlib動態代理;

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

import java.lang.reflect.Method;

//MethodInterceptor:方法上的攔截
public class ParentsFactory implements MethodInterceptor {
    private Parents parents;

    public ParentsFactory() {
        parents = new Parents();
    }


    /**
     * 基於子類的動態代理
     * 要求:
     * 被代理物件不能是最終類
     * 用到的類:
     * Enhancer
     * 用到的方法:
     * create(Class, Callback)
     * 方法的引數:
     * Class:被代理物件的位元組碼
     * Callback:如何代理
     */
    //增強器 ,把parents創造一個子類
    public Parents createParentsSon() {
        //使用位元組碼增強器,去增強我們的父類
        Enhancer enhancer = new Enhancer(); //位元組碼增強器,可以讀懂class檔案
        //enhancer 指定一個物件
        enhancer.setSuperclass(Parents.class);//反射
        //使用工廠,換行(建立子類)
        enhancer.setCallback(this);
        //建立子類
        Parents son = (Parents) enhancer.create(); //多型
        return son;
    }

    /**
     * 執行被代理物件的任何方法,都會經過該方法。在此方法內部就可以對被代理物件的任何
     方法進行增強。
     *
     * 引數:
     * 前三個和基於介面的動態代理是一樣的。
     * MethodProxy:當前執行方法的代理物件。
     * 返回值:
     * 當前執行方法的返回值
     */
    //方法的攔截
    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        Integer score = (Integer) method.invoke(parents, objects);
        if (score != null) {
            score = score + 30;
        }
        return score;
    }
}

測試類:

package com.jh.spring14cjlib動態代理;

import org.junit.Test;

public class Spring14Test {
    @Test
    public void test01(){
        ParentsFactory parentsFactory = new ParentsFactory();
        Parents parentsSon = parentsFactory.createParentsSon();
        int score = parentsSon.getScore();
        System.out.println(score);
    }
}

6.總結:

CGLib建立的動態代理物件比JDK建立的動態代理物件的效能更高,但是CGLib建立物件時所花費的時間卻比JDK多的的。

7.問題:

為什麼,要在不改變原始碼的基礎上,去寫一個代理類增強一些功能呢?

因為專案大了,就有主要的功能和次要的功能,要想主要功能和次要功能一起執行,必須用AOP

我再解釋下面向物件和麵向切面,面向物件OOP,面向的是主要功能的物件,而AOP是面向物件OOP的一個補充,面向次要功能的物件

目的是為了降低耦合度,提高程式碼的複用性。(自己總結的,僅供參考,你懂的,嘻嘻)。