動(dòng)態(tài)代理在Java中是很重要的一部分,在很多框架中都會(huì)用到,如Spring中的AOP、Hadoop中的RPC等。為此在這把我對(duì)Java中的動(dòng)態(tài)代理的理解分享給大家,同時(shí)寫了一個(gè)模擬AOP編程的實(shí)例。(Demo實(shí)例提供下載)
引入場(chǎng)景
如果要對(duì)第三方提供的JAR包中的某個(gè)類中的某個(gè)方法的前后加上自己的邏輯,比如打LOG,注意此時(shí)我們只有第三方提供的CLASS文件,因此根本不可能去修改別人的源代碼,那該怎么辦?
有兩種方法可以實(shí)現(xiàn),一種是利用繼承,另一種是利用聚合。舉例說明:
假設(shè)第三方中提供一個(gè)Run接口,里面只一個(gè)run方法,以及它的實(shí)現(xiàn)類Person。
Run.java
- <span style="font-size: 14px;">package com.cloud.proxy.example;
-
- public interface Run {
- public void run();
- }</span>
Person.java
- <span style="font-size: 14px;">package com.cloud.proxy.example;
-
- public class Person implements Run {
-
- @Override
- public void run() {
- System.out.println("person running...");
- }
-
- }</span>
第一種利用繼承實(shí)現(xiàn)
SuperMan1.java
- <span style="font-size: 14px;">package com.cloud.proxy.example;
-
- public class SuperMan1 extends Person {
-
- @Override
- public void run() {
- //方法開始前打LOG
- LOG.info("super man1 before run...");
- super.run();
- //方法結(jié)束后打LOG
- LOG.info("super man1 after run...");
- }
-
- }</span>
第二種利用聚合實(shí)現(xiàn)
SuperMan2.java
- <span style="font-size: 14px;">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();
- //方法結(jié)束后打LOG
- LOG.info("super man2 after run...");
- }
-
- }</span>
這兩種實(shí)現(xiàn)方式,哪一種更好呢?
顯然是第二種利用聚合實(shí)現(xiàn)方法好,因?yàn)檫@種方式很靈活,同時(shí)又不會(huì)有多層的父子類關(guān)系。而繼承最不好的地方就是不靈活,同時(shí)會(huì)很容易形成臃腫的父子類關(guān)系,不利于后期的維護(hù)。
點(diǎn)題
其實(shí)SuperMan1類和SuperMan2類都是Person類的代理類,對(duì)Person類中的方法進(jìn)行加強(qiáng),只不過這種代理是靜態(tài)代理,很受限制。因?yàn)镾uperMan1和SuperMan2只能代理Run類型的類,其它類型沒法代理。為了解決這個(gè)問題,Java中引入動(dòng)態(tài)代理。
理解動(dòng)態(tài)代理
看了上面的分析,動(dòng)態(tài)代理的意思就是一個(gè)類的(比如Person)的代理類(比如SuperMan2)是動(dòng)態(tài)生成的,也就是說這個(gè)代理類不是提前寫好的,是在程序運(yùn)行時(shí)動(dòng)態(tài)的生成的。而且能夠代理實(shí)現(xiàn)了某個(gè)接口的任何類型的類。我們暫且把動(dòng)態(tài)代理這個(gè)過程當(dāng)作一個(gè)黑箱子,然后看它的輸入和輸出。對(duì)于輸入,就是要被代理的類和它實(shí)現(xiàn)的接口,對(duì)于輸出就是代理類,如圖所示:
分析動(dòng)態(tài)代理過程
根據(jù)上圖,我們大致可以得知?jiǎng)討B(tài)代理會(huì)有這樣的過程:
1.根據(jù)輸入的接口,利用反射機(jī)制,肯定可以拿到有哪些方法;
2.根據(jù)輸入的被代理類,同樣利用反射機(jī)制,肯定去調(diào)用其實(shí)現(xiàn)的方法。
到了這里,好像少了一點(diǎn)東西,就是少了對(duì)某個(gè)方法的前后的加強(qiáng)的邏輯。那么該如何解決這個(gè)問題呢?為此,我們必須對(duì)輸入進(jìn)行改造,如圖:
看圖我們可以發(fā)現(xiàn),被代理類不是直接給黑箱子了,而是先給Handler這樣的一個(gè)類,再給黑箱子,那么在Handler類中我們就是添加相應(yīng)的加強(qiáng)的邏輯了。
Java中的動(dòng)態(tài)代理
其實(shí)上面對(duì)動(dòng)態(tài)代理的分析過程,也就是Java中動(dòng)態(tài)代理的過程。我們來一一對(duì)應(yīng)一下:
黑箱子:就是Java中的Proxy類;
Handler:就是Java中的InvocationHandler的子類。
利用Proxy類中的newProxyInstance靜態(tài)方法,就可以動(dòng)態(tài)生成一個(gè)代理類。這個(gè)方法有三個(gè)參數(shù):
第一個(gè)是要一個(gè)類加載器,它的作用是將動(dòng)態(tài)生成的代理類的字節(jié)碼文件加載到JVM虛擬機(jī)中,一般我們可以用被代理類的加載器;
第二個(gè)是被代理類實(shí)現(xiàn)的接口的Class類;
第三個(gè)是InvocationHandler的子類,在這個(gè)類中的invoke方法中,對(duì)某個(gè)方法的前后加入加強(qiáng)的邏輯。
實(shí)例
這個(gè)實(shí)例是簡(jiǎn)單的模擬Spring是的AOP機(jī)制,即只要我們?cè)谂渲梦募蜷_了事務(wù)機(jī)制,那么在調(diào)用方法時(shí)就會(huì)開啟事務(wù),同樣我們?cè)谂渲梦募P(guān)閉了事務(wù)機(jī)制,那么在調(diào)用方法時(shí)就不會(huì)開啟事務(wù)了。
AOP.java
- <span style="font-size: 14px;">package com.cloud.proxy;
-
- public interface AOP {
- public void show(String str);
- public String say(String str);
- }</span>
AOPImpl,java
- <span style="font-size: 14px;">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;
- }
-
- }</span>
AOPHandler.java
- <span style="font-size: 14px;">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...");
- }
- }</span>
Client.java
- <span style="font-size: 14px;">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);
- }
- }</span>
JVMCache.java
- <span style="font-size: 14px;">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;
- }
- }</span>
XMLUtil.java
- <span style="font-size: 14px;">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;
- }
- }</span>
config.xml
- <span style="font-size: 14px;"><?xml version="1.0" encoding="UTF-8"?>
- <config>
- <transaction>true</transaction>
- </config></span>
運(yùn)行結(jié)果
當(dāng)config.xml文件中配置為true時(shí),運(yùn)行結(jié)果:
- <span style="font-size: 14px;">Transaction start...
- show: cloud
- Transaction commit...
- Transaction start...
- Transaction commit...
- say: 芝加哥09</span>
我們可以發(fā)現(xiàn),在執(zhí)行每個(gè)方法的前會(huì)開啟事務(wù),每個(gè)方法后提交事務(wù)。
當(dāng)config.xml文件中配置為false時(shí),運(yùn)行結(jié)果:
- <span style="font-size: 14px;">show: cloud
- say: 芝加哥09</span>
我們可以發(fā)現(xiàn)這樣就可以將事務(wù)機(jī)制關(guān)掉。
這樣我們模擬了一個(gè)AOP編程。
|