您的位置:首页 > 编程语言 > Java开发

AOP学习一:JDK动态代理

2013-05-15 20:20 344 查看
AOP,Aspect Oriented Programming,就是所谓的面向切面编程。这个词语的含义,网上的解释已经很多很多了,理解起来并不怎么难,照葫芦画瓢也能开发出带aop功能的模块出来。开始我也是这样,但只是会用,对它的原理却并没有做过深入地学习。之前一段时间挺闲,于是就打算从头开始认认真真地去研究下aop的原理。于是便有了如下的一套学习笔记,分享出来,只要照着上面的代码敲一遍,相信也会对aop的实现有所掌握。

在开始aop学习之前,先来说下两种动态代理技术:JDK动态代理及CGLib动态代理。aop便是以这两者为基础,实现了切面编程的功能。

何为代理?当一个类被AOP织入增强后,就产出了一个代理类,这个类融合了原类和一些需要添加的增强逻辑。根据不同的代理方式,代理类既可能是和原类具有相同接口的类,也可能就是原类的子类,所以我们可以采用调用原类相同的方式调用代理类。

Spring AOP使用了两种代理机制:一种是基于JDK的动态代理;另一种是基于CGLib的动态代理。之所以需要两种代理机制,很大程度上是因为JDK本身只提供接口的代理,而不支持类的代理。下面依次来讲述这两种动态代理的实现。

JDK动态代理

JDK的动态代理主要涉及到java.lang.reflect包中的两个类:Proxy和InvocationHandler。其中InvocationHandler是一个接口,可以通过实现该接口来定义横切逻辑,并通过反射机制调用目标类的代码,动态地将横切逻辑和业务逻辑编织在一起。而Proxy利用InvocationHandler动态创建一个符合某一接口的实例,生成目标类的代理对象。下面给出使用示例:

业务接口:

[java] view
plaincopy

package proxy.jdk;

public interface ForumService {

void removeTopic(int topicId);

void removeForum(int forumId);

}

业务接口实现类:

[java] view
plaincopy

package proxy.jdk;

public class ForumServiceImpl implements ForumService {

@Override

public void removeTopic(int topicId) {

System.out.println("模拟删除Topic记录:" + topicId);

try {

Thread.sleep(20);

} catch (Exception e) {

throw new RuntimeException(e);

}

}

@Override

public void removeForum(int forumId) {

System.out.println("模拟删除Forum记录:" + forumId);

try {

Thread.sleep(40);

} catch (Exception e) {

throw new RuntimeException(e);

}

}

}

上面的实现类中,只是简单地实现了接口中定义的方法。现在,我们要实现这样的功能:如果这两个方法被调用,则记录下调用日志,并且简单计算下执行时间。如果照正常的逻辑来处理,那应该是类似下面的逻辑代码:

[java] view
plaincopy

@Override

public void removeTopic(int topicId) {

System.out.println("调用目标对象方法 [removeTopic]开始...");

long start = System.currentTimeMillis();

System.out.println("模拟删除Topic记录:" + topicId);

try {

Thread.sleep(20);

} catch (Exception e) {

throw new RuntimeException(e);

}

long end = System.currentTimeMillis();

System.out.println("调用目标对象方法[removeTopic]结束.总共花费了: " + (end - start) + "ms");

}

日志的记录及时间的计算代码包围在核心的业务代码外,并且同样的代码实现需要对removeForum这个方法再写一遍。可想而知,如果有多个方法要处理,这样的重复处理的代码需要写多次。既浪费时间,又使代码变得臃肿。现在,我们通过JDK动态代理技术,来实现上面的功能:

实现了InvocationHandler接口的类:

[java] view
plaincopy

package proxy.jdk;

import java.lang.reflect.InvocationHandler;

import java.lang.reflect.Method;

public class PerformationHandler implements InvocationHandler {

// 目标业务类

private Object target;

public PerformationHandler(Object target) {

this.target = target;

}

/**

* 将横切逻辑代码与业务代码编织到一起

*/

@Override

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

String methodName = target.getClass().getName() + "." +method.getName();

System.out.println("调用目标对象方法 [" + methodName + "]开始...");

long start = System.currentTimeMillis();

// 通过反射调用目标类的目标方法

Object obj = method.invoke(target, args);

long end = System.currentTimeMillis();

System.out.println("调用目标对象方法[" + methodName + "]结束.总共花费了: " + (end - start) + "ms");

return obj;

}

}

InvocationHandler接口定义了一个invoke()方法,proxy是最终生成的代理实例,一般不会用到;method是被代理目标实例的某个具体方法,通过它可以发起目标实例方法的反射调用;args是通过被代理实例某一方法的入参,在方法反射调用时使用。

按照我们正常调用的逻辑,实现代码应该如下:

[java] view
plaincopy

// 正常的业务实现

ForumService forumService = new ForumServiceImpl();

forumService.removeTopic(10);

forumService.removeForum(13);

但现在我们使用了动态代理,因此就通过代理来调用。

通过Proxy结合PerformationHandler创建业务接口的代理实例:

[java] view
plaincopy

// 通过动态代理来实现

ForumService target = new ForumServiceImpl();

PerformationHandler handler = new PerformationHandler(target);

ForumService proxy = (ForumService) Proxy.newProxyInstance(target.getClass().getClassLoader(), target

.getClass().getInterfaces(), handler);

proxy.removeForum(10);

proxy.removeTopic(12);

通过Proxy的newProxyInstance()静态方法为混合了业务处理逻辑和性能监视逻辑的handler创建一个符合ForumService接口的代理实例。该方法的第一个入参为类加载器;第二个入参为创建代理所需要实现的一组接口;第三个参数是整合了业务逻辑和性能监视逻辑(这里即所谓的横切逻辑)的编织器对象。生成的代理实例实现了目标业务类的所有接口,即ForumServiceImpl的ForumService接口。这样,我们就可以按照调用ForumService接口实现相同的方式调用代理实例。运行以上代码,输出结果如下:

调用目标对象方法 [proxy.jdk.ForumServiceImpl.removeForum]开始...

模拟删除Forum记录:10

调用目标对象方法[proxy.jdk.ForumServiceImpl.removeForum]结束.总共花费了: 40ms

调用目标对象方法 [proxy.jdk.ForumServiceImpl.removeTopic]开始...

模拟删除Topic记录:12

调用目标对象方法[proxy.jdk.ForumServiceImpl.removeTopic]结束.总共花费了: 20ms

我们发现,程序的运行效果和直接在业务类中编写性能监视逻辑的效果一致,但是在这里,原来分散的横切逻辑代码已经被抽取到PerformationHandler中,当调用代理对象的removeForum()和removeTopic()方法时,便调用了invoke()方法。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: