Spring AOP的织入1
本文相关代码(来自官方源码spring-test模块)请参见spring-framework org.springframework.mylearntest包下。
AspectJ采用ajc编译器作 为它的织入器;JBoss AOP使用自定义的ClassLoader作为它的织入器;而在Spring AOP中,使用类org.springframework.aop.framework.ProxyFactory作为织入器。
使用方法:
-
传入需要织入的对象
ProxyFactory weaver = new ProxyFactory(target);
-
将要应用到目标对象的Advisor绑定到织入器上
- 如果不是Introduction的Advice类型,Proxy内部就会为这些Advice构造相应的Advisor,只不过在为它们构造Advisor中使用的Pointcut为Pointcut.TRUE。
- 如果是Introduction类型,则会根据该Introduction具体类型进行区分;如果是Introduction的子类实现,框架内部会为其构造一个DefaultIntroductionAdvisor ;如果是DynamicIntroductionAdvice的子实现类,框架内部将抛出AOPConfigException异常(因为无法从DynamicIntroductionAdvice取得必要的目标对象信息)
weaver.addAdvisor(advisor);
- 获取代理对象
Object proxyObject = weaver.getProxy();
基于接口的代理
Test4ProxyFactory
package org.springframework.mylearntest.aop.weaver;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import java.util.Date;
/**
* @Author: whalefall
* @Date: 2020/7/15 22:53
*/
@SuppressWarnings({"rawtypes", "Deprecated"})
public class Test4ProxyFactory {
public static void main(String[] args) {
/*// 1. 传入需要织入的对象
ProxyFactory weaver = new ProxyFactory(new Tester());
// weaver.setTarget(new Tester());
// 2. 将要应用到目标对象的Advisor绑定到织入器上
ApplicationContext context = new ClassPathXmlApplicationContext("advisor/defaultadvisor/defaultadvisor.xml");
Advisor advisor = (Advisor) context.getBean("advisor");
weaver.addAdvisor(advisor);
Object proxyObject = weaver.getProxy();
System.out.println(proxyObject.getClass());
// out: class org.springframework.mylearntest.aop.advice.perinstance.Tester$$EnhancerBySpringCGLIB$$8e739b5b
*/
// 目标类有实现接口的用法
// 只要不将ProxyFactory的optimize和proxyTargetClass设置为true
// 那么ProxyFactory都会按照面向接口进行代理
MockTask task = new MockTask();
ProxyFactory weaver = new ProxyFactory(task);
// weaver.setInterfaces(new Class[]{ITask.class});
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.setMappedNames("execute");
advisor.setAdvice(new PerformanceMethodInterceptor());
weaver.addAdvisor(advisor);
ITask proxyObj = (ITask)weaver.getProxy();
// com.sun.proxy.$Proxy0
// System.out.println(proxyObj.getClass());
// 只能强制转化为接口类型,不能转化为实现类类型 否则会抛出ClassCastException
// ITask proxyObj = (MockTask)weaver.getProxy();
proxyObj.execute(new Date());
}
}
基于类代理
TestCGLib
package org.springframework.mylearntest.aop.weaver.baseonclass;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.NameMatchMethodPointcutAdvisor;
import org.springframework.mylearntest.aop.advice.perclass.PerformanceMethodInterceptor;
/**
* @Author: whalefall
* @Date: 2020/7/17 23:31
*/
public class Test4CGLib {
public static void main(String[] args) {
ProxyFactory weaver = new ProxyFactory(new Executable());
NameMatchMethodPointcutAdvisor advisor = new NameMatchMethodPointcutAdvisor();
advisor.addMethodName("execute");
advisor.setAdvice(new PerformanceMethodInterceptor());
weaver.addAdvisor(advisor);
Executable proxyObject = (Executable)weaver.getProxy();
proxyObject.execute();
// org.springframework.mylearntest.aop.weaver.baseonclass.Executable$$EnhancerBySpringCGLIB$$37e40619
System.out.println("proxyObject class: " + proxyObject.getClass());
}
}
如果目标类没有实现任何接口,不管proxyTargetClass的属性是什么,ProxyFactoy会采用基于类的代理
如果ProxyFactoy的proxyTargetClass属性值被设置为true,ProxyFactoy会采用基于类的代理
如果ProxyFactoy的optimize属性被设置为true,ProxyFactory会采用基于类的代理。
Introduction的织入
Introduction可以为已经存在的对象类型添加新的行为,只能应用于对象级别的拦截,而不是通常Advice的方法级别的拦截,所以在Introduction的织入过程中,不需要指定Pointcut,而只需要指定目标接口类型。
Spring的Introduction支持只能通过接口定义为当前对象添加新的行为。所以,我们需要在织入的时机,指定新织入的接口类型。
Test4Introduction
package org.springframework.mylearntest.aop.weaver.introduction;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.mylearntest.aop.advice.perinstance.Developer;
import org.springframework.mylearntest.aop.advice.perinstance.IDeveloper;
import org.springframework.mylearntest.aop.advice.perinstance.ITester;
import org.springframework.mylearntest.aop.advice.perinstance.TesterFeatureIntroductionInterceptor;
/**
* @Author: whalefall
* @Date: 2020/7/19 0:02
*/
@SuppressWarnings("rawtypes")
public class Test4Introduction {
public static void main(String[] args) {
ProxyFactory weaver = new ProxyFactory(new Developer());
weaver.setInterfaces(new Class[]{IDeveloper.class, ITester.class});
TesterFeatureIntroductionInterceptor advice = new TesterFeatureIntroductionInterceptor();
weaver.addAdvice(advice);
// DefaultIntroductionAdvisor advisor = new DefaultIntroductionAdvisor(advice,advice);
// weaver.addAdvisor(advisor);
Object proxy = weaver.getProxy();
((ITester)proxy).testSoftware();
((IDeveloper)proxy).developSoftware();
System.out.println("proxy = " + proxy);
}
}
ProxyFactory本质
Spring AOP框架内使用AopProxy对使用的不用的代理实现机制进行了适度的抽象,主要有针对JDK动态代理和CGLIB两种机制的AopProxy两种实现,分别是Cglib2AopProxy和JdkDynamicAopProxy两种实现。动态代理需要通过InvocationHandler提供调用拦截,所以JdkDynamicAopProxy同时实现了InvocationHandler接口。采用抽象工厂模式,通过org.springframework.aop.framework.AopProxyFactory进行。
AopProxyFactory
public interface AopProxyFactory {
AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException;
}
AopProxyFactory根据传入的AdvisedSupport实例提供的相关信息,来决定 生成什么类型的AopProxy,具体的工作由AopProxyFactory具体的实现类来完成。即org.springframework.aop.framework.DefaultAopProxyFactory。
DefaultAopProxyFactory
package org.springframework.aop.framework;
import java.io.Serializable;
import java.lang.reflect.Proxy;
import org.springframework.aop.SpringProxy;
@SuppressWarnings("serial")
public class DefaultAopProxyFactory implements AopProxyFactory, Serializable {
@Override
public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException {
// 如果传入的AdvisedSupport实例的isOptimize或者isProxyTargetClass方法返回true,
// 或者目标对象没有实现任何接口,则采用CGLIB生成代理对象,否则使用动态代理。
if (config.isOptimize() || config.isProxyTargetClass() || hasNoUserSuppliedProxyInterfaces(config)) {
Class<?> targetClass = config.getTargetClass();
if (targetClass == null) {
throw new AopConfigException("TargetSource cannot determine target class: " +
"Either an interface or a target is required for proxy creation.");
}
if (targetClass.isInterface() || Proxy.isProxyClass(targetClass)) {
return new JdkDynamicAopProxy(config);
}
return new ObjenesisCglibAopProxy(config);
}
else {
return new JdkDynamicAopProxy(config);
}
}
private boolean hasNoUserSuppliedProxyInterfaces(AdvisedSupport config) {
Class<?>[] ifcs = config.getProxiedInterfaces();
return (ifcs.length == 0 || (ifcs.length == 1 && SpringProxy.class.isAssignableFrom(ifcs[0])));
}
}
AdvisedSupport是一个生成代理对象所需要的信息的载体。
- 一类以org.springframework.aop.framework.ProxyConfig为首的,记载生成代理对象的控制信息;
- 一类以org.springframework.aop.framework.Advised为首,承载生成代理对象的所需要的必要信息,比如相关目标类、Advice、Advisor等。
ProxyConfig就是普通的JavaBean,定义了五个boolean型的属性,分别控制在生成代理对象的时候,应该采取哪些措施。
- ProxyTargetClass:如果这个属性设置如true,则ProxyFactory将会使用CGLIB对目标对象进行代理。默认值为false。
- optimize:该属性主要用于告知代理对象是否需要采取进一步的优化措施。如果代理对象生成之后,即使为其添加或者移除了相应的人Advice,代理对象也可以忽略这种变动。如果这个属性设置如true,则ProxyFactory将会使用CGLIB对目标对象进行代理。默认值为false。
- opaque:该属性用于控制生成的代理对象是否可以强制转化为Advised,默认值为false,表示任何生成的代理对象都可以强制转型为Advised,我们可以通过Advised查询代理对象的一些状态。
- exposeProxy:设置exposeProxy,可以让Spring AOP框架在生成代理对象时,将当前代理对象绑定到ThreadLocal。如果目标对象需要访问当前代理对象,可以通过
AopContext.currentProxy()
拿到代理对象。出于性能方面考虑,该属性默认为false。 - frozen:如果将frozen设置为true,那么一旦针对dialing对象生成的各项信息配置完成,则不容许更改。比如ProxyFactory的设 置完毕,并且frozen为true,则不能对Advice进行任何变动,这样可以优化代理对象的性能,默认情况下为false。
要生成代理对象,只有ProxyConfig提供的信息还不够,我们还需要生成代理对象的一些具体信息,比如,要针对哪些目标类生成代理对象,要为代理对象加入何种横切逻辑等,这些信息可以通过org.springframework.aop.framework.Advised设置。默认情况下Spring AOP框架返回的代理对象都可以强制转型为Advised,已查询代理对象的相关信息。
我们可以使用Advised接口访问相应代理对象所有持有的Advisor,进行添加Advisor、一处Advisor等相关动作。即使代理对象已经生成完毕,也可对其进行操作,直接操作Advised,更多时候用于测试场景,可以帮助我们检查生成的代理对象是否如所期望的那样。
ProxyFactory集AopProxy和AdvisedSupport于一身,可以通过AdvisedSupport设置生成代理对象所需要的相关信息,可以通过AopProxy生成代理对象。为了重用相关逻辑,Spring AOP框架在实现的时候,将一些公用的逻辑抽取到了org.springframework.aop.frameworkx.ProxyCreatorSupport中,自身继承了AdvisedSupport,所以就能具有设置生成代理对象所需要的相关信息。
为了简化生成不同类型AopProxy的工作,ProxyCreatorSupport内部持有一个AopProxyFactory实例,默认采用的是DefaultAopProxyFactory。