概述
Java1.3以后,JAVA提供了动态代理技术,允许开发者在运行期创建接口的代理实例。
JDK的动态代理主要涉及java.lang.reflect包中的两个类:Proxy和InvocationHandler.
InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑并通过反射机制调用目标类的代码,动态的将横切逻辑和业务逻辑编织在一起。
而Proxy利用InvocationHandler动态的创建一个符合某一接口的实例,生成目标类的代理对象。
改造
代码已托管到Github—> https://github.com/yangshangwei/SpringMaster
首先我们移除性能监视的横切代码,如下
package com.xgj.aop.base.jdkproxy; /** * * * @ClassName: ForumServiceImpl * * @Description: ForumService实现类 * * @author: Mr.Yang * * @date: 2017年8月12日 下午4:14:30 */ public class ForumServiceImpl implements ForumService { @Override public void removeTopic(int topicId) { // 模拟业务逻辑 System.out.println("模拟删除Topic,topicId=" + topicId); try { Thread.currentThread().sleep((long) (Math.random() * 1000 * 10)); } catch (InterruptedException e) { e.printStackTrace(); } } @Override public void removeForum(int forumId) { // 模拟业务逻辑 System.out.println("模拟删除forum,forumId=" + forumId); try { Thread.currentThread().sleep((long) (Math.random() * 1000 * 10)); } catch (InterruptedException e) { e.printStackTrace(); } } }
如上只编写了业务代码,移除的性能监视横切代码,放哪里呢?
InvocationHandler就是横切代码的家,编写实现类实现InvocationHandler 接口,重写invoke方法
比如:
package com.xgj.aop.base.jdkproxy; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class PerformanceHandler implements InvocationHandler { private Object target; /** * * * @Title:PerformanceHandler * * @Description:构造函数, 入参target为业务目标类 * * @param target */ public PerformanceHandler(Object target) { this.target = target; } // 性能监视的横切代码 + 业务代码 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 开始监控 入参 方法的全限定名称 PerformanceMonitor.begin(target.getClass().getName() + "." + method.getName()); // 通过反射调用业务类的目标方法 Object object = method.invoke(target, args); // 结束监控 PerformanceMonitor.end(); return object; } }
分析:
invoke方法中PerformanceMonitor相关的为性能监视的横切代码,我们发现,横切代码只出现一次,而不是像原来那样每个类中都有。
method.invoke()通过Java反射机制间接调用目标对象的方法,这样 InvocationHandler的invoke方法就将横切逻辑代码和业务类方法的业务逻辑代码编织到一起,所以可以将InvocationHandler看成一个编织器。
具体分析这段代码:
首先实现了InvocationHandler接口,重写invoke方法
public Object invoke(Object proxy, Method method, Object[] args)
其中
proxy是最终生成的代理实例,一般不会用到
method是被代理目标实例的某个具体方法,通过它可以发起目标实例方法的反射调用
args是被代理实例某个方法的入参,在方法反射调用时使用。
其次,在构造函数里通过target传入希望被代理的目标对象。 在invoke方法中,将目标对象传递给method.invoke()方法,并调用目标实例的方法。
测试类,利用Proxy创建ForumService接口的代理实例:
package com.xgj.aop.base.jdkproxy; import java.lang.reflect.Proxy; public class ForumServiceTest { public static void main(String[] args) { // 希望被代理的目标业务类 ForumService target = new ForumServiceImpl(); // 将目标类业务和横切代码编织到一起 PerformanceHandler handler = new PerformanceHandler(target); // 根据编织了目标业务类逻辑和性能监控横切逻辑的InvocationHandler实例创建代理实例 ForumService proxy = (ForumService) Proxy.newProxyInstance(target .getClass().getClassLoader(), target.getClass().getInterfaces(), handler); // 调用代理实例 proxy.removeTopic(3); proxy.removeForum(1); } }
上述代码完成了业务类代码和横切面代码的编织工作并生成了代理实例。
PerformanceHandler handler = new PerformanceHandler(target); —>将性能监视横切逻辑编织到ForumService实例中。
然后通过Proxy的 newProxyInstance()讲台方法为编织了业务逻辑和心梗监视横切逻辑的handler创建了一个符合ForumService接口的代理实例。
第一个入参为类加载器
第二个入参为创建代理实例所需要实现的一组接口
第三个参数是整了业务逻辑和横切逻辑的编织器对象。
按照上述设置,这个代理实例就实现了目标业务类的所有接口,即ForumServiceImpl的ForumService接口。
这样就可以按照调用ForumService接口实例相同的方式调用代理实例了。
代理实例方法调用的时序图:
运行结果:
其他相关接口/类
ForumService.java
package com.xgj.aop.base.jdkproxy; /** * * * @ClassName: ForumService * * @Description: ForumService接口 * * @author: Mr.Yang * * @date: 2017年8月12日 下午4:13:31 */ public interface ForumService { /** * * * @Title: removeTopic * * @Description: 根据topicId删除Topic * * @param topicId * * @return: void */ void removeTopic(int topicId); /** * * * @Title: removeForum * * @Description: 根据forumId删除Forum * * @param forumId * * @return: void */ void removeForum(int forumId); }
PerformanceMonitor.java
package com.xgj.aop.base.jdkproxy; public class PerformanceMonitor { // 通过一个ThreadLocal保存与调用线程相关的性能监视信息 private static ThreadLocal<MethoPerformance> performanceLocal = new ThreadLocal<MethoPerformance>(); /** * * * @Title: begin * * @Description: 启动对某一目标方法的性能监视 * * @param method * * @return: void */ public static void begin(String method) { System.out.println("begin to monitor:" + method); MethoPerformance methoPerformance = new MethoPerformance(method); performanceLocal.set(methoPerformance); } /** * * * @Title: end * * @Description: 输出性能监视结果 * * @param method * * @return: void */ public static void end() { MethoPerformance methoPerformance = performanceLocal.get(); // 打印出方法性能监视的结果信息 methoPerformance.printPerformance(); } }
MethoPerformance.java
package com.xgj.aop.base.jdkproxy; public class MethoPerformance { private long beginTime; private long endTime; private String methodName; /** * * * @Title:MethoPerformance * * @Description:构造函数 * * @param methodName */ public MethoPerformance(String methodName) { super(); this.methodName = methodName; this.beginTime = System.currentTimeMillis(); } /** * * * @Title: printPerformance * * @Description: 计算耗时 * * * @return: void */ public void printPerformance() { endTime = System.currentTimeMillis(); long cost = endTime - beginTime; System.out.println(methodName + " costs " + cost / 1000 + "秒\n"); } }