这是我参与更文挑战的第14天,活动详情查看:更文挑战
AOP的织入
俗话说得好,“往事俱备,只欠东风”,各个模块我们已经实现好了,剩下的工作,就是拼接各个模块。
要进行织入,AspectJ采用ajc编译器作为它的织入器,而Spring AOP中,使用类org.springframework.aop.framework.ProxyFactory作为织入器。注意,ProxyFactory并非Spring AOP中唯一可用的织入器,而是最基本的一个织入器实现。
使用ProxyFactory来进行横切逻辑的织入很简单。我们知道,Spring AOP是基于代理模式的AOP实现,织入过程完成后,会返回织入了横切逻辑的目标对象。为ProxyFactory提供必要的“生产原材料”之后,ProxyFactory就会返回那个织入完成的代理对象。
ProxyFactory weaver = new ProxyFactory(yourTargetObject);
Advisor advisor = ...;
weaver.addAdvisor(advisor);
Object proxyObject = weaver.getProxy();
//现在就可以使用proxyObject了
使用ProxyFactory只需要指定如下两个最基本的东西:
1.要对其进行织入的目标对象。可以通过ProxyFactory的构造方法直接传入,也可以在ProxyFactory构造完成之后,通过相应的setter方法进行设置。
2.将要应用到目标对象的Aspect(Spring里叫做Advisor)。
但是,在不同的应用场景下,我们可以指定更多ProxyFactory的控制属性,以便让ProxyFactory帮我们生成必要的代理对象。我们知道,Spring AOP在使用代理模式实现AOP的过程中采用了动态代理和CGLIB两种机制,分别对实现了某些接口的目标类和没有实现任何接口的目标类进行代理,所以,在使用ProxyFactory对目标类进行代理的时候,会通过ProxyFactory的某些行为控制属性对这两种情况进行区分。
在继续下面的内容之前,先设定一个简单的场景,以便大家结合实际情况来查看和分析在不同场景下,ProxyFactory在使用方式上的细微差异。假设我们的目标类型定义如下:
public interface ITask {
void execute(TaskExecutionContext ctx);
}
public class MockTask implements ITask {
public void execute(TaskExecutionContext ctx) {
System.out.println("task executed");
}
}
有了要拦截的目标类,还得有织入到Joinpoint处的横切逻辑,也就是要用到某个Advice实现,定义如下:
public class PerformanceMethodInterceptor implements MethodInterceptor {
public Object invoke(MethodInvocation invocation) throws Throwable {
StopWatch watch = new StopWatch();
try {
watch.start();
return invocation.proceed();
}
finally {
watch.stop();
}
}
}
有了这些之后,让我们来看一下使用ProxyFactory对实现了ITask接口的目标类,以及没有实现任何接口的目标类如何进行代理。
1.基于接口的代理
MockTask实现了ITask接口,要对这种实现了某些接口的目标类进行代理,我们可以为ProxyFactory明确指定代理的接口类型,如下所示:
MockTask task = new MockTask();
ProxyFactory weaver = new ProxyFactory(task);
weaver.setInterfaces(new class[] {ITask.class});
NameMatcherMethodPointcutAdvisor advisor = new NameMatcherMethodPointcutAdvisor();
advisor.setMappedName("execute");
advisor.setAdvice(new PerformanceMethodInterceptor());
weaver.addAdvisor(advisor);
ITask proxyObject = (ITask)weaver.getProxy();//基于接口的代理,只能强转成接口
proxyObject.execute(null);
通过setInterfaces()方法可以明确告知ProxyFactory,我们要对ITask接口类型进行代理。另外,在这里,我们通过NameMatchMethodPointcutAdvisor来指定Pointcut和相应的Advice(PerformanceMethodInterceptor)。至于什么类型的Pointcut、Advice以及Advisor,我们完全可以根据个人的喜好或者具体场景来使用。
不过,如果没有其他行为属性的干预,我们也可以不使用setInterfaces()方法明确指定具体的接口类型(即去掉setInterfaces那行代码)。这样默认情况下,ProxyFactory只要检测到目标类实现了相应的接口,也会对目标类进行基于接口的代理。
简单点来说,如果目标类实现了至少一个接口,不管我们有没有通过ProxyFactory的setInterfaces()方法明确指定要对特定的接口类型进行代理,只要不将ProxyFactory的optimize和proxyTargetClass两个属性的值设为true,那么ProxyFactory都会按照面向接口进行代理。
2.基于类的代理
如果目标类没有实现任何接口,那么,默认情况下,ProxyFactory会对目标类进行基于类的代理,即CGLIB。假设我们现在有一个对象,定义如下:
public class Executable {
public void execute() {
System.out.println("Executable without any Interfaces");
}
}
如果使用Executable作为目标对象类,那么,ProxyFactory就会对其进行基于类的代理,如下代码演示 了使用ProxyFactory对Executable进行织入的过程:
ProxyFactory weaver = new ProxyFactory(new Executable());
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setMapperName("execute");
advisor.setAdvice(new PerformanceMethodInterceptor());
weaver.addAdvisor(advisor);
Executable proxyObject = (Executable)weaver.getProxy();
proxyObject.execute();
System.out.println(proxyObject.getClass());
最终的代理对象是基于CGLIB的。
但是,即使目标对象类实现了至少一个接口,我们也可以通过proxyTargetClass属性强制ProxyFactory采用基于类的代理。以上面的ITask为例,它实现了ITask接口,默认情况下ProxyFactory对其会采用基于接口的代理,但是,通过ProxyTargetClass,我们可以改变这种默认行为,代码如下:
MockTask task = new MockTask();
ProxyFactory weaver = new ProxyFactory(task);
weaver.setProxyTargetClass(true);//***
NameMatcherMethodPointcutAdvisor advisor = new NameMatcherMethodPointcutAdvisor();
advisor.setMappedName("execute");
advisor.setAdvice(new PerformanceMethodInterceptor());
weaver.addAdvisor(advisor);
ITask proxyObject = (MockTask)weaver.getProxy();//*** 与基于接口代理的区别,CGLIB返回的代理类是目标类的子类,可以强转
proxyObject.execute(null);
除此之外,如果将ProxyFactory的optimize属性设定为true的话,ProxyFactory也会采用基于类的代理。
总的来说,如果满足以下列出的三种情况中的任何一种,ProxyFactory将对目标类进行基于类的代理:
1.如果目标类没有实现任何接口
2.如果ProxyFactory的proxyTargetClass属性值被设置为true
3.如果ProxyFactory的optimize属性值被设置为true