AOP详解

前言

AOP面向切面编程,作为Spring中核心的功能之一,这里简单讲解了一下AOP的使用和原理。

讲解AOP之前需要提前掌握几个知识:IOC,Bean的生命周期,BeanPostProcessor,代理模式。尤其是后置处理器(BeanPostProcessor)是Spring在AOP的实现方式

AOP的基本使用

首先先复习一下AOP的基本概念和基本使用,这里只贴出注解方式(基于SpringBoot)。

AOP的增强类型

  1. @Before: 前置增强
  2. @After: 后置增强
  3. @AfterReturning: 返回增强
  4. @AfterThrowing: 异常增强
  5. @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;
}
}

// Aop配置
@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) {
// 注意在这个方法中已经把AnnotationAwareAspectJAutoProxyCreator的字节码传入方法了
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;
}
// 这个cls 是 AnnotationAwareAspectJAutoProxyCreator.class 注册的是它
RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
beanDefinition.setSource(source);
beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
// 重点:注册了一个Bean
registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
return beanDefinition;
}
}

这里可以看出@EnableAspectJAutoProxy实际就是注册了AnnotationAwareAspectJAutoProxyCreator这个bean而已。
而这个类其实就是用来为bean创建动态代理的,同时它实现了BeanPostProcessor,可以很好的在Bean的初始化前后修改Bean

AnnotationAwareAspectJAutoProxyCreator

注意这里的postProcessBeforeInstantiation并不是BeanPostProcessor的初始化步骤。而是InstantiationAwareBeanPostProcessorBeanPostProcessor接口的扩展。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface InstantiationAwareBeanPostProcessor extends BeanPostProcessor {
/*它是有IOC启动时。Spring特别处理调用的*/
@Nullable
default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
return null;
}

/*bean的生命周期 也就是bean创建后调用, 它是继承BeanPostProcessor而来的*/
@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 {
// Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
// 给后置处理器一个机会 用代理类替换目标类。(我个人觉得这里有点迷惑性) 实际这里永远返回null
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 {
// 创建bean
Object beanInstance = doCreateBean(beanName, mbdToUse, args);
if (logger.isTraceEnabled()) {
logger.trace("Finished creating instance of bean '" + beanName + "'");
}
return beanInstance;
}
catch (BeanCreationException | ImplicitlyAppearedSingletonException ex) {
// A previously detected exception with proper bean creation context already,
// or illegal singleton state to be communicated up to DefaultSingletonBeanRegistry.
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) {
// 在AnnotationAwareAspectJAutoProxyCreator中这里永远返回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()) {
// 这里实际就是手工调用了所有的 InstantiationAwareBeanPostProcessor 的初始化方法
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)) {
// 判断该Bean是否已经被增强处理过
if (this.advisedBeans.containsKey(cacheKey)) {
return null;
}
// 判断是否为基础类 比如:Advice.class,Pointcut.class等AOP基础类就不需要增强了
// 判断是否可以忽略,这里比较重点
// 感兴趣的可以去深入理解,主要是对bean进行匹配同时对该Bean的增强代码进行缓存
if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) {
this.advisedBeans.put(cacheKey, Boolean.FALSE);
return null;
}
}
// 判断自定义的 目标资源? 实际这里正常都是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;
}

// 获取当前bean的 增强。 前面的初始化已经处理好了。如果获取到了则表示该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(){ }
}

// C的是测试,重点在A和B
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`下的所有子孙类
类目:可以使用*匹配
参数:使用(..),两点表示匹配所有

同样我们使用 且(&&)、或(||)、非(!)来组合切入点表达式。