吃透Java中的动态代理
動態(tài)代理在Java中是很重要的一部分,在很多框架中都會用到,如Spring中的AOP、Hadoop中的RPC等。為此在這把我對Java中的動態(tài)代理的理解分享給大家,同時寫了一個模擬AOP編程的實例。(Demo實例提供下載)
引入場景
如果要對第三方提供的JAR包中的某個類中的某個方法的前后加上自己的邏輯,比如打LOG,注意此時我們只有第三方提供的CLASS文件,因此根本不可能去修改別人的源代碼,那該怎么辦?
有兩種方法可以實現(xiàn),一種是利用繼承,另一種是利用聚合。舉例說明:
假設第三方中提供一個Run接口,里面只一個run方法,以及它的實現(xiàn)類Person。
Run.java
package com.cloud.proxy.example;public interface Run {public void run(); }?Person.java
package com.cloud.proxy.example;public class Person implements Run {@Overridepublic void run() {System.out.println("person running...");}}?第一種利用繼承實現(xiàn)
SuperMan1.java
package com.cloud.proxy.example;public class SuperMan1 extends Person { @Override public void run() { //方法開始前打LOG LOG.info("super man1 before run..."); super.run(); //方法結束后打LOG LOG.info("super man1 after run..."); }}?第二種利用聚合實現(xiàn)
SuperMan2.java
package com.cloud.proxy.example;public class SuperMan2 implements Run { private Run person; public SuperMan2(Run person) { this.person = person; } @Override public void run() { //方法開始前打LOG LOG.info("super man2 before run..."); person.run(); //方法結束后打LOG LOG.info("super man2 after run..."); }}?這兩種實現(xiàn)方式,哪一種更好呢?
顯然是第二種利用聚合實現(xiàn)方法好,因為這種方式很靈活,同時又不會有多層的父子類關系。而繼承最不好的地方就是不靈活,同時會很容易形成臃腫的父子類關系,不利于后期的維護。
點題
其實SuperMan1類和SuperMan2類都是Person類的代理類,對Person類中的方法進行加強,只不過這種代理是靜態(tài)代理,很受限制。因為SuperMan1和SuperMan2只能代理Run類型的類,其它類型沒法代理。為了解決這個問題,Java中引入動態(tài)代理。
理解動態(tài)代理
看了上面的分析,動態(tài)代理的意思就是一個類的(比如Person)的代理類(比如SuperMan2)是動態(tài)生成的,也就是說這個代理類不是提前寫好的,是在程序運行時動態(tài)的生成的。而且能夠代理實現(xiàn)了某個接口的任何類型的類。我們暫且把動態(tài)代理這個過程當作一個黑箱子,然后看它的輸入和輸出。對于輸入,就是要被代理的類和它實現(xiàn)的接口,對于輸出就是代理類,如圖所示:
?分析動態(tài)代理過程
根據(jù)上圖,我們大致可以得知動態(tài)代理會有這樣的過程:
1.根據(jù)輸入的接口,利用反射機制,肯定可以拿到有哪些方法;
2.根據(jù)輸入的被代理類,同樣利用反射機制,肯定去調(diào)用其實現(xiàn)的方法。
到了這里,好像少了一點東西,就是少了對某個方法的前后的加強的邏輯。那么該如何解決這個問題呢?為此,我們必須對輸入進行改造,如圖:
?看圖我們可以發(fā)現(xiàn),被代理類不是直接給黑箱子了,而是先給Handler這樣的一個類,再給黑箱子,那么在Handler類中我們就是添加相應的加強的邏輯了。
Java中的動態(tài)代理
其實上面對動態(tài)代理的分析過程,也就是Java中動態(tài)代理的過程。我們來一一對應一下:
黑箱子:就是Java中的Proxy類;
Handler:就是Java中的InvocationHandler的子類。
利用Proxy類中的newProxyInstance靜態(tài)方法,就可以動態(tài)生成一個代理類。這個方法有三個參數(shù):
第一個是要一個類加載器,它的作用是將動態(tài)生成的代理類的字節(jié)碼文件加載到JVM虛擬機中,一般我們可以用被代理類的加載器;
第二個是被代理類實現(xiàn)的接口的Class類;
第三個是InvocationHandler的子類,在這個類中的invoke方法中,對某個方法的前后加入加強的邏輯。
實例
這個實例是簡單的模擬Spring是的AOP機制,即只要我們在配置文件打開了事務機制,那么在調(diào)用方法時就會開啟事務,同樣我們在配置文件關閉了事務機制,那么在調(diào)用方法時就不會開啟事務了。
AOP.java
package com.cloud.proxy;public interface AOP {public void show(String str);public String say(String str); }?AOPImpl,java
package com.cloud.proxy;public class AOPImpl implements AOP { @Override public void show(String str) { System.out.println("show: " + str); } @Override public String say(String str) { return "say: " + str; }}?AOPHandler.java
package com.cloud.proxy;import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.util.Map;public class AOPHandler implements InvocationHandler { private Object obj; private boolean flag; public AOPHandler(Object obj) { this.obj = obj; } public void setFlag(Map<String, String> config) { if (null == config) { flag = false; } else { if (config.containsKey("transaction") && "true".equalsIgnoreCase(config.get("transaction"))) { flag = true; } else { flag = false; } } } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if (flag) { doBefore(); } Object result = method.invoke(obj, args); if (flag) { doAfter(); } return result; } private void doBefore() { System.out.println("Transaction start..."); } private void doAfter() { System.out.println("Transaction commit..."); } }?Client.java
package com.cloud.proxy;import java.lang.reflect.Proxy;import com.cloud.proxy.util.JVMCache;public class Client { public static void main(String[] args) throws Exception { AOPImpl impl = new AOPImpl(); AOPHandler handler = new AOPHandler(impl); handler.setFlag(JVMCache.getConfig()); AOP aop = (AOP) Proxy.newProxyInstance(AOPImpl.class.getClassLoader(), new Class<?>[] {AOP.class}, handler); aop.show("cloud"); String result = aop.say("芝加哥09"); System.out.println(result); } }?JVMCache.java
package com.cloud.proxy.util;import java.util.Map;public class JVMCache { private static Map<String, String> config; public synchronized static Map<String, String> getConfig() throws Exception { if (null == config) { config = XMLUtil.parseXML(); } return config; } }?XMLUtil.java
package com.cloud.proxy.util;import java.io.InputStream; import java.util.HashMap; import java.util.Map;import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory;import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.Node; import org.w3c.dom.NodeList;public class XMLUtil { public static Map<String, String> parseXML() throws Exception { Map<String, String> result = new HashMap<String, String>(); DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); DocumentBuilder db = dbf.newDocumentBuilder(); InputStream in = XMLUtil.class.getClassLoader().getResourceAsStream("config.xml"); Document document = db.parse(in); Element root = document.getDocumentElement(); NodeList xmlNodes = root.getChildNodes(); for (int i = 0; i < xmlNodes.getLength(); i++) { Node config = xmlNodes.item(i); if (null != config && config.getNodeType() == Node.ELEMENT_NODE) { String nodeName = config.getNodeName(); if ("transaction".equals(nodeName)) { String textContent = config.getTextContent(); result.put("transaction", textContent); } } } return result; } }?config.xml
<?xml version="1.0" encoding="UTF-8"?> <config><transaction>true</transaction> </config>運行結果
當config.xml文件中配置為true時,運行結果:
Transaction start... show: cloud Transaction commit... Transaction start... Transaction commit... say: 芝加哥09?我們可以發(fā)現(xiàn),在執(zhí)行每個方法的前會開啟事務,每個方法后提交事務。
當config.xml文件中配置為false時,運行結果:
show: cloud say: 芝加哥09?我們可以發(fā)現(xiàn)這樣就可以將事務機制關掉。
這樣我們模擬了一個AOP編程。
總結
以上是生活随笔為你收集整理的吃透Java中的动态代理的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Java动态调用方法
- 下一篇: java美元兑换,(Java实现) 美元