天天看点

springboot(4.2) springAOPspring AOP

spring AOP

spring家族包括很多产品和组件,比如spring-framework、springboot、springcloud等等,其中spring-framework是springboot的基础,而springboot又是springcloud的基础,本文就spring-framework5.x相关知识进行学习。本文主要以spring注解的方式进行学习和总结。

在spring-framework中,最为核心的组件为IOC和AOP,本文就AOP的基础知识进行学习和总结。文章下面所谓的spring 特指spring-framework。

AOP

注意:使用spring的AspectJ来实现AOP时,必须要使用注解@EnableAspectJAutoProxy开启对Aspectj的支持

AOP 概念,是什么?

定义:面向切面编程,就像面向对象编程一样,是一种编程思想。

为什么需要面向切面编程?

当我们在做业务时,首先关注的是功能性需求的实现,同时还需要关注些其他非功能性需求,如限制用户访问频率、对用户访问进行鉴权、数据库事务、记录日志等等,而这些非功能性需求都是独立于功能性需求之外的程序逻辑,如果将其植入到功能性需求对应的代码逻辑中,那么代码将更加复杂、耦合性更高,不利于后期的系统维护,

因此我们需要一种手段,让功能性需求和非功能性需求分开,同时还能实现功能需求和这些非功能需求,而AOP面向切面编程的思想就是解决这个问题的一种手段。

AOP 和springAOP的关系

AOP是一种编程思想,而springAOP只是AOP的一种实现方式,可以理解成AOP是一个接口,而springAOP是一个具体的实现。

在spring中,有两种方式都能实现AOP:

  1. 基于注解的方式:借助了AspectJ的语法来实现AOP,现在多数使用这种方式
  2. 基于配置的方式:配置复杂,基本已被弃用

注意:spring的AspectJ并不是以AspectJ为基础进行的扩展,而只是借助了AspectJ的语法而已。AspectJ也是一种AOP的实现方式,但和spring是竞争关系。

名词介绍

  1. Join point:连接点,springAOP代理的最小单位为Java对象的一个方法,连接点可以理解为要代理的某个方法
  2. Pointcut:切点,一批需要被代理的方法,即一个或多个连接点
  3. Advice:通知,包括2个方面,一个是代理的时机,第二个是执行的逻辑。是在连接点对应的方法之前、之后、完成、异常或前后执行相关的逻辑。
  4. Aspect:切面,可以理解成一个类,这个类需要将某些方法配置为连接点,然后将某些连接点组成具体的切点,再进行代理时机和代理逻辑的组织,这个类就叫切面。
  5. Target object:目标对象,需要被代理的类,即连接点对应的类
  6. AOP proxy:代理对象,spring通过对切面的解析,生成一个基于目标对象的新类,新类根据切面的配置,能对目标对象需要代理的方法的一种功能上的增强
  7. Weaving:编织,按照通知里的代理时机,将通知里的执行逻辑代码插入到代理对象中,然后将目标对象也插入到代理对象的合适位置,这个组装过程就叫编织。

这样说还是比较抽象,我们看看如下代码,结合注释,就很容易理解上述相关名称了:

@Component //需要让spring加载
@Aspect //声明这是一个切面类
public class MyAop {
    /**
     * Pointcut 定义一个切点
     * execution(* xx.UserController.*(..)) 类xx.UserController下的每个方法为一个连接点
     * 这些连接点一起组成一个切点
     */
    @Pointcut("execution(* com..study.spring.controller.UserController.*(..))")
    private void cutMethod() {}
    /**
     * 通知,包括2方面,
     * 执行时机:在执行目标方法的前后执行,目标方法为切点cutMethod()对应的切点
     * 执行逻辑:方法的代码即为执行逻辑
     */
    @Around("cutMethod()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("AOP前操作");
        Object object = joinPoint.proceed();
        System.out.println("AOP后操作");
        return object;
    }
}
           

spring AOP的通知类型

  1. @Before前置通知:执行目标方法前
  2. @AfterReturning返回通知:目标方法正常返回后
  3. @AfterThrowing异常通知:目标方法异常返回后
  4. @After结束通知:目标方法执行完成后,不管是正常结束还是异常结束都会被回调
  5. @Around环绕通知:目标方法执行前后

具体逻辑见如下示例和示例结果

@Component
@Aspect
public class MyAop {

    @Pointcut("execution(* com..study.spring.controller.UserController.*(..))")
    private void cutMethod() {}
    @Before("cutMethod()")
    public void befor(){
        System.out.println("Before前置通知");
    }
    @AfterReturning("cutMethod()")
    public void afterReturning(){
        System.out.println("AfterReturning返回通知");
    }
    @AfterThrowing("cutMethod()")
    public void afterThrowing(){
        System.out.println("AfterThrowing异常通知");
    }
    @After("cutMethod()")
    public void after(){
        System.out.println("After结束通知");
    }
    @Around("cutMethod()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("AOP环绕通知-前操作");
        Object object = joinPoint.proceed();
        System.out.println("AOP环绕通知-后操作");
        return object;
    }
}
           

上述代码,如果没有抛出异常,则执行结果如下:

  1. AOP环绕通知-前操作
  2. Before前置通知
  3. AOP环绕通知-后操作
  4. After结束通知
  5. AfterReturning返回通知

同样是上述代码,如果目标方法抛出异常,则执行结果如下:

  1. AOP环绕通知-前操作
  2. Before前置通知
  3. After结束通知 (因为目标方法异常了,因此“AOP环绕通知-后操作”将不会被执行)
  4. AfterThrowing异常通知

配置连接点的方式

  1. execution:执行正则匹配,结果为对应的连接点,最为常用,如execution(* xx.UserController.*(…)),表示UserController类中所有方法
  2. within:目标为某个类,如within(com.xyz.service.),表示com.xyz.service包下的所有类,within(com.xyz.service…)表示包及其子包下的所有类
  3. this:代理类是某个类型的子类时,如this(com.xyz.service.AccountService),表示生成的代理类是AccountService的子类时,需要注意cglib和jdk动态代理的区别
  4. target:目标类是某个类型的子类时,target(com.xyz.service.AccountService),表示AOP的目标类是AccountService的子类时
  5. args:目标参数匹配,如args(java.io.Serializable),表示只要方法参数为一个且类型为Serializable时
  6. @target:注解匹配,如 @target(org.springframework.transaction.annotation.Transactional)表示目标类有@Transactional注解
  7. @args:目标参数注解匹配, @args(com.xyz.security.Classified)表示目标参数为一个且参数被@Classified注解修饰
  8. @within:注解匹配,如@within(org.springframework.transaction.annotation.Transactional)表示目标类有@Transactional注解
  9. @annotation:注解匹配,比较常用,如@annotation(org.springframework.transaction.annotation.Transactional)表示目标类有@Transactional注解

一个完整的例子

需求:对UserServiceImpl类的方法进行增强

@Component
public class UserServiceImpl{
    public List<UserPO> findAll(){
        List<UserPO> list = new ArrayList<>();
        System.out.println("UserServiceImpl......");
        return list;
    }
}
@Component //需要让spring加载
@Aspect //声明这是一个切面类
public class MyAop {
    /**
     * Pointcut 定义一个切点
     * execution(* xx.UserServiceImpl.*(..)) 类UserServiceImpl下的每个方法为一个连接点
     * 这些连接点一起组成一个切点
     */
    @Pointcut("execution(* com.study.spring.service.UserServiceImpl.*(..))")
    private void cutMethod() {}
    /**
     * 通知,包括2方面,
     * 执行时机:在执行目标方法的前后执行,目标方法为切点cutMethod()对应的切点
     * 执行逻辑:方法的代码即为执行逻辑
     */
    @Around("cutMethod()")
    public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
        System.out.println("AOP前操作");
        Object object = joinPoint.proceed();
        System.out.println("AOP后操作");
        return object;
    }
}

@ComponentScan("com.study.spring")
@EnableAspectJAutoProxy //切记要加上这个注解,开启AspectJ的支持
public class Main {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Main.class);
        UserService bean = context.getBean(UserService.class);
        bean.findAll();
        System.out.println("1111111");
    }
}

           

spring AOP源码解析

我们先复习下springIOC文章中,Java bean变成springbean的一个过程,即spring启动过程,如下图所示:

springboot(4.2) springAOPspring AOP

通过上一篇springIOC文章的学习和总结,我们知道,spring会为目标对象生成一个代理对象,并将代理对象存储到单例池中,那么spring是如何根据目标对象生成代理对象的呢?

当目标对象经历实例化、初始化等操作后,spring会调用所有的BeanPostProcessor,进行初始化后处理,即postProcessAfterInitialization方法。

在这些后置处理器中,有一个叫AnnotationAwareAspectJAutoProxyCreator的处理器,

在其父类AbstractAutoProxyCreator中实现了BeanPostProcessor接口的前后处理方法,前置处理方法直接返回bean,后置方法如下图所示:

springboot(4.2) springAOPspring AOP

wrapIfNecessary方法内容如下:

springboot(4.2) springAOPspring AOP

这个方法先从缓存中获取,如果获取到对象了则直接返回,否则就会创建一个对象的代理,缓存代理并返回代理对象。createProxy源码如下:

springboot(4.2) springAOPspring AOP

该方法主要是创建一个代理工厂,并对代理工厂进行相关设置,重点在最后一句代理工厂获取代理。

springboot(4.2) springAOPspring AOP

进入createAopProxy方法:

springboot(4.2) springAOPspring AOP

getAopProxyFactory返回一个AopProxyFactory类型对象。AopProxyFactory是一个接口,并且只有一个实现类DefaultAopProxyFactory。接下来看看createAopProxy方法。

springboot(4.2) springAOPspring AOP

该方法根据给定的类配置来创建不同的代理 AopProxy,该方法返回一个AopProxy,AopProxy是一个接口,有3个实现类:CglibAopProxy、JdkDynamicAopProxy、ObjenesisCglibAopProxy(该类为CglibAopProxy的子类)。

JdkDynamicAopProxy的继承关系如下:

springboot(4.2) springAOPspring AOP

CglibAopProxyCglibAopProxy 继承结构没什么,主要是众多内部类,这些内部类是为了实现了 Cglib 的各种回调而实现的。主要实现了 MethodInterceptor 接口,

Callback 接口,Joinpoint 接口,Invocation 接口等待,总之是实现了Spring 的 cglib 模块的各种接口。

最终返回一个AopProxy对象,可能是jdk动态代理也可能是CGLIB的动态代理。回到getProxy方法中,根据AopProxy创建具体对象。

springboot(4.2) springAOPspring AOP

下图为JdkDynamicAopProxy方式创建代理:

springboot(4.2) springAOPspring AOP

下图为CglibAopProxy方式创建代理:

springboot(4.2) springAOPspring AOP

如上2图所示,这里我们能看到jdk或者CGLIB包中的相关类了,到此,代理对象创建完毕。重点类和方法调用时序图:

springboot(4.2) springAOPspring AOP

上述就是springAOP的全部知识,需要能帮助到大家。