Sprng AOP 其实就是代理,它可以在运行时动态地将代码切入到类的指定方法、指定位置上。即可以使用 AOP 在方法执行前或执行之后(批量)执行一些额外的操作。AOP 采用第三方库实现动态代理,可以以接口或父类的形式实现代理。
环境说明
- JDK 17
- Spring 6.0.6
- Spring-aspects 6.0.6
环境准备
添加 Spring 和 spring-aspects 依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>6.0.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>6.0.6</version>
</dependency>
使用配置实现AOP
创建 spring 配置文件,引入 aop 命名空间并为其指定 xsd 规范文件(用于编写标签时给出提示,防止配置出错):
<?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 http://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
然后需要明确哪个类的哪个方法需要被切入,在方法执行之前还是之后切入以及配置 spring 进行切入。
首先指定需要切入的方法为 Student 的 study() 方法,然后将 Student 类(目标 Bean)交由容器管理:
public class Student {
public void study(){
System.out.println(str + "室友还在打游戏,我狠狠的学Java,太爽了");
}
}
<bean id="student" class="com.test.entity.Student"/>
创建一个新的类,并将要执行的操作封装为一个方法,同时这个类也要注册为 Bean:
public class StudentAOP {
public void beforeStudy(){
System.out.println("我和富二代考进了同一所大学,我们都有光明的未来!!!");
}
public void afterStudy() {
System.out.println("为什么毕业了他们都继承家产,我还倒给他们打工,我努力的意义在哪里...");
}
}
<bean id="studentAOP" class="com.test.entity.StudentAOP"/>
之后配置切入的相关信息:
<aop:config>
<aop:pointcut id="test" expression="execution(* com.test.entity.Student.study(String))"/>
<aop:aspect ref="studentAOP">
<aop:before method="beforeStudy" pointcut-ref="test"/>
<aop:after method="afterStudy" pointcut-ref="test"/>
</aop:aspect>
</aop:config>
上述配置文件首先配置了一个切点,后续切入操作通过切点 id 进行切入,并且切点通过 expression 表达式来指定需要切入的方法(参考)这里使用 execution),格式:修饰符 包名.类名.方法名称(方法参数)
- 修饰符:public、protected、private、包括返回值类型、static等等(使用*代表任意修饰符)
- 包名:如com.test(* 代表全部,比如com.*代表com包下的全部包)
- 类名:使用*也可以代表包下的所有类
- 方法名称:可以使用*代表全部方法
- 方法参数:填写对应的参数即可,比如(String, String),也可以使用*来代表任意一个参数,使用…代表所有参数。
接着指定将要将要切入的 Bean ,并通过 <aop:before >等标签添加切入方式。
如果需要在切入方法中获取目标方法的参数,可以为切入方法添加一个JoinPoint参数,通过该参数可以获取目标方法的参数数组:
public void study(String str){
System.out.println(str + "室友还在打游戏,我狠狠的学Java,太爽了");
}
public void afterStudy(JoinPoint point) {
System.out.println(point.getArgs()[0] + "为什么毕业了他们都继承家产,我还倒给他们打工,我努力的意义在哪里...");
}
注意需要同步修改目标方法的切点配置:
<aop:pointcut id="test" expression="execution(* com.test.entity.Student.study(String))"/>
环绕方法可以自定义目标方法执行前后需要执行的操作,相当于直接代理了目标方法:
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕方法开始前");
joinPoint.proceed();
System.out.println("参数0:" + joinPoint.getArgs()[0]);
System.out.println("环绕方法开始后");
};
<aop:config>
<aop:pointcut id="test" expression="execution(* com.test.entity.Student.study(String))"/>
<aop:aspect ref="studentAOP">
<aop:before method="beforeStudy" pointcut-ref="test"/>
<aop:after method="afterStudy" pointcut-ref="test"/>
<aop:around method="around" pointcut-ref="test"/>
</aop:aspect>
</aop:config>
注意如果目标方法有返回值,那么环绕方法也需要有对应返回值。
AOP 中的术语:
- 通知(Advice): AOP 框架中的增强处理,通知描述了切面何时执行以及如何执行增强处理,也就是我们上面编写的方法实现。
- 连接点(join point): 连接点表示应用执行过程中能够插入切面的一个点,这个点可以是方法的调用、异常的抛出,实际上就是我们在方法执行前或是执行后需要做的内容。
- 切点(PointCut): 可以插入增强处理的连接点,可以是方法执行之前也可以方法执行之后,还可以是抛出异常之类的。
- 切面(Aspect): 切面是通知和切点的结合,我们之前在xml中定义的就是切面,包括很多信息。
- 引入(Introduction):引入允许我们向现有的类添加新的方法或者属性。
- 织入(Weaving): 将增强处理添加到目标对象中,并创建一个被增强的对象,我们之前都是在将我们的增强处理添加到目标对象,也就是织入(这名字挺有文艺范的)
使用接口实现AOP
原理类似于动态代理,通过一个类实现 Advice 接口,并配置 aop:advisor 标签,可以实现在方法开始执行之前或是执行之后会调用我们实现的接口:
public class AOP implements MethodBeforeAdvice, AfterReturningAdvice {
@Override
public void before(Method method, Object[] args, Object target) {
System.out.println("通过Advice实现AOP");
}
@Override
public void afterReturning(Object returnValue, Method method, Object[] args, Object target) {
System.out.println("After");
}
}
<bean id="student" class="com.test.entity.Student"/>
<bean id="aop" class="com.test.entity.AOP"/>
<aop:config>
<aop:pointcut id="test" expression="execution(* com.test.entity.Student.study())"/>
<aop:advisor advice-ref="aop" pointcut-ref="test"/>
</aop:config>
通过实现 MethodInterceptor 接口可以实现类似于环绕方法的效果:
public class AOP implements MethodInterceptor{
@Override
public Object invoke(MethodInvocation invocation) throws Throwable {
System.out.println("环绕之前");
invocation.proceed();
System.out.println("环绕之后");
return null;
}
}
使用注解实现AOP
首先编写配置类主类:
@EnableAspectJAutoProxy
@ComponentScan("com.test.entity")
@Configuration
public class MainConfig {
}
然后通过@Component 注解注册实体类:
@Component
public class Student {
public void study(){
System.out.println("室友还在打游戏,我狠狠的学Java,太爽了");
}
}
接着在定义AOP增强操作的类上添加@Aspect
注解和@Component
将其注册为Bean,对于增强方法,直接在方法之上添加对应注解即可实现 AOP, 同时也可以同xml方式一样给方法添加 JoinPoint 参数来获取目标方法的参数信息:
@Aspect
@Component
public class StudentAOP {
@Before("execution(* com.test.entity.Student.study())")
public void beforeStudy(){
System.out.println("我和富二代考进了同一所大学,我们都有光明的未来!!!");
}
@After("execution(* com.test.entity.Student.study())")
public void afterStudy() {
System.out.println("为什么毕业了他们都继承家产,我还倒给他们打工,我努力的意义在哪里...");
}
@Around("execution(* com.test.entity.Student.study())")
public void around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("环绕方法开始前");
joinPoint.proceed();
System.out.println("环绕方法开始后");
};
}