SpringAOP基本概念和约定流程
在实际的开发中有些内容并不是面向对象能够解决的,比如数据库事务,对于企业级应用非常重要,比如在电商系统中订单模块和交易模块,一边是交易记录一边是账户数据,就需要对这两者有统一的事务管理,交易和账户的数据库事务要么全程成功,要么全失败,但在交易记录和账户记录而言,各自都符合OOP的规范,在这些场景下就需要面向切面编程
AOP编程有重要的意义,首先它可以拦截一些方法,然后把各个对象组成一个整体,约定好了动态流程后,就可以在交易前后、交易完成或异常时进行必要的动作,看一下如下例子
/*
* 记录购买纪录
* @productId 产品编号
* @record 购买记录
**/
public void savePurchaseRecord(Long productId, PurchaseRecord record){
Sqlsession sqlsession = null;
try{
sqlsession = SqlSessionFactoryUtils.openSqlSession();
ProductMapper productMapper = sqlSession.getMapper(ProductMapper.class);
Product product = productMapper.getProduct(productId);
//判断库存是否大于购买量
if(product.getStock() >= record.getQuantity()){
//减库存,更新数据库记录
product.setStock(product.getStock()-record.getQuantity());
productMapper.update(product);
//保存交易记录
PurchaseRecordMapper purchaseRecordMapper = sqlSession.getMapper(PurchaseRecordMapper.class);
pucchaseREcordMapper.Save(record);
sqlSession.commit();
}
} catch (Exception ex){
//异常回滚事务
ex.printStackTrace();
sqlSession.rollback();
} finally{
//关闭数据库会话
if(sqlSession!=null){
sqlSession.close();
}
}
}
如果按照AOP的设计思维,如下代码所示
@Autowired
private ProductMapper productMapper = null;
@Autowired
private PurchaseRecordMapper purchaseRecordMapper = null;
... ...
@Transactional
public void updateProduct(Long productId, PurchaseRecord record){
Product product = productMapper.getProduct(productId);
//判断库存是否大于购买量
if(product.getStock() >= record.getQuantity()){
//减库存,更新数据库记录
product.setStock(product.getStock()-record.getQuantity());
productMapper.update(product);
//保存交易记录
pucchaseREcordMapper.Save(record);
}
这段代码中使用了注解@Transactional
,没有任何关于打开或者关闭数据库资源的代码,也没有提交和回滚数据库事务,但却完成了相同的数据库相关处理,并且消除了try...catch...finally...
代码段,实际代码更聚焦在业务处理上,而不是在数据库事务和资源管理上,这边是AOP的意义
与此同时,在这里@Transactional
显而易见的的作用就是通过数据库连接池获得或者打开数据库链接,开启事务并做必要的设置;然后指定对应的SQL,处理数据;遇到异常回滚;没有异常则提交事务;关闭数据库链接;实际上作为AOP,在某种意义上将他就是根据一个流程做一定的封装,然后通过动态代理技术,将代码植入到对应的环节中,就类似于上边这个例子
这就是典型的约定大于配置的例子,方法上挂@Transactional
, 那么该方法就默认启用数据库事务、如果方法出现异常则回滚、没有异常则提交事务、且启动完成连库、关闭数据库连接动作
然而这只是一个最简单的闭环例子,在实际的开发中是更复杂的情况,例如在一个方法里要批量处理多个对象,不可能一个对象的处理出现了异常,就全部回滚,SpringAOP对这些复杂的场景提供了不错的支持
AOP是通过动态代理模式,管控各个对象操作的切面环境,管理包括日志、数据库事务等操作,从而可以在反射原有对象方法之前,正常返回或异常返回之后,插入自己的逻辑代码,有时候甚至可以取代原有代码的后续处理
SpringAOP常用术语
- 切面(Aspect)
可以理解为环境或者场景,例如上边的例子,数据库事务直接贯穿整个代码层面,这就是一个切面,它能够在目标对象的方法前或者方法后,产生异常或者正常返回后切入开发者的代码,甚至取代原有代码,在AOP中可以把它理解为一个拦截器,例如下边这个代码,一个简单的拦截器接口
package org.aop.demo;
/**
* 该接口定义了拦截器的行为。
* 拦截器用于在特定的切面之前、之后、返回或抛出异常时执行额外的逻辑。
*/
public interface Interceptor {
/**
* 在目标方法执行之前执行的逻辑。
*
* @param obj 传递给目标方法的对象,可以是任意类型。
*/
public void before(Object obj);
/**
* 在目标方法执行之后执行的逻辑,无论方法执行是否成功。
*
* @param obj 传递给目标方法的对象,可以是任意类型。
*/
public void after(Object obj);
/**
* 在目标方法正常返回时执行的逻辑。
*
* @param obj 目标方法返回的对象。
*/
public void afterReturning(Object obj);
/**
* 在目标方法抛出异常时执行的逻辑。
*
* @param obj 与异常相关联的对象,可以是任意类型。
*/
public void afterThrowing(Object obj);
}
一个实现类
package org.aop.demo.interceptor;
import org.aop.demo.Interceptor;
/**
* RoleInterceptor 类实现了 Interceptor 接口,
* 用于在特定时机拦截并处理角色信息的打印。
*/
public class RoleInterceptor implements Interceptor {
/**
* 在目标方法执行前执行的逻辑。
* 主要用于准备打印角色信息的前置工作。
*
* @param obj 目标方法的参数,此处未使用
*/
@Override
public void before(Object obj) {
System.out.println("准备打印角色信息");
}
/**
* 在目标方法执行后执行的逻辑,无论方法执行是否成功。
* 主要用于进行打印角色信息后的善后处理。
*
* @param obj 目标方法的返回值,此处未使用
*/
@Override
public void after(Object obj) {
System.out.println("已经完成角色信息的打印处理");
}
/**
* 在目标方法正常返回后执行的逻辑。
* 主要用于确认打印功能已完成且一切正常。
*
* @param obj 目标方法的返回值,此处未使用
*/
@Override
public void afterReturning(Object obj) {
System.out.println("刚刚完成打印功能,一切正常。");
}
/**
* 在目标方法抛出异常后执行的逻辑。
* 主要用于处理打印功能执行时可能出现的异常情况。
*
* @param obj 异常对象,此处未使用
*/
@Override
public void afterThrowing(Object obj) {
System.out.println("打印功能执行异常了,查看角色对象为空了吗?");
}
}
以上代码便是一个切面,可以使用动态代理技术将其植入到流程中
- 通知(Adice)
当进入切面后的切面方法,她根据在代理对象的真实方法被调用前、后的顺序和逻辑进行区分 -
- 环绕通知(around):在动态代理中,它可以取代当前被拦截对象的方法,可以通过参数和反射调用被拦截对象的方法,它允许在方法调用前后执行自定义的逻辑,并且可以决定是否执行方法本身,以及如何处理方法的返回值和异常
-
- 前置通知(before):在动态代理反射目标对象方法或者环绕通知前执行的通知功能
-
- 后置通知(after):在动态代理反射目标对象方法或者环绕通知后执行的通知功能,无论是否抛出异常它都会被执行
-
- 返回通知(afterrunning):在动态代理反射目标对象方法或者在环绕通知后执行的通知功能
-
- 异常通知(afterThrowing):在动态代理反射目标对象方法或者在环绕通知产生异常后执行的通知功能
如下代码所示
package org.aop.demo;
/**
* ProxyBeanFactory类用于通过动态代理生成代理对象。
*
* @param <T> 代理对象的类型
*/
public class ProxyBeanFactory {
/**
* 根据传入的对象和拦截器获取一个代理对象。
* 该方法通过ProxyBeanUtil的静态方法获取代理对象,代理对象会拦截并处理方法调用。
*
* @param obj 被代理的对象
* @param interceptor 拦截器,用于拦截并处理方法调用
* @return 代理对象,其类型与被代理对象相同
*/
public static <T> T getBean(T obj, Interceptor interceptor) {
// 通过ProxyBeanUtil获取代理对象
return (T) ProxyBeanUtil.getBean(obj, interceptor);
}
}
getBean
方法保存了目标对象、拦截器、和参数,然后生成JDK动态代理对象(proxy),同时绑定绑定了ProxyBeanUtil
的一个实例作为其代理类,这样当代理对象调用方法时,就会进入到ProxyBeanUtil
实例的invoke
方法中,在invoke
方法中定义了一个标志字段exceptionFlag
,通过这个标志判断反射原有对象方法的时候是否发生了异常,然后定义了通过判断这个标志字段的值,代码应该如何继续执行
package org.aop.demo;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
/**
* 代理工具类,用于创建动态代理对象。
*/
class ProxyBeanUtil implements InvocationHandler {
// 目标对象,代理对象会拦截并处理该对象的方法调用
private Object obj;
// 拦截器,用于在方法执行前后添加额外的逻辑
private Interceptor interceptor = null;
/**
* 创建并返回一个代理对象,该对象会拦截调用的方法,并执行拦截器的逻辑。
*
* @param obj 目标对象,将对该对象的方法进行拦截。
* @param interceptor 拦截器对象,用于执行拦截逻辑。
* @return 代理对象,其方法调用会触发拦截器的逻辑。
*/
public static Object getBean(Object obj, Interceptor interceptor) {
ProxyBeanUtil _this = new ProxyBeanUtil();
// 保存目标对象和拦截器
_this.obj = obj;
_this.interceptor = interceptor;
// 使用Java动态代理生成代理对象
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), _this);
}
/**
* 当调用代理对象的方法时,会执行此方法。
*
* @param proxy 代理对象本身。
* @param method 被调用的方法。
* @param args 方法调用的参数。
* @return 方法的返回值。
* @throws Throwable 如果方法执行过程中抛出异常,则抛出该异常。
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
Object retObj = null;
// 标记是否方法执行过程中产生异常
boolean exceptionFlag = false;
// 在方法执行前调用拦截器的before方法
interceptor.before(obj);
try {
// 尝试执行原方法
retObj = method.invoke(obj, args);
} catch (Exception ex) {
// 如果方法执行异常,则标记异常发生
exceptionFlag = true;
} finally {
// 无论是否异常,都执行拦截器的after方法
interceptor.after(obj);
}
// 根据是否发生异常,调用不同的拦截器方法
if (exceptionFlag) {
interceptor.afterThrowing(obj);
} else {
interceptor.afterReturning(obj);
}
// 返回方法执行结果
return retObj;
}
}
- 引入:允许开发者在现有的类里添加自定义的类和方法,以增强现有类的功能
- 切点:并非所有的程序都需要AOP,需要告诉AOP什么情况下在哪里植入切面及其他内容,这个便是切点的作用,它也是一个判断条件
- 连接点:一个具体的点,经过切点的判断后,如果符合切点的条件,就植入切面内容,在Spring中一般链接是某个具体的方法,如下代码所示
/**
* RoleService接口定义了角色服务的相关操作。
* 它提供了一个方法来打印角色信息。
*/
package org.aop.demo.service;
import org.aop.pojo.Role;
public interface RoleService {
/**
* 打印角色信息。
* @param role 角色对象,包含了角色的详细信息。
* 该方法没有返回值,主要是为了演示AOP方面的拦截器或者切面如何作用于方法上。
*/
public void printRole(Role role);
}
package org.aop.demo.service.impl;
import org.aop.demo.service.RoleService;
import org.aop.pojo.Role;
/**
* RoleService接口的实现类,提供关于角色操作的具体实现。
*/
public class RoleServiceImpl implements RoleService {
/**
* 打印角色信息。
* @param role 角色对象,包含角色的id、名称和备注信息。
* 该方法不返回任何内容,只是将角色信息打印到控制台。
*/
@Override
public void printRole(Role role) {
// 格式化输出角色对象的信息
System.out.println("{id =" + role.getId() + ", roleName=" + role.getRoleName() + ", note=" + role.getNote() + "}");
}
}
- 植入:它是一个生成代理对象的过程,实际代理的方法分为静态代理和动态代理,静态代理是在编译class文件时,通过类加载器(ClassLoader)生成的代码逻辑,类加载时就生成了,也就是说该静态代理的代码逻辑在运行前就生成的,但在Spring中并不使用这样的方式;动态代理是在运行时动态生成的方式,这是SpringAOP采用的方式,Spring以JDK或者CGLIB动态代理来生成代理对象
可以使用如下代码测试上边的代码实现
package org.aop.main;
import org.aop.demo.Interceptor;
import org.aop.demo.ProxyBeanFactory;
import org.aop.demo.interceptor.RoleInterceptor;
import org.aop.demo.service.RoleService;
import org.aop.demo.service.impl.RoleServiceImpl;
import org.aop.pojo.Role;
/**
* AOP演示的主类。
* 该类用于展示如何使用代理模式拦截并处理方法的执行。
*/
public class DemoMain {
/**
* 程序的入口点。
* 该方法主要演示了如何通过代理模式,在方法执行前、执行后以及出现异常时进行拦截处理。
* @param args 命令行参数(未使用)
*/
public static void main(String[] args) {
// 实例化角色服务
RoleService roleService = new RoleServiceImpl();
// 实例化拦截器
Interceptor interceptor = new RoleInterceptor();
// 通过代理工厂获取被拦截器代理后的角色服务
RoleService proxy = ProxyBeanFactory.getBean(roleService, interceptor);
// 测试正常方法调用
Role role = new Role(1L, "role_name_1", "role_note_1");
proxy.printRole(role);
System.out.println("\n######## 测试afterthrowing方法 ########");
// 故意触发异常,以测试异常拦截处理
role = null;
proxy.printRole(role);
}
}
Spring中的AOP
AOP并不是Spring独有的,Spring只是支持AOP编程的框架之一,有些框架的AOP能够对方法参数进行拦截,有些框架的AOP仅对方法进行拦截,而SpringAOP是仅对方法拦截的AOP,Spring中实现方法拦截有几种方式:使用ProxyFactoryBean
和对应的接口实现AOP、使用XML配置AOP、使用注解@Aspect
驱动切面、使用AspectJ注入切面,其中常用的还是注解和XML,一主一辅的形式
<!-- 引入Spring框架的面向切面编程库 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>6.1.6</version>
</dependency>
<!-- 引入AspectJ运行时依赖,用于支持面向切面编程(AOP) -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.9.22.1</version>
</dependency>
<!-- 引入AspectJ Weaver依赖,用于在编译时或运行时将切面织入到Java类型中 -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.22.1</version>
</dependency>
使用注解@Aspect
开发SpringAOP
选择连接点
SpringAOP是方法级别的AOP框架,是以某个类的某个方法作为连接点的,用动态代理的理论语言来说,就是要拦截哪个方法植入对应的AOP通知, 如下代码过程可清晰理解,首先定义一个接口
package org.aop.springaop.service;
import org.aop.pojo.Role;
/**
* RoleService接口定义了角色服务的相关操作。
*/
public interface RoleService {
/**
* 打印角色信息。
* @param role 角色对象,包含角色的详细信息。
*/
public void printRole(Role role);
}
然后定义相关的POJO和接口的实现类
package org.aop.pojo;
/**
* Role类用于表示角色信息。
*/
public class Role {
private Long id; // 角色的唯一标识
private String roleName; // 角色名称
private String note; // 角色的备注信息
/**
* 无参构造函数,用于创建一个空的角色对象。
*/
public Role() {
}
/**
* 带参数的构造函数,用于创建一个具有指定id、角色名称和备注的角色对象。
*
* @param id 角色的唯一标识。
* @param roleName 角色的名称。
* @param note 角色的备注信息。
*/
public Role(Long id, String roleName, String note) {
this.id = id;
this.roleName = roleName;
this.note = note;
}
// 下面是getter和setter方法,用于访问和修改角色的属性值。
/**
* 获取角色的唯一标识。
* @return 返回角色的id。
*/
public Long getId() {
return id;
}
/**
* 设置角色的唯一标识。
* @param id 要设置的角色id。
*/
public void setId(Long id) {
this.id = id;
}
/**
* 获取角色的名称。
* @return 返回角色的名称。
*/
public String getRoleName() {
return roleName;
}
/**
* 设置角色的名称。
* @param roleName 要设置的角色名称。
*/
public void setRoleName(String roleName) {
this.roleName = roleName;
}
/**
* 获取角色的备注信息。
* @return 返回角色的备注信息。
*/
public String getNote() {
return note;
}
/**
* 设置角色的备注信息。
* @param note 要设置的角色备注信息。
*/
public void setNote(String note) {
this.note = note;
}
}
package org.aop.springaop.service.impl;
import org.aop.springaop.service.RoleService;
import org.aop.pojo.Role;
import org.springframework.stereotype.Component;
/**
* RoleService的实现类,提供关于角色的打印服务。
*/
@Component
public class RoleServiceImpl implements RoleService {
/**
* 打印一个角色的信息。
* @param role 角色对象,包含角色的id、名称和备注。
*/
public void printRole(Role role) {
// 打印角色的详细信息
System.out.println("{id: " + role.getId() + ", " + "role_name : " + role.getRoleName() + ", " + "note : " + role.getNote() + "}");
}
}
如果把printRole方法作为AOP的连接点,接下来只需要创建代理对象,其拦截类RoleServiceImpl的printRole方法,就可以在约定的流程中植入各种AOP通知方法了
创建切面
从动态代理的角度来说切面就如同一个拦截器,在Spring中只要使用注解@Aspect
注解一个类,SpringIoC容器就会认为这是一个切面,如下代码所示
package org.aop.springaop.aspect;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
/**
* RoleAspectOriginal 类定义了一个角色方面的切面,
* 通过注解方式定义了切点和通知(Advice),以拦截指定方法的执行。
*/
@Aspect
public class RoleAspectOriginal {
// 定义切点表达式,拦截 RoleServiceImpl 类中 printRole 方法的执行
private static final String POINTCUT_EXPRESS = "execution(* org.aop.springaop.service.impl.RoleServiceImpl.printRole(..))";
/**
* 在 printRole 方法执行前执行的通知。
* 该方法无参数和返回值,主要用于在目标方法执行前执行一些预处理逻辑。
*/
@Before(POINTCUT_EXPRESS)
public void beforePrintRole() {
System.out.println("beforePrintRole");
}
/**
* 在 printRole 方法执行后执行的通知。
* 该方法无参数和返回值,主要用于在目标方法执行后执行一些后处理逻辑。
*/
@After(POINTCUT_EXPRESS)
public void afterPrintRole() {
System.out.println("afterPrintRole");
}
/**
* 在 printRole 方法正常返回后执行的通知。
* 该方法无参数,返回值为 void,主要用于处理方法正常返回后的逻辑。
*/
@AfterReturning(POINTCUT_EXPRESS)
public void afterReturningPrintRole() {
System.out.println("afterReturningPrintRole");
}
/**
* 在 printRole 方法抛出异常后执行的通知。
* 该方法无参数和返回值,主要用于处理方法执行中抛出的异常。
*/
@AfterThrowing(POINTCUT_EXPRESS)
public void afterThrowingPrintRole() {
System.out.println("afterThrowingPrintRole");
}
}
切点
并不是所有程序都需要使用AOP,因此需要做判断,这个判断就是切点,在上边的代码中
// 定义切点表达式,拦截 RoleServiceImpl 类中 printRole 方法的执行
private static final String POINTCUT_EXPRESS = "execution(* org.aop.springaop.service.impl.RoleServiceImpl.printRole(..))";
该代码定义了execution
的正则表达式,Spring通过这个正则判断是否需要拦截方法,其中execution
代表执行方法时会触发;*
代表任意返回类型的方法;后面紧跟的是类的全限定名和被拦截的方法名;(..)
代表任意参数;
这行代码的意思就是Spring会拦截printRole方法,并按照AOP通知的规则把方法植入流程
AspectJ 指示器(Pointcut Designators 或 Pointcut Expressions)是 AspectJ 语言的一部分,用于构建切点表达式。切点表达式是用于精确指定哪些方法或构造器匹配切点的语句。AspectJ 提供了一系列的指示器,这些指示器可以组合使用来定义复杂的匹配规则。以下是一些常见的 AspectJ 指示器:
- execution():这是最常用的指示器,用于匹配方法的执行。它可以指定包名、类名、方法名、返回类型以及参数类型
- within():这个指示器匹配在特定类型内部的连接点
within(com.example.service.MyService)
- this() 和 target():this() 匹配当前正在执行的代理对象的类型,而 target() 匹配实际的目标对象类型
- args():匹配特定参数类型的连接点
- @args():匹配具有特定注解的参数
@args(com.example.annotation.MyAnnotation)
匹配那些至少有一个参数带有 MyAnnotation 注解的方法 - @annotation():匹配具有特定方法或类注解的连接点
@annotation(com.example.annotation.MyAnnotation)
匹配那些被 MyAnnotation 注解的方法 - @within() 和 @withincode():@within() 匹配具有特定类注解的类型中的连接点,而 @withincode() 匹配在带有特定注解的代码块中的连接点
- @target():匹配其类型被特定注解的方法
@target(com.example.annotation.MyAnnotation)
匹配那些其类型被 MyAnnotation 注解的方法 - @effective():匹配具有特定注解的运行时可见性
- call():与 execution() 类似,但匹配方法的调用,而不是执行
这些指示器可以单独使用,也可以组合使用,通过逻辑运算符 &&(与)、||(或)、!(非)来构建更复杂的匹配规则。在 Spring AOP 中,虽然支持 AspectJ 的切点表达式,但不是所有的指示器都被完全支持,例如 call() 通常在纯 Spring AOP 中不可用,如果是在XML里配置,那么可以使用and代替&&,使用or代替||,使用not代替!
@Before(value = "execution(* org.aop.springaop.service.impl.RoleServiceImpl.printRole(..))&&within(org.aop.springaop.service.impl.*)")
这段代码使用winthin限定execution定义的正则式下的包的匹配,这样Spring就只会将org.aop.springaop.service.impl
包下的类的printRole
方法作为切点了
为了减少冗长的表达式编写,借助注解@Pointcut
,可以将代码改造一下,如下所示
package org.aop.springaop.aspect;
import org.aspectj.lang.annotation.*;
/**
* RoleAspectOriginal 类定义了一个角色方面的切面,
* 通过注解方式定义了切点和通知(Advice),以拦截指定方法的执行。
*/
@Aspect
public class RoleAspectOriginal {
// 定义切点表达式,拦截 RoleServiceImpl 类中 printRole 方法的执行
private static final String POINTCUT_EXPRESS = "execution(* org.aop.springaop.service.impl.RoleServiceImpl.printRole(..))";
/**
* 定义一个切面,该切面针对满足特定表达式的并且位于指定包下的方法执行。
* 该切面不接受任何参数,也不返回任何值,它仅定义了一个切入点,用于在满足条件的方法执行前后执行额外的操作(例如日志记录)。
*
* @Pointcut 用于定义切入点,POINTCUT_EXPRESS 是一个预定义的表达式,"&& within(org.aop.springaop.service.impl.*)"
* 表示进一步限制只有在 org.aop.springaop.service.impl 包下的类中的方法才匹配。
*/
@Pointcut(POINTCUT_EXPRESS +"&& within(org.aop.springaop.service.impl.*)")
public void printRole() {
}
/**
* 在 printRole 方法执行前执行的通知。
* 该方法无参数和返回值,主要用于在目标方法执行前执行一些预处理逻辑。
*/
@Before("printRole()")
public void beforePrintRole() {
System.out.println("beforePrintRole");
}
/**
* 在 printRole 方法执行后执行的通知。
* 该方法无参数和返回值,主要用于在目标方法执行后执行一些后处理逻辑。
*/
@After("printRole()")
public void afterPrintRole() {
System.out.println("afterPrintRole");
}
/**
* 在 printRole 方法正常返回后执行的通知。
* 该方法无参数,返回值为 void,主要用于处理方法正常返回后的逻辑。
*/
@AfterReturning("printRole()")
public void afterReturningPrintRole() {
System.out.println("afterReturningPrintRole");
}
/**
* 在 printRole 方法抛出异常后执行的通知。
* 该方法无参数和返回值,主要用于处理方法执行中抛出的异常。
*/
@AfterThrowing("printRole()")
public void afterThrowingPrintRole() {
System.out.println("afterThrowingPrintRole");
}
}
找到了连接点,创建了切面并且设置了切点,接下来就是让他们联动工作了
package org.aop.springaop.config;
import org.aop.springaop.aspect.RoleAspectOriginal;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* AOP配置类,用于设置Spring AOP的相关配置。
* 通过@EnableAspectJAutoProxy开启对AspectJ切面的支持。
* 通过@ComponentScan指定扫描包路径,以便自动发现和注册组件。
*/
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("org.aop.springaop")
public class AopConfig {
/**
* 定义一个Bean,返回RoleAspectOriginal的实例。
* 这个方法没有参数。
* @return 返回RoleAspectOriginal的一个新实例。
*/
@Bean
public RoleAspectOriginal getRoleAspectOriginal() {
return new RoleAspectOriginal();
}
}
代码中@EnableAspectJAutoProxy
表示启用AspectJ框架的自动代理,这样Spring就会生成动态的代理对象,方可使用AOP,getRoleAspectOriginal()
方法则生成一个切面实例
/**
* AOP主类,用于展示Spring AOP的使用。
*/
package org.aop.main;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.aop.springaop.config.AopConfig; // 引入AOP配置类
import org.aop.springaop.service.RoleService; // 引入角色服务接口
import org.aop.pojo.Role; // 引入角色实体类
public class AopMain {
/**
* 应用的主入口函数。
* @param args 命令行参数
*/
public static void main(String[] args) {
// 使用注解方式配置SpringApplicationContext
ApplicationContext ctx = new AnnotationConfigApplicationContext(AopConfig.class);
// 使用XML配置文件加载Spring ApplicationContext
ApplicationContext ctx2 = new ClassPathXmlApplicationContext("spring-cfg.xml");
// 从ApplicationContext中获取RoleService的实例
RoleService roleService = ctx2.getBean(RoleService.class);
// 创建Role对象并设置其属性,然后调用printRole方法展示角色信息
Role role = new Role();
role.setId(1L);
role.setRoleName("role_name_1");
role.setNote("note_1");
roleService.printRole(role);
// 将role设置为null,然后再次调用printRole方法展示处理空角色的逻辑
role = null;
roleService.printRole(role);
}
}
执行代码结果如下
"C:\Program Files\Java\jdk-17.0.1\bin\java.exe" "-javaagent:C:\Users\EDY\AppData\Local\Programs\IntelliJ IDEA Ultimate\lib\idea_rt.jar=57018:C:\Users\EDY\AppData\Local\Programs\IntelliJ IDEA Ultimate\bin" -Dfile.encoding=UTF-8 -classpath D:\Programs\SpringAOP\target\classes;C:\Users\EDY\.m2\repository\org\mybatis\mybatis\3.5.16\mybatis-3.5.16.jar;C:\Users\EDY\.m2\repository\org\slf4j\slf4j-api\2.0.13\slf4j-api-2.0.13.jar;C:\Users\EDY\.m2\repository\org\slf4j\slf4j-reload4j\2.0.13\slf4j-reload4j-2.0.13.jar;C:\Users\EDY\.m2\repository\ch\qos\reload4j\reload4j\1.2.22\reload4j-1.2.22.jar;C:\Users\EDY\.m2\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;C:\Users\EDY\.m2\repository\org\apache\logging\log4j\log4j-core\2.23.1\log4j-core-2.23.1.jar;C:\Users\EDY\.m2\repository\org\apache\logging\log4j\log4j-api\2.23.1\log4j-api-2.23.1.jar;C:\Users\EDY\.m2\repository\com\mysql\mysql-connector-j\8.3.0\mysql-connector-j-8.3.0.jar;C:\Users\EDY\.m2\repository\com\google\protobuf\protobuf-java\3.25.1\protobuf-java-3.25.1.jar;C:\Users\EDY\.m2\repository\org\springframework\spring-core\6.1.6\spring-core-6.1.6.jar;C:\Users\EDY\.m2\repository\org\springframework\spring-jcl\6.1.6\spring-jcl-6.1.6.jar;C:\Users\EDY\.m2\repository\org\springframework\spring-beans\6.1.6\spring-beans-6.1.6.jar;C:\Users\EDY\.m2\repository\org\springframework\spring-context\6.1.6\spring-context-6.1.6.jar;C:\Users\EDY\.m2\repository\io\micrometer\micrometer-observation\1.12.5\micrometer-observation-1.12.5.jar;C:\Users\EDY\.m2\repository\io\micrometer\micrometer-commons\1.12.5\micrometer-commons-1.12.5.jar;C:\Users\EDY\.m2\repository\org\springframework\spring-context-support\6.1.6\spring-context-support-6.1.6.jar;C:\Users\EDY\.m2\repository\org\springframework\spring-expression\6.1.6\spring-expression-6.1.6.jar;C:\Users\EDY\.m2\repository\org\springframework\spring-aop\6.1.6\spring-aop-6.1.6.jar;C:\Users\EDY\.m2\repository\org\springframework\spring-web\6.1.6\spring-web-6.1.6.jar;C:\Users\EDY\.m2\repository\org\springframework\spring-webmvc\6.1.6\spring-webmvc-6.1.6.jar;C:\Users\EDY\.m2\repository\org\aspectj\aspectjrt\1.9.22.1\aspectjrt-1.9.22.1.jar;C:\Users\EDY\.m2\repository\org\aspectj\aspectjweaver\1.9.22.1\aspectjweaver-1.9.22.1.jar org.aop.main.AopMain
beforePrintRole
{id: 1, role_name : role_name_1, note : note_1}
afterReturningPrintRole
afterPrintRole
beforePrintRole
afterThrowingPrintRole
afterPrintRole
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "org.aop.pojo.Role.getId()" because "role" is null
at org.aop.springaop.service.impl.RoleServiceImpl.printRole(RoleServiceImpl.java:20)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:354)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:64)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:57)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:49)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:58)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
at jdk.proxy2/jdk.proxy2.$Proxy27.printRole(Unknown Source)
at org.aop.main.AopMain.main(AopMain.java:36)
也可以通过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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 启用Spring AOP,使用AspectJ风格的切面编程 -->
<aop:aspectj-autoproxy />
<!-- 定义一个切面角色,该切面定义了权限控制的逻辑 -->
<bean id="roleAspectOriginal"
class="org.aop.springaop.aspect.RoleAspectOriginal" />
<!-- 定义角色服务的实现,该服务可能会被切面拦截以进行权限检查 -->
<bean id="roleService"
class="org.aop.springaop.service.impl.RoleServiceImpl"/>
</beans>
到此就完成了一个简单的AOP过程
环绕通知
在整个AOP体系里,环绕通知的作用非常强大,它可以同时实现前置通知和后置通知的功能,并且保留了调度目标对象原有方法的功能,强大且灵活,但灵活的另一面就是复杂,如果不需要大量改变业务逻辑,一般很少使用它尽量避免没必要的错误出现,继续用代码串一下过程,首先在切面代码中通过注解@Around
加入环绕通知
/**
* 环绕通知
* @param jp —— 连接点(具体的某个方法)
*/
@Around("printRole()")
public void around(ProceedingJoinPoint jp) {
System.out.println("around before ....");
try {
jp.proceed();
} catch (Throwable e) {
e.printStackTrace();
}
System.out.println("around after ....");
}
这个环绕通知里面的参数ProceedingJoinPoint
是一个连接点,代表某个方法,环绕通知的参数是Spring提供的,它会通过反射来执行目标对象的方法,然后再执行测试代码,结果如下
"C:\Program Files\Java\jdk-17.0.1\bin\java.exe" "-javaagent:C:\Users\Davieyang.D.Y\AppData\Local\Programs\IntelliJ IDEA Ultimate\lib\idea_rt.jar=59564:C:\Users\Davieyang.D.Y\AppData\Local\Programs\IntelliJ IDEA Ultimate\bin" -Dfile.encoding=UTF-8 -classpath D:\Programs\Java\SpringAOP\target\classes;C:\Users\Davieyang.D.Y\.m2\repository\org\mybatis\mybatis\3.5.16\mybatis-3.5.16.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\slf4j\slf4j-api\2.0.13\slf4j-api-2.0.13.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\slf4j\slf4j-reload4j\2.0.13\slf4j-reload4j-2.0.13.jar;C:\Users\Davieyang.D.Y\.m2\repository\ch\qos\reload4j\reload4j\1.2.22\reload4j-1.2.22.jar;C:\Users\Davieyang.D.Y\.m2\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\apache\logging\log4j\log4j-core\2.23.1\log4j-core-2.23.1.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\apache\logging\log4j\log4j-api\2.23.1\log4j-api-2.23.1.jar;C:\Users\Davieyang.D.Y\.m2\repository\com\mysql\mysql-connector-j\8.3.0\mysql-connector-j-8.3.0.jar;C:\Users\Davieyang.D.Y\.m2\repository\com\google\protobuf\protobuf-java\3.25.1\protobuf-java-3.25.1.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\springframework\spring-core\6.1.6\spring-core-6.1.6.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\springframework\spring-jcl\6.1.6\spring-jcl-6.1.6.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\springframework\spring-beans\6.1.6\spring-beans-6.1.6.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\springframework\spring-context\6.1.6\spring-context-6.1.6.jar;C:\Users\Davieyang.D.Y\.m2\repository\io\micrometer\micrometer-observation\1.12.5\micrometer-observation-1.12.5.jar;C:\Users\Davieyang.D.Y\.m2\repository\io\micrometer\micrometer-commons\1.12.5\micrometer-commons-1.12.5.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\springframework\spring-context-support\6.1.6\spring-context-support-6.1.6.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\springframework\spring-expression\6.1.6\spring-expression-6.1.6.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\springframework\spring-aop\6.1.6\spring-aop-6.1.6.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\springframework\spring-web\6.1.6\spring-web-6.1.6.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\springframework\spring-webmvc\6.1.6\spring-webmvc-6.1.6.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\aspectj\aspectjrt\1.9.22.1\aspectjrt-1.9.22.1.jar;C:\Users\Davieyang.D.Y\.m2\repository\org\aspectj\aspectjweaver\1.9.22.1\aspectjweaver-1.9.22.1.jar org.aop.main.AopMain
around before ....
beforePrintRole
{id: 1, role_name : role_name_1, note : note_1}
afterReturningPrintRole
afterPrintRole
around after ....
around before ....
beforePrintRole
afterThrowingPrintRole
afterPrintRole
around after ....
java.lang.NullPointerException: Cannot invoke "org.aop.pojo.Role.getId()" because "role" is null
at org.aop.springaop.service.impl.RoleServiceImpl.printRole(RoleServiceImpl.java:20)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:354)
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.aspectj.AspectJAfterThrowingAdvice.invoke(AspectJAfterThrowingAdvice.java:64)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.adapter.AfterReturningAdviceInterceptor.invoke(AfterReturningAdviceInterceptor.java:57)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.aspectj.AspectJAfterAdvice.invoke(AspectJAfterAdvice.java:49)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.adapter.MethodBeforeAdviceInterceptor.invoke(MethodBeforeAdviceInterceptor.java:58)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.aspectj.MethodInvocationProceedingJoinPoint.proceed(MethodInvocationProceedingJoinPoint.java:89)
at org.aop.springaop.aspect.RoleAspectOriginal.around(RoleAspectOriginal.java:71)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethodWithGivenArgs(AbstractAspectJAdvice.java:637)
at org.springframework.aop.aspectj.AbstractAspectJAdvice.invokeAdviceMethod(AbstractAspectJAdvice.java:627)
at org.springframework.aop.aspectj.AspectJAroundAdvice.invoke(AspectJAroundAdvice.java:71)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
at jdk.proxy2/jdk.proxy2.$Proxy28.printRole(Unknown Source)
at org.aop.main.AopMain.main(AopMain.java:38)
Process finished with exit code 0
植入
植入也可以成为织入,是生成代理对象的过程,在上述代码中,连接点所在的类都是拥有接口的类,但如果没有接口,Spring也能提供AOP功能,是否拥有接口并不是使用SpringAOP的强制要求,在设计模式中如果说到JDK动态代理,是必须有借口的,但如果不适用JDK动态代理,而是使用CGLIB动态代理则不需要,因此Spring提供了一个规则,如果实现类有接口,Spring就提供JDK动态代理,从而植入各个通知,如下图所示
当类不存在接口的时候,没办法使用JDK动态代理,Spring会使用CGLIB动态代理来生成代理对象,在接口编程已经很普及的情况下,使用接口加实现类的方式更佳,更有利于定义和实现分离,更有利于实现变化和替换
向通知传递参数
在SpringAOP的各类通知中,对于除环绕通知外的通知也希望能够传递参数,改造一下连接点接口
package org.aop.springaop.service;
import org.aop.pojo.Role;
/**
* RoleService接口定义了角色服务的相关操作。
*/
public interface RoleService {
/**
* 打印角色信息。
* @param role 角色对象,包含角色的详细信息。
*/
public void printRole(Role role);
/**
* 打印指定角色的信息,并按照指定的排序方式输出。
*
* @param role 需要打印的角色对象,包含角色的详细信息。
* @param sort 角色的排序方式,用于指定角色信息的输出顺序。
* @return 该方法没有返回值。
*/
public void printRole(Role role, int sort);
}
同时改造实现类
package org.aop.springaop.service.impl;
import org.aop.springaop.service.RoleService;
import org.aop.pojo.Role;
import org.springframework.stereotype.Component;
/**
* RoleService的实现类,提供关于角色的打印服务。
*/
@Component
public class RoleServiceImpl implements RoleService {
/**
* 打印一个角色的信息。
* @param role 角色对象,包含角色的id、名称和备注。
*/
public void printRole(Role role) {
System.out.println("{id: " + role.getId() + ", " + "role_name : " + role.getRoleName() + ", " + "note : " + role.getNote() + "}");
}
/**
* 打印角色信息和排序值。
* 该方法会详细打印一个角色对象的信息,包括角色的ID、名称和备注,然后打印排序值。
*
* @param role 角色对象,包含角色的详细信息。
* @param sort 角色的排序值,用于表示角色的优先级或顺序。
*/
@Override
public void printRole(Role role, int sort) {
// 打印角色的详细信息
System.out.println("{id: " + role.getId() + ", " + "role_name : " + role.getRoleName() + ", " + "note : " + role.getNote() + "}");
// 打印角色的排序值
System.out.println(sort);
}
}
这个方法存在两个参数,一个是角色一个是排序,要把这个方法作为连接点,也就是要使用切面拦截这个方法,切面中的方法可以这样改造
/**
* 在目标方法执行前执行的切面逻辑。
* 该切面通过@Before注解指定切点表达式,确保在调用printRole()方法并且方法参数为Role类型和int类型的sort时执行。
*
* @param role 表示角色的对象,用于可能的角色校验或角色相关的逻辑处理。
* @param sort 表示排序的整数,用于可能的排序逻辑处理或调试信息的输出。
*/
@Before("printRole() && args(role, sort) ")
public void before(Role role, int sort) {
System.out.println("sort before ...."); // 在执行目标方法前输出排序相关的调试信息
}
这样在切点的匹配上加入参数,就可以获取目标对象原有方法的参数了
引入
SpringAOP通过动态代理技术,把各类通知植入它所约定的流程中,在这个过程中也可以通过引入其他的类得到更好的实现,比如当角色为空的时候不执行printRole()
,可以引入一个检测器进行检测,首先定义一个接口,如下代码所示
/**
* RoleVerifier接口定义了角色验证的行为。
* 该接口只有一个方法verify,用于验证给定的角色是否满足某种条件。
*/
package org.aop.springaop.verifier;
import org.aop.pojo.Role;
public interface RoleVerifier {
/**
* 验证给定的角色是否满足某种条件。
*
* @param role 需要验证的角色对象。
* @return boolean 如果角色满足验证条件,返回true;否则返回false。
*/
public boolean verify(Role role);
}
其实现类代码如下
package org.aop.springaop.verifier.impl;
import org.aop.springaop.verifier.RoleVerifier;
import org.aop.pojo.Role;
/**
* 实现了RoleVerifier接口,用于验证角色是否有效。
*/
public class RoleVerifierImpl implements RoleVerifier {
/**
* 验证给定的角色是否非空。
*
* @param role 待验证的角色对象。
* @return 返回true如果角色非空,否则返回false。
*/
@Override
public boolean verify(Role role) {
// 验证角色是否非空
return role != null;
}
}
该实现类仅仅检测role对象是否为空,然后改写切面代码,如下所示
/**
* 通过AOP为RoleServiceImpl类增强功能,引入RoleVerifier接口的实现RoleVerifierImpl。
* 这个注解定义了一个切面,它将RoleVerifierImpl的实例注入到名为roleVerifier的字段中,
* 从而为RoleServiceImpl类添加了额外的行为。
*
* @DeclareParents 用于定义切面,其中:
* - value 指定了需要增强的类,这里使用了通配符+,表示增强RoleServiceImpl及其所有子类。
* - defaultImpl 指定了用来增强原有类的实现类,这里是RoleVerifierImpl。
*
* @field roleVerifier 用于注入RoleVerifierImpl实例,提供角色验证的功能。
*/
@DeclareParents(
// 需要增强的类
value = "org.aop.springaop.service.impl.RoleServiceImpl+",
// 引入哪个类去增强原有类的功能
defaultImpl = RoleVerifierImpl.class)
public RoleVerifier roleVerifier;
然后修改执行代码如下
/**
* AOP主类,用于展示Spring AOP的使用。
*/
package org.aop.main;
import org.aop.springaop.verifier.RoleVerifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.aop.springaop.config.AopConfig; // 引入AOP配置类
import org.aop.springaop.service.RoleService; // 引入角色服务接口
import org.aop.pojo.Role; // 引入角色实体类
public class AopMain {
/**
* 应用的主入口函数。
* @param args 命令行参数
*/
public static void main(String[] args) {
// 使用注解方式配置SpringApplicationContext
ApplicationContext ctx = new AnnotationConfigApplicationContext(AopConfig.class);
// 使用XML配置文件加载Spring ApplicationContext
ApplicationContext ctx2 = new ClassPathXmlApplicationContext("spring-cfg.xml");
// 从ApplicationContext中获取RoleService的实例
RoleService roleService = ctx.getBean(RoleService.class);
RoleVerifier roleVerifier = (RoleVerifier)roleService;
// 创建Role对象并设置其属性,然后调用printRole方法展示角色信息
Role role = new Role();
role.setId(1L);
role.setRoleName("role_name_1");
role.setNote("note_1");
/**
* 验证指定角色是否合法。
* 该代码块首先通过roleVerifier对象的verify方法对role进行验证。
* 如果验证成功,则打印"role is verified",并调用roleService的printRole方法详细打印角色信息。
*/
if (roleVerifier.verify(role)) {
System.out.println("role is verified");
roleService.printRole(role,1);
}
}
}
使用强制转换后,可以把roleService转化为RoleVerifier接口对象,然后使用verify方法
SpringAOP依赖于动态代理实现,生成JDK代理对象是通过类似以下代码实现的
/**
* 创建并返回一个代理对象,该对象会拦截调用的方法,并执行拦截器的逻辑。
*
* @param obj 目标对象,将对该对象的方法进行拦截。
* @param interceptor 拦截器对象,用于执行拦截逻辑。
* @return 代理对象,其方法调用会触发拦截器的逻辑。
*/
public static Object getBean(Object obj, Interceptor interceptor) {
ProxyBeanUtil _this = new ProxyBeanUtil();
// 保存目标对象和拦截器
_this.obj = obj;
_this.interceptor = interceptor;
// 使用Java动态代理生成代理对象
return Proxy.newProxyInstance(obj.getClass().getClassLoader(),
obj.getClass().getInterfaces(), _this);
}
obj.getClass().getInterfaces()
意味着代理对象可以挂在多个接口下,也就是说只要让代理对象挂到RoleService和RoleVerifier两个接口下,就可以通过强制转换,让代理对象在这两个接口之间转换,进而可以调用引入的方法,这样就能够通过引入功能,在原有的基础上再次增强Bean的功能
同样的如果RoleServiceImpl没有接口,它也会使用CGLIB动态代理,使用增强者类(Enhancer)也会有一个interfaces的属性,允许代理对象挂到对应的多个接口下,做到JDK动态代理的效果
使用XML配置开发SpringAOP
殊途同归,原理相同,需要在XML中引入AOP的命名空间,如下是AOP可配置的元素表
定义一个新的连接点,接口和实现类如下
package org.aop.springaop.service;
import org.aop.pojo.Role;
public interface PrintRoleService {
public void printRole(Role role);
}
package org.aop.springaop.service.impl;
import org.aop.pojo.Role;
import org.aop.springaop.service.PrintRoleService;
public class PrintRoleServiceImpl implements PrintRoleService {
@Override
public void printRole(Role role) {
System.out.println("id="+role.getId()+ ",");
System.out.println("role_name="+ role.getRoleName()+",");
System.out.println("note="+role.getNote());
}
}
定义一个切面,代码如下
package org.aop.springaop.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
public class XmlAspect {
public void before() {
System.out.println("before");
}
public void after() {
System.out.println("after");
}
public void afterReturning() {
System.out.println("afterReturning");
}
public void afterThrowing() {
System.out.println("afterThrowing");
}
public void around(ProceedingJoinPoint jp) {
System.out.println("around before ......");
try {
jp.proceed();
} catch (Throwable e) {
new RuntimeException("回调目标对象方法,产生异常......");
}
System.out.println("around after ......");
}
}
这里没有任何注解,会使用XML向SpringIoC容器描述它们,如下所示
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 定义切面,这里定义了一个名为xmlAspect的切面,它指向org.aop.springaop.aspect.XmlAspect类 -->
<bean id="xmlAspect" class="org.aop.springaop.aspect.XmlAspect" />
<!-- 定义一个Bean,名为printRoleService,它指向PrintRoleServiceImpl类的实现 -->
<bean id="printRoleService" class="org.aop.springaop.service.impl.PrintRoleServiceImpl" />
<!-- 配置AOP,定义如何将切面应用到目标Bean上 -->
<aop:config>
<!-- 引用xmlAspect作为切面,并定义了一系列的通知(before, after, after-throwing, after-returning)以及它们对应的执行时机(切点) -->
<aop:aspect ref="xmlAspect">
<!-- 在目标方法执行前执行before方法 -->
<aop:before method="before" pointcut="execution(* org.aop.springaop.service.impl.PrintRoleServiceImpl.printRole(..))" />
<!-- 在目标方法执行后,无论执行结果如何,执行after方法 -->
<aop:after method="after" pointcut="execution(* org.aop.springaop.service.impl.PrintRoleServiceImpl.printRole(..))" />
<!-- 在目标方法抛出异常时执行afterThrowing方法 -->
<aop:after-throwing method="afterThrowing" pointcut="execution(* org.aop.springaop.service.impl.PrintRoleServiceImpl.printRole(..))" />
<!-- 在目标方法正常返回时执行afterReturning方法 -->
<aop:after-returning method="afterReturning" pointcut="execution(* org.aop.springaop.service.impl.PrintRoleServiceImpl.printRole(..))" />
<aop:around method="around" pointcut="execution(* org.aop.springaop.service.impl.PrintRoleServiceImpl.printRole(..))" />
</aop:aspect>
</aop:config>
</beans>
也可以定义切点然后引入,如下所示
<?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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 定义一个切面 Bean,命名为 xmlAspect,其类型为 org.aop.springaop.aspect.XmlAspect -->
<bean id="xmlAspect" class="org.aop.springaop.aspect.XmlAspect" />
<!-- 定义一个服务 Bean,命名为 printRoleService,其类型为 PrintRoleServiceImpl 的实现类 -->
<bean id="printRoleService" class="org.aop.springaop.service.impl.PrintRoleServiceImpl" />
<!-- 配置 AOP,定义切面、切点和通知 -->
<aop:config>
<!-- 引用 xmlAspect 作为此配置中的切面 -->
<aop:aspect ref="xmlAspect">
<!-- 定义一个切点,其表达式匹配 PrintRoleServiceImpl 类的 printRole 方法 -->
<aop:pointcut id="printRole" expression="execution(* org.aop.springaop.service.impl.PrintRoleServiceImpl.printRole(..))" />
<!-- 在 printRole 方法执行前执行 before 方法 -->
<aop:before method="before" pointcut-ref="printRole"/>
<!-- 在 printRole 方法执行后执行 after 方法 -->
<aop:after method="after" pointcut-ref="printRole"/>
<!-- 在 printRole 方法抛出异常时执行 afterThrowing 方法 -->
<aop:after-throwing method="afterThrowing" pointcut-ref="printRole"/>
<!-- 在 printRole 方法正常返回时执行 afterReturning 方法 -->
<aop:after-returning method="afterReturning" pointcut-ref="printRole"/>
<aop:around method="around" pointcut-ref="printRole"/>
</aop:aspect>
</aop:config>
</beans>
使用同样的代码测试就行了
/**
* AOP主类,用于展示Spring AOP的使用。
*/
package org.aop.main;
import org.aop.springaop.verifier.RoleVerifier;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.aop.springaop.config.AopConfig; // 引入AOP配置类
import org.aop.springaop.service.RoleService; // 引入角色服务接口
import org.aop.pojo.Role; // 引入角色实体类
public class AopMain {
/**
* 应用的主入口函数。
* @param args 命令行参数
*/
public static void main(String[] args) {
// 使用注解方式配置SpringApplicationContext
ApplicationContext ctx = new AnnotationConfigApplicationContext(AopConfig.class);
// 使用XML配置文件加载Spring ApplicationContext
ApplicationContext ctx2 = new ClassPathXmlApplicationContext("spring-cfg2.xml");
// 从ApplicationContext中获取RoleService的实例
RoleService roleService = ctx.getBean(RoleService.class);
RoleVerifier roleVerifier = (RoleVerifier)roleService;
// 创建Role对象并设置其属性,然后调用printRole方法展示角色信息
Role role = new Role();
role.setId(1L);
role.setRoleName("role_name_1");
role.setNote("note_1");
/**
* 验证指定角色是否合法。
* 该代码块首先通过roleVerifier对象的verify方法对role进行验证。
* 如果验证成功,则打印"role is verified",并调用roleService的printRole方法详细打印角色信息。
*/
if (roleVerifier.verify(role)) {
System.out.println("role is verified");
roleService.printRole(role,1);
}
}
}
接下来是向通知传递参数,修改before方法,和之前的before方法不能同时存在,否则xml里配置容易飘红,尽量避免冲突
public void before(Role role) {
System.out.println("role_id= " + role.getId() +" before ......");
}
XML里的配置修改如下
<aop:before method="before" pointcut="execution(* org.aop.springaop.service.impl.PrintRoleServiceImpl.printRole(..)) and args(role)" />
使用了"and"代替了"&&" XML中&有特殊的含义,这个前边讲过,使用XML的形式也可以实现引入,将代理对象下挂到多个接口之下,这样就能够引入新的方法了,接下来修改切面代码
package org.aop.springaop.aspect;
import org.aop.pojo.Role;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aop.springaop.verifier.RoleVerifier;
public class XmlAspect {
private RoleVerifier roleVerifier = null;
public void setRoleVerifier(RoleVerifier roleVerifier) {
this.roleVerifier = roleVerifier;
}
//public void before() {
// System.out.println("before");
//}
public void before(Role role) {
System.out.println("role_id= " + role.getId() +" before ......");
}
public void after() {
System.out.println("after");
}
public void afterReturning() {
System.out.println("afterReturning");
}
public void afterThrowing() {
System.out.println("afterThrowing");
}
public void around(ProceedingJoinPoint jp) {
System.out.println("around before ......");
try {
jp.proceed();
} catch (Throwable e) {
new RuntimeException("回调目标对象方法,产生异常......");
}
System.out.println("around after ......");
}
}
修改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:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.0.xsd">
<!-- 定义切面,这里定义了一个名为xmlAspect的切面,它指向org.aop.springaop.aspect.XmlAspect类 -->
<bean id="xmlAspect" class="org.aop.springaop.aspect.XmlAspect" />
<!-- 定义一个Bean,名为printRoleService,它指向PrintRoleServiceImpl类的实现 -->
<bean id="printRoleService" class="org.aop.springaop.service.impl.PrintRoleServiceImpl" />
<!-- 配置AOP,定义如何将切面应用到目标Bean上 -->
<aop:config>
<!-- 引用xmlAspect作为切面,并定义了一系列的通知(before, after, after-throwing, after-returning)以及它们对应的执行时机(切点) -->
<aop:aspect ref="xmlAspect">
<!-- 引入增强:为匹配的类型动态地引入(继承)一个接口或实现类 -->
<!-- 匹配需要引入增强的类型的表达式,这里指匹配PrintRoleServiceImpl及其子类 -->
<!-- 指定需要引入的接口,这里是要实现RoleVerifier接口 -->
<!-- 指定实现RoleVerifier接口的默认实现类 -->
<aop:declare-parents
types-matching="org.aop.springaop.service.impl.PrintRoleServiceImpl+"
implement-interface="org.aop.springaop.verifier.RoleVerifier"
default-impl="org.aop.springaop.verifier.impl.RoleVerifierImpl"/>
<!-- 在目标方法执行前执行before方法 -->
<aop:before method="before" pointcut="execution(* org.aop.springaop.service.impl.PrintRoleServiceImpl.printRole(..))" />
<!-- 在目标方法执行后,无论执行结果如何,执行after方法 -->
<aop:before method="before" pointcut="execution(* org.aop.springaop.service.impl.PrintRoleServiceImpl.printRole(..)) and args(role)" />
<aop:after method="after" pointcut="execution(* org.aop.springaop.service.impl.PrintRoleServiceImpl.printRole(..))" />
<!-- 在目标方法抛出异常时执行afterThrowing方法 -->
<aop:after-throwing method="afterThrowing" pointcut="execution(* org.aop.springaop.service.impl.PrintRoleServiceImpl.printRole(..))" />
<!-- 在目标方法正常返回时执行afterReturning方法 -->
<aop:after-returning method="afterReturning" pointcut="execution(* org.aop.springaop.service.impl.PrintRoleServiceImpl.printRole(..))" />
<aop:around method="around" pointcut="execution(* org.aop.springaop.service.impl.PrintRoleServiceImpl.printRole(..))" />
</aop:aspect>
</aop:config>
</beans>
然后再执行测试代码即可
经典AOP实例
例如完成PrintRoleServiceImpl类中的printRole方法的切面前置和环绕通知,则printRole是AOP的连接点,先定义一个类来实现前置通知和环绕通知,要实现MethodBeforeAdvice
接口的before方法和MethodInterceptor
接口的invoke方法,如下代码所示
package org.aop.springaop.aspect;
import java.lang.reflect.Method;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.springframework.aop.MethodBeforeAdvice;
/**
* ProxyFactoryBeanAspect类实现了MethodBeforeAdvice和MethodInterceptor接口,
* 用于在Spring AOP中定义切面逻辑。
*/
public class ProxyFactoryBeanAspect implements MethodBeforeAdvice, MethodInterceptor {
/**
* 方法执行前的前置通知。
*
* @param method 将要执行的方法。
* @param params 方法的参数数组。
* @param roleService 方法执行的上下文对象。
* @throws Throwable 如果在前置通知中发生异常。
*/
@Override
public void before(Method method, Object[] params, Object roleService)
throws Throwable {
System.out.println("前置通知!!");
}
/**
* 对MethodInterceptor接口的实现,用于拦截方法调用。
*
* @param invocation 方法调用的邀请,包含目标对象和方法等信息。
* @return 返回方法的执行结果。
* @throws Throwable 如果在方法执行中发生异常。
*/
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
// 继续执行被拦截的方法
return invocation.proceed();
}
}
有了这个类之后,还需要对SpringIoC容器描述对应的信息,通过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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.0.xsd">
<!-- 定义一个bean,其类型为ProxyFactoryBeanAspect,用于切面编程 -->
<bean id="proxyFactoryBeanAspect" class="org.aop.springaop.aspect.ProxyFactoryBeanAspect" />
<!-- 设定代理类,实现对PrintRoleService接口的代理 -->
<bean id="roleService" class="org.springframework.aop.framework.ProxyFactoryBean">
<!-- 指定代理的是接口 -->
<property name="proxyInterfaces" value="org.aop.springaop.service.PrintRoleService"/>
<!-- 设置ProxyFactoryBean要代理的目标类 -->
<property name="target">
<bean class="org.aop.springaop.service.impl.PrintRoleServiceImpl" />
</property>
<!-- 定义通知,指定使用的拦截器 -->
<property name="interceptorNames">
<list>
<!-- 引入定义好的spring bean作为拦截器 -->
<value>proxyFactoryBeanAspect</value>
</list>
</property>
</bean>
</beans>
执行测试代码即可验证过,代码如下
package org.aop.main;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.aop.pojo.Role;
import org.aop.springaop.service.PrintRoleService;
public class XMLAopMain {
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring-cfg4.xml");
Role role = new Role();
role.setId(1L);
role.setRoleName("role_name");
role.setNote("note");
PrintRoleService roleService = (PrintRoleService) ctx.getBean("roleService");
roleService.printRole(role);
}
}
这样SpringAOP就用起来了,ProxyFactoryBeanAspect
实现了接口MethodBeforeAdvice
和MethodInterceptor
,然后在XML文件中配置就可以使用AOP,此外Spring对后置通知、返回通知、异常通知也分别提供了接口,分别是AfterAdvice
、AfterReturningAdvice
和Throws Advice
这是个以前的经典方式,现在已经不是主流方式了
多个切面
首先定义一个切点方法
package org.aop.springaop.aspect;
public interface MultiBean {
public void tryMulti();
}
实现类如下
package org.aop.springaop.aspect.impl;
import org.springframework.stereotype.Component;
import org.aop.springaop.aspect.MultiBean;
@Component
public class MultiBeanImpl implements MultiBean {
@Override
public void tryMulti() {
System.out.println("test multi aspects!!");
}
}
连接点定义好后,定义三个切面,如下代码所示
package org.aop.springaop.multi.aspect;
import org.aspectj.lang.annotation.*;
@Aspect
public class Aspect1{
private static final String POINTCUT_EXP = "execution(* org.aop.springaop.multi.bean.impl.MultiBeanImpl.tryMulti(..))";
@Pointcut(POINTCUT_EXP)
public void print() {
}
@Before("print()")
public void before() {
System.out.println("before 1 ......");
}
@After("print()")
public void after() {
System.out.println("after 1 ......");
}
@AfterThrowing("print()")
public void afterThrowing() {
System.out.println("afterThrowing 1 ......");
}
@AfterReturning("print()")
public void afterReturning() {
System.out.println("afterReturning 1 ......");
}
}
package org.aop.springaop.multi.aspect;
import org.aspectj.lang.annotation.*;
@Aspect
public class Aspect2 {
private static final String POINTCUT_EXP = "execution(* org.aop.springaop.multi.bean.impl.MultiBeanImpl.tryMulti(..))";
@Pointcut(POINTCUT_EXP)
public void print() {
}
@Before("print()")
public void before() {
System.out.println("before 2 ......");
}
@After("print()")
public void after() {
System.out.println("after 2 ......");
}
@AfterThrowing("print()")
public void afterThrowing() {
System.out.println("afterThrowing 2 ......");
}
@AfterReturning("print()")
public void afterReturning() {
System.out.println("afterReturning 2 ......");
}
}
package org.aop.springaop.multi.aspect;
import org.aspectj.lang.annotation.*;
@Aspect
public class Aspect3 {
private static final String POINTCUT_EXP = "execution(* org.aop.springaop.multi.bean.impl.MultiBeanImpl.tryMulti(..))";
@Pointcut(POINTCUT_EXP)
public void print() {
}
@Before("print()")
public void before() {
System.out.println("before 3 ......");
}
@After("print()")
public void after() {
System.out.println("after 3 ......");
}
@AfterThrowing("print()")
public void afterThrowing() {
System.out.println("afterThrowing 3 ......");
}
@AfterReturning("print()")
public void afterReturning() {
System.out.println("afterReturning 3 ......");
}
}
这样就有了3个切面,接下来进行配置设置
package org.aop.springaop.config;
import org.aop.springaop.multi.aspect.Aspect1;
import org.aop.springaop.multi.aspect.Aspect2;
import org.aop.springaop.multi.aspect.Aspect3;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
/**
* Spring配置类,定义了AOP的配置。
* 使用@EnableAspectJAutoProxy启用AspectJ自动代理。
* @ComponentScan注解自动扫描包下的组件。
*/
@Configuration
@EnableAspectJAutoProxy
@ComponentScan("org.aop.springaop.multi.*")
public class MultiConfig {
/**
* 定义并返回一个Aspect1的实例,该实例是一个切面。
* @return 返回一个初始化好的Aspect1实例。
*/
@Bean
public Aspect1 getAspect1() {
return new Aspect1();
}
/**
* 定义并返回一个Aspect2的实例,该实例是一个切面。
* @return 返回一个初始化好的Aspect2实例。
*/
@Bean
public Aspect2 getAspect2() {
return new Aspect2();
}
/**
* 定义并返回一个Aspect3的实例,该实例是一个切面。
* @return 返回一个初始化好的Aspect3实例。
*/
@Bean
public Aspect3 getAspect3() {
return new Aspect3();
}
}
这个时候如果执行代码,发现他们的执行顺序并不确定,很显然多个切面是无序的,Spring提供了多种方法使其有序,例如@Ordered
注解,如下代码所示
package org.aop.springaop.multi.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
@Aspect
@Order(1)
public class Aspect1{
private static final String POINTCUT_EXP = "execution(* org.aop.springaop.multi.bean.impl.MultiBeanImpl.tryMulti(..))";
@Pointcut(POINTCUT_EXP)
public void print() {
}
@Before("print()")
public void before() {
System.out.println("before 1 ......");
}
@After("print()")
public void after() {
System.out.println("after 1 ......");
}
@AfterThrowing("print()")
public void afterThrowing() {
System.out.println("afterThrowing 1 ......");
}
@AfterReturning("print()")
public void afterReturning() {
System.out.println("afterReturning 1 ......");
}
}
package org.aop.springaop.multi.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
@Aspect
@Order(2)
public class Aspect2 {
private static final String POINTCUT_EXP = "execution(* org.aop.springaop.multi.bean.impl.MultiBeanImpl.tryMulti(..))";
@Pointcut(POINTCUT_EXP)
public void print() {
}
@Before("print()")
public void before() {
System.out.println("before 2 ......");
}
@After("print()")
public void after() {
System.out.println("after 2 ......");
}
@AfterThrowing("print()")
public void afterThrowing() {
System.out.println("afterThrowing 2 ......");
}
@AfterReturning("print()")
public void afterReturning() {
System.out.println("afterReturning 2 ......");
}
}
package org.aop.springaop.multi.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
@Aspect
@Order(3)
public class Aspect3 {
private static final String POINTCUT_EXP = "execution(* org.aop.springaop.multi.bean.impl.MultiBeanImpl.tryMulti(..))";
@Pointcut(POINTCUT_EXP)
public void print() {
}
@Before("print()")
public void before() {
System.out.println("before 3 ......");
}
@After("print()")
public void after() {
System.out.println("after 3 ......");
}
@AfterThrowing("print()")
public void afterThrowing() {
System.out.println("afterThrowing 3 ......");
}
@AfterReturning("print()")
public void afterReturning() {
System.out.println("afterReturning 3 ......");
}
}
这样就会有序执行,SpringAOP的实现方式是动态代理,在多个代理的情况下,Spring底层是通过责任链模式来处理多个切面的
还可以让切面实现org.springframework.core.Ordered
接口,它定义了一个getOrder
方法,取代@Order
注解,可以将代码改写为
package org.aop.springaop.multi.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.core.Ordered;
@Aspect
public class Aspect1 implements Ordered{
private static final String POINTCUT_EXP = "execution(* org.aop.springaop.multi.bean.impl.MultiBeanImpl.tryMulti(..))";
@Pointcut(POINTCUT_EXP)
public void print() {
}
@Before("print()")
public void before() {
System.out.println("before 1 ......");
}
@After("print()")
public void after() {
System.out.println("after 1 ......");
}
@AfterThrowing("print()")
public void afterThrowing() {
System.out.println("afterThrowing 1 ......");
}
@AfterReturning("print()")
public void afterReturning() {
System.out.println("afterReturning 1 ......");
}
/**
* 获取当前对象的优先级顺序。
* 这个方法没有参数。
*
* @return 返回一个整数,表示当前对象的优先级顺序。返回的值越小,优先级越高。
*/
@Override
public int getOrder() {
return 1;
}
}
package org.aop.springaop.multi.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.core.Ordered;
@Aspect
public class Aspect2 implements Ordered{
private static final String POINTCUT_EXP = "execution(* org.aop.springaop.multi.bean.impl.MultiBeanImpl.tryMulti(..))";
@Pointcut(POINTCUT_EXP)
public void print() {
}
@Before("print()")
public void before() {
System.out.println("before 2 ......");
}
@After("print()")
public void after() {
System.out.println("after 2 ......");
}
@AfterThrowing("print()")
public void afterThrowing() {
System.out.println("afterThrowing 2 ......");
}
@AfterReturning("print()")
public void afterReturning() {
System.out.println("afterReturning 2 ......");
}
@Override
public int getOrder() {
return 2;
}
}
package org.aop.springaop.multi.aspect;
import org.aspectj.lang.annotation.*;
import org.springframework.core.Ordered;
@Aspect
public class Aspect3 implements Ordered{
private static final String POINTCUT_EXP = "execution(* org.aop.springaop.multi.bean.impl.MultiBeanImpl.tryMulti(..))";
@Pointcut(POINTCUT_EXP)
public void print() {
}
@Before("print()")
public void before() {
System.out.println("before 3 ......");
}
@After("print()")
public void after() {
System.out.println("after 3 ......");
}
@AfterThrowing("print()")
public void afterThrowing() {
System.out.println("afterThrowing 3 ......");
}
@AfterReturning("print()")
public void afterReturning() {
System.out.println("afterReturning 3 ......");
}
@Override
public int getOrder() {
return 3;
}
}
也可以通过XML的方式进行配置,只需要给<aop:aspect>
增加一个属性order
排序即可