更新時間:2022-09-05 10:21:58 來源:動力節點 瀏覽918次
相信大家或多或少的了解過AOP,都知道它是面向切面編程,在網上搜索可以找到很多的解釋。這里我用一句話來總結:AOP是能夠讓我們在不影響原有功能的前提下,為軟件橫向擴展功能。那么橫向擴展怎么理解呢,我們在WEB項目開發中,通常都遵守三層原則,包括控制層(Controller)->業務層(Service)->數據層(dao),那么從這個結構下來的為縱向,它具體的某一層就是我們所說的橫向。我們的AOP就是可以作用于這某一個橫向模塊當中的所有方法。
講到動態代理就不得不說代理模式了,代理模式的定義:給某一個對象提供一個代理,并由代理對象控制對原對象的引用。代理模式包含如下角色:subject:抽象主題角色,是一個接口。該接口是對象和它的代理共用的接口; RealSubject:真實主題角色,是實現抽象主題接口的類; Proxy:代理角色,內部含有對真實對象RealSubject的引用,從而可以操作真實對象。代理對象提供與真實對象相同的接口,以便代替真實對象。同時,代理對象可以在執行真實對象操作時,附加其他的操作,相當于對真實對象進行封裝。如下圖所示:
那么代理又分為靜態代理和動態代理,這里寫兩個小的demo,動態代理采用的就是JDK代理。舉個例子就是現在一個班上的學生需要交作業,現在由班長代理交作業,那么班長就是代理,學生就是被代理的對象。
首先,我們創建一個Person接口。這個接口就是學生(被代理類),和班長(代理類)的公共接口,他們都有交作業的行為。這樣,學生交作業就可以讓班長來代理執行。
/**
* Created by Mapei on 2018/11/7
* 創建person接口
*/
public interface Person {
//交作業
void giveTask();
}
Student類實現Person接口,Student可以具體實施交作業這個行為。
/**
* 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 + "交語文作業");
}
}
StudentsProxy類,這個類也實現了Person接口,但是還另外持有一個學生類對象,那么他可以代理學生類對象執行交作業的行為。
/**
* Created by Mapei on 2018/11/7
* 學生代理類,也實現了Person接口,保存一個學生實體,這樣就可以代理學生產生行為
*/
public class StudentsProxy implements Person{
//被代理的學生
Student stu;
public StudentsProxy(Person stu) {
// 只代理學生對象
if(stu.getClass() == Student.class) {
this.stu = (Student)stu;
}
}
//代理交作業,調用被代理學生的交作業的行為
public void giveTask() {
stu.giveTask();
}
}
下面測試一下,看代理模式如何使用:
/**
* Created by Mapei on 2018/11/7
*/
public class StaticProxyTest {
public static void main(String[] args) {
//被代理的學生林淺,他的作業上交有代理對象monitor完成
Person linqian = new Student("林淺");
//生成代理對象,并將林淺傳給代理對象
Person monitor = new StudentsProxy(linqian);
//班長代理交作業
monitor.giveTask();
}
}
運行結果:
這里并沒有直接通過林淺(被代理對象)來執行交作業的行為,而是通過班長(代理對象)來代理執行了。這就是代理模式。代理模式就是在訪問實際對象時引入一定程度的間接性,這里的間接性就是指不直接調用實際對象的方法,那么我們在代理過程中就可以加上一些其他用途。比如班長在幫林淺交作業的時候想告訴老師最近林淺的進步很大,就可以輕松的通過代理模式辦到。在代理類的交作業之前加入方法即可。這個優點就可以運用在spring中的AOP,我們能在一個切點之前執行一些操作,在一個切點之后執行一些操作,這個切點就是一個個方法。這些方法所在類肯定就是被代理了,在代理過程中切入了一些其他操作。
動態代理和靜態代理的區別是,靜態代理的的代理類是我們自己定義好的,在程序運行之前就已經變異完成,但是動態代理的代理類是在程序運行時創建的。相比于靜態代理,動態代理的優勢在于可以很方便的對代理類的函數進行統一的處理,而不用修改每個代理類中的方法。比如我們想在每個代理方法之前都加一個處理方法,我們上面的例子中只有一個代理方法,如果還有很多的代理方法,就太麻煩了,我們來看下動態代理是怎么去實現的。
首先還是定義一個Person接口:
/**
* Created by Mapei on 2018/11/7
* 創建person接口
*/
public interface Person {
//交作業
void giveTask();
}
接下來是創建需要被代理的實際類,也就是學生類:
/**
* 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 + "交語文作業");
}
}
創建StuInvocationHandler類,實現InvocationHandler接口,這個類中持有一個被代理對象的實例target。InvocationHandler中有一個invoke方法,所有執行代理對象的方法都會被替換成執行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:代表動態代理對象
* method:代表正在執行的方法
* args:代表調用目標方法時傳入的實參
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理執行" +method.getName() + "方法");
Object result = method.invoke(target, args);
return result;
}
}
那么接下來我們就可以具體的創建代理對象了。
/**
* Created by Mapei on 2018/11/7
* 代理類
*/
public class ProxyTest {
public static void main(String[] args) {
//創建一個實例對象,這個對象是被代理的對象
Person linqian = new Student("林淺");
//創建一個與代理對象相關聯的InvocationHandler
InvocationHandler stuHandler = new StuInvocationHandler<Person>(linqian);
//創建一個代理對象stuProxy來代理linqian,代理對象的每個執行方法都會替換執行Invocation中的invoke方法
Person stuProxy = (Person) Proxy.newProxyInstance(Person.class.getClassLoader(), new Class<?>[]{Person.class}, stuHandler);
//代理執行交作業的方法
stuProxy.giveTask();
}
}
我們執行代理測試類,首先我們創建了一個需要被代理的學生林淺,將林淺傳入stuHandler中,我們在創建代理對象stuProxy時,將stuHandler作為參數,那么所有執行代理對象的方法都會被替換成執行invoke方法,也就是說,最后執行的是StuInvocationHandler中的invoke方法。所以在看到下面的運行結果也就理所當然了。
那么到這里問題就來了,為什么代理對象執行的方法都會通過InvocationHandler中的invoke方法來執行,帶著這個問題,我們需要看一下動態代理的源碼,對他進行簡單的分析。
上面我們使用Proxy類的newProxyInstance方法創建了一個動態代理對象,看一下他的源碼:
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);
}
}
然后,我們需要重點關注Class cl = getProxyClass0(loader, intfs)這句代碼,這里產生了代理類,這個類就是動態代理的關鍵,由于是動態生成的類文件,我們將這個類文件打印到文件中。
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文件進行反編譯,我們看看jdk為我們生成了什么樣的內容:
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;
/**
*注意這里是生成代理類的構造方法,方法參數為InvocationHandler類型,看到這,是不是就有點明白
*為何代理對象調用方法都是執行InvocationHandler中的invoke方法,而InvocationHandler又持有一個
*被代理對象的實例,就可以去調用真正的對象實例。
*/
public $Proxy0(InvocationHandler paramInvocationHandler)
throws
{
super(paramInvocationHandler);
}
//這個靜態塊本來是在最后的,我把它拿到前面來,方便描述
static
{
try
{
//看看這兒靜態塊兒里面的住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());
}
}
/**
*
*這里調用代理對象的giveMoney方法,直接就調用了InvocationHandler中的invoke方法,并把m3傳了進去。
*this.h.invoke(this, m3, null);我們可以對將InvocationHandler看做一個中介類,中介類持有一個被代理對象,在invoke方法中調用了被代理對象的相應方法。通過聚合方式持有被代理對象的引用,把外部對invoke的調用最終都轉為對被代理對象的調用。
*/
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);
}
}
}
在動力節點Java動態代理技術文檔中還有更多的知識等著大家去學習,感興趣的小伙伴可以了解一下。
0基礎 0學費 15天面授
有基礎 直達就業
業余時間 高薪轉行
工作1~3年,加薪神器
工作3~5年,晉升架構
提交申請后,顧問老師會電話與您溝通安排學習