更新時間:2022-09-05 10:21:58 來源:動力節(jié)點 瀏覽1347次
相信大家或多或少的了解過AOP,都知道它是面向切面編程,在網(wǎng)上搜索可以找到很多的解釋。這里我用一句話來總結(jié):AOP是能夠讓我們在不影響原有功能的前提下,為軟件橫向擴(kuò)展功能。那么橫向擴(kuò)展怎么理解呢,我們在WEB項目開發(fā)中,通常都遵守三層原則,包括控制層(Controller)->業(yè)務(wù)層(Service)->數(shù)據(jù)層(dao),那么從這個結(jié)構(gòu)下來的為縱向,它具體的某一層就是我們所說的橫向。我們的AOP就是可以作用于這某一個橫向模塊當(dāng)中的所有方法。
講到動態(tài)代理就不得不說代理模式了,代理模式的定義:給某一個對象提供一個代理,并由代理對象控制對原對象的引用。代理模式包含如下角色:subject:抽象主題角色,是一個接口。該接口是對象和它的代理共用的接口; RealSubject:真實主題角色,是實現(xiàn)抽象主題接口的類; Proxy:代理角色,內(nèi)部含有對真實對象RealSubject的引用,從而可以操作真實對象。代理對象提供與真實對象相同的接口,以便代替真實對象。同時,代理對象可以在執(zhí)行真實對象操作時,附加其他的操作,相當(dāng)于對真實對象進(jìn)行封裝。如下圖所示:


那么代理又分為靜態(tài)代理和動態(tài)代理,這里寫兩個小的demo,動態(tài)代理采用的就是JDK代理。舉個例子就是現(xiàn)在一個班上的學(xué)生需要交作業(yè),現(xiàn)在由班長代理交作業(yè),那么班長就是代理,學(xué)生就是被代理的對象。
首先,我們創(chuàng)建一個Person接口。這個接口就是學(xué)生(被代理類),和班長(代理類)的公共接口,他們都有交作業(yè)的行為。這樣,學(xué)生交作業(yè)就可以讓班長來代理執(zhí)行。
/**
* Created by Mapei on 2018/11/7
* 創(chuàng)建person接口
*/
public interface Person {
//交作業(yè)
void giveTask();
}
Student類實現(xiàn)Person接口,Student可以具體實施交作業(yè)這個行為。
/**
* Created by Mapei on 2018/11/7
*/
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
public void giveTask() {
System.out.println(name + "交語文作業(yè)");
}
}
StudentsProxy類,這個類也實現(xiàn)了Person接口,但是還另外持有一個學(xué)生類對象,那么他可以代理學(xué)生類對象執(zhí)行交作業(yè)的行為。
/**
* Created by Mapei on 2018/11/7
* 學(xué)生代理類,也實現(xiàn)了Person接口,保存一個學(xué)生實體,這樣就可以代理學(xué)生產(chǎn)生行為
*/
public class StudentsProxy implements Person{
//被代理的學(xué)生
Student stu;
public StudentsProxy(Person stu) {
// 只代理學(xué)生對象
if(stu.getClass() == Student.class) {
this.stu = (Student)stu;
}
}
//代理交作業(yè),調(diào)用被代理學(xué)生的交作業(yè)的行為
public void giveTask() {
stu.giveTask();
}
}
下面測試一下,看代理模式如何使用:
/**
* Created by Mapei on 2018/11/7
*/
public class StaticProxyTest {
public static void main(String[] args) {
//被代理的學(xué)生林淺,他的作業(yè)上交有代理對象monitor完成
Person linqian = new Student("林淺");
//生成代理對象,并將林淺傳給代理對象
Person monitor = new StudentsProxy(linqian);
//班長代理交作業(yè)
monitor.giveTask();
}
}
運行結(jié)果:

這里并沒有直接通過林淺(被代理對象)來執(zhí)行交作業(yè)的行為,而是通過班長(代理對象)來代理執(zhí)行了。這就是代理模式。代理模式就是在訪問實際對象時引入一定程度的間接性,這里的間接性就是指不直接調(diào)用實際對象的方法,那么我們在代理過程中就可以加上一些其他用途。比如班長在幫林淺交作業(yè)的時候想告訴老師最近林淺的進(jìn)步很大,就可以輕松的通過代理模式辦到。在代理類的交作業(yè)之前加入方法即可。這個優(yōu)點就可以運用在spring中的AOP,我們能在一個切點之前執(zhí)行一些操作,在一個切點之后執(zhí)行一些操作,這個切點就是一個個方法。這些方法所在類肯定就是被代理了,在代理過程中切入了一些其他操作。
動態(tài)代理和靜態(tài)代理的區(qū)別是,靜態(tài)代理的的代理類是我們自己定義好的,在程序運行之前就已經(jīng)變異完成,但是動態(tài)代理的代理類是在程序運行時創(chuàng)建的。相比于靜態(tài)代理,動態(tài)代理的優(yōu)勢在于可以很方便的對代理類的函數(shù)進(jìn)行統(tǒng)一的處理,而不用修改每個代理類中的方法。比如我們想在每個代理方法之前都加一個處理方法,我們上面的例子中只有一個代理方法,如果還有很多的代理方法,就太麻煩了,我們來看下動態(tài)代理是怎么去實現(xiàn)的。
首先還是定義一個Person接口:
/**
* Created by Mapei on 2018/11/7
* 創(chuàng)建person接口
*/
public interface Person {
//交作業(yè)
void giveTask();
}
接下來是創(chuàng)建需要被代理的實際類,也就是學(xué)生類:
/**
* Created by Mapei on 2018/11/7
*/
public class Student implements Person {
private String name;
public Student(String name) {
this.name = name;
}
public void giveTask() {
System.out.println(name + "交語文作業(yè)");
}
}
創(chuàng)建StuInvocationHandler類,實現(xiàn)InvocationHandler接口,這個類中持有一個被代理對象的實例target。InvocationHandler中有一個invoke方法,所有執(zhí)行代理對象的方法都會被替換成執(zhí)行invoke方法。
/**
* Created by Mapei on 2018/11/7
*/
public class StuInvocationHandler<T> implements InvocationHandler {
//invocationHandler持有的被代理對象
T target;
public StuInvocationHandler(T target) {
this.target = target;
}
/**
* proxy:代表動態(tài)代理對象
* method:代表正在執(zhí)行的方法
* args:代表調(diào)用目標(biāo)方法時傳入的實參
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理執(zhí)行" +method.getName() + "方法");
Object result = method.invoke(target, args);
return result;
}
}
那么接下來我們就可以具體的創(chuàng)建代理對象了。
/**
* Created by Mapei on 2018/11/7
* 代理類
*/
public class ProxyTest {
public static void main(String[] args) {
//創(chuàng)建一個實例對象,這個對象是被代理的對象
Person linqian = new Student("林淺");
//創(chuàng)建一個與代理對象相關(guān)聯(lián)的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<Person>(linqian);
//創(chuàng)建一個代理對象stuProxy來代理linqian,代理對象的每個執(zhí)行方法都會替換執(zhí)行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
//代理執(zhí)行交作業(yè)的方法
stuProxy.giveTask();
}
}
我們執(zhí)行代理測試類,首先我們創(chuàng)建了一個需要被代理的學(xué)生林淺,將林淺傳入stuHandler中,我們在創(chuàng)建代理對象stuProxy時,將stuHandler作為參數(shù),那么所有執(zhí)行代理對象的方法都會被替換成執(zhí)行invoke方法,也就是說,最后執(zhí)行的是StuInvocationHandler中的invoke方法。所以在看到下面的運行結(jié)果也就理所當(dāng)然了。

那么到這里問題就來了,為什么代理對象執(zhí)行的方法都會通過InvocationHandler中的invoke方法來執(zhí)行,帶著這個問題,我們需要看一下動態(tài)代理的源碼,對他進(jìn)行簡單的分析。
上面我們使用Proxy類的newProxyInstance方法創(chuàng)建了一個動態(tài)代理對象,看一下他的源碼:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
throws IllegalArgumentException
{
Objects.requireNonNull(h);
final Class<?>[] intfs = interfaces.clone();
final SecurityManager sm = System.getSecurityManager();
if (sm != null) {
checkProxyAccess(Reflection.getCallerClass(), loader, intfs);
}
/*
* Look up or generate the designated proxy class.
*/
Class<?> cl = getProxyClass0(loader, intfs);
/*
* Invoke its constructor with the designated invocation handler.
*/
try {
if (sm != null) {
checkNewProxyPermission(Reflection.getCallerClass(), cl);
}
final Constructor<?> cons = cl.getConstructor(constructorParams);
final InvocationHandler ih = h;
if (!Modifier.isPublic(cl.getModifiers())) {
AccessController.doPrivileged(new PrivilegedAction<Void>() {
public Void run() {
cons.setAccessible(true);
return null;
}
});
}
return cons.newInstance(new Object[]{h});
} catch (IllegalAccessException|InstantiationException e) {
throw new InternalError(e.toString(), e);
} catch (InvocationTargetException e) {
Throwable t = e.getCause();
if (t instanceof RuntimeException) {
throw (RuntimeException) t;
} else {
throw new InternalError(t.toString(), t);
}
} catch (NoSuchMethodException e) {
throw new InternalError(e.toString(), e);
}
}
然后,我們需要重點關(guān)注Class cl = getProxyClass0(loader, intfs)這句代碼,這里產(chǎn)生了代理類,這個類就是動態(tài)代理的關(guān)鍵,由于是動態(tài)生成的類文件,我們將這個類文件打印到文件中。
byte[] classFile = ProxyGenerator.generateProxyClass("$Proxy0", Student.class.getInterfaces());
String path = "/Users/mapei/Desktop/okay/65707.class";
try{
FileOutputStream fos = new FileOutputStream(path);
fos.write(classFile);
fos.flush();
System.out.println("代理類class文件寫入成功");
}catch (Exception e) {
System.out.println("寫文件錯誤");
}
對這個class文件進(jìn)行反編譯,我們看看jdk為我們生成了什么樣的內(nèi)容:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import proxy.Person;
public final class $Proxy0 extends Proxy implements Person
{
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
/**
*注意這里是生成代理類的構(gòu)造方法,方法參數(shù)為InvocationHandler類型,看到這,是不是就有點明白
*為何代理對象調(diào)用方法都是執(zhí)行InvocationHandler中的invoke方法,而InvocationHandler又持有一個
*被代理對象的實例,就可以去調(diào)用真正的對象實例。
*/
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
//這個靜態(tài)塊本來是在最后的,我把它拿到前面來,方便描述
static
{
try
{
//看看這兒靜態(tài)塊兒里面的住giveTask通過反射得到的名字m3,其他的先不管
m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
m3 = Class.forName("proxy.Person").getMethod("giveTask", new Class[0]);
m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
return;
}
catch (NoSuchMethodException localNoSuchMethodException)
{
throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
}
catch (ClassNotFoundException localClassNotFoundException)
{
throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
}
}
/**
*
*這里調(diào)用代理對象的giveMoney方法,直接就調(diào)用了InvocationHandler中的invoke方法,并把m3傳了進(jìn)去。
*this.h.invoke(this, m3, null);我們可以對將InvocationHandler看做一個中介類,中介類持有一個被代理對象,在invoke方法中調(diào)用了被代理對象的相應(yīng)方法。通過聚合方式持有被代理對象的引用,把外部對invoke的調(diào)用最終都轉(zhuǎn)為對被代理對象的調(diào)用。
*/
public final void giveTask()
throws
{
try
{
this.h.invoke(this, m3, null);
return;
}
catch (Error|RuntimeException localError)
{
throw localError;
}
catch (Throwable localThrowable)
{
throw new UndeclaredThrowableException(localThrowable);
}
}
}
在動力節(jié)點Java動態(tài)代理技術(shù)文檔中還有更多的知識等著大家去學(xué)習(xí),感興趣的小伙伴可以了解一下。
Java實驗班
0基礎(chǔ) 0學(xué)費 15天面授
Java就業(yè)班
有基礎(chǔ) 直達(dá)就業(yè)
Java夜校直播班
業(yè)余時間 高薪轉(zhuǎn)行
Java在職加薪班
工作1~3年,加薪神器
Java架構(gòu)師班
工作3~5年,晉升架構(gòu)
提交申請后,顧問老師會電話與您溝通安排學(xué)習(xí)