用动态代理实现面向切面编程,以下实例实现在特定方法前后添加日志和事务的的功能。
1)Proxy 类 (真实对象的代理) 的三个参数
前两个参数用于生成$Proxy 的构造器,此构造器再以invocationHandler 为参数生成$Proxy 实例。
- classLoader: 类加载器,它由真实对象userService 得到,但其实跟userService 不是一一对应的关系;用任何一个对象都可以用同样的方法得到一样的类加载器;既然如此,用测试类本身也可以得到类加载器:this.getClass().getClassLoader(),再把this 省略,就变成了getClass().getClassLoader()。
类加载器打印出来就是:sun.misc.Launcher$AppClassLoader@18b4aac2 - interfaces: 接口数组,真实对象userService 所实现的所有接口。Proxy 用类加载器和接口数组这两个参数得到动态生成的Proxy 的Class 对象(getProxyClass0(loader, intfs)),再用此Class 对象获取其构造器(cl.getConstructor(constructorParams)),由构造器生成Proxy 实例(cons.newInstance(new Object[]{h}), 此处的h 就是Proxy 第三个参数invocationHandler).
- invocationHandler: 直译是调用处理器,它这个对象本质是Proxy 类的代理,也就是真实对象的代理 的代理。Proxy类实现了真实对象的接口,所以它的对象可以调用真实对象的方法,调用时是指派invocationHandler 去实际执行系统增强业务(即日志、事务等) 和真实业务。
2)InvocationHandler 的invoke 三个参数的作用
- proxy: 就是Proxy 的一个动态实例,用Idea debug 显示为:{$Proxy4@862} com.qf.service.impl.UserServiceImpl@5a8806ef. 这个参数在invoke 方法体内可以完全不用,所以很难理解它的作用,甚至知乎、Stackoverflow 上的回答都感觉不太对。Stackoverflow 上一个回答说作用是可以将代理对象返回以进行连续调用,并写了demo(https://stackoverflow.com/questions/22930195/understanding-proxy-arguments-of-the-invoke-method-of-java-lang-reflect-invoca),难道不需要连续调用的话就没用了吗?又查了很多资料,终于找到了一个比较符合逻辑的解释:invoke 第二个参数是method 怎么来的?$Proxy 动态实例 (即第一个参数proxy) 生成后(是class 文件),通过反向编译,可以看到里面有如下一段静态代码块(来源:http://rejoy.iteye.com/blog/1627405),也就是说只有proxy 实例在InvocationHandler 实现类里加载才能产生第二个参数method (静态代码块是虚拟机加载类的时候执行的,而且只执行一次),所以$Proxy 实例要把自己传给InvocationHandler 的invoke 方法。
m3 = Class.forName("dynamic.proxy.UserService").getMethod("add", new Class[0]);
- method: 真实对象要实现的业务方法,由$Proxy 实例的静态代码块得到。
- args: 第二个参数method 的参数。
3)两个概念:系统增强服务(日志、事务、权限等) 与系统真实业务分离,放到 InvocationHandler 实现类invoke 方法里面的 这些出代码片段就是一个切面,本文Demo 中真实业务add() 就是切入点。
以下为InvocationHandler 实现类和测试类(含创建$Proxy 动态实例) 的代码。
public class DynamicProxy implements InvocationHandler { private TransactionManager tx; private LoggerManager log; private Object object; public DynamicProxy() { } //object 参数为真实对象,此例中为userService 对象 public DynamicProxy(TransactionManager tx, LoggerManager log, Object object) { this.tx = tx; this.log = log; this.object = object; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { tx.begin(); log.beginLog(); Object invoke = method.invoke(object, args); log.endLog(); tx.commit(); return invoke; } }
@Test public void testDynamicAOP(){ LoggerManager log = new LoggerManager(); TransactionManager tm = new TransactionManager(); IUserService userService = new UserServiceImpl(); ClassLoader loader = userService.getClass().getClassLoader(); Class<?>[] interfaces = userService.getClass().getInterfaces(); InvocationHandler handler = new DynamicProxy(tm, log, userService); IUserService userServiceProxy = (IUserService) Proxy.newProxyInstance(loader, interfaces, handler); userServiceProxy.add(new User("Rock")); }