目錄
- 一、代理
- 1. 什么是代理?
- 2. 使用代理模式的作用
- 3. 實作代理的方式
- 二、靜態代理
- 1. 模擬用戶購買u盤
- 2. 靜態代理的缺點
- 三、動態代理
- 四、 JDK 動態代理
- 1. InvocationHandler介面
- 2. Method 類
- 3. Proxy類
- 4. 實作動態代理的步驟
- 五、cgLib 代理
- 六、練習
動態代理(理解): 基于反射機制,
現在需要知道以下的就行:
-
什么是動態代理 ?
使用jdk的反射機制,創建物件的能力, 創建的是代理類的物件, 而不用你創建類檔案,不用寫java檔案,
動態:在程式執行時,呼叫jdk提供的方法才能創建代理類的物件,
jdk動態代理,必須有介面,目標類必須實作介面, 沒有介面時,需要使用cglib動態代理
- 知道動態代理能做什么 ?
可以在不改變原來目標方法功能的前提下, 可以在代理中增強自己的功能代碼,
程式開發中的意思,
比如:你所在的專案中,有一個功能是其他人(公司的其它部門,其它小組的人)寫好的,你可以使用,
GoNong.class , GoNong gn = new GoNong(), gn.print();
你發現這個功能,現在還缺點, 不能完全滿足我專案的需要, 我需要在gn.print()執行后,需要自己在增加代碼,
用代理實作 gn.print()呼叫時, 增加自己代碼, 而不用去改原來的 GoNong檔案
后面會有,mybatis ,spring
一、代理
1. 什么是代理?
代購, 中介,換ip,商家等等
比如有一家美國的大學, 可以對全世界招生, 留學中介(代理)
留學中介(代理): 幫助這家美國的學校招生, 中介是學校的代理, 中介是代替學校完成招生功能,
代理特點:
1. 中介和代理他們要做的事情是一致的: 招生,
2. 中介是學校代理, 學校是目標,
3. 家長---中介(學校介紹,辦入學手續)----美國學校,
4. 中介是代理,不能白干活,需要收取費用,
5. 代理不讓你訪問到目標,
或者是買東西都是商家賣, 商家是某個商品的代理, 你個人買東西, 肯定不會讓你接觸到廠家的,
在開發中也會有這樣的情況, 你有a類, 本來是呼叫c類的方法, 完成某個功能, 但是c不讓a呼叫,
a -----不能呼叫 c的方法,
在a 和 c 直接 創建一個 b 代理, c讓b訪問,
a --訪問b---訪問c
2. 使用代理模式的作用
- 功能增強: 在你原有的功能上,增加了額外的功能, 新增加的功能,叫做功能增強,
- 控制訪問: 代理類不讓你訪問目標,例如商家不讓用戶訪問廠家,
3. 實作代理的方式
靜態代理和動態代理
二、靜態代理
靜態代理是指,代理類在程式運行前就已經定義好.java 源檔案,其與目標類的關系在 程式運行前就已經確立,在程式運行前代理類已經編譯為.class 檔案,
-
代理類是自己手工實作的,自己創建一個java類,表示代理類,
-
同時你所要代理的目標類是確定的,
-
特點: 實作簡單 、容易理解
缺點:
當你的專案中,目標類和代理類很多時候,有以下的缺點:
- 當目標類增加了, 代理類可能也需要成倍的增加, 代理類數量過多,
- 當你的介面中功能增加了, 或者修改了,會影響眾多的實作類,廠家類,代理都需要修改,
1. 模擬用戶購買u盤
用戶是客戶端類
- 商家:代理,代理某個品牌的u盤,
- 廠家:目標類,
- 三者的關系: 用戶(客戶端)---商家(代理)---廠家(目標)
- 商家和廠家都是賣u盤的,他們完成的功能是一致的,都是賣u盤,
實作步驟:
- 創建一個介面,定義賣u盤的方法, 表示你的廠家和商家做的事情
package com.md.service;
/**
* @author MD
* @create 2020-08-03 9:06
*/
// 表示功能,廠家和商家都要完成的功能
public interface UsbSell {
// 定義方法,回傳值為u盤的價格
float sell(int amount);
}
- 創建廠家類,實作1步驟的介面
package com.md.factory;
import com.md.service.UsbSell;
/**
* @author MD
* @create 2020-08-03 9:11
*/
// 目標類,金士頓廠家
public class UsbKingFactory implements UsbSell {
@Override
public float sell(int amount) {
return 50.0f;
}
}
- 創建商家,就是代理,也需要實作1步驟中的介面,
package com.md.shangjia;
import com.md.factory.UsbKingFactory;
import com.md.service.UsbSell;
import java.lang.reflect.AccessibleObject;
/**
* @author MD
* @create 2020-08-03 9:13
*/
// 代理類,這是商家,代理金士頓u盤銷售
public class Taobao implements UsbSell {
// 訪問目標類
// 宣告商家代理的廠家是誰
private UsbSell factory = new UsbKingFactory();
// 實作銷售u盤的功能
@Override
public float sell(int amount) {
// 向廠家發送訂單,告訴廠家,進行發貨,這是進貨價格
float price = factory.sell(amount);
// 商家加價獲得利潤,增強功能
price+=50;
// 在目標類的方法呼叫之后,剩下寫的其他功能,都是增加功能
System.out.println("淘寶商家給你一個大的優惠卷");
return price;
}
}
- 創建商家,不同的商家銷售同一款產品,也是代理,也需要實作1步驟中的介面,
package com.md.shangjia;
import com.md.factory.UsbKingFactory;
import com.md.service.UsbSell;
/**
* @author MD
* @create 2020-08-03 9:32
*/
// 代理類
public class Weishang implements UsbSell {
// 代理的也是金士頓
private UsbSell factory = new UsbKingFactory();
@Override
public float sell(int amount) {
float price = factory.sell(amount);
// 增強功能,每個代理根據自己的模式有不同的增強功能
price+=30;
System.out.println("微商:親,要五星好評喲!");
return price;
}
}
- 創建客戶端類,呼叫商家的方法買一個u盤,
package com.md;
import com.md.shangjia.Taobao;
import com.md.shangjia.Weishang;
/**
* @author MD
* @create 2020-08-03 9:23
*/
public class Shopping {
public static void main(String[] args) {
// 創建代理的商家物件
Taobao taobao = new Taobao();
float price = taobao.sell(1);
System.out.println("通過淘寶購買的u盤:"+price);
Weishang weishang = new Weishang();
float sell = weishang.sell(1);
System.out.println("通過微商購買的u盤:"+sell);
}
}
2. 靜態代理的缺點
-
代碼復雜,難于管理
代理類和目標類實作了相同的介面,每個代理都需要實作目標類的方法,這樣就出現了大量的代 碼重復,如果介面增加一個方法,除了所有目標類需要實作這個方法外,所有代理類也需要實作 此方法,增加了代碼維護的復雜度,
-
代理類依賴目標類,代理類過多
代理類只服務于一種型別的目標類,如果要服務多個型別,勢必要為每一種目標類都進行代理, 靜態代理在程式規模稍大時就無法勝任了,代理類數量過多,
三、動態代理
動態代理是指代理類物件在程式運行時由 JVM 根據反射機制動態生成的,動態代理不需要定義代理類的.java 源檔案,
動態代理其實就是 jdk 運行期間,動態創建 class 位元組碼并加載到 JVM,
動態代理的實作方式常用的有兩種:
使用 JDK 動態代理,和通過 CGLIB 動態代理,
四、 JDK 動態代理
jdk 動態代理是基于 Java 的反射機制實作的,使用 jdk 中介面和類實作代理物件的動態創建,
Jdk 的動態要求目標物件必須實作介面,這是 java 設計上的要求,
從 jdk1.3 以來,java 語言通過 java.lang.reflect 包提供三個類支持代理模式 Proxy, Method 和 InvocationHandler
1. InvocationHandler介面
InvocationHandler 介面叫做呼叫處理器,負責完呼叫目標方法,并增強功能,
通過代理物件執行目標介面中的方法,會把方法的呼叫分派給呼叫處理器 (InvocationHandler)的實作類,
執行實作類中的 invoke()方法,我們需要把功能代理寫在 invoke ()方法中
在 invoke 方法中可以截取對目標方法的呼叫,在這里進行功能增強
invoke()方法:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
}
// proxy:代表生成的代理物件
// method:代表目標方法
// args:代表目標方法的引數
//第一個引數 proxy 是 jdk 在運行時賦值的,在方法中直接使用,第二個引數后面介紹, 第三個引數是方法執行的引數, 這三個引數都是 jdk 運行時賦值的,無需程式員給出,
Java 的動態代理是 建立在反射機制之上的, 實作了 InvocationHandler 介面的類用于加強目標類的主業務邏輯,這個介面中有一個 方法 invoke(),具體加強的代碼邏輯就是定義在該方法中的,通過代理物件執行介面中的方法時,會自動呼叫 invoke()方法,
總結:
InvocationHandler 介面:表示你的代理要干什么,怎么用:
- 創建類實作介面InvocationHandler
- 重寫invoke()方法, 把原來靜態代理中代理類要完成的功能,寫在這
2. Method 類
invoke()方法的第二個引數為 Method 類物件,該類有一個方法也叫 invoke(),可以呼叫目標方法,這兩個 invoke()方法,雖然同名,但無關,
這個類的invoke方法
public Object invoke(Object obj , Object...args){
}
// obj:表示目標物件
// args:表示目標方法引數,也就是上面invoke方法中的第三個引數
該方法的作用是:
- 呼叫執行 obj 物件所屬類的方法,這個方法由其呼叫者 Method 物件確定,
在代碼中,一般的寫法為 method.invoke(target, args); 其中,method 為上一層 invoke 方法的第二個引數,這樣,即可呼叫了目標類的目標方法,
3. Proxy類
通過 JDK 的 java.lang.reflect.Proxy 類 實 現 動 態 代 理 ,會 使 用 其 靜 態 方 法 newProxyInstance(),依據目標物件、業務介面及呼叫處理器三者,自動生成一個動態代理對 象
Proxy類:核心的物件,創建代理物件,之前創建物件都是 new 類的構造方法()
現在我們是使用Proxy類的方法,代替new的使用,
方法: 靜態方法 newProxyInstance()
作用是: 創建代理物件, 等同于靜態代理中的TaoBao taoBao = new TaoBao();
回傳值:就是代理物件
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
// 1. ClassLoader loader 類加載器,負責向記憶體中加載物件的, 使用反射獲取物件的ClassLoader類a , a.getCalss().getClassLoader(), 目標物件的類加載器
// 2. Class<?>[] interfaces: 介面, 目標物件實作的介面,也是反射獲取的,
// 3. InvocationHandler h : 我們自己寫的,代理類要完成的功能,
4. 實作動態代理的步驟
和靜態代理實作相同的功能
- 創建介面,定義目標類要完成的功能
package com.md.service;
/**
* @author MD
* @create 2020-08-03 10:36
*/
// 目標介面
public interface UsbSell {
float sell(int count);
}
- 創建目標類實作介面
package com.md.factory;
import com.md.service.UsbSell;
/**
* @author MD
* @create 2020-08-03 10:37
*/
// 目標類
public class UsbKingFactory implements UsbSell {
// 目標方法
@Override
public float sell(int count) {
System.out.println("目標類:執行了目標方法");
return 50.0f;
}
}
- 創建InvocationHandler介面的實作類,在invoke方法中完成代理類的功能
package com.md.handler;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author MD
* @create 2020-08-03 10:43
*/
// 必須實作InvocationHandler介面,完成代理類需要的功能
// 1. 呼叫目標方法
// 2. 增強功能
public class MySellHandler implements InvocationHandler {
//
private Object target = null;
// 動態代理,目標物件不是固定的,需要什么就傳入什么,之后通過 new MySellHandler(),就可以把目標物件傳進來
public MySellHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 1. 向廠家發送訂單,告訴廠家,進行發貨,這是進貨價格
// float price = factory.sell(amount);
// 2. 商家加價獲得利潤,增強功能
//price+=50;
// 3. 在目標類的方法呼叫之后,剩下寫的其他功能,都是增加功能
// System.out.println("淘寶商家給你一個大的優惠卷");
// 1. 執行目標方法,和上面等價,呼叫目標方法,傳入目標物件和相應的引數
Object res = method.invoke(target,args);
// 2. 增強功能
if (res != null){
Float price = (Float)res;
price+=50;
res = price;
}
// 3. 在目標類的方法呼叫之后,剩下寫的其他功能,都是增加功能
System.out.println("淘寶商家給你一個大的優惠卷");
return res;
}
}
- 使用Proxy類的靜態方法,創建代理物件, 并把回傳值轉為介面型別
package com.md;
import com.md.factory.UsbKingFactory;
import com.md.handler.MySellHandler;
import com.md.service.UsbSell;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @author MD
* @create 2020-08-03 10:55
*/
public class Shopping {
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);
// 4. 通過代理執行方法
float price = proxy.sell(1);
System.out.println("通過動態代理呼叫方法:"+price);
}
}
五、cgLib 代理
CGLIB(Code Generation Library)是一個開源專案,是一個強大的,高性能,高質量的 Code 生成類 庫,它可以在運行期擴展 Java 類與實作 Java 介面,它廣泛的被許多 AOP 的框架使用,例如 Spring AOP,
使用 JDK 的 Proxy 實作代理,要求目標類與代理類實作相同的介面,若目標類不存在 介面,則無法使用該方式實作,
但對于無介面的類,要為其創建動態代理,就要使用 CGLIB 來實作,
CGLIB 代理的生成 原理是生成目標類的子類,而子類是增強過的,這個子類物件就是代理物件,所以,使用 CGLIB 生成動態代理,要求目標類必須能夠被繼承,即不能是 final 的類,
cglib 經常被應用在框架中,例如 Spring ,Hibernate 等,Cglib 的代理效率高于 Jdk,對 于 cglib 一般的開發中并不使用,做了一個了解就可以,
六、練習
- 定義介面
package com.md.service;
/**
* @author MD
* @create 2020-08-03 20:51
*/
public interface Dog {
void info();
void run();
}
- 介面的具體實作類
package com.md.factory;
import com.md.service.Dog;
/**
* @author MD
* @create 2020-08-03 20:51
*/
public class GunDog implements Dog {
@Override
public void info() {
System.out.println("我是狗");
}
@Override
public void run() {
System.out.println("狗在跑");
}
}
- 增加方法
package com.md.Util;
/**
* @author MD
* @create 2020-08-03 20:53
*/
public class DogUtil {
public void method1(){
System.out.println("我是第一個增強方法");
}
public void method2(){
System.out.println("我是第二個增強方法");
}
}
-
實作 InnovationHandler 介面
動態代理的核心環節就是要實作 InvocaitonHandler 介面,每個動態代理類都必須要實作 InvocationHandler 介面,并且每個代理類的實體都關聯到了一個 InvocationHandler 介面實作類的實體物件,當我們通過代理物件呼叫一個方法的時候,這個方法的呼叫就會被轉發為由 InvocationHandler 這個介面的 invoke() 方法來進行呼叫,具體的實作如下:
package com.md.handler;
import com.md.Util.DogUtil;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author MD
* @create 2020-08-03 20:55
*/
public class MyHandler implements InvocationHandler {
private Object target;
// 需要被代理的物件
public void setTarget(Object target) {
this.target = target;
}
// 當我們呼叫被代理物件的方法時,invvoke方法會取而代之
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
DogUtil dogUtil = new DogUtil();
// 增強方法
dogUtil.method1();
// 用反射呼叫Dog class中的方法
Object res = method.invoke(target, args);
dogUtil.method2();
return res;
}
}
- 使用Proxy類的靜態方法,創建代理物件, 并把回傳值轉為介面型別
package com.md;
import com.md.factory.GunDog;
import com.md.handler.MyHandler;
import com.md.service.Dog;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
/**
* @author MD
* @create 2020-08-03 21:14
*/
public class DogTest {
public static void main(String[] args) {
// 實體化被代理物件
Dog dog = new GunDog();
// 實體化MyHandler物件,
MyHandler myHandler = new MyHandler();
myHandler.setTarget(dog);
// 用被代理物件產生一個代理物件
Dog proxy = (Dog) Proxy.newProxyInstance(dog.getClass().getClassLoader(), dog.getClass().getInterfaces(), myHandler);
// 代理物件呼叫被代理物件的方法
proxy.info();
System.out.println("-------------------");
proxy.run();
}
}
// 運行結果
/*
我是第一個增強方法
我是狗
我是第二個增強方法
-------------------
我是第一個增強方法
狗在跑
我是第二個增強方法
*/
還是需要注意newProxyInstance() ,要把回傳值轉為介面型別,以及注意引數
- 引數一:類加載器物件即用哪個類加載器來加載這個代理類到 JVM 的方法區
- 引數二:介面表明該代理類需要實作的介面
- 引數三:是呼叫處理器類實體即 InvocationHandler 的實作的實體物件
這個函式是 JDK 為了程式員方便創建代理物件而封裝的一個函式,因此你呼叫newProxyInstance()時直接創建了代理物件
注意:上面的情況是兩個方法都增加了功能,若只想在run()里添加增加功能該怎么辦?
使用method.getName()獲取方法名
package com.md.handler;
import com.md.Util.DogUtil;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
/**
* @author MD
* @create 2020-08-03 20:55
*/
public class MyHandler implements InvocationHandler {
private Object target;
// 需要被代理的物件
public void setTarget(Object target) {
this.target = target;
}
// 當我們呼叫被代理物件的方法時,invvoke方法會取而代之
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
DogUtil dogUtil = new DogUtil();
Object res = null;
// 獲取當前呼叫方法的名字,給指定的方法添加新的功能
String methodName = method.getName();
if("run".equals(methodName)){
dogUtil.method1();
res = method.invoke(target, args);
dogUtil.method2();
}else{
res = method.invoke(target, args);
}
return res;
}
}
這樣就可以給指定的方法添加新功能
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/110735.html
標籤:Java
