1漫仆、為什么要動態(tài)代理
動態(tài)代理的作用其實就是在不修改原代碼的前提下,對已有的方法進行增強躺孝。
關(guān)鍵點:
不修改原來已有的代碼(滿足設(shè)計模式的要求)
對已有方法進行增強
2瓮钥、舉個栗子
我們用一個很簡單的例子來說明:Hello類,有一個introduction方法湖苞。
現(xiàn)在我們的需求就是不修改Hello類的introduction方法拯欧,在introduction之前先sayHello,在introduction之后再sayGoodBye
3袒啼、實現(xiàn)方式
JAVA中哈扮,實現(xiàn)動態(tài)代理有兩種方式,一種是JDK提供的蚓再,一種是第三方庫CgLib提供的滑肉。特點如下:
JDK動態(tài)代理:被代理的目標類需要實現(xiàn)接口
CgLib方式:可以對任意類實現(xiàn)動態(tài)代理
3.1、JDK動態(tài)代理
JDK動態(tài)代理需要實現(xiàn)接口摘仅,然后通過對接口方法的增強來實現(xiàn)動態(tài)代理
所以要使用JDK動態(tài)代理的話靶庙,我們首先要創(chuàng)建一個接口,并且被代理的方法要在這個接口里面
3.1.1娃属、創(chuàng)建一個接口
我們創(chuàng)建一個接口如下:
Personal.java
public interface Personal {
/**
* 被代理的方法
*/
void introduction();
}
3.1.2六荒、實現(xiàn)接口
創(chuàng)建接口實現(xiàn)類护姆,并且完成introduction方法
PersonalImpl.java
public class PersonalImpl implements Personal {
@Override
public void introduction() {
System.out.println("我是程序員!");
}
}
3.1.3掏击、創(chuàng)建代理類
JDK代理的關(guān)鍵就是這個代理類了卵皂,需要實現(xiàn)InvocationHandler
在代理類中,所有方法的調(diào)用都好分發(fā)到invoke方法中砚亭。我們在invoke方法完成對方法的增強即可
JDKProxyFactory.java
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
public class JDKProxyFactory<T> implements InvocationHandler {
/**
* 目標對象
*/
private T target;
/**
* 構(gòu)造函數(shù)傳入目標對象
*
* @param target 目標對象
*/
public JDKProxyFactory(T target) {
this.target = target;
}
/**
* 獲取代理對象
*
* @return 獲取代理
*/
public T getProxy() {
return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), this);
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 對方法增強
System.out.println("大家好灯变!");
// 調(diào)用原方法
Object result = method.invoke(target, args);
// 方法增強
System.out.println("再見!");
return result;
}
}
就這樣捅膘,JDK動態(tài)代理的代碼就完成了添祸,接下來寫一份測試代碼
3.1.4、編寫測試代碼
為了方便測試寻仗,我們編寫一個test方法
同時為了查看class文件刃泌,還添加了一個generatorClass方法,這個方法可以將動態(tài)代理生成的.class輸出到文件
ProxyTest.java
import org.junit.Test;
import sun.misc.ProxyGenerator;
import java.io.FileOutputStream;
import java.io.IOException;
public class ProxyTest {
@Test
public void testJdkProxy() {
// 生成目標對象
Personal personal = new PersonalImpl();
// 獲取代理對象
JDKProxyFactory<Personal> proxyFactory = new JDKProxyFactory<>(personal);
Personal proxy = proxyFactory.getProxy();
// 將proxy的class字節(jié)碼輸出到文件
generatorClass(proxy);
// 調(diào)用代理對象
proxy.introduction();
}
/**
* 將對象的class字節(jié)碼輸出到文件
*
* @param proxy 代理類
*/
private void generatorClass(Object proxy) {
FileOutputStream out = null;
try {
byte[] generateProxyClass = ProxyGenerator.generateProxyClass(proxy.getClass().getSimpleName(), new Class[]{proxy.getClass()});
out = new FileOutputStream(proxy.getClass().getSimpleName() + ".class");
out.write(generateProxyClass);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (out != null) {
try {
out.close();
} catch (IOException e) {
// TODO Auto-generated catch block
}
}
}
}
}
3.1.5署尤、查看運行結(jié)果
可以看到耙替,運行test方法之后,控制臺打印出如下:
大家好沐寺!
我是程序員林艘!
再見!
我們在introduction方法前和后都成功增加了功能混坞,讓這個程序員的自我介紹瞬間變得更加有禮貌了。
3.1.6钢坦、探探動態(tài)代理的秘密
動態(tài)代理的代碼并不多究孕,那么JDK底層是怎么幫我們實現(xiàn)的呢?
在測試的時候我們將動態(tài)生成的代理類的class字節(jié)碼輸出到了文件爹凹,我們可以反編譯看看厨诸。
結(jié)果有點長,就不全部貼出來了禾酱,不過我們可以看到微酬,里面有一個introduction方法如下:
/**
* the invocation handler for this proxy instance.
* @serial
*/
protected InvocationHandler h;
protected Proxy(InvocationHandler h) {
Objects.requireNonNull(h);
this.h = h;
}
public final void introduction() throws {
try {
super.h.invoke(this, m3, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
原來,生成的代理對象里面颤陶,引用了我們的InvocationHandler颗管,然后在將introduction方法里面調(diào)用了InvocationHandler的introduction,而InvocationHandler是由我們編寫的代理類滓走,在這里我們增加了sayHello和sayGoodBye操作垦江,然后還調(diào)用了原對象的introduction方法,就這樣完成了動態(tài)代理搅方。
3.2比吭、CgLib動態(tài)代理
CgLib動態(tài)
3.2.1绽族、創(chuàng)建被代理對象
由于CgLib不需要實現(xiàn)接口,所以我們不需要創(chuàng)建接口文件了(當然衩藤,你要有接口也沒有問題)
直接創(chuàng)建目標類吧慢,實現(xiàn)introduction方法
PersonalImpl.java
public class PersonalImpl {
public void introduction() {
System.out.println("我是程序員!");
}
}
3.2.2赏表、創(chuàng)建代理類
同樣娄蔼,我們也需要創(chuàng)建代理類,并且在這里實現(xiàn)增強的邏輯底哗,這次我們不是實現(xiàn)InvocationHandler接口了岁诉,而是實現(xiàn)CgLib提供的接口MethodInterceptor,都是類似的跋选,MethodInterceptor中涕癣,全部方法調(diào)用都會交給intercept處理,我們在intercept添加處理邏輯即可前标。
CgLibProxyFactory.java
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CgLibProxyFactory<T> implements MethodInterceptor {
/**
* 獲取代理對象
*
* @param tClass 被代理的目標對象
* @return 代理對象
*/
public T getProxyByCgLib(Class<T> tClass) {
// 創(chuàng)建增強器
Enhancer enhancer = new Enhancer();
// 設(shè)置需要增強的類的類對象
enhancer.setSuperclass(tClass);
// 設(shè)置回調(diào)函數(shù)
enhancer.setCallback(this);
// 獲取增強之后的代理對象
return (T) enhancer.create();
}
/**
* 代理類方法調(diào)用回調(diào)
*
* @param obj 這是代理對象坠韩,也就是[目標對象]的子類
* @param method [目標對象]的方法
* @param args 參數(shù)
* @param proxy 代理對象的方法
* @return 返回結(jié)果,返回給調(diào)用者
* @throws Throwable
*/
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("大家好炼列!");
Object result = proxy.invokeSuper(obj, args);
System.out.println("再見只搁!");
return result;
}
}
3.2.3、編寫測試代碼
在剛才的測試方法中俭尖,我們添加一個cglib的測試方法:
@Test
public void testCgLibProxy() {
// 生成被代理的目標對象
PersonalImpl personal = new PersonalImpl();
// 獲取代理類
CgLibProxyFactory<PersonalImpl> proxyFactory = new CgLibProxyFactory<>();
PersonalImpl proxy = proxyFactory.getProxyByCgLib((Class<PersonalImpl>) personal.getClass());
// 將proxy的class字節(jié)碼輸出到文件
generatorClass(proxy);
// 調(diào)用代理對象
proxy.introduction();
}
3.2.4氢惋、查看運行結(jié)果
運行測試用例,可以看到跟JDK的實現(xiàn)一樣的效果
大家好稽犁!
我是程序員焰望!
再見!
3.2.5已亥、探探動態(tài)代理的秘密
跟JDK的測試一樣熊赖,我們也來看看生成的class文件
public final void introduction() throws {
try {
super.h.invoke(this, m7, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
可以發(fā)現(xiàn),與JDK的動態(tài)代理并沒有區(qū)別虑椎。
4震鹉、如何選擇
既然有兩種實現(xiàn)方式,那么到底應該怎么選擇呢捆姜?
就兩個原則:
目標類有接口實現(xiàn)的传趾,JDK和CgLib都可以選擇,你開心就好
目標類沒有實現(xiàn)任何接口娇未,那只能用CgLib了
5墨缘、后記
其實在第一次看到動態(tài)代理的時候,我就想不明白,我們都把目標類new出來了镊讼,為什么還要將目標類丟給代理類呢宽涌?為什么不直接調(diào)用目標類對應的方法呢?
后來才發(fā)現(xiàn)蝶棋,原來我沒搞清楚動態(tài)代理的使用場景卸亮,場景很清晰,就是:
不修改原來已有的代碼(滿足設(shè)計模式的要求)
對已有方法進行增強
關(guān)鍵是增強玩裙,代理類里面我們是可以添加很多處理邏輯的兼贸,從而實現(xiàn)增強效果。就像黃牛搶票比我們厲害些一樣吃溅。