文章目录
前言
Spring AOP,即Aspect-Oriented Programming的缩写,意为面向切面编程,是Spring框架中的一个重要内容。它是一种编程范式,用于在不修改源代码的情况下向现有应用程序添加新功能。
一、基本概念
- 切面(Aspect):切面是AOP的核心概念,它定义了在何时、何地以及以何种方式执行通知(Advice)。切面由通知和切入点(Pointcut)共同组成,用于描述在哪些连接点(JoinPoint)上执行哪些增强(Advice)操作。
- 连接点(JoinPoint):连接点是程序执行过程中的一个点,如方法的执行或异常的抛出等。在Spring AOP中,连接点通常指的是方法的执行点。
- 切入点(Pointcut):切入点用于定义通知将要应用的具体连接点。它是一组连接点的集合,通过表达式(如正则表达式或AspectJ表达式)来指定哪些连接点应该被增强。
- 通知(Advice):通知定义了切面要完成的工作以及何时执行这些工作。它是增强功能的实际代码,可以在连接点之前、之后或抛出异常时执行。Spring AOP支持多种类型的通知,包括前置通知(Before)、后置通知(After)、返回通知(AfterReturning)、异常通知(AfterThrowing)和环绕通知(Around)。
- 目标对象(Target):目标对象是被增强的对象,它包含主业务逻辑。在AOP中,目标对象不会直接执行其方法,而是通过代理对象来执行,代理对象会在目标方法执行前后插入增强代码。
- 代理对象(Proxy):代理对象是Spring AOP通过动态代理技术为目标对象创建的一个对象。代理对象会在目标方法执行前后插入通知代码,从而实现增强功能。根据目标对象是否实现接口,Spring AOP会使用JDK动态代理或CGLIB代理来生成代理对象。
- 织入(Weaving):织入是将切面应用到目标对象以创建代理对象的过程。在Spring AOP中,织入通常发生在运行时,通过动态代理技术实现。
二、Pointcut切点表达式
1. 通配符
通配符 | 说明 |
---|---|
* | 匹配任何数量字符。在类型模式中,它通常用于匹配任何类型或类名的一部分。例如,*Service 可以匹配任何以 Service 结尾的类名。 |
. . | 匹配任何数量字符的重复。在类型模式中,它用于匹配任何数量的子包或类名中的任意部分。例如,com.example…* 可以匹配 com.example 包及其所有子包下的任何类。在方法参数模式中,它用于匹配任何数量的参数 |
+ | 匹配指定类型的子类型。这个通配符仅能作为后缀放在类型模式后边。例如,java.lang.Number+ 匹配 java.lang 包下的任何 Number 的子类型,如 Integer、Double 等。 |
示例:
- java.lang.String:精确匹配 String 类型。
- java.*.String:匹配 java 包下的任何一级子包中的 String 类型,但不包括更深层次的子包,如 java.lang.String 匹配,但 java.lang.ss.String 不匹配。
- java…*:匹配 java 包及其所有子包下的任何类型,如 java.lang.String、java.util.List 等。
- java.lang.*ing:匹配 java.lang 包下所有以 ing 结尾的类型。
2、execution 表达式
execution 是最常用的切点指示器,用于匹配方法执行的连接点。其语法如下:
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern? name-pattern(param-pattern) throws-pattern?)
表达式 | 说明 |
---|---|
modifiers-pattern?: | 方法的修饰符(如 public、protected)模式,可选。 |
ret-type-pattern: | 返回类型模式。 |
declaring-type-pattern?: | 声明类型模式(即类名),可选,表示方法所在的类。 |
name-pattern(param-pattern): | 方法名(参数模式)。 |
throws-pattern?: | 抛出的异常类型模式,可选。 |
示例:
// 匹配 com.example.service 包下所有类的所有方法执行。
@Pointcut("execution(* com.example.service.*.*(..))")
public void serviceLayerExecution() {}
3、within 表达式
within 指示器用于匹配指定类型内所有方法的执行
// 匹配 com.example.service 包及其子包下所有类的所有方法执行。
@Pointcut("within(com.example.service..*)")
public void withinServicePackage() {}
4、this 和 target 表达式
this 和 target 指示器分别用于匹配当前 AOP 代理对象的引用和代理对象的目标对象的引用。
@Pointcut("this(com.example.MyType)")
public void thisIsMyType() {}
@Pointcut("target(com.example.MyType)")
public void targetIsMyType() {}
5、args 表达式
args 指示器用于匹配运行时方法参数为指定类型的连接点。
// 匹配所有方法,其参数列表中包含至少一个 java.io.Serializable 类型的参数。
@Pointcut("args(java.io.Serializable)")
public void methodWithSerializableParam() {}
6、@annotation 表达式
@annotation 指示器用于匹配那些方法上标注了特定注解的连接点。
// 匹配所有标注了 @Service 注解的方法
@Pointcut("@annotation(org.springframework.stereotype.Service)")
public void serviceMethods() {}
7、组合表达式
切点表达式还可以使用逻辑运算符(如 &&、||、!)进行组合,以构建更复杂的切点表达式。
// 匹配 com.example.service 包下所有被 @Transactional 注解标注的方法。
@Pointcut("execution(* com.example.service.*.*(..)) && @annotation(org.springframework.transaction.annotation.Transactional)")
public void transactionalServiceMethods() {}
三、实现方式
Spring AOP提供了多种实现方式,包括基于代理的AOP、@AspectJ注解驱动的切面、纯POJO切面以及注入式AspectJ切面。其中,基于代理的AOP和@AspectJ注解驱动的切面是较为常用的方式。
1、基于代理的AOP(不推荐)
引入pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定义UserService接口
public interface UserService {
public String query();
}
实现类UserServiceImpl
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public String query() {
return "查询用户信息!";
}
}
定义切面
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class MyBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object[] args, Object target) throws Throwable {
// 在目标方法执行之前执行的逻辑
System.out.println("Before method: " + method.getName());
}
}
xml配置
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 定义目标 bean -->
<bean id="UserService" class="com.example.UserServiceImpl"/>
<!-- 定义 advice bean -->
<bean id="myBeforeAdvice" class="com.example.MyBeforeAdvice"/>
<!-- 配置代理 -->
<bean id="myServiceProxy" class="org.springframework.aop.framework.ProxyFactoryBean">
<property name="target" ref="UserService"/>
<property name="interceptorNames">
<list>
<value>myBeforeAdvice</value>
</list>
</property>
</bean>
</beans>
测试类
@Component
public class AspectTest implements ApplicationRunner {
@Autowired
private UserService userService;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("AspectTest = " + userService.query());
}
}
结果
2、@AspectJ注解驱动的切面
引入pom
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
定义UserService接口
public interface UserService {
public String query();
}
实现类UserServiceImpl
import org.springframework.stereotype.Service;
@Service
public class UserServiceImpl implements UserService {
@Override
public String query() {
return "查询用户信息!";
}
}
定义切面
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
@Aspect
@Component
public class LoggingAspect {
// 定义一个切入点表达式,匹配service包下所有类的所有方法
@Pointcut("execution(* fly.aspect.UserService.*(..))")
public void serviceCut() {
}
// 在切点处,方法执行前执行此通知
@Before("serviceCut()")
public void before(JoinPoint joinPoint) {
System.out.println("Method is being before.");
}
// 在切点处,方法执行后执行此通知
@After("serviceCut()")
public void after(JoinPoint joinPoint) {
System.out.println("Method is being after.");
}
// 环绕通知,环绕增强,相当于MethodInterceptor
@Around("serviceCut()")
public Object Around(ProceedingJoinPoint joinPoint) throws Throwable {
System.out.println("Method is being Around before.");
Object o = joinPoint.proceed();
System.out.println("Method is being Around after result : " + o);
return o;
}
// 后置返回通知
@AfterReturning(returning = "result", pointcut = "serviceCut()")
public void doAfterReturning(Object result) throws Throwable {
System.out.println("Method is being Return : " + result);
}
// 后置异常通知
@AfterThrowing("serviceCut()")
public void throwing(JoinPoint jp) {
System.out.println("Method is being throwing : ");
}
}
测试类
@Component
public class AspectTest implements ApplicationRunner {
@Autowired
private UserService userService;
@Override
public void run(ApplicationArguments args) throws Exception {
System.out.println("AspectTest = " + userService.query());
}
}
结果
总结
Spring AOP(Aspect-Oriented Programming,面向切面编程)在Spring框架中扮演着至关重要的角色。它的主要意义在于提供了一种强大的机制来模块化横切关注点(cross-cutting concerns),这些关注点通常与业务逻辑没有直接关联,但又是多个业务模块所共有的,比如日志记录、事务管理、安全控制、性能监控等。