Spring框架专题(六)-Spring框架之AOP

1.AOP面向切面编程

1.1.AOP介绍

OOP(Object Oriented Programming ) 面向对象编程,万物皆对象!

AOP(Aspect Oriented Programming),即面向切面编程,可以说AOP是OOP(Object Oriented Programming,面向对象编程)的补充和完善。OOP引入封装、继承、多态等概念来建立一种对象层次结构,用于模拟公共行为的一个集合。不过OOP允许开发者定义纵向的关系,但并不适合定义横向的关系,例如日志功能。日志代码往往横向地散布在所有对象层次中,而与它对应的对象的核心功能毫无关系对于其他类型的代码,如安全性、异常处理和透明的持续性也都是如此,这种散布在各处的无关的代码被称为横切(cross cutting),在OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP技术恰恰相反,它利用一种称为"横切"的技术,剖解开封装的对象内部,并将那些影响了多个类的公共行为封装到一个可重用模块,并将其命名为\,即切面。所谓"切面",简单说就是那些与业务无关,却为业务模块所共同调用的逻辑或责任封装起来,便于减少系统的重复代码降低模块之间的耦合度,并有利于未来的可操作性可维护性

​ 使用"横切"技术,AOP把软件系统分为两个部分:核心关注点横切关注点。业务处理的主要流程是核心关注点,与之关系不大的部分是横切关注点。横切关注点的一个特点是,他们经常发生在核心关注点的多处,而各处基本相似,比如权限认证日志打印事务处理。AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来。

在这里插入图片描述

1.2.AOP图解

在这里插入图片描述

在这里插入图片描述

​ AOP编程底层思想是代理设计模式

Spring框架底层使用的代理设计模式来完成AOP

通过代理对象来调用原有对象(目标对象)的方法

​ 代理对象方法前后都可以插入代码,这些代码就是增强处理

​ 通过动态代理,可以在指定位置执行对应流程。这样可以将一些横向的功能抽离出来形成一个独立的模块,然后在指定位置插入这些功能。这样的思想就是面向切面编程,即AOP

*在Spring中动态代理的使用:

  • 1.如果目标对象实现了接口,默认情况下会采用JDK代理来实现AOP
  • 2.如果目标对象实现了接口,也可以强制使用CGLIB代理来实现AOP,需要指定:
    • 可以强制使用CGlib:
      • 在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=“true”/>
      • springboot项目配置: spring.aop.proxy-target-class=false
  • 3.如果目标对象没有实现接口,必须采用CGLIB库,Spring会自动在JDK和CGLIB之间切换

2.AOP7大术语

在这里插入图片描述

Target Object:目标类需要被代理的类。例如:UserServiceImpl;

Join Point:连接点,所谓的连接点是指那些可能被拦截到的方法。例如:getargs();

Pointcut:切入点已经被增强的连接点。例如:addUser();

Advice:增强/通知,增强代码。例如:afer、before等;

Weaving:织入,是指把增强advice应用到目标对象target来创建新的代理对象proxy的过程;

Proxy:代理类通知+切入点(由动态代理自动生成的类)

Aspect:切面,是切入点pointcut和通知advice的结合。

具体可以根据下面这张图来理解:
在这里插入图片描述

补充:第8大术语(已过时)

Introduction:引介,引介是一种特殊的增强,它为类添加一些属性和方法。这样,即使一个业务类原本没有实现某个接口,通过AOP的引介功能,我们可以动态地为该业务类添加接口的实现逻辑,让业务类成为这个接口的实现类。

3.Spring的AOP

3.1.Spring AOP基于xml

3.1.1.在pom.xml中导入AOP依赖

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-aspects</artifactId>
		<version>5.3.2</version>
    </dependency>

项目中所需以来如下:

	<dependencies>
        <!-- Junit测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <!--Spring依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.2</version>
        </dependency>

        <!--SpringAOP依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.2</version>
        </dependency>

        <!--Spring测试依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.2</version>
        </dependency>

        <!--Lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

3.1.2.创建实体类User

package com.dream.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private Integer id;
    private String name;
}

3.1.3.准备操作对象

  • 先创建UserService接口:
package com.dream.service;

import com.dream.pojo.User;

public interface UserService {

    int addUser(User u);

    int deleteUser(int id);
}

再创建UserServiceImpl实现类

package com.dream.service.impl;

import com.dream.pojo.User;
import com.dream.service.UserService;

/**
 * 目标类
 */
public class UserServiceImpl implements UserService {

    @Override
    public int addUser(User u) {
        System.out.println("新增User:" + u);
        return 1;
    }

    @Override
    public int deleteUser(int id) {
        System.out.println("删除User:"+id);
        return 1;
    }
}

3.1.4.创建增强类

package com.dream.tx;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;

import java.util.Arrays;

/**
 * 增强类
 */
public class Transaction {

    /**
     * 01-前置通知:在目标方法之前被执行
     */
    public void openTx(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();//被拦截的方法的方法名
        Object[] args = joinPoint.getArgs();//被拦截的方法的参数
        //封装的目标方法的信息
        System.out.println("开启事务....方法名:"+name+",参数是:"+ Arrays.toString(args));
    }

    /**
     * 02-返回通知:目标方法返回之后进行通知(如果目标方法有异常不会执行)
     */
    public void CommitTix(JoinPoint joinPoint,Object value){
        String name = joinPoint.getSignature().getName();//被拦截的方法的方法名
        Object[] args = joinPoint.getArgs();//被拦截的方法的参数
        System.out.println("提交事务...方法名:"+name+",参数是:"+Arrays.toString(args)+",返回值"+value);
    }

    /**
     * 03-异常通知:在目标方法出现异常就会异常通知
     */
    public void Rollback(JoinPoint joinPoint,Exception e){
        String name = joinPoint.getSignature().getName();//被拦截的方法的方法名
        Object[] args = joinPoint.getArgs();//被拦截的方法的参数
        System.out.println("回顾事务....方法名:"+name+",参数是:"+ Arrays.toString(args));
        System.out.println("异常类型-->"+e.getMessage());
    }

    /**
     * 04-后置通知:目标方法返回之后进行通知(无论目标方法是否有异常都执行)
     */
    public void finallyMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();//被拦截的方法的方法名
        Object[] args = joinPoint.getArgs();//被拦截的方法的参数
        System.out.println("关闭数据库资源...."+name+",参数是:"+ Arrays.toString(args));
    }

    /**
     * 05-环绕通知:前面四个通知的组合
     * ProceedingJoinPoint只支持环绕通知
     */
    /*public Object aroundAdvice(ProceedingJoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        Object result = null;
        try {
            System.out.println("开启事务....方法名:"+name+",参数是:"+ Arrays.toString(args));
            result = joinPoint.proceed();//调用目标方法
            System.out.println("提交事务....方法名:"+name+",参数是:"+ Arrays.toString(args)+",返回值:"+result);
        } catch (Throwable throwable) {
            System.out.println("回顾事务....方法名:"+name+",参数是:"+ Arrays.toString(args)+",异常信息:"+throwable.getMessage());
        }finally {
            System.out.println("关闭数据库资源...."+name+",参数是:"+ Arrays.toString(args));
        }
        return result;
    }*/
}

3.1.5.织入目标对象

  • 创建applicationContext.xml,在该xml文件中配置。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 			xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">

    <!--1.目标类对象-->
    <bean id="target" class="com.dream.service.impl.UserServiceImpl"/>

    <!--2.增强类对象-->
    <bean id="tx" class="com.dream.tx.Transaction"/>

    <!--3.配置如何代理(配置AOP)-->
    <aop:config>
        <!--3.1.配置切入点(哪些类中哪些方法需要被代理)
            expression="切入点表达式"
        -->
        <aop:pointcut id="pointcut" expression="execution(* com.dream.service.impl.*.*(..))"/>
        <!--3.2.配置通知-->
        <aop:aspect ref="tx">
            <!--前置通知-->
            <aop:before method="openTx" pointcut-ref="pointcut"/>
            <!--后置通知(返回通知)-->
            <aop:after-returning method="CommitTix" pointcut-ref="pointcut" returning="value"/>
            <!--最终通知-->
            <aop:after method="finallyMethod" pointcut-ref="pointcut"/>
            <!--异常通知-->
            <aop:after-throwing method="Rollback" pointcut-ref="pointcut" throwing="e"/>
            <!--环绕通知-->
            <!--<aop:around method="aroundAdvice" pointcut-ref="pointcut"/>-->
        </aop:aspect>
    </aop:config>

    <!--开启CGLIB动态代理-->
    <aop:aspectj-autoproxy proxy-target-class="false"/>
</beans>

注意:xmlns:aop="http://www.springframework.org/schema/aop"在头部导入了aop命名空间。

  • pointcut:配置切入点(哪些类中哪些方法需要被代理);

  • aspect:配置通知

    • before:前置通知
    • after-returning:返回通知(后置通知),如果有异常就不会执行
    • after-throwing:异常通知
    • after:最终通知(后置通知)
    • around:环绕通知,最强大的通知,以上四个通知的结合
  • <aop:aspectj-autoproxy proxy-target-class=“false”/>:开启CGLIB动态代理

3.1.6.测试类

package com.dream.test;

import com.dream.pojo.User;
import com.dream.service.UserService;
import com.dream.service.impl.UserServiceImpl;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

public class TestUser {

    @Test
    public void test01(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService us = context.getBean(UserService.class);

        int count = us.addUser(new User(111, "测试"));
        System.out.println(count);

        System.out.println("---------------------");

        int count1 = us.deleteUser(1);
        System.out.println(count1);
    }
}

3.2.Spring AOP基于注解

3.2.1.在pom.xml中导入AOP依赖

	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-aspects</artifactId>
		<version>5.3.2</version>
    </dependency>

项目中所需以来如下:

	<dependencies>
        <!-- Junit测试 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <!--Spring依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.2</version>
        </dependency>

        <!--SpringAOP依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>5.3.2</version>
        </dependency>

        <!--Spring测试依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.3.2</version>
        </dependency>

        <!--Lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.16.18</version>
            <scope>provided</scope>
        </dependency>

    </dependencies>

3.2.2.创建实体类User

package com.dream.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {

    private Integer id;
    private String name;
}

3.2.3.准备操作对象

  • 先创建UserService接口:
package com.dream.service;

import com.dream.pojo.User;

public interface UserService {

    int addUser(User u);

    int deleteUser(int id);
}

再创建UserServiceImpl实现类

package com.dream.service.impl;

import com.dream.pojo.User;
import com.dream.service.UserService;

/**
 * 目标类
 */
@Service
public class UserServiceImpl implements UserService {

    @Override
    public int addUser(User u) {
        System.out.println("新增User:" + u);
        return 1;
    }

    @Override
    public int deleteUser(int id) {
        System.out.println("删除User:"+id);
        return 1;
    }
}

3.2.4.创建配置类

package com.dream.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@Configuration
@ComponentScan(value = "com.dream.*")
@EnableAspectJAutoProxy//开启注解支持
public class SpringConfig {
    
}

@Configuration:指明该类是配置类

@ComponentScan(value = “com.dream.*”):配置路径

@EnableAspectJAutoProxy:开启注解支持

3.2.5.创建增强类

package com.dream.tx;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;
import sun.awt.SunHints;

import java.util.Arrays;

/**
 * 增强类
 * @Aspect 增强类
 */
@Component
@Aspect
public class Transaction {

    /**
     * 01-前置通知:在目标方法之前被执行
     */
    @Before(value = "execution(* com.dream.service.impl.*.*(..))")
    public void openTx(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();//被拦截的方法的方法名
        Object[] args = joinPoint.getArgs();//被拦截的方法的参数
        //封装的目标方法的信息
        System.out.println("开启事务....方法名:"+name+",参数是:"+ Arrays.toString(args));
    }

    /**
     * 02-返回通知:目标方法返回之后进行通知(如果目标方法有异常不会执行)
     */
    @AfterReturning(value = "execution(* com.dream.service.impl.*.*(..))",returning = "value")
    public void CommitTix(JoinPoint joinPoint,Object value){
        String name = joinPoint.getSignature().getName();//被拦截的方法的方法名
        Object[] args = joinPoint.getArgs();//被拦截的方法的参数
        System.out.println("提交事务...方法名:"+name+",参数是:"+Arrays.toString(args)+",返回值"+value);
    }

    /**
     * 03-异常通知:在目标方法出现异常就会异常通知
     */
    @AfterThrowing(value = "execution(* com.dream.service.impl.*.*(..))",throwing = "e")
    public void Rollback(JoinPoint joinPoint,Exception e){
        String name = joinPoint.getSignature().getName();//被拦截的方法的方法名
        Object[] args = joinPoint.getArgs();//被拦截的方法的参数
        System.out.println("回顾事务....方法名:"+name+",参数是:"+ Arrays.toString(args));
        System.out.println("异常类型-->"+e.getMessage());
    }

    /**
     * 04-后置通知:目标方法返回之后进行通知(无论目标方法是否有异常都执行)
     */
    @After(value = "execution(* com.dream.service.impl.*.*(..))")
    public void finallyMethod(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();//被拦截的方法的方法名
        Object[] args = joinPoint.getArgs();//被拦截的方法的参数
        System.out.println("关闭数据库资源...."+name+",参数是:"+ Arrays.toString(args));
    }

    /**
     * 05-环绕通知
     * ProceedingJoinPoint只支持环绕通知
     */
    @Around(value = "execution(* com.dream.service.impl.*.*(..))")
    public Object aroundAdvice(ProceedingJoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();
        Object[] args = joinPoint.getArgs();
        Object result = null;
        try {
            System.out.println("开启事务....方法名:"+name+",参数是:"+ Arrays.toString(args));
            result = joinPoint.proceed();//调用目标方法
            System.out.println("提交事务....方法名:"+name+",参数是:"+ Arrays.toString(args)+",返回值:"+result);
        } catch (Throwable throwable) {
            System.out.println("回顾事务....方法名:"+name+",参数是:"+ Arrays.toString(args)+",异常信息:"+throwable.getMessage());
        }finally {
            System.out.println("关闭数据库资源...."+name+",参数是:"+ Arrays.toString(args));
        }
        return result;
    }
}

@Aspect:声明该类是增强类。

@Before:前置通知的注解,在目标方法之前被执行。

@AfterReturning:返回通知的注解,目标方法返回之后进行通知(如果目标方法有异常不会执行)。

@AfterThrowing:异常通知,在目标方法出现异常就会异常通知。

@After:后置通知,目标方法返回之后进行通知(无论目标方法是否有异常都执行)。

@Around:环绕通知,以上四种通知的结合

注意:ProceedingJoinPoint只支持环绕通知

3.2.6.测试类

package com.dream.test;

import com.dream.config.SpringConfig;
import com.dream.pojo.User;
import com.dream.service.UserService;
import com.dream.service.impl.UserServiceImpl;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import javax.annotation.Resource;

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class TestUser {

    @Resource
    UserService us;

    @Test
    public void test01(){

        int count = us.addUser(new User(111, "测试"));
        System.out.println(count);

        System.out.println("---------------------");

        int count1 = us.deleteUser(1);
        System.out.println(count1);
    }
}

3.3.Spring的五种通知

3.3.1.五种通知类型

通知说明
Before Advice前置通知,目标方法运行前执行
After Returning Advice后置通知(返回后通知),目标方法返回数据后执行
After Throwing Advice异常通知,目标方法抛出异常后执行
After Advice最终通知 (后置通知),目标方法运行后执行
Around Advice环绕通知,最强大通知,自定义通知执行的时机,可决定目标方法是否运行

3.3.2.五种通知的注解

名称注解说明
前置通知@Before用于配置前置通知。指定增强的方法在切入点之前执行
后置通知@AfterReturning用于配置返回后通知。指定增强的方法在切入点之后执行,有异常就不执行,也叫返回后通知
异常通知@AfterThrowing用于配置异常通知。指定增强的方法出行异常时执行
最终通知@After用于配置后置通知。指定增强的方法在切入点之后执行,无论有没有异常都执行,也叫后置通知
环绕通知@Around用于配置环绕通知。指定增强的方法在切入点之前或之后执行都可

注解中参数的含义如下图:

在这里插入图片描述

3.4.五种通知的执行顺序问题

3.4.1.Spring基于xml的通知执行顺序

3.4.1.1.XML文件配置说明

在这里插入图片描述

图片来源:《Java EE企业级应用开发教程》

3.4.1.2.各种通知说明

配置前置通知:在切入点方法执行之前执行
配置后置通知(返回通知):在切入点方法正常执行之后执行。它和异常通知永远只能执行一个
配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个
配置最终通知:无论切入点方法是否正常执行,它都会在其后面执行
配置环绕通知:可以在代码中手动控制增强方法何时执行

补:后置通知和最终通知的区别

​ 后置通知时在方法成功执行后会执行的,如果出现异常就不执行;而最终通知时无论是否出现异常都会执行的,感觉类似于finally。

3.4.1.3.在配置同一个切入点且不出现异常时的执行顺序

在这里插入图片描述

注意:椭圆中顺序不固定,具体顺序与配置文件的配置顺序有关

3.4.1.4.几种情况
情况1:
		<!--3.2.配置通知-->
        <aop:aspect ref="tx">
            <!--环绕通知-->
            <aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
            <!--前置通知-->
            <aop:before method="openTx" pointcut-ref="pointcut"/>
            <!--最终通知-->
            <aop:after method="finallyMethod" pointcut-ref="pointcut"/>
            <!--后置通知(返回通知)-->
            <aop:after-returning method="CommitTix" pointcut-ref="pointcut" returning="value"/>
            <!--异常通知-->
            <aop:after-throwing method="Rollback" pointcut-ref="pointcut" throwing="e"/>
        </aop:aspect>

执行结果:

环绕 --> 前置通知
前置通知
新增User:User(id=111, name=测试)
环绕 --> 后置通知
环绕 --> 最终通知
最终通知
后置通知

情况2:
		<!--3.2.配置通知-->
        <aop:aspect ref="tx">
            <!--环绕通知-->
            <aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
            <!--前置通知-->
            <aop:before method="openTx" pointcut-ref="pointcut"/>
            <!--后置通知(返回通知)-->
            <aop:after-returning method="CommitTix" pointcut-ref="pointcut" returning="value"/>
            <!--最终通知-->
            <aop:after method="finallyMethod" pointcut-ref="pointcut"/>
            <!--异常通知-->
            <aop:after-throwing method="Rollback" pointcut-ref="pointcut" throwing="e"/>
        </aop:aspect>

执行结果:

环绕 --> 前置通知
前置通知
新增User:User(id=111, name=测试)
环绕 --> 后置通知
环绕 --> 最终通知
后置通知
最终通知

情况3:
		<!--3.2.配置通知-->
        <aop:aspect ref="tx">
            <!--前置通知-->
            <aop:before method="openTx" pointcut-ref="pointcut"/>
            <!--环绕通知-->
            <aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
            <!--最终通知-->
            <aop:after method="finallyMethod" pointcut-ref="pointcut"/>
            <!--后置通知(返回通知)-->
            <aop:after-returning method="CommitTix" pointcut-ref="pointcut" returning="value"/>
            <!--异常通知-->
            <aop:after-throwing method="Rollback" pointcut-ref="pointcut" throwing="e"/>
        </aop:aspect>

执行结果:

前置通知
环绕 --> 前置通知
新增User:User(id=111, name=测试)
环绕 --> 后置通知
环绕 --> 最终通知
最终通知
后置通知

情况4:
		<!--3.2.配置通知-->
        <aop:aspect ref="tx">
            <!--前置通知-->
            <aop:before method="openTx" pointcut-ref="pointcut"/>
            <!--最终通知-->
            <aop:after method="finallyMethod" pointcut-ref="pointcut"/>
            <!--环绕通知-->
            <aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
            <!--后置通知(返回通知)-->
            <aop:after-returning method="CommitTix" pointcut-ref="pointcut" returning="value"/>
            <!--异常通知-->
            <aop:after-throwing method="Rollback" pointcut-ref="pointcut" throwing="e"/>
        </aop:aspect>

执行结果:

前置通知
环绕 --> 前置通知
新增User:User(id=111, name=测试)
后置通知
环绕 --> 后置通知
环绕 --> 最终通知
最终通知

情况5:
		<!--3.2.配置通知-->
        <aop:aspect ref="tx">
            <!--前置通知-->
            <aop:before method="openTx" pointcut-ref="pointcut"/>
            <!--后置通知(返回通知)-->
            <aop:after-returning method="CommitTix" pointcut-ref="pointcut" returning="value"/>
            <!--最终通知-->
            <aop:after method="finallyMethod" pointcut-ref="pointcut"/>
            <!--异常通知-->
            <aop:after-throwing method="Rollback" pointcut-ref="pointcut" throwing="e"/>
            <!--环绕通知-->
            <aop:around method="aroundAdvice" pointcut-ref="pointcut"/>
        </aop:aspect>

执行结果:

前置通知
环绕 --> 前置通知
新增User:User(id=111, name=测试)
环绕 --> 后置通知
环绕 --> 最终通知
后置通知
最终通知

特殊:这种情况下,在执行完环绕通知后,后置通知(或异常通知)和最终通知的执行顺序与配置顺序相反。

3.4.1.5.结论

在XML中配置AOP,通知的执行顺序和Spring版本无关;和通知的声明顺序有关,我们可以确定前置通知一定在目标方法之前其他通知和配置顺序有关,声明在前,先执行

3.4.2.Spring基于注解的通知执行顺序

注解开发中,在Spring 5.2.7版本之前(Spring5.2.6),在以下两种情况:

3.4.2.1.正常情况[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tPsM9Hm8-1629094208955)(D:\Typora\图片\AOP注解版正常情况.png)]
3.4.2.2.异常情况

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6nQqNSma-1629094208960)(D:\Typora\图片\AOP注解版异常情况.png)]

3.4.2.3.Spring5.2.7版本之后的执行顺序研究

在官网中找到如下解释:

https://docs.spring.io/spring-framework/docs/5.2.9.RELEASE/spring-framework-reference/core.html#aop-ataspectj-advice-ordering
在这里插入图片描述
As of Spring Framework 5.2.7, advice methods defined in the same @Aspect class that need to run at the same join point are assigned precedence based on their advice type in the following order, from highest to lowest precedence: @Around, @Before, @After, @AfterReturning, @AfterThrowing.

3.4.2.4.结论

从Spring5.2.7开始,在相同@Aspect类中,通知方法将根据其类型按照从高到低的优先级进行执行:@Around,@Before ,@AfterReturning, @After,@AfterThrowing。

3.5.Spring的AOP多切面通知

3.5.1.同一个切面类多个相同通知

 	/**
     * 01-前置通知:在目标方法之前被执行!
     */
    @Before(value = "execution(* com.bruce.service.impl.*.*(..))")
    public void openTx1(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();//被拦截的方法的方法名
        Object[] args = joinPoint.getArgs(); //被拦截的方法的参数
        //封装的目标方法的信息
        System.out.println("前置openTx1-开启事务....方法名:"+name+",参数是:"+ Arrays.toString(args));
    }

    @Before(value = "execution(* com.bruce.service.impl.*.*(..))")
    public void openTx2(JoinPoint joinPoint){
        String name = joinPoint.getSignature().getName();//被拦截的方法的方法名
        Object[] args = joinPoint.getArgs(); //被拦截的方法的参数
        //封装的目标方法的信息
        System.out.println("前置openTx2-开启事务....方法名:"+name+",参数是:"+ Arrays.toString(args));
    }

运行结果:

前置openTx1-开启事务…方法名:addUser,参数:[User{id=1,name=“测试”}]

前置openTx2-开启事务…方法名:addUser,参数:[User{id=1,name=“测试”}]

问:那么这两个通知的执行顺序如何决定?

答:由上述代码和执行结果可以得出,跟方法名有关,比如:以a开头的就比b开头的执行顺序要早,以此类推。

3.5.2.Spring之AOP多切面通知方法的运行顺序

指定切面的优先级:

  • 在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的;
  • 切面的优先级可以通过实现Ordered接口或利用**@Order**注解指定;
  • 实现Ordered接口,getOrder()方法的返回值越小,优先级越高;
  • 若使用**@Order**注解,序号出现在注解中,值越小优先级越高

3.5.3.Order注解修饰方法

public class Cat {

    String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Cat(){

    }

    public Cat(String name){
        this.name=name;
        System.out.println(name);
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                '}';
    }
}
package com.bruce.config;

import com.bruce.pojo.Cat;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;

import java.util.List;

@Configuration
public class CatConfig {

    @Bean
    @Order(3)
    public Cat cat1(){
        return new Cat("cat1()");
    }


    @Bean
    @Order(4)
    public Cat cat2(){
        return new Cat("cat2()");
    }

    @Bean
    @Order(1)
    public Cat cat3(){
        return new Cat("cat3()");
    }

    @Bean
    public Cat Cat4(List<Cat> cats){
        System.out.println(cats);
        for (Cat cat : cats) {
            System.out.println("--->"+cat);
        }
        return cats.get(0);
    }

}

运行结果:

cat1()

cat2()

cat3()

[Cat{name=‘cat3()’},Cat{name=‘cat1’},Cat{name=‘cat2()’}]

由上述代码和运行结果可得出如下结论:

@Order注解不能决定Spring容器加载Bean的顺序,只能决定执行顺序!

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

釣餌锒鐺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值