彻底了解AOP
本篇文章主要介绍如何快速使用Spring AOP,以及Spring AOP的一个基本原理。
首先我们在介绍如何使用XML
格式来使用Spring AOP
。
#
AOP概念在使用AOP之前我们简单来介绍一下,不得不提的几个重要概念,官网也有说明
- Aspect 切面(包括Advice,Pointcut),通常指一个类
- Join point 连接点,通常指类中需要被代理的方法
- Advice 通知(建议),通常指需要增强的代码。分为(戳我查看详细):前,后,环绕,返回,抛异常
- Pointcut 切入点,通常指通知与连接点的关联,也就是定义连接点的表达式。语法戳这里
#
AOP使用搞清楚概念之后,我们在来实际操作,我来做几个例子。我们先定义以下几个类
Person
public interface Person { void run(); int getMoney();}
Man
public class Man implements Person { @Override public void run() { System.out.println("by car"); }
@Override public int getMoney() { return 1-000-000-000; }}
Animal
public class Animal { public void run(){ System.out.println("四条腿跑"); } public void say(){ int i = 1 / 0; }}
#
XML方式使用AOP定义好后我们接下来我们想把 run
方法添加一个环绕通知,getMoney
添加一个返回通知,say
添加一个异常通知。那么接下来我们来创建一个切面Aspect
@Componentpublic class AspectBean { public void before(JoinPoint point){ System.out.println("this is before"); } public void after(JoinPoint point) throws Throwable{ System.out.println("this is after +" + point); } public void afterReturn(JoinPoint point,Object aa){ System.out.println("this is afterReturn +" + point); } public void round(JoinPoint point) throws Throwable{ System.out.println("this is round before +" + point); ((ProceedingJoinPoint)point).proceed(); System.out.println("this is round after +" + point); } public void throwException(JoinPoint point,Throwable exs){ System.out.println("error +" + point); }}
创建好切面后。我先来看看用XML
配置方式,因为这里只测试切面相关的XML
配置,其它Bean注入此处省略。
<aop:config > <aop:aspect id="aspectBean" ref="aspectBean" > <aop:after-returning returning="afterObj" method="afterReturn" pointcut="execution(* com.lykos.aop..*.getMoney(..))"></aop:after-returning> <aop:after-throwing throwing="ex" method="throwException" pointcut="execution(* com.lykos.aop..*.say(..))"></aop:after-throwing> <aop:pointcut id="roundPointCut" expression="execution(* com.lykos.aop..*.run(..))"/> <aop:around method="round" pointcut-ref="roundPointCut"></aop:around> </aop:aspect></aop:config>
上面介绍的是用<aop:aspect>
在注入我们的切面。其实我们还可以用<aop:config>
,如下:
<aop:config> <aop:advisor advice-ref="aroundAdvice" pointcut="execution(* com.lykos.aop..*.run(..))"></aop:advisor> <aop:advisor advice-ref="afterReturnAdvice" pointcut="execution(* com.lykos.aop..*.getMoney(..))"></aop:advisor></aop:config>
不过用<aop:config>
这个时,我们的通知需要实现Advice
接口,如上面的aroundAdvice
,afterReturnAdvice
定义分别如下:
public class AroundAdvice implements MethodInterceptor { @Override public Object invoke(MethodInvocation invocation) throws Throwable { return invocation.proceed(); }}
public class AfterReturnAdvice implements AfterReturningAdvice { @Override public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable { System.out.println("afterReturning"); }}
#
Annotation方式使用AOP以上就是用XML
格式使用了我们的Spring AOP,接下来我们在用注解方式实现一遍,注意Spring AOP的注解是支
持 AspectJ
的。所以我们用AspectJ
的注解。在使用注解之前我们需要先开启支持注解,开启方式也有两种,XML <aop:aspectj-autoproxy/>
和 Annotation @EnableAspectJAutoProxy
。
以下Annotation
事例功能等同于上面,这里需要注意的就是@Component
需要和@Aspect
一起使用。
@Component@Aspectpublic class AspectJBean { @AfterReturning(value = "execution(* com.lykos.aop..*.getMoney(..))",returning = "xx") public void afterReturn(JoinPoint point,Object aa){ System.out.println("this is afterReturn +" + point); }
/** * 这里用pointcut修饰方法,以便在@Around上直接使用 */ @Pointcut("execution(* com.lykos.aop..*.run(..))") public void aroundPointcut(){}
@Around("aroundPointcut()") public void round(JoinPoint point) throws Throwable{ System.out.println("this is round before +" + point); ((ProceedingJoinPoint)point).proceed(); System.out.println("this is round after +" + point); } @AfterThrowing(value = "execution(* com.lykos.aop..*.say(..))",throwing = "ex") public void throwException(JoinPoint point,Throwable ex){ System.out.println("error +" + point); }}
#
AOP原理Spring AOP是支持JDK动态代理和Cglib动态代理的,默认只要类有实现接口就是用的JDK动态代理,如果没有实现任何接口则使用Cglib动态代理。
#
Annotation启用流程#
@EnableAspectJAutoProxy 启动流程前面我们讲过,要使用AOP我们需要使用@EnableAspectJAutoProxy
,接下来我们看看@EnableAspectJAutoProxy
为我们做了什么。根据定义我们发现帮我们引入了AspectJAutoProxyRegistrar
@Import(AspectJAutoProxyRegistrar.class)public @interface EnableAspectJAutoProxy { boolean proxyTargetClass() default false; boolean exposeProxy() default false;
}
#
AspectJAutoProxyRegistrarAspectJAutoProxyRegistrar
是实现了ImportBeanDefinitionRegistrar
接口,其定义如下
public interface ImportBeanDefinitionRegistrar { void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry);}
可以发现他就是一个注册Bean的接口,并且还可以实EnvironmentAware
,BeanFactoryAware
,BeanClassLoaderAware
,ResourceLoaderAware
接口,也就是说我们在使用registerBeanDefinitions
方法做Bean注册时可以拿到以上列的相关对象。而他通常和@Import
一起使用。类似@Import(AspectJAutoProxyRegistrar.class)
。那么到这里我们知道AspectJAutoProxyRegistrar
是注册Bean的类,接下来我们看看他到底注册的哪个Bean
class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { // (1) 注册 AnnotationAwareAspectJAutoProxyCreator AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry); AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); // (2)获取EnableAspectJAutoProxy 属性值 if (enableAspectJAutoProxy != null) { if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } } }
}
#
(1) 注册 AnnotationAwareAspectJAutoProxyCreatorAnnotationAwareAspectJAutoProxyCreator
最终是实现了BeanPostProcessor
接口中的postProcessAfterInitialization
方法,通过Spring 系列篇之后置处理器我们知道postProcessAfterInitialization
是可以改变我们Bean的实际返回值的。那么重点来了,Spring AOP就是在这里帮我们做的代理。
@Override public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) { if (bean != null) { Object cacheKey = getCacheKey(bean.getClass(), beanName); if (this.earlyProxyReferences.remove(cacheKey) != bean) { return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
上面会走wrapIfNecessary
-> AbstractAutoProxyCreator.createProxy
AbstractAutoProxyCreator.createProxy
protected Object createProxy(Class<?> beanClass, @Nullable String beanName, @Nullable Object[] specificInterceptors, TargetSource targetSource) { if (this.beanFactory instanceof ConfigurableListableBeanFactory) { AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); } ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); //判断是否强制走Cglib代理 if (!proxyFactory.isProxyTargetClass()) { if (shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } else { //分析需要代理的对象是否实现了接口,如果实现了接口,则使用Jdk 动态代理 evaluateProxyInterfaces(beanClass, proxyFactory); } } //此处获取到的Advisor 来源在 【4. Advice】 提到 Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); proxyFactory.addAdvisors(advisors); proxyFactory.setTargetSource(targetSource); customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy); if (advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); } //会创建AopProxy对象,并使用他为我们创建实际代理对象 return proxyFactory.getProxy(getProxyClassLoader());}
上面我们需要关注两个信息。
- 第一个是
proxyTargetClass
这个属性,如果为ture代表强制使用Cglib做代理,如果不是就会调用evaluateProxyInterfaces
分析是否有实现接口,如果有则使用JDK动态代理。 - 第二个是proxyFactory.getProxy这个方法会先帮我们创建一个
AopProxy
对象,然后使用他创建实际的代理对象。其实AopProxy
是一个接口,真正实现他的,如图: 无论是JdkDynamicAopProxy
还是CglibAopProxy
都会在其代理的方法上使用List<Object> chain = this.advised.getInterceptorsAndDynamicInterceptionAdvice(method, targetClass)
并使用chain
创建ReflectiveMethodInvocation
对象并执行proceed
方法,依次链路执行我们切面方法。
#
(2)获取EnableAspectJAutoProxy 属性值当我们添加EnableAspectJAutoProxy时会有两个属性
- 第一个 proxyTargetClass 前面已经介绍过了,是用来强制设置用Cglib代理的
- 第二个 exposeProxy 意思是是否暴露代理对象,这个设置为true后,我们可以用
AopContext.currentProxy()
获取当前代理类。因为无论是使用的JdkDynamicAopProxy
还是CglibAopProxy
创建代理类,内部都会有,setCurrentProxy
的动作
if (this.advised.exposeProxy) { // Make invocation available if necessary. oldProxy = AopContext.setCurrentProxy(proxy); setProxyContext = true;}
#
<aop:aspectj-autoproxy> 启动流程以上讲的是使用@EnableAspectJAutoProxy
开启AOP代理,那么<aop:aspectj-autoproxy>
又是什么原理呢?其实原理是一样的,至于如何解析的<aop:aspectj-autoproxy>
这里就不说了
Spring 系列篇之彻底了解Annotation的使用 中有详细说明。最终也是使用AspectJAutoProxyBeanDefinitionParser
注册了AnnotationAwareAspectJAutoProxyCreator
对象。
#
Advice前面整体讲解了Spring AOP是如何创建我们的一个代理对象,还没有涉及到如何使用,解析我们最开始讲解的Aspect
,Advice
,Pointcut
,Join point
那么在这小节我们就来看看Spring 是如何处理@Aspect
或者 <aop:aspect>
的。
#
@Aspect之前也说过@Aspect
需要和@Component
一起使用,目的是将这个Bean作为一个普通Spring Bean注入到容器中
#
<aop:advisor><aop:advisor>
是通过ConfigBeanDefinitionParser.parse
方法解析并注册到容器中
public BeanDefinition parse(Element element, ParserContext parserContext) { CompositeComponentDefinition compositeDef = new CompositeComponentDefinition(element.getTagName(), parserContext.extractSource(element)); parserContext.pushContainingComponent(compositeDef);
configureAutoProxyCreator(parserContext, element); //此处解析<aop 元素,并构建Advisor BeanDefinition 注入到容器中 List<Element> childElts = DomUtils.getChildElements(element); for (Element elt: childElts) { String localName = parserContext.getDelegate().getLocalName(elt); if (POINTCUT.equals(localName)) { parsePointcut(elt, parserContext); } else if (ADVISOR.equals(localName)) { parseAdvisor(elt, parserContext); } else if (ASPECT.equals(localName)) { parseAspect(elt, parserContext); } }
parserContext.popAndRegisterContainingComponent(); return null;}
无论是通过@Aspect
还是<aop:advisor>
最终目的都是在AnnotationAwareAspectJAutoProxyCreator
创建代理对象时,能够通过findCandidateAdvisors
方法中识别并解析定义的切面。
protected List<Advisor> findCandidateAdvisors() { // Add all the Spring advisors found according to superclass rules. // 在Spring 容器中找Advisor类型的bean,如<aop:advisor> 注册的bean List<Advisor> advisors = super.findCandidateAdvisors(); // Build Advisors for all AspectJ aspects in the bean factory. //在Spring 容器中找@Aspect修饰的Bean 并将其解析成Advisor if (this.aspectJAdvisorsBuilder != null) { advisors.addAll(this.aspectJAdvisorsBuilder.buildAspectJAdvisors()); } return advisors;}
#
配置代理类class文件生成如果需要查询动态代理生成的Class文件可以添加以下配置
Cglib 设置生成class目录System.setProperty(DebuggingClassWriter.DEBUG_LOCATION_PROPERTY,"/yourpath");
jdk 动态代理 目录在当前工作空间/com/sun下System.setProperty("sun.misc.ProxyGenerator.saveGeneratedFiles","true");