1. 程式人生 > 其它 >JDK 動態代理(AOP)

JDK 動態代理(AOP)

概述

  • 什麼是動態代理
    1、使用 jdk 的反射機制,建立物件的能力, 建立的是代理類的物件。 而不用你建立類檔案。不用寫 java 檔案。
    2、動態:在程式執行時,呼叫 jdk 提供的方法才能建立代理類的物件。
    3、jdk 動態代理,必須有介面,目標類必須實現介面,沒有介面時,需要使用 cglib 動態代理。
  • 動態代理能做什麼
    1、可以在不改變原來目標方法功能的前提下,可以在代理中增強自己的功能程式碼。
    2、背景示例。
    比如:你所在的專案中,有一個功能是其他人(公司的其它部門,其它小組的人)寫好的,你可以使用(但是隻有 Class 檔案):Demo.class。
Demo dm = new Demo();
dm.print();

當這個功能缺少時, 不能完全滿足我專案的需要。我需要在 dm.print() 執行後,需要自己在增加程式碼。
這時就會用代理實現 dm.print() 呼叫時, 增加自己程式碼, 而不用去改原來的 Demo 檔案。
(在 mybatis,spring 中也有應用)

現實中的代理

代購,中介,換 ip,商家等等。

例1

比如有一家美國的大學, 可以對全世界招生。 這時就會有留學中介(代理)。
留學中介(代理): 幫助這家美國的學校招生,中介是學校的代理, 中介是代替學校完成招生功能。

  • 代理特點:
    1、中介和代理他們要做的事情是一致的:招生。
    2、中介是學校代理, 學校是目標。
    3、家長---中介(學校介紹,辦入學手續)---美國學校。
    4、中介是代理,不能白乾活,需要收取費用(功能增強)。
    5、代理不讓你訪問到目標。
  • 為什麼要找中介 ?
    1、中介是專業的, 方便
    2、家長現在不能自己去找學校。 家長沒有能力訪問學校。 或者美國學校不接收個人來訪。
    3、買東西都是商家賣, 商家是某個商品的代理, 你個人買東西, 肯定不會讓你接觸到廠家的。
    4、在開發中也會有這樣的情況, 你有a類, 本來是呼叫c類的方法, 完成某個功能。 但是c不讓a呼叫。
    a -----不能呼叫 c的方法。
    在a 和 c 直接 建立一個 b 代理, c讓b訪問。
    a --訪問b---訪問c
例2

登入,註冊有驗證碼,驗證碼是手機簡訊。
中國移動,聯通,電信都能發簡訊。他們都有子公司,或者關聯公司,他們面向社會提供簡訊的傳送功能。
張三的專案需要傳送簡訊功能,他去找子公司,或者關聯公司,然後這些公司再去找中國移動,聯通,電信。

使用代理模式的作用

1、功能增強:在你原有的功能上,增加了額外的功能。新增加的功能,叫做功能增強。
2、控制訪問:代理類不讓你訪問目標,例如商家不讓使用者訪問廠家。

實現代理的方式

分為靜態代理、動態代理。

靜態代理:

1、代理類是自己手工實現的,自己建立一個java類,表示代理類。
2、同時你所要代理的目標類是確定的。

特點

1、實現簡單。
2、容易理解。

缺點

當專案中目標類和代理類很多時候:
1、當目標類增加了,代理類可能也需要成倍的增加。代理類數量過多。
2、當你的介面中功能增加了,或者修改了,會影響眾多的實現類,廠家類,代理都需要修改。影響比較多。

模擬一個使用者購買u盤的行為
  • 背景
    使用者是客戶端類
    商家:代理,代理某個品牌的u盤。
    廠家:目標類。
    三者的關係: 使用者(客戶端)---商家(代理)---廠家(目標)
    商家和廠家都是賣u盤的,他們完成的功能是一致的,都是賣u盤。
  • 實現步驟:
    1、建立一個介面,定義賣u盤的方法, 表示你的廠家和商家做的事情。
    2、創建廠家類,實現1步驟的介面。
    3、建立商家,就是代理,也需要實現1步驟中的介面。
    4、建立客戶端類,呼叫商家的方法買一個u盤。
  • 代理類完成的功能:
    1、目標類中方法的呼叫
    2、功能增強

動態代理

1、在程式執行過程中,使用jdk的反射機制,建立代理類物件, 並動態的指定要代理目標類。換句話說: 動態代理是一種建立java物件的能力,讓你不用建立TaoBao類,就能建立代理類物件。
2、在靜態代理中目標類很多時候,可以使用動態代理,避免靜態代理的缺點。
3、即使動態代理中目標類很多,但是代理類數量可以很少;當修改了介面中的方法時,不會影響代理類。
4、動態代理有cglib 動態代理和 jdk 動態代理。

  • 動態代理的實現:
    1、jdk 動態代理(理解):使用 java 反射包中的類和介面實現動態代理的功能。反射包 java.lang.reflect,裡面有三個類:
InvocationHandler
Method
Proxy

cglib 動態代理(瞭解)

1、cglib 是第三方的工具庫,用於建立代理物件。
2、cglib 的原理是繼承, cglib 通過繼承目標類,建立它的子類,在子類中重寫父類中同名的方法, 實現功能的修改。
3、因為 cglib 是繼承,重寫方法,所以要求目標類不能是 final 的, 方法也不能是 final 的。
4、cglib 的要求目標類比較寬鬆, 只要能繼承就可以了。
5、cglib 在很多的框架中使用,比如 mybatis,spring 框架中都有使用。

jdk 動態代理

InvocationHandler 介面(呼叫處理器)

就一個方法 invoke()。
invoke():表示代理物件要執行的功能程式碼。你的代理類要完成的功能就寫在 invoke() 方法中。

  • 代理類完成的功能:
    1、呼叫目標方法,執行目標方法的功能
    2、功能增強,在目標方法呼叫時,增加功能。
  • 方法原型:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable;

引數:

Object proxy:jdk建立的代理物件,無需賦值。
Method method:目標類中的方法,jdk提供method物件的
Object[] args:目標類中方法的引數, jdk提供的。
InvocationHandler 介面:表示你的代理要幹什麼、怎麼用

1、建立類實現介面 InvocationHandler
2、重寫invoke()方法, 把原來靜態代理中代理類要完成的功能,寫在這。
Method類:表示方法的, 確切的說就是目標類中的方法。
作用:通過Method可以執行某個目標類的方法,Method.invoke();
method.invoke(目標物件,方法的引數)
Object ret = method.invoke(service2, "李四");
說明:
method.invoke()就是用來執行目標方法的
等同於靜態代理中的:
//向廠家傳送訂單,告訴廠家,我買了u盤,廠家發貨
float price = factory.sell(amount); //廠家的價格。

3)Proxy類:核心的物件,建立代理物件。之前建立物件都是 new 類的構造方法()
現在我們是使用Proxy類的方法,代替new的使用。
方法: 靜態方法 newProxyInstance()
作用是: 建立代理物件, 等同於靜態代理中的TaoBao taoBao = new TaoBao();

引數:
1、ClassLoader loader 類載入器,負責向記憶體中載入物件的。 使用反射獲取物件的ClassLoader類a,
a.getCalss().getClassLoader(),目標物件的類載入器。
2、Class[] interfaces:介面,目標物件實現的介面,也是反射獲取的。 3、InvocationHandler h:我們自己寫的,代理類要完成的功能。 返回值:就是代理物件 public static Object newProxyInstance(ClassLoader loader, Class[] interfaces,
InvocationHandler h)

實現動態代理的步驟:

1、建立介面,定義目標類要完成的功能.
2、建立目標類實現介面。
3、建立 InvocationHandler 介面的實現類,在 invoke 方法中完成代理類的功能。
(1)、呼叫目標方法;
(2)、增強功能;
3、使用 Proxy 類的靜態方法,建立代理物件。 並把返回值轉為介面型別。

程式碼

UsbSell.java:

package com.proxy.sevice;

public interface UsbSell {
    float sell(int amount);
}

UsbKingFactory.java:

package com.proxy.factory;

import com.proxy.sevice.UsbSell;

/**
 * 目標類
 */
public class UsbKingFactory implements UsbSell {
    @Override
    public float sell(int amount) {
        System.out.println("目標類中執行 sell 方法");
        return 85.0f;
    }
}

MySellHandler.java:

package com.proxy.handler;

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

public class MySellHandler implements InvocationHandler {
    private Object target = null;

    //動態代理物件目標是活動的,不是固定的,需要傳進來
    //傳入的是誰,就給誰代理
    public MySellHandler(Object target) {
        //給目標物件賦值
        this.target = target;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object res = null;
        /* float price = factory.sell(amount);
        用以下程式碼替換 */
        res = method.invoke(target, args);//執行目標方法
        /*
        * 代理商加價(增強功能)
        * 在目標類的方法呼叫後,做的其他功能就是功能增強
        * */
        if (res != null){
            float price = (float) res;
            res = (price + 25) * Float.parseFloat(args[0].toString());
        }
        return res;
    }
}

MainShop.java:

package com.proxy;

import com.proxy.factory.UsbKingFactory;
import com.proxy.handler.MySellHandler;
import com.proxy.sevice.UsbSell;

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

public class MainShop {
    public static void main(String[] args) {
        //建立代理類,使用 Proxy
        //1、建立目標物件
        UsbSell factory = new UsbKingFactory();
        //2、建立 InvocationHandler 物件
        InvocationHandler handler = new MySellHandler(factory);
        //3、建立代理物件
        UsbSell proxy = (UsbSell) Proxy.newProxyInstance(factory.getClass().getClassLoader(),
                factory.getClass().getInterfaces(),
                handler);
        //通過代理物件執行方法
        float price = proxy.sell(3);
        System.out.println("通過動態代理物件呼叫方法:"+price);
    }
}

B站評論的筆記