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的顺序,只能决定执行顺序!