java動態代理的理解
代理模式是設計模式的其中一種,也是Java相關框架中的重要應用,我也是初學者, 個人見解, 不喜勿噴, 簡單的說就是需要進行功能增強的代理類和原本真實物件的被代理類會實作同樣的介面,代理類的位元組碼檔案是在jvm運行的時候動態生成該類(下面進行的介紹),但是代理類會多去繼承一個Java中的Proxy 類,代理類負責為代理類(也就是生成真是物件的java類)預處理資訊、增強資訊、過濾資訊最終把已經增強的轉發給代理類,然而,回想之后,代理類又是誰生成的呢? 因此,還需要一個類去動態的生成代理類,這個類在撰寫的時候還需要用到一個Java中的invocationhandler類,這個類是用于增強被代理類中的方法,也就是誰繼承了invocationhandler,誰就要去實作該介面對需要增強的類的方法(該介面中的invoke方法), 并且通過呼叫代理類生成器的生成代理類方法,就會去呼叫該實作類的invoke方法, 這是個人自己的理解,所說的動態生成器類就是在這個生成動態代理類的Java類中,不能有別的自己撰寫的Java類的參考(可以在該類中看是否有import匯入自己撰寫的類),萬事俱備, 只欠東風, 那就是搞一個測驗, 去看下按照自己的理解是否可行,
1.首先先撰寫dao和service的代碼
dao實作部分代碼
public class EmployeeDAOImpl implements IEmployeeDAO { public void save(Employee emp) { System.out.println("保存員工"); } public void update(Employee emp) { System.out.println("修改員工"); } }
service實作部分代碼
public class EmployeeServiceImpl implements IEmployeeService { private IEmployeeDAO dao; public void setDao(IEmployeeDAO dao) { this.dao = dao; } public void save(Employee emp) { dao.save(emp); System.out.println("保存成功"); } }
2.撰寫需要增強被代理物件的方式, 增強其功能, 比如事務增強
//模擬事務管理器: public class TransactionManager { public void begin() { System.out.println("開啟事務"); } public void commit() { System.out.println("提交事務"); } public void rollback() { System.out.println("回滾事務"); } }
3.撰寫生成代理類的代理類生成器
先介紹下proxy類和invocationhandler類中用到的方法
proxy類:
Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the superclass of all dynamic proxy classes created by those methods.
Proxy類就是用來動態生成一個代理物件的類,我就叫它為動態代理類生成器吧,它提供了許多的方法,但是我們用的最多的就是 newProxyInstance 這個方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException
這個方法的作用就是獲取到增強版的被代理物件,也就是代理類,其接收三個引數,這三個引數所代表的含義:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h) throws IllegalArgumentException loader: 一個ClassLoader物件,就是定義了由哪個物件的classloader來對要生成的代理物件進行加載,一般使用的是被代理物件的類加載器 interfaces: 一個Interface物件的陣列,就是我將要給我需要代理的物件提供一組什么介面,如果我提供了一組介面給它,那么這個代理物件就宣稱實作了該介面(多型),
這樣我就能呼叫這組介面中的方法了, 通俗的講就是被代理類所實作的介面,而代理類也需要這個介面,否則它怎么知道你要用到哪個方法呢 h: 一個InvocationHandler物件,就是當我這個動態代理物件在呼叫方法的時候,會關聯到哪一個InvocationHandler物件上,也就是需要怎么實作對被代理物件的加強
invocationhandler類:
InvocationHandler is the interface implemented by the invocation handler of a proxy instance. Each proxy instance has an associated invocation handler. When a method is invoked on a proxy instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.
每一個動態代理類都必須要實作InvocationHandler這個介面,并且每個代理類的實體都關聯到了一個handler,當我們通過代理物件呼叫一個方法的時候,這個方法的呼叫就會被轉發為由InvocationHandler這個介面的 invoke 方法來進行呼叫,比較抽象, 按照我的理解就是, 你用動態代理類生成器的時候,生成完代理物件了, 但是你僅僅是生成了這個代理物件, 你還需要告訴別人, 你是怎么加強代理物件的呢 ,InvocationHandler這個介面的唯一的一個方法 invoke 方法:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable
這個方法的作用就是對動態代理類生成的增強版的被代理物件的方法,也就是代理類的種方法,如何加強的,其接收三個引數,這三個引數所代表的含義:
Object invoke(Object proxy, Method method, Object[] args) throws Throwable proxy: 所代理的那個真實物件 method: 所要呼叫真實物件的某個方法的Method物件 args: 呼叫真實物件某個方法時接受的引數
重頭戲來了, 在這里我是直接讓TransactionManagerAdvice類去實作了InvocationHandler這個類,這個也就是上面提到的用于實作對代理類中方法的增強, 通過invoke方法去增強,TransactionManagerAdvice既是動態代理類生成器(用到的是proxy類中的newProxyInstance方法), 也是對被代理物件(也就是真實物件)增強的方式(用到的是invocationhandler介面中的invoke方法),其中這兩個成員變數, 我用的是基于xml的DI中的setter方法注入,因此也提供了兩個成員變數的setter方法,
動態代理類生成器如下:
//事務的增強操作 public class TransactionManagerAdvice implements InvocationHandler { private Object target;//真實物件(對誰做增強) private TransactionManager txManager;//事務管理器(模擬) public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } public void setTarget(Object target) { this.target = target; } //創建一個代理物件 public <T> T getProxyObject() { return (T) Proxy.newProxyInstance(target.getClass().getClassLoader(), //類加載器,一般跟上真實物件的類加載器 target.getClass().getInterfaces(), //真實物件所實作的介面(JDK動態代理必須要求真實物件有介面) this);//如何做事務增強的物件,誰繼承了invocationhandler,誰就是需要做增強的物件,并且真實物件呼叫方法會去呼叫 //該實作類的invoke方法, 自己的理解 } //如何為真實物件的方法做增強的具體操作 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (method.getName().startsWith("get") || method.getName().startsWith("list")) { return method.invoke(target, args);//放行 } Object ret = proxy; txManager.begin(); try { //--------------------------------------------------------------- ret = method.invoke(target, args);//呼叫真實物件的方法 //--------------------------------------------------------------- txManager.commit(); } catch (Exception e) { e.printStackTrace(); txManager.rollback(); } return ret; } }
4.撰寫測驗類
@SpringJUnitConfig
//用于加載組態檔的內容,不加引數默認去找: 測驗類名稱-context.xml public class App { //用于生成增強代理類的代理類生成器 @Autowired private TransactionManagerAdvice advice; //代理物件:com.sun.proxy.$Proxy19 @Test void testSave() throws Exception { //獲取代理物件 IEmployeeService proxy = advice.getProxyObject(); proxy.save(new Employee()); } }
5.組態檔的內容如下(組態檔的名稱: App-context.xml)
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.3.xsd"> <!--因為 <bean id="employeeDAO" class="cn.wolfcode.dao.impl.EmployeeDAOImpl" /> <bean id="transactionManager" class="cn.wolfcode.tx.TransactionManager" /> <bean id="employeeService" class="cn.wolfcode.service.impl.EmployeeServiceImpl"> <property name="dao" ref="employeeDAO" /> </bean> <!-- 配置一個事務增強的類, 也就是代理類生成器 --> <bean id="transactionManagerAdvice" class="cn.wolfcode.tx.TransactionManagerAdvice"> <property name="target" ref="employeeService"/> <property name="txManager" ref="transactionManager"/> </bean> </beans>
5.運行保存(save)測驗,結果如下:

最終大功告成, 測驗通過,成功,
個人筆記: 理解延伸
看看動態代理在底層是如何進行操作的
先從動態代理生成的部分開始
第一步: 通過proxy類中的靜態方法newproxyinstance方法,根據傳入的引數,動態生成出代理類的Java檔案,只不過他不會出現在編譯時期,而是直接到達運行時期,利用類加載器生成該份位元組碼檔案而已
第二步: 就是通過代理物件的invoke方法對所用的方法進行增強,因為在前面的TransactionManagerAdvice類中已經定義了如何增強功能, 所以在這個動態生成的代理類中的invoke方法就是多型特性呼叫自己撰寫的invoke方法
第三步: 測驗方法
1.首先先借助一個類:這個類的作用是生成代理類的位元組碼檔案, 并且把這份位元組碼檔案存放在被代理類的包下面(也就是代理類的位元組碼檔案), 之前我也是很好奇,為什么代理類還要這樣得到, 這也就是前面也沒提到的, 動態代理是不會生成代理類的Java檔案的, 因為動態代理的程序中,我們并沒有實際看到代理類的代碼實作,而且動態代理中被代理物件和代理物件是通過InvocationHandler介面實作類和proxy類中的newproxyinstance方法來完成的代理程序的,其中具體是怎樣操作的,為什么代理物件執行的方法都會通過InvocationHandler中的invoke方法來執行,生成動態代理類的代碼如下
import java.io.FileOutputStream; import cn.wolfcode.service.impl.EmployeeServiceImpl; import sun.misc.ProxyGenerator; public class DynamicProxyClassGenerator { public static void main(String[] args) throws Exception { generateClassFile(EmployeeServiceImpl.class, "EmployeeServiceProxy"); } //生成代理類的位元組碼檔案-->Java反編譯工具-->Java檔案 public static <T>void generateClassFile(Class<T> targetClass, String proxyName) throws Exception { //根據類資訊和提供的代理類名稱,生成位元組碼 byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, targetClass.getInterfaces()); String path = targetClass.getResource(".").getPath(); System.out.println(path); FileOutputStream out = null; //保留到硬碟中 out = new FileOutputStream(path + proxyName + ".class"); out.write(classFile); out.close(); } }
2.生成位元組碼檔案之后,找到這份位元組碼檔案,通過jd-gui反編譯工具生成 .java檔案, 下面的就是動態代理的代理類的代碼實作
public final class EmployeeServiceProxy extends Proxy implements IEmployeeService { private static final long serialVersionUID = 1L; private static Method method_equals; private static Method method_toString; private static Method method_hashCode; private static Method method_update; private static Method method_save; public EmployeeServiceProxy(InvocationHandler paramInvocationHandler) { super(paramInvocationHandler); } static { try { method_equals = Class.forName("java.lang.Object").getMethod("equals",new Class[] { Class.forName("java.lang.Object") }); method_toString = Class.forName("java.lang.Object").getMethod("toString", new Class[0]); method_hashCode = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]); method_update = Class.forName("cn.wolfcode.service.IEmployeeService").getMethod("update",new Class[] { Class.forName("cn.wolfcode.domain.Employee") }); method_save = Class.forName("cn.wolfcode.service.IEmployeeService").getMethod("save",new Class[] { Class.forName("cn.wolfcode.domain.Employee") }); } catch (Exception e) { } } public final boolean equals(Object paramObject) { try { return ((Boolean) this.h.invoke(this, method_equals, new Object[] { paramObject })).booleanValue(); } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final String toString() { try { return (String) this.h.invoke(this, method_toString, null); } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final int hashCode() { try { return ((Integer) this.h.invoke(this, method_hashCode, null)).intValue(); } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void save(Employee paramEmployee) { try { this.h.invoke(this, method_save, new Object[] { paramEmployee });//多型呼叫,用的是TransactionManagerAdvice中的invoke方法 return; } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } } public final void update(Employee paramEmployee) { try { this.h.invoke(this, method_update, new Object[] { paramEmployee });//多型呼叫 return; } catch (Error | RuntimeException localError) { throw localError; } catch (Throwable localThrowable) { throw new UndeclaredThrowableException(localThrowable); } }
}
上面標紅的就是在上面代理類生成器中的invoke方法
3.最終通過測驗方法的代理類生成器獲取代理類物件,然后呼叫save方法進行測驗,成功
總結
我們可以對InvocationHandler的實作類看做一個中介類,理解成它就是代理類物件的中介類持有一個被代理物件,在invoke方法中呼叫了被代理物件的相應方法,通過聚合方式持有被代理物件的參考,把外部對invoke的呼叫最終都轉為對被代理物件的呼叫,代理類呼叫自己方法時,通過自身持有的中介類物件來呼叫中介類物件的invoke方法,從而達到代理執行被代理物件的方法,也就是說,動態代理通過中介類實作了具體的代理功能,
---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
部分參考:https://www.cnblogs.com/gonjan-blog/p/6685611.html
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/191467.html
標籤:Java
下一篇:Java-注解學習
