关于通知的总结
通过上文的分析我们可以发现,通知总共可以分为这么几类
- 普通的通知(前置,后置,异常等,没有实现MethodInterceptor接口)
- 环绕通知(实现了MethodInterceptor接口)
- 引入通知(需要提供额外的引入的信息,实现了MethodInterceptor接口)
上面的分类并不标准,只是为了方便大家记忆跟理解,虽然我们普通的通知没有直接实现MethodInterceptor接口,但其实它的底层也是依赖于拦截器来完成的,大家可以看看下面这个类
class MethodBeforeAdviceAdapter implements AdvisorAdapter, Serializable { @Override public boolean supportsAdvice(Advice advice) { return (advice instanceof MethodBeforeAdvice); } // 根据传入的一个前置通知,创建一个对应的拦截器 @Override public MethodInterceptor getInterceptor(Advisor advisor) { MethodBeforeAdvice advice = (MethodBeforeAdvice) advisor.getAdvice(); return new MethodBeforeAdviceInterceptor(advice); } } public class MethodBeforeAdviceInterceptor implements MethodInterceptor, BeforeAdvice, Serializable { private final MethodBeforeAdvice advice; public MethodBeforeAdviceInterceptor(MethodBeforeAdvice advice) { Assert.notNull(advice, "Advice must not be null"); this.advice = advice; } // 实际上还是利用拦截器,在方法执行前调用了通知的before方法完成了前置通知 @Override public Object invoke(MethodInvocation mi) throws Throwable { this.advice.before(mi.getMethod(), mi.getArguments(), mi.getThis()); return mi.proceed(); } }
Advisor (绑定通知跟切点)
一个Advisor实际上就是一个绑定在指定切点上的通知。在前面的例子我们可以发现,有两种添加通知的方式
// 一个Advisor代表的是一个已经跟指定切点绑定了的通知 // 在这个例子中意味着环绕通知不会作用到toString方法上 Advisor advisor = new DefaultPointcutAdvisor(new DmzPointcut(), new DmzAroundAdvice()); // 添加一个绑定了指定切点的环绕通知 proxyFactory.addAdvisor(advisor); // 添加一个返回后的通知 proxyFactory.addAdvice(new DmzAfterReturnAdvice());
一种是直接添加了一个Advisor,还有一种是添加一个Advice,后者也会被转换成一个Advisor然后再进行添加,没有指定切点的通知是没有任何意义的
public void addAdvice(Advice advice) throws AopConfigException { int pos = this.advisors.size(); // 默认添加到集合的最后一个位置 addAdvice(pos, advice); } // 这个方法添加通知 public void addAdvice(int pos, Advice advice) throws AopConfigException { Assert.notNull(advice, "Advice must not be null"); // 如果是一个引入通知,那么构建一个DefaultIntroductionAdvisor // DefaultIntroductionAdvisor会匹配所有类 if (advice instanceof IntroductionInfo) { addAdvisor(pos, new DefaultIntroductionAdvisor(advice, (IntroductionInfo) advice)); } // 不能直接添加一个不是IntroductionInfo的DynamicIntroductionAdvice(动态引入通知) else if (advice instanceof DynamicIntroductionAdvice) { throw new AopConfigException("DynamicIntroductionAdvice may only be added as part of IntroductionAdvisor"); } else { // 如果是普通的通知,那么会创建一个DefaultPointcutAdvisor // DefaultPointcutAdvisor所定义的切点会匹配所有类以及所有方法 addAdvisor(pos, new DefaultPointcutAdvisor(advice)); } }
ProxyCreatorSupport
这个类的主要作用是为创建一个AOP代理对象提供一些功能支持,通过它的getAopProxyFactory能获取一个创建代理对象的工厂。
// 这里我只保留了这个类中的关键代码 public class ProxyCreatorSupport extends AdvisedSupport { private AopProxyFactory aopProxyFactory; // 空参构造,默认会创建一个DefaultAopProxyFactory // 通过这个ProxyFactory可以创建一个cglib代理或者jdk代理 public ProxyCreatorSupport() { this.aopProxyFactory = new DefaultAopProxyFactory(); } // 通过这个方法可以创建一个具体的代理对象 protected final synchronized AopProxy createAopProxy() { if (!this.active) { activate(); } // 实际就是使用DefaultAopProxyFactory来创建一个代理对象 // 可以看到在调用createAopProxy方法时,传入的参数是this // 这是因为ProxyCreatorSupport本身就保存了创建整个代理对象所需要的配置信息 return getAopProxyFactory().createAopProxy(this); } }
另外通过上面的UML类图还能看到,ProxyCreatorSupport继承了AdvisedSupport,AdvisedSupport继承了ProxyConfig。
ProxyConfig
其中ProxyConfig是所有的AOP代理工厂的父类,它包含了创建一个AOP代理所需要的基础的通用的一些配置信息
// 这里省略了一些getter跟setter方法 public class ProxyConfig implements Serializable { // 是否开启cglib代理,默认不开启使用jdk动态代理 private boolean proxyTargetClass = false; // 是否启用优化,默认为false,按照官网对这个参数的解释 // 这个优化是针对cglib,如果设计为true的话,会做一些侵入性的优化 // 是否开启在jdk代理的情况下没有影响 // 官网中特地说明了,除非对cglib的优化非常了解,否则不要开启这个参数 private boolean optimize = false; // 生成的代理类是否需要实现Advised接口,这个接口可以向外提供操作通知的方法 // 如果为false会实现 // 为true的话,不会实现 boolean opaque = false; // 是否将当前的配置类暴露到一个线程上下文中,如果设置为true的话 // 可以通过AopContext.currentProxy()来获取到当前的代理对象 boolean exposeProxy = false; // 标志着是否冻结整个配置,如果冻结了,那么配置信息将不允许修改 private boolean frozen = false; }
AdvisedSupport
当我们为某个对象创建代理时,除了需要上面的ProxyConfig提供的一些基础配置外,起码还需要知道
- 需要执行的通知是哪些?
- 目标对象是谁?
- 创建出来的代理需要实现哪些接口?
而这些配置信息是由AdvisedSupport提供的,AdvisedSupport本身实现了Advised接口,Advised接口定义了管理通知的方法。
在了解了上面的API后我们来看看Spring提供了几种创建AOP代理的方式
- ProxyFactoryBean
- ProxyFactory
- Auto-proxy
ProxyFactoryBean的方式创建AOP代理
使用示例
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <bean class="com.dmz.spring.initalize.service.DmzService" name="dmzService"/> <bean id="aroundAdvice" class="com.dmz.spring.initalize.aop.advice.DmzAroundAdvice"/> <bean id="dmzProxy" class="org.springframework.aop.framework.ProxyFactoryBean"> <!----> <property name="proxyInterfaces" value="java.lang.Runnable"/> <property name="proxyTargetClass" value="true"/> <property name="target" ref="dmzService"/> <property name="interceptorNames"> <list> <value>aroundAdvice</value> </list> </property> </bean> </beans>
// 目标类 public class DmzService { @Override public String toString() { System.out.println("dmzService toString invoke"); return "dmzService"; } public void testAop(){ System.out.println("testAop invoke"); } } // 通知 public class DmzAroundAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { System.out.println("aroundAdvice invoked"); return invocation.proceed(); } } public class SourceMain { public static void main(String[] args) { ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("application-init.xml"); DmzService dmzProxy = ((DmzService) cc.getBean("dmzProxy")); dmzProxy.testAop(); } }
ProxyFactoryBean介绍
跟普通的FactoryBean一样,这个类的主要作用就是通过getObject方法能够获取一个Bean,不同的是这个类获取到的是代理后的Bean。
我们查看这个类的继承关系可以发现
这个类除了实现了FactoryBean接口以及一些Aware接口外,额外还继承了ProxyCreatorSupport类。它是一个factoryBean,所以我们重点就关注它的getObject方法即可。
public Object getObject() throws BeansException { // 初始化通知链 // 这里主要就是将在XML中配置的通知添加到 // AdvisedSupport管理的配置中去 initializeAdvisorChain(); if (isSingleton()) { // 如果是单例的,那么获取一个单例的代理对象 return getSingletonInstance(); } else { if (this.targetName == null) { logger.warn("Using non-singleton proxies with singleton targets is often undesirable. " + "Enable prototype proxies by setting the 'targetName' property."); } // 如果是原型的,获取一个原型的代理对象 return newPrototypeInstance(); } }
关于这段代码就不做过多分析了,它其实就两步(不管是哪种方式创建代理,都分为这两步)
1.完善创建代理需要的配置信息
2.创建代理
其中配置信息分为两部分,其一是AppConfig管理的通用的配置信息,其二是AdvisedSupport管理的通知信息。通用的配置信息我们可以直接在XML中配置,例如在上面的例子中我们就配置了proxyTargetClass属性,而通知信息即使我们在XML中配置了也还需要做一层转换,在前面我们也提到过了,所有的Advice都会被转换成Advisor添加到配置信息中。
ProxyFactory的方式创建AOP代理
使用示例(略,见开头)
ProxyFactory介绍
从上面我们可以看出,ProxyFactory也继承自ProxyCreatorSupport,从之前的例子我们也能感受到,使用它的API来创建一个代理对象也是要先去设置相关的配置信息,最后再调用创建代理的方法
我们之后要分析的自动代理内部就是通过创建了一个ProxyFactory来获取代理对象的。
我们可以对比下ProxyFactoryBean跟ProxyFactory在创建代理对象时的代码
ProxyFactory
public Object getProxy() { // 调用了ProxyCreatorSupport的createAopProxy()方法创建一个AopProxy对象 // 然后调用AopProxy对象的getProxy方法 return createAopProxy().getProxy(); }
ProxyFactoryBean
private synchronized Object getSingletonInstance() { if (this.singletonInstance == null) { this.targetSource = freshTargetSource(); if (this.autodetectInterfaces && getProxiedInterfaces().length == 0 && !isProxyTargetClass()) { Class<?> targetClass = getTargetClass(); if (targetClass == null) { throw new FactoryBeanNotInitializedException("Cannot determine target class for proxy"); } setInterfaces(ClassUtils.getAllInterfacesForClass(targetClass, this.proxyClassLoader)); } super.setFrozen(this.freezeProxy); // 重点就看这里 // 这里调用了ProxyCreatorSupport的createAopProxy()方法创建一个AopProxy对象 // 而getProxy方法就是调用创建的AopProxy的getProxy方法 this.singletonInstance = getProxy(createAopProxy()); } return this.singletonInstance; } protected Object getProxy(AopProxy aopProxy) { return aopProxy.getProxy(this.proxyClassLoader); }
综上,我们可以得出结论,不管是通过哪种方式创建AOP代理,核心代码就一句
createAopProxy().getProxy()