`
ln_ydc
  • 浏览: 266720 次
  • 性别: Icon_minigender_1
  • 来自: 青岛
社区版块
存档分类
最新评论

反射实现AOP动态代理

    博客分类:
  • Java
阅读更多

概念:

AOP是Aspect Oriented Programming的缩写,意思是面向切面编程

 

功能:

日志记录,性能统计,安全控制,事务处理,异常处理等

 

原理:

AOP通过反射机制实现动态代理,具体看下面举例吧。

 

举例:

在业务方法执行前后加日志记录

业务类接口IHello.java

package demo.aop;

public interface IHello {
	/**
	 * 业务处理A
	 * 
	 * @param name
	 */
	void sayHello(String name);

	/**
	 * 业务处理B
	 * 
	 * @param name
	 */
	void sayGoodBye(String name);
}

 IHello.java的一个实现类

package demo.aop;

public class Hello implements IHello{

	public void sayHello(String name) {
		System.out.println("hello " + name);
	}

	public void sayGoodBye(String name) {
		System.out.println(name + " goodbye!");
	}

}

 

日志记录相关的类:Logger类和Level枚举

package demo.aop;

public enum Level {
	DEBUG, INFO
}

 

package demo.aop;

import java.util.Date;

public class Logger {
	public static void logging(Level level, String context) {
		if (Level.DEBUG.equals(level)) {
			System.out.println("DEBUG\t" + new Date().toString() + "\t"
					+ context);
		} else if (Level.INFO.equals(level)) {
			System.out.println("INFO\t" + new Date().toString() + "\t"
					+ context);
		}
	}
}

 

 下面我们为这个sayHello业务方法加上日志记录的业务,我们在不改变原代码的情况下实现该功能

思路1:写一个类实现IHello接口,并依赖Hello这个类

package demo.aop;

/**
 * Hello代理类
 * 
 * @author ydc
 * @date 6:55:42 PM Jul 31, 2013
 */
public class ProxyHello implements IHello {
	private IHello hello;

	public ProxyHello(IHello hello) {
		this.hello = hello;
	}

	public void sayHello(String name) {
		Logger.logging(Level.DEBUG, "sayHello method start");
		hello.sayHello(name);
		Logger.logging(Level.INFO, "sayHello method end");
	}

	public void sayGoodBye(String name) {
		hello.sayGoodBye(name);
	}

}

 测试一下

package demo.aop;

public class AopTest {
	public static void main(String[] args) {
		new AopTest().test1();
	}

	public void test1() {
		// 无日志记录功能
		IHello hello1 = new Hello();

		// 有日志记录功能
		IHello hello2 = new ProxyHello(new Hello());

		hello1.sayHello("wallet white");
		System.out.println("------------------------------");
		hello2.sayHello("wallet white");
		
	}
}

 结果输出:

hello wallet white
------------------------------
DEBUG	Wed Jul 31 21:46:38 CST 2013	sayHello method start
hello wallet white
INFO	Wed Jul 31 21:46:38 CST 2013	sayHello method end

 

 缺点:如果像Hello这样的类很多,那么我们就要写很多个ProxyHello这样的类

 

思路2:在jdk1.3以后,jdk提供了一个API java.lang.reflect.InvocationHandler的类,这个类可以让我们在JVM调用某个类的方法时动态的为这些方法做些其他事

 

写一个代理类实现DynaProxyHello实现InvocationHandler接口

package demo.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Hello动态代理类 代理对象与被代理对像解藕
 * 
 * @author ydc
 * @date 6:56:27 PM Jul 31, 2013
 */
public class DynaProxyHello implements InvocationHandler {
	/**
	 * 要处理的对象
	 */
	private Object delegate;

	/**
	 * 动态生成方法被处理过后的对象(写法固定)
	 * 
	 * @param delegate
	 * @return Object
	 */
	public Object bind(Object delegate) {
		this.delegate = delegate;
		return Proxy.newProxyInstance(
				this.delegate.getClass().getClassLoader(), this.delegate
						.getClass().getInterfaces(), this);
	}

	/**
	 * 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说, 要处理的对象的方法只能通过此方法調用,此方法是动态的
	 */
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Object result = null;
		try {
			// 执行原来的方法之前记录日志
			Logger.logging(Level.DEBUG, method.getName() + " method start");

			// JVM通过这条语句执行原来的方法(反射机制)
			result = method.invoke(this.delegate, args);

			// 执行原来的方法之后记录日志
			Logger.logging(Level.INFO, method.getName() + " method end");
		} catch (Exception e) {
			e.printStackTrace();
		}

		// 返回方法返回值给调用者
		return result;

	}
}

 测试一下

package demo.aop;

public class AopTest {
	public static void main(String[] args) {
		new AopTest().test2();
	}
	
	public void test2() {
		IHello hello = (IHello) new DynaProxyHello().bind(new Hello());
		hello.sayHello("wallet white");
                System.out.println("------------------");
		hello.sayGoodBye("wallet white");
	}
}

 结果输出:

DEBUG	Wed Jul 31 21:57:49 CST 2013	sayHello method start
hello wallet white
INFO	Wed Jul 31 21:57:49 CST 2013	sayHello method end
------------------
DEBUG	Wed Jul 31 21:57:49 CST 2013	sayGoodBye method start
wallet white goodbye!
INFO	Wed Jul 31 21:57:49 CST 2013	sayGoodBye method end

 

由此可知,采用面向接口编程,对于任何对象的方法执行之前要加上记录日志的操作都是可以的。

代理对象(DynaProxyHello)自动去代理执行被代理对象(Hello)中的每一个方法,一个InvocationHandler接口就把我们的代理对象和被代理对象解藕了。

 

问题:要是为不同的业务方法前加的日志记录不同,就需要写多个DynaProxyHello类

 

加强版:把DynaProxyHello对象和日志操作对象(Logger)解藕

我们要在被代理对象的方法前面或者后面加上日志操作代码 ,那么我们可以抽象一个接口,该接口就只有两个方法

package demo.aop;

import java.lang.reflect.Method;

public interface IOperation {
	/**
	 * 方法执行之前的操作
	 * 
	 * @param method
	 */
	void start(Method method);

	/**
	 * 方法执行之后的操作
	 * 
	 * @param method
	 */
	void end(Method method);
}

 写一个实现上面接口的类,把他作为真正的操作者,

package demo.aop;

import java.lang.reflect.Method;
/**
 * 该类将代理者与操作者解藕
 * 
 * @author ydc
 * @date 7:22:24 PM Jul 31, 2013
 */
public class LoggerOperation implements IOperation {

	public void end(Method method) {
		Logger.logging(Level.DEBUG, method.getName() + "method end");
	}

	public void start(Method method) {
		Logger.logging(Level.INFO, method.getName() + "method start");

	}

}

 新建一个代理对象DynaProxyHello2

package demo.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * Hello动态代理类 代理对象与被代理对像解藕
 * 
 * @author ydc
 * @date 6:56:27 PM Jul 31, 2013
 */
public class DynaProxyHello2 implements InvocationHandler {
	/**
	 * 操作者
	 */
	private Object proxy;

	/**
	 * 要处理的对象
	 */
	private Object delegate;

	/**
	 * 动态生成方法被处理过后的对象(写法固定)
	 * 
	 * @param delegate
	 * @param proxy
	 * @return Object
	 */
	public Object bind(Object delegate, Object proxy) {
		this.delegate = delegate;
		this.proxy = proxy;
		return Proxy.newProxyInstance(
				this.delegate.getClass().getClassLoader(), this.delegate
						.getClass().getInterfaces(), this);
	}

	/**
	 * 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说, 要处理的对象的方法只能通过此方法調用,此方法是动态的
	 */
	@SuppressWarnings("unchecked")
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Object result = null;
		try {
			// 反射得到操作者的实例
			Class clazz = this.proxy.getClass();
			// 反射得到操作者的start方法
			Method start = clazz.getDeclaredMethod("start",
					new Class[]{Method.class});
			// 反射执行start方法
			start.invoke(this.proxy, new Object[]{method});

			// JVM通过这条语句执行原来的方法(反射机制)
			result = method.invoke(this.delegate, args);

			// 反射得到操作者的end方法
			Method end = clazz.getDeclaredMethod("end",
					new Class[]{Method.class});
			// 反射执行end方法
			end.invoke(this.proxy, new Object[]{method});
		} catch (Exception e) {
			e.printStackTrace();
		}

		// 返回方法返回值给调用者
		return result;

	}
}

 测试一下

package demo.aop;

public class AopTest {
	public static void main(String[] args) {
		new AopTest().test3();
	}
	public void test3() {
		IHello hello = (IHello) new DynaProxyHello2().bind(new Hello(),
				new LoggerOperation());
		hello.sayHello("wallet white");
		System.out.println("------------------");
		hello.sayGoodBye("wallet white");
	}
}

 结果输出:

INFO	Wed Jul 31 22:12:08 CST 2013	sayHellomethod start
hello wallet white
DEBUG	Wed Jul 31 22:12:08 CST 2013	sayHellomethod end
------------------
INFO	Wed Jul 31 22:12:08 CST 2013	sayGoodByemethod start
wallet white goodbye!
DEBUG	Wed Jul 31 22:12:08 CST 2013	sayGoodByemethod end

 

本文主要参考了下面列出的参考文献的第2篇文章

文章最后留下一个问题:如果不想让所有方法都被日志记录,我们应该怎么去解藕?

也给了一个明确的思路:将需要日志记录的方法写在配置文件里

我在这里做法如下:

---------------------------------------------------------------------------------------------

在馆内添加一个配置文件aop.xml(demo.aop)

<?xml version="1.0" encoding="gbk"?>
<aop>
	<clazz name="demo.aop.Hello">
		<method name="sayHello" />
	</clazz>
	<clazz name="demo.aop.Hello2">
		<method name="sayHello" />
		<method name="sayGoodBye" />
	</clazz>
</aop>

 在这里是完全匹配,由配置可以看出,对于类demo.aop.Hello内的sayHello方法和类demo.aop.Hello2内的sayHello,sayGoodBye方法添加日志记录功能

 

添加一个读取配置文件的工具类

package demo.aop;

import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
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 AopUtil {

	private static Map<String, List<String>> aopMap = null;

	public static Map<String, List<String>> initConfig() {
		if (aopMap != null) {
			return aopMap;
		}
		aopMap = new HashMap<String, List<String>>();
		String filePath = Class.class.getClass().getResource(
				"/demo/aop/aop.xml").getPath();
		File f = new File(filePath);

		Element element = null;
		DocumentBuilder db = null;
		DocumentBuilderFactory dbf = null;
		try {
			dbf = DocumentBuilderFactory.newInstance();
			db = dbf.newDocumentBuilder();
			Document dt = db.parse(f);
			element = dt.getDocumentElement(); // 根元素 aop
			NodeList clazzNodes = element.getChildNodes(); // 根元素下的子节点 clazz
			int cLen = clazzNodes.getLength();

			// 遍历包名.类名 clazz
			for (int i = 0; i < cLen; i++) {
				Node node1 = clazzNodes.item(i);
				if ("clazz".equals(node1.getNodeName())) {
					String clazzName = node1.getAttributes().getNamedItem(
							"name").getNodeValue();
					NodeList nodeDetail = node1.getChildNodes();

					List<String> methodList = new ArrayList<String>();

					// 遍历方法名
					for (int j = 0; j < nodeDetail.getLength(); j++) {
						Node detail = nodeDetail.item(j);
						if ("method".equals(detail.getNodeName())) {
							String methodName = detail.getAttributes()
									.getNamedItem("name").getNodeValue();
							methodList.add(methodName);
						}
					}
					aopMap.put(clazzName, methodList);
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

//		for (Map.Entry<String, List<String>> entry : aopMap.entrySet()) {
//			System.out.println(entry.getKey());
//			List<String> mList = entry.getValue();
//			if (mList != null)
//				for (String s : mList) {
//					System.out.println(s);
//				}
//		}

		return aopMap;
	}
}

 

修改动态代理类

package demo.aop;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
import java.util.Map;

/**
 * Hello动态代理类 代理对象与被代理对像解藕
 * 
 * @author ydc
 * @date 6:56:27 PM Jul 31, 2013
 */
public class DynaProxyHello3 implements InvocationHandler {
	/**
	 * 操作者
	 */
	private Object proxy;

	/**
	 * 要处理的对象
	 */
	private Object delegate;

	/**
	 * 动态生成方法被处理过后的对象(写法固定)
	 * 
	 * @param delegate
	 * @param proxy
	 * @return Object
	 */
	public Object bind(Object delegate, Object proxy) {
		this.delegate = delegate;
		this.proxy = proxy;
		return Proxy.newProxyInstance(
				this.delegate.getClass().getClassLoader(), this.delegate
						.getClass().getInterfaces(), this);
	}

	/**
	 * 要处理的对象中的每个方法会被此方法送去JVM调用,也就是说, 要处理的对象的方法只能通过此方法調用,此方法是动态的
	 */
	@SuppressWarnings("unchecked")
	public Object invoke(Object proxy, Method method, Object[] args)
			throws Throwable {
		Object result = null;
		try {
			// 初始化配置文件
			Map<String, List<String>> aopMap = AopUtil.initConfig();

			// 要执行的方法所在包与类名
			String clazzName = this.delegate.getClass().getName();
			String methodName = method.getName();

			// 是否调用日志记录的标志
			boolean isOpen = false;

			for (Map.Entry<String, List<String>> entry : aopMap.entrySet()) {
				// 匹配方法所在的包名与类名
				if (clazzName.equals(entry.getKey())) {
					List<String> mList = entry.getValue();
					if (mList != null) {
						for (String m : mList) {
							if (methodName.equals(m)) {
								isOpen = true;
								break;
							}
						}
					}
				}
			}

			// 反射得到操作者的实例
			Class clazz = this.proxy.getClass();
			if (isOpen) {
				// 反射得到操作者的start方法
				Method start = clazz.getDeclaredMethod("start",
						new Class[]{Method.class});
				// 反射执行start方法
				start.invoke(this.proxy, new Object[]{method});
			}

			// JVM通过这条语句执行原来的方法(反射机制)
			result = method.invoke(this.delegate, args);

			if (isOpen) {
				// 反射得到操作者的end方法
				Method end = clazz.getDeclaredMethod("end",
						new Class[]{Method.class});
				// 反射执行end方法
				end.invoke(this.proxy, new Object[]{method});
			}
		} catch (Exception e) {
			e.printStackTrace();
		}

		// 返回方法返回值给调用者
		return result;

	}
}

 

这样,再对上述代码进行测试:

package demo.aop;

public class AopTest {
	public static void main(String[] args) {
		new AopTest().test4();
	}
	public void test4() {
		IHello hello = (IHello) new DynaProxyHello3().bind(new Hello(),
				new LoggerOperation());

		hello.sayHello("wallet white");
		System.out.println("------------------");
		hello.sayGoodBye("wallet white");
	}
}

 结果输出:

INFO	Thu Aug 01 09:35:03 CST 2013	sayHellomethod start
hello wallet white
DEBUG	Thu Aug 01 09:35:03 CST 2013	sayHellomethod end
------------------
wallet white goodbye!

 在这里我们看到 Hello类内的sayGoodBye方法没有日志记录功能。

 

参考:

1.http://baike.baidu.com/view/73626.htm

2.http://www.blogjava.net/DoubleJ/archive/2008/03/04/183796.html

3.http://www.cnblogs.com/200911/archive/2012/10/09/2716882.html

分享到:
评论
1 楼 prince_of_ 2016-06-22  
这篇文章非常赞,支持楼主

相关推荐

Global site tag (gtag.js) - Google Analytics