声明:本文取材于《精通Spring2.x》上的经典例子。
要了解Spring AOP,建议先熟悉一下设计模式中的代理模式(不基于编程语言的代理模式,关键是理解其思想)。
场景:在各个业务方法中添加进行方法性能测试的逻辑,输出测试方法性能信息(比如运行所花费的时间)。
本例假设业务为ForumServiceImpl,它实现了ForumService接口,该接口提供两个业务操作,见代码:
interface ForumService{
void removeTopic(int topicId);
void removeForum(int forumId);
}
public class ForumServiceImpl implements ForumService{
public void removeTopic(int topicId){
PerformanceMonitor.begin("removeTopic");//性能测试代码
System.out.println("模拟删除Topic记录:"+topicId);
try{
Thread.currentThread().sleep(20);
}catch(Exception e){
e.printStackTrace();
}
PerformanceMonitor.end();//性能测试代码
}
public void removeForum(int forumId){
PerformanceMonitor.begin("removeForum");//性能测试代码
System.out.println("模拟删除Forum记录..."+forumId);
try{
Thread.currentThread().sleep(400);
}catch(Exception e){
e.printStackTrace();
}
PerformanceMonitor.end();//性能测试代码
}
}
所有注视了“性能测试代码”的代码行,都是用于进行性能测试的附加代码,跟真正的业务逻辑没有太大关系。一、跟真正业务无关的代码,二,零散在业务逻辑各个角落中的代码,对于满足这两个特性的代码,正是
Spring AOP所要解决的问题,即把零散在业务逻辑各个角落中零散的非业务逻辑代码抽取出来进行统一管理,使得开发人员只需集中精力在真正的业务逻辑代码。
代码中的PerformanceMonitor是用于性能监测的工具类,它通过ThreadLocal保存一个MethodPerformance实例,见代码:
class MethodPerformance{
private long begin;
private String monitorMethod;//监控的方法
public MethodPerformance(String method){
this.monitorMethod = method;
this.begin = System.currentTimeMillis();
}
public void printPerformance(){
System.out.println("End Monitor...");
System.out.println(monitorMethod+" took "+(System.currentTimeMillis()-this.begin)+" milli seconds!");
}
}
class PerformanceMonitor{
private static ThreadLocal<MethodPerformance> performanceRecord = new ThreadLocal<MethodPerformance>();
public static void begin(String method){
System.out.println("Begin Monitor...");
MethodPerformance performance = new MethodPerformance(method);
performanceRecord.set(performance);
}
public static void end(){
MethodPerformance methodPerformance = performanceRecord.get();
methodPerformance.printPerformance();
}
}
下面看测试代码:
ForumService forumService = new ForumServiceImpl();
forumService.removeForum(10);
forumService.removeTopic(1);
运行结果:
Begin Monitor...
模拟删除Forum记录...10
End Monitor...
removeForum took 400 milli seconds!
Begin Monitor...
模拟删除Topic记录:1
End Monitor...
removeTopic took 20 milli seconds!
当然,各位看官眼明早就看出了程序中的问题了吧:把非业务逻辑操作混杂于业务逻辑!我们的目标是:将性能测试代码从业务逻辑中剔除。实现方法是动态代理,有基于JDK的动态代理和给予cglib的动态代理,本文先讲讲JDK Dynamic Delegate。
为了更好的理解,看客需要了解以下两个个名词:目标对象,代理对象。
Java本身支持动态代理,提供了两个接口:InvocationHandler和Proxy。InvocationHandler包含invoke方法,用于调用代理对象的目标方法,Proxy充当工厂角色,用于封装代理对象。先看代码再解释,先注释掉业务方法中用于性能测试的四行代码:
public class ForumServiceImpl implements ForumService{
public void removeTopic(int topicId){
//PerformanceMonitor.begin("removeTopic");
System.out.println("模拟删除Topic记录:"+topicId);
try{
Thread.currentThread().sleep(20);
}catch(Exception e){
e.printStackTrace();
}
//PerformanceMonitor.end();
}
public void removeForum(int forumId){
//PerformanceMonitor.begin("removeForum");
System.out.println("模拟删除Forum记录..."+forumId);
try{
Thread.currentThread().sleep(400);
}catch(Exception e){
e.printStackTrace();
}
//PerformanceMonitor.end();
}
}
然后,增加一个调用处理器,即InvocationHandler接口的实现,它用于封装目标对象的方法调用,在invoke方法中我们可以加入所有附加的业务逻辑。在这里,我们将性能测试的代码加到其中,而不是直接加到业务类中与业务逻辑混杂:
class PerformanceHandler implements InvocationHandler{
private Object target;//目标对象
public PerformanceHandler(){}
public PerformanceHandler(Object target){
this.target = target;//用于指定目标对象
}
@Override
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable {
/*proxy:代理对象,method:目标类的方法,此例中即ForumService提供的两个的业务方法*/
PerformanceMonitor.begin(target.getClass().getName()+"."+method.getName()+"()");//在业务逻辑方法之前加入性能测试代码
Object obj = method.invoke(target, args);//使用反射调用目标类中的方法
PerformanceMonitor.end();//在业务逻辑方法之后加入性能测试代码
return obj;
}
}
这样就实现了性能测试代码和业务逻辑代码的分离,测试代码为:
ForumService target = new ForumServiceImpl();
PerformanceHandler handler = new PerformanceHandler(target);
ForumService proxyForum = (ForumService)Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(), handler);
proxyForum.removeForum(1);
proxyForum.removeTopic(200);
此处,Proxy.getProxyInstance方法根据目标类的类加载器和所实现的接口,实现了基于特定InvocationHandler的代理对象,后面的操作中我们就可以透明的使用代理对象进行业务逻辑操作了。
问题总结:
- 动态代理对目标类中所有的方法都进行了拦截!如果此时ForumService加入了一个简单方法无需进行性能测试,
也逃不过InvocationHandler的拦截。当然,可以在invoke方法中加入简单判断:if(method.getName().equals(targetMethod)){...},
这样就可以对目标对象的目标方法进行区别对待。另外,AOP对此提供了更好的解决办法:IntroductionInterceptor。
- 从target.getClass().getInterfaces()可以看出,JDK动态代理需要目标对象必须实现一定的接口,如果此例中我们ForumServiceImpl不实现ForumService接口呢?
那么通过Proxy获得代理对象的代码将出现异常,异常描述类似于:$Proxy0 can't be cast to...即类型转换错误。这点cglib做得很成功。
- JDK动态代理是每次在使用代理对象时动态的改变目标类的信息,因此使用代理对象时执行效率受到了影响,这点cglib占有优势。