tags: [Spring]
categories: [技术笔记]


概述

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

  1. 装饰器模式,比如JDK中的I/O流
  1. InputStream inputStream = new LineNumberInputStream(new BufferedInputStream(new FileInputStream("")));
  1. 静态代理模式

  2. 适配器模式

  3. 动态代理,包括 Proxy 和 CGLIB 等方法进行动态代理

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

概念

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

  • Advice:方法拦截逻辑,可控制该方法执行前和方法执行后或者出现异常时候的执行的逻辑

  • PointCut: 在哪些地方的方法应用拦截

  • Advisor里面有且只有一个 Advice,可以对多个 PointCut 执行 Advice 方法。

Spring 核心:Spring AOP 用法和原理简析 - 图1

注解配置 Spring AOP

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

依赖

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

或者在 SpringBoot

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

注解

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

@EnableAspectJAutoProxy

在 配置文件Bean 上 打上 @Aspect

@Aspect

配置 PointCut

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

一些配置规则

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

一些实践中的配置

@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

@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 中实现了这个方法:

@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

Spring 核心:Spring AOP 用法和原理简析 - 图2

  1. 保存这个Bean的接口方法

  2. 根据接口方法和配置决定使用 JDK Proxy 代理生成器(JdkDynamicAopProxy) 还是 CGLIB 代理生成器(ObjenesisCglibAopProxy) 来生成代理对象,一般情况下:

    1. 如果被代理的目标类实现了一个或多个自定义的接口,那么就会使用 JDK 动态代理

    2. 如果没有实现任何接口,会使用 CGLIB 实现代理。

  3. 生成代理对象后,把这个代理对象替换掉原来的 Bean

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

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?