刚接触动态代理模式的时候,感觉着东西确实有意思,但仔细想想貌似平常很少情况下会被使用。后来知道了Spring AOP的底层是采用动态代理实现的。
项目中采用spring声明式的事务控制、确实能够感受到AOP的存在,也能隐约明白AOP的底层实现和动态代码有莫大的关系。JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态将横切逻辑和业务逻辑编织在一起。
横切逻辑平时项目中出现的确实不多,但是某些场景下还是存在的。最典型的的例子便是事务控制,还有比如某些方法需要进行性能监控、某些方法需要进行用户合法性的验证、某些方法需要进行日志记录等。
下面通过方法的性能监控横切逻辑作为例子,了解下动态代理的一些知识。
package proxy.train; public class PerformanceMonitor { private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>(); private long begin; private long end; private String serviceMethod; public void begin(String method) { System.out.println("begin monitor..."); begin = System.currentTimeMillis(); this.serviceMethod = method; } public void end() { System.out.println("end monitor..."); end = System.currentTimeMillis(); long elapse = end -begin; System.out.println(serviceMethod + "花费" + elapse + "毫秒\n"); } }
这是一个简单的性能监控横切逻辑类,当业务方法被调用的时候、将先运行该监控类的begin方法,当业务方法运行结束后将执行监控类的end方法,已获得业务方法的执行时间。
package proxy.train; public class ServiceImpl implements Service{ public void removeTopic(int topicId) { System.out.println("模拟删除Topic记录: " + topicId); try { Thread.sleep(60); } catch (InterruptedException e) { e.printStackTrace(); } } public void removeForum(int forumId) { System.out.println("模拟删除Forum记录: " + forumId); try { Thread.sleep(80); } catch (InterruptedException e) { e.printStackTrace(); } } }
上面是一个业务类,通过实现InvocationHandler接口的动态代理类、可以很好地将业务逻辑和横切逻辑组织在一起。
package proxy.train; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MyInvocationHandler implements InvocationHandler { private Object target;//目标业务类 public void setTarget(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { PerformanceMonitor pm = new PerformanceMonitor(); pm.begin(method.getName()); Object result = method.invoke(target, args);//通过反射调用目标类的目标方法 pm.end(); return result; } }
package proxy.train; import java.lang.reflect.Proxy; public class MainTest { public static void main(String[] args) { ForumService fs = new ForumServiceImpl(); MyInvocationHandler handler = new MyInvocationHandler(); handler.setTarget(fs); ForumService fs1 = (ForumService) Proxy.newProxyInstance(fs.getClass().getClassLoader(), fs.getClass().getInterfaces(), handler); fs1.removeTopic(1024); fs1.removeForum(13); } }
上面的动态代理方法很好地将业务逻辑与横切逻辑编织在了一起。但是上面的实现方式也存在非常大的几个缺陷:
1、目标类的所有方法都被添加了横切逻辑,某些时候我们只想给部分特定的方法添加横切逻辑;
2、采用了硬编码的方式指定了植入逻辑的植入点,即在业务目标方法的开始和结束处植入横切逻辑;
3、我们手工编写代理实例的创建过程,为不同类创建代理时,需要分别编写相应的创建代码,无法通用。
这三个问题在AOP中占用重要的地位,Spring AOP的主要工作就是围绕以上三点展开的:Spring AOP通过Pointcut(切点)指定在哪些类的哪些方法上植入横切逻辑,通过Advice(增强)描述横切逻辑和方法的具体植入点(方法前、方法后、方法两端等)。Spring通过Advisor(切面)将Pointcut和Advice两者组装在一起。通过Advisor的信息,Spring就利用JDK动态代理技术采用统一的方式为目标Bean创建植入的代理对象。