AOP详解
前言
AOP面向切面编程,作为Spring中核心的功能之一,这里简单讲解了一下AOP的使用和原理。
讲解AOP之前需要提前掌握几个知识:IOC,Bean的生命周期,BeanPostProcessor,代理模式。尤其是后置处理器(BeanPostProcessor)是Spring在AOP的实现方式
AOP的基本使用
首先先复习一下AOP的基本概念和基本使用,这里只贴出注解方式(基于SpringBoot)。
AOP的增强类型
@Before
: 前置增强
@After
: 后置增强
@AfterReturning
: 返回增强
@AfterThrowing
: 异常增强
@Around
: 环绕增强
AOP的基本概念
- JoinPoint(连接点):被拦截的点,在Spring指类的任意方法。
- Pointcut(切入点):需要切入的点。可以理解为是一个匹配条件,用它来找到和判断某个方法是不是连接点。
- Advice(通知 / 增强):Advice是织入到目标类连接点上的一段代码,上面的5大类型就是指的是Advice。
- Weaving(织入): 织入就是增强的代码何时添加到连接点。目前AOP只要有三种:编译期织入、装载期织入、运行时织入。Spring选择运行时织入,主要通过动态代理来实现。
AOP的基本使用
在SpringBoot只需要加入AOP的POM资源即可使用Spring Aop。
我这里使用的SpringBoot的2.1.9.RELEASE
版本。AOP作为Spring Framework的核心功能,版本差异不大。
1 2 3 4
| <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-aop</artifactId> </dependency>
|
下面列出简单的AOP配置和使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
| @Component public class AopService implements AopServiceI{ @Override public String aopTest(String name){ System.out.println("我是AOP 实际方法:{}"+name); int i = 2/0; return name; } }
@Aspect @Component public class AopTestAspect {
@Pointcut("execution(public * com.duteliang.webbase.aop.*.*(..))") public void pointCut(){}
@Before("pointCut()") public void doBefore(JoinPoint joinPoint){ System.out.println("我是AOP 前置增强!"); }
@After("pointCut()") public void doAfter(JoinPoint joinPoint){ System.out.println("我是AOP 后置增强!"); }
@AfterReturning(value = "pointCut()",returning = "result") public void doAfterReturning(JoinPoint joinPoint,String result){ System.out.println("我是AOP 返回增强!返回值:"+result); }
@AfterThrowing(value = "pointCut()",throwing = "e") public void doAfterThrowing(JoinPoint joinPoint,Exception e) { System.out.println("我是AOP 异常增强!异常:"+e.getMessage()); }
@Around(value = "pointCut()") public Object Around(ProceedingJoinPoint pjp) throws Throwable { System.out.println("我是环绕AOP 前置通知!"); Object proceed; try { proceed = pjp.proceed(); }catch (Throwable t){ System.out.println("我是环绕AOP 异常通知!异常:"+t.getMessage()); throw t; } finally { System.out.println("我是环绕AOP 后置通知!"); } System.out.println("我是环绕AOP 返回通知!返回值:"+proceed); return proceed; } }
|
这里不帖执行代码流程了。毕竟估计都很熟练了。 顶多有些对PointCut
的配置写法不怎么熟悉,我在后面章节主要说明一下其语法即可。
AOP的原理
前面已经讲过在Spring中的AOP主要是通过动态代理来实现。什么是动态代理?可以看下我的其他文章有动态代理这一部分。简单讲解了动态代理的问题。
这里主要以SpringBoot为基础来简单说明(PS:源码很复杂,我不大想把复杂的源码讲解的很清楚,主要写了一下我比较关注的点)
@EnableAspectJAutoProxy
Spring要引入AOP其实还需要添加该注解@EnableAspectJAutoProxy
。SpringBoot已经默认帮我们加了(在AopAutoConfiguration
中可找到相关代码),所以导致我们无感知。
看下该注解的代码
1 2 3 4 5 6 7 8 9 10 11
| @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Import(AspectJAutoProxyRegistrar.class) public @interface EnableAspectJAutoProxy {
boolean proxyTargetClass() default false;
boolean exposeProxy() default false;
}
|
这种注解很明显,重点在@Import中。继续跟上
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| class AspectJAutoProxyRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions( AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { AopConfigUtils.registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry);
AnnotationAttributes enableAspectJAutoProxy = AnnotationConfigUtils.attributesFor(importingClassMetadata, EnableAspectJAutoProxy.class); if (enableAspectJAutoProxy != null) { if (enableAspectJAutoProxy.getBoolean("proxyTargetClass")) { AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry); } if (enableAspectJAutoProxy.getBoolean("exposeProxy")) { AopConfigUtils.forceAutoProxyCreatorToExposeProxy(registry); } } } } public abstract class AopConfigUtils { @Nullable public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary( BeanDefinitionRegistry registry, @Nullable Object source) { return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source); } @Nullable private static BeanDefinition registerOrEscalateApcAsRequired( Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {
Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) { BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME); if (!cls.getName().equals(apcDefinition.getBeanClassName())) { int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName()); int requiredPriority = findPriorityForClass(cls); if (currentPriority < requiredPriority) { apcDefinition.setBeanClassName(cls.getName()); } } return null; } RootBeanDefinition beanDefinition = new RootBeanDefinition(cls); beanDefinition.setSource(source); beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE); beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition); return beanDefinition; } }
|
这里可以看出@EnableAspectJAutoProxy
实际就是注册了AnnotationAwareAspectJAutoProxyCreator
这个bean而已。
而这个类其实就是用来为bean创建动态代理的,同时它实现了BeanPostProcessor
,可以很好的在Bean的初始化前后修改Bean
AnnotationAwareAspectJAutoProxyCreator
注意这里的postProcessBeforeInstantiation
并不是BeanPostProcessor
的初始化步骤。而是InstantiationAwareBeanPostProcessor
对BeanPostProcessor
接口的扩展。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor { @Nullable default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { return null; }
@Override @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } }
|
想要找到具体源码。这还要进入IOC启动的经典方法refresh
中
大致类图:refresh –> finishBeanFactoryInitialization –> preInstantiateSingletons –> getBean –> doGetBean –> createBean
具体类在:AbstractAutowireCapableBeanFactory
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException { ... 忽略无关代码 try { Object bean = resolveBeforeInstantiation(beanName, mbdToUse); if (bean != null) { return bean; } } catch (Throwable ex) { throw new BeanCreationException(mbdToUse.getResourceDescription(), beanName, "BeanPostProcessor before instantiation of bean failed", ex); } try { Object beanInstance = doCreateBean(beanName, mbdToUse, args); if (logger.isTraceEnabled()) { logger.trace("Finished creating instance of bean '" + beanName + "'"); } return beanInstance; } catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) { throw ex; } catch (Throwable ex) { throw new BeanCreationException( mbdToUse.getResourceDescription(), beanName, "Unexpected exception during bean creation", ex); } ... 忽略无关代码 }
|
内部使用的后置处理器初始化(postProcessBeforeInstantiation)
接着进入看下它为什么要给后置处理器一个机会?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| @Nullable protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) { Object bean = null; if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) { if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { Class<?> targetType = determineTargetType(beanName, mbd); if (targetType != null) { bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName); if (bean != null) { bean = applyBeanPostProcessorsAfterInitialization(bean, beanName); } } } mbd.beforeInstantiationResolved = (bean != null); } return bean; } @Nullable protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName); if (result != null) { return result; } } } return null; }
|
来看下AnnotationAwareAspectJAutoProxyCreator
的初始化方法
这里不展开了,展开就太大了。我写下具体做了什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| @Override public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) { Object cacheKey = getCacheKey(beanClass, beanName);
if (!StringUtils.hasLength(beanName) || !this.targetSourcedBeans.contains(beanName)) { if (this.advisedBeans.containsKey(cacheKey)) { return null; } if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return null; } } TargetSource targetSource = getCustomTargetSource(beanClass, beanName); if (targetSource != null) { if (StringUtils.hasLength(beanName)) { this.targetSourcedBeans.add(beanName); } Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; }
return null; }
|
所以后置处理器的初始化方法,主要就是为后面的after方法做缓存准备,比如准备增强代码,缓存需要被代理的bean的Advice
标准的后置处理器的After方法
接下来就是正常的后置处理器的使用了。 会在Bean的生命周期的过程中调用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| @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; }
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) { return bean; } if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }
Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; }
this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }
|
这部分相对好理解多了。创建代理类的核心部分,我贴下代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| public AopProxy createAopProxy(AdvisedSupport config) throws AopConfigException { 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); } }
|
可以看出通过其方法的接口的情况来选择走什么代理。
总结
整体下来AOP的核心在于AnnotationAwareAspectJAutoProxyCreator
其实它是一个特殊的BeanPostProcessor
后置处理器。
通过InstantiationAwareBeanPostProcessor
来初始化bean的增强代码。同时保存缓存
通过BeanPostProcessor
的后置处理器,根据该bean是否有增强代码。来选择是否包装成代理类
这里没有讲解他的增强(Advice)是怎么缓存和实现的。有兴趣可以自己阅读源码!
扩充:PointCut的写法
在实际使用中有时候AOP可能用的比较少,对于切点表达式可能很生疏。这里简单讲解一下其语法。
由于没有找到画这个比较好的工具,凑合着看吧
首先看下指示器的部分,也就是execution
大致可分为以下的表格
AspectJ指示器 |
描述 |
execution() |
用于匹配连接点的执行方法(常用) |
@annotation() |
限定匹配带有注解的连接点(常用) |
args() |
限制连接点匹配参数为指定类型的执行方法 |
@args() |
限制连接点匹配参数由指定注解标注的执行方法 |
this() |
限制连接点匹配AOP代理的bean引用为指定类型的类 |
target |
限制连接点匹配目标对象为指定类型的类 |
within() |
限制连接点匹配指定的包类型 |
@target() |
限制连接点匹配特定的执行对象,这些对象对于的类要具有指定类型的注解 |
@within() |
限制连接点匹配指定注解所标记的类型 |
bean() |
匹配bean的名称,支持*,这是Spring扩展独有的 |
每个指示器几句话概况说明一哈
- execution() 这个就不说明了,常用的,注意public也可以不写。AOP本身就只能代理Public类型
- @annotation(注解A) 匹配方法上有注解A上的情况(注意是方法,类上不行)
- args(String) 匹配方法上参数为String的且只有一个。多个可以这么写:@Pointcut(“args(String,..)”) 但是第一个必须是String
- @args(注解A) 匹配方法上面第一个参数有注解A,多个和args一样(注意是第一个参数上面有注解A,而不是第一个参数为注解A)
- this(类A) 匹配类A下的所有方法
- target(类A) 匹配类A下的所有方法
- within() 主要是对包的扫描,比如within(包名.*) 可以对该包下所有的类进行拦截
注意 within 可以写*,但是target不行,target必须写一个完整的类名
- @target(注解A) 匹配类上面有注解A的所有方法,注意和@annotation做比较
- @within(注解A) 匹配被调用方法上是否有注解A
- bean() 匹配bean的名称 支持*符号
@target和@within的区别
这里对于@target和@within特别说明一下,两者看起来好像一样都是作用于类上的注解。实际上关注的点不一样。
@target关注的是类上是否有注解,@within关注的是调用方法上是否有注解
下面写个简单的例子说明
1 2 3 4 5 6 7 8 9 10 11
| @注解 public class A extends B { } class B { public void aop(){ } }
class C{ private A a; public void test(){ a.aop() } }
|
上面这个例子如果使用@within
就不会被拦截。为什么? 因为@within
关注的是调用方法上的类上是否有注解,上面A在调用aop
方法时,实际是调用的父类的aop
方法,而B类没有注解,所以拦截不到。如果A重写了aop
方法就可以拦截到了。因为那时候实际调用的就是A上的方法。所以@within
关注的是方法本身上面类的注解
上面换成@target
就可以拦截到。@target
只关注调用者本身是不是有注解,A上面有注解。我是用A来调用aop
方法的,我不管这个aop
是谁的,只要A上面有注解就行。
大概思路就是如此,大家可以自己测试一下!
this的特别之处
这里也把this
说明一下,因为这个也有点不好理解。看起来好像和target
一样
this
的要求比较严格,它实际匹配的是代理类型。
1 2 3 4 5 6 7
| class A implements B { public void aop(){} } interface B{ void aop() }
this(A)
|
上面的代码。正常情况走的JDK动态代理。我们知道JDK动态代理只能代理接口。所有实际的代理类是B,而我们现在扫描的A所有匹配不到
如果我们强制走cglib代理,那么代理类就是A本身。这样就能匹配到了。
同样大家可以自己测试!
其他参数写法
把指示器理解完,后面的一些写法其实都可以猜到了!
返回值:可以使用,也可以指定类型
包名:可以使用 .. 来扫描所有子包,比如:`com.duteliang.webbase.aop..就可以扫描
com.duteliang.webbase.aop`下的所有子孙类
类目:可以使用*匹配
参数:使用(..),两点表示匹配所有
同样我们使用 且(&&)、或(||)、非(!)来组合切入点表达式。