概述

AOP(Aspect Oriented Programming) 面向切面编程,实质上是对我们的对象进行增强,并且提供良好的管理机制。
对于对象增强,可以有以下几种方法

  1. 装饰器模式,比如JDK中的I/O流

    1
    InputStream inputStream = new LineNumberInputStream(new BufferedInputStream(new FileInputStream("")));
  2. 静态代理模式

  3. 适配器模式
  4. 动态代理,包括 Proxy 和 CGLIB 等方法进行动态代理

SpringAOP 是一套 AOP 的解决方案,他比传统的对象增强更容易管理和扩展。
在对象增强上,它的规则是:基于动态代理来实现。默认地,如果使用接口方法的,用 JDK 提供的动态代理实现,如果没有接口,使用 CGLIB 实现。SpringAOP 基于 IOC 容器,动态代理之后,会把原对象替换成动态代理的对象。

概念

Advisor 是 AOP 的一个概念,他是 保存AOP配置 的一个单位

  • Advice:方法拦截逻辑,可控制该方法执行前和方法执行后或者出现异常时候的执行的逻辑
  • PointCut: 在哪些地方的方法应用拦截
  • Advisor__: __里面有且只有一个 Advice,可以对多个 PointCut 执行 Advice 方法。

image.png | left | 380x301

注解配置 Spring AOP

SpringAOP 和 AspectJ 没多大关系,而仅仅是使用了 AspectJ 中的概念,包括使用的注解也是直接来自于 AspectJ 的包(有点迷)。

依赖

1
2
3
4
5
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.11</version>
</dependency>

或者在 SpringBoot

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>

注解

注解开启,在Main 类上面标上这个注解

1
@EnableAspectJAutoProxy

在 配置文件Bean 上 打上 @Aspect

1
@Aspect

配置 PointCut

1
2
3
4
5
@Aspect
public class SystemArchitecture {
@Pointcut("execution(* transfer(..))")// the pointcut expression
private void anyOldTransfer() {}// the pointcut signature
}

一些配置规则

1
2
3
4
5
6
7
8
9
10
@Pointcut("execution(* transfer(..))")
// 方法签名
@Pointcut("within(com.javadoop.springaoplearning.service..*)")
// 包下所有类的所有方法
@Pointcut("execution( .*(..))
@annotation(com.javadoop.annotation.Subscribe)")
// 指定注解的所有方法
@Pointcut("bean(*Service)")
// 指定 bean 名的所有方法
// 通常 "." 代表一个包名,".." 代表包及其子包,方法参数任意匹配使用两个点 ".."

一些实践中的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Aspect
public class SystemArchitecture {

// web 层
@Pointcut("within(com.javadoop.web..*)")
public void inWebLayer() {}

// service 层
@Pointcut("within(com.javadoop.service..*)")
public void inServiceLayer() {}

// dao 层
@Pointcut("within(com.javadoop.dao..*)")
public void inDataAccessLayer() {}

// service 实现,注意这里指的是方法实现,其实通常也可以使用 bean(*ServiceImpl)
@Pointcut("execution(* com.javadoop..service.*.*(..))")
public void businessService() {}

// dao 实现
@Pointcut("execution(* com.javadoop.dao.*.*(..))")
public void dataAccessOperation() {}
}

配置 Advice

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
57
58
@Aspect
public class AdviceExample {

// 这里会用到我们前面说的 SystemArchitecture
// 下面方法就是写拦截 "dao层实现"
@Before("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ... 实现代码
}

@Before("com.javadoop.springaoplearning.aop_spring_2_aspectj.SystemArchitecture.businessService()")
public void logArgs(JoinPoint joinPoint) {
System.out.println("方法执行前,打印入参:" + Arrays.toString(joinPoint.getArgs()));
}

@AfterReturning("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
public void doAccessCheck() {
// ...
}

@AfterReturning(
pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()",
returning="retVal")
public void doAccessCheck(Object retVal) {
// 这样,进来这个方法的处理时候,retVal 就是相应方法的返回值,是不是非常方便
// ... 实现代码
}

// 异常返回
@AfterThrowing("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
public void doRecoveryActions() {
// ... 实现代码
}

@AfterThrowing(
pointcut="com.javadoop.aop.SystemArchitecture.dataAccessOperation()",
throwing="ex")
public void doRecoveryActions(DataAccessException ex) {
// ... 实现代码
}

// 注意理解它和 @AfterReturning 之间的区别,这里会拦截正常返回和异常的情况
@After("com.javadoop.aop.SystemArchitecture.dataAccessOperation()")
public void doReleaseLock() {
// 通常就像 finally 块一样使用,用来释放资源。
// 无论正常返回还是异常退出,都会被拦截到
}

// 感觉这个很有用吧,既能做 @Before 的事情,也可以做 @AfterReturning 的事情
@Around("com.javadoop.aop.SystemArchitecture.businessService()")
public Object doBasicProfiling(ProceedingJoinPoint pjp) throws Throwable {
// start stopwatch
Object retVal = pjp.proceed();
// stop stopwatch
return retVal;
}

}

advisor会由Spring给我们生成。

原理解析

在 Spring中,Bean 初始化结束后,会对每一个 bean 调用一次实现 BeanPostProcessor 接口的 Bean postProcessAfterInitialization() 方法。

当我们通过注解配置 AOP (XML差不多),Spring 会 给我们注册一个 AnnotationAwareAspectJAutoProxyCreator 的 Bean,这个 Bean 就实现了 BeanPostProcessor 接口 的 postProcessAfterInitialization() 方法。

这个方法传入 原始Bean,返回处理过的 Bean ,我们在这里就可以对 这个 Bean 偷梁换柱,换成 Proxy 对象。实现我们代理的目的。

在 ProxyCreater 中实现了这个方法:

1
2
3
4
5
6
7
8
9
10
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean != null) {
Object cacheKey = getCacheKey(bean.getClass(), beanName);
if (!this.earlyProxyReferences.contains(cacheKey)) {
return wrapIfNecessary(bean, beanName, cacheKey);
}
}
return bean;
}

这个方法干了这些事情:

  1. 把这个 bean 匹配的AOP增强配置打包成 advisor

image.png | left | 747x109

  1. 保存这个Bean的接口方法
  2. 根据接口方法和配置决定使用 JDK Proxy 代理生成器(JdkDynamicAopProxy) 还是 CGLIB 代理生成器(ObjenesisCglibAopProxy) 来生成代理对象,一般情况下:
    1. 如果被代理的目标类实现了一个或多个自定义的接口,那么就会使用 JDK 动态代理
    2. 如果没有实现任何接口,会使用 CGLIB 实现代理。
  3. 生成代理对象后,把这个代理对象替换掉原来的 Bean

简单讲一下创建 JDK Proxy 代理,CGLIB类似,但是操纵比较复杂。
Proxy 使用方法见 Java 动态代理机制 (一) JDK Proxy 详解
JdkDynamicAopProxy -> getProxy

1
2
3
4
5
6
7
8
9
10
11
public Object getProxy(ClassLoader classLoader) {
if (logger.isDebugEnabled()) {
logger.debug("Creating JDK dynamic proxy: target source is " + this.advised.getTargetSource());
}
 
Class<?>[] proxiedInterfaces = AopProxyUtils.completeProxiedInterfaces(this.advised, true);
  findDefinedEqualsAndHashCodeMethods(proxiedInterfaces);
// 获取这个Bean的所有接口,调用 JDK 的 Proxy 生成 Proxy
// 第三个是 InvocationHandler,代理类实现了 InvocationHandler 接口,有 invoke 方法
return Proxy.newProxyInstance(classLoader, proxiedInterfaces, this);
}

当我们调用 被 Proxy 代理的类的时候,都会调用到 JdkDynamicAopProxy 的 invoke 方法,invoke 方法 在我们原方法的周围做一些增强(从advisor中获取我们写好的增强函数)。剩下的就交给 JDK 来处理了。

参考资料

Spring AOP 源码解析
Spring AOP 使用介绍,从前世到今生
What is the difference between Advisor and Aspect in AOP?