文章目录
1. 简介
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如:事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP 基于动态代理。如果要代理的对象,实现了某个接口,那么Spring AOP会使用 JDK 代理,去创建代理对象;而对于没有实现接口的对象,使用 CGLIB 生成一个被代理对象的子类来作为代理。
AspectJ 算是 Java 生态系统中最完整的 AOP 框架,Spring AOP 也已经集成了 AspectJ。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单。
Spring AOP 和 AspectJ AOP 有什么区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。
Spring AOP 基于代理,而 AspectJ 基于字节码操作。
当切面比较少,两者性能差异不大;当切面比较多的话,最好选择 AspectJ ,它比 Spring AOP 快很多。
2. 术语
2.1 切面(Aspect)
切面是通知(Advice)和切点(Pointcut)的结合。
通知和切点共同定义了切面的全部内容——它是什么,在何时和何处完成其功能。
2.2 通知(Advice)
通知是切面开启后,切面的方法。
- 前置通知(before):在目标方法或环绕通知调用前执行。
- 后置通知(after):在目标方法或环绕通知完成后执行。无论是否抛出异常,都会执行。
- 返回通知(afterReturning):在目标方法或环绕通知完成后执行。
- 异常通知(afterThrowing):在目标方法或环绕通知产生异常后执行。
- 环绕通知(around):包裹了目标方法,可同时定义前置通知和后置通知。
2.3 引入(Introduction)
引入允许我们在现有的类里添加自定义的类和方法。
2.4 切点(Pointcut)
切点是指通知(Advice)所要织入(Weaving)的具体位置。
切点定义了在何处工作,也就是真正被切入的地方,也就是在哪个方法应用通知。切点的定义会匹配通知所有要织入的一个或多个连接点。
2.5 连接点(Join point)
连接点是在应用执行过程中能够插入切面(Aspect)的一个点。这些点可以是调用方法时、甚至修改一个字段时。
连接点是一个虚拟的概念,可以理解为所有满足切点扫描条件的所有的时机。
切点表达式 | 描述 |
---|---|
execution | 匹配方法执行的连接点,这是最常用的匹配。 |
within | 匹配连接点指定的包。 |
args | 匹配参数为指定类型的方法。 |
this | 匹配代理对象。 |
target | 匹配目标对象。 |
@within | 匹配带有指定注解的类。 |
@annotaion | 匹配带有指定注解的方法。 |
@args | 匹配带有指定注解的类作为参数的方法。 |
2.6 织入(Weaving)
织入是一个生成代理对象的过程,是把切面应用到目标对象并创建新的代理对象的过程。切面在指定的连接点被织入到目标对象中。在目标对象的生命周期里有多个点可以织入。
- 编译器:切面在目标类编译时被织入。这种方式需要特殊的编译器。
- 类加载期:切面在目标类被引入应用之前增强该目标类的字节码。
- 运行期:切面在应用运行的某个时刻被织入。
3. 使用
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
3.1 execution
匹配方法执行的连接点
execution(modifiers-pattern? ret-type-pattern declaring-type-pattern?name-pattern(param-pattern) throws-pattern?)
modifiers-pattern
:方法的可见性。ret-type-pattern
:方法的返回值类型。declaring-type-pattern
:方法所在类的全路径名。name-pattern
:方法名类型。param-pattern
:方法的参数类型。throws-pattern
:方法抛出的异常类型。
创建接口和接口实现
public interface AopService {
void first(Integer i);
}
@Service("aopService")
public class AopServiceImpl implements AopService {
@Override
public void first(Integer i) {
if (i == null) {
System.out.println("AopServiceImpl first(" + i + ") throw new NullPointerException()");
throw new NullPointerException();
}
System.out.println("AopServiceImpl first(" + i + ")");
}
}
定义切面
@Aspect
@Component
public class ExecutionAop {
// 匹配AopService下带任意参数的方法
@Pointcut("execution(public * com.shpun.service.AopService.*(..))")
public void executionAopPointcut() {
}
@Before("executionAopPointcut()")
public void doBefore() {
System.out.println("ExecutionAop Before");
}
@After("executionAopPointcut()")
public void doAfter() {
System.out.println("ExecutionAop After");
}
@Around("executionAopPointcut()")
public Object doAround(ProceedingJoinPoint joinPoint) {
System.out.println("ExecutionAop Around before");
Object result;
try {
result = joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e.getMessage(), e);
}
System.out.println("ExecutionAop Around after");
return result;
}
@AfterReturning("executionAopPointcut()")
public void doAfterReturning() {
System.out.println("ExecutionAop AfterReturning");
}
@AfterThrowing("executionAopPointcut()")
public void doAfterThrowing() {
System.out.println("ExecutionAop AfterThrowing");
}
}
测试
@SpringBootTest
public class ExecutionAopTest {
@Autowired
private AopService aopService;
@Test
public void executionAopTest() {
aopService.first(1);
System.out.println("------");
aopService.first(null);
}
}
结果打印
ExecutionAop Around before
ExecutionAop Before
AopServiceImpl first(1)
ExecutionAop AfterReturning
ExecutionAop After
ExecutionAop Around after
------
ExecutionAop Around before
ExecutionAop Before
AopServiceImpl first(null) throw new NullPointerException()
ExecutionAop AfterThrowing
ExecutionAop After
3.2 within
匹配连接点指定的包
within(declaring-type-pattern)
declaring-type-pattern
:方法所在类的全路径名。
定义切面
@Aspect
@Component
public class WithinAop {
// 匹配com.shpun.service包及子包下的所有类
@Pointcut("within(com.shpun.service..*)")
public void withinAopPointcut() {
}
...
// 和 ExecutionAop 中类似,注意修改切点
...
}
3.3 args
匹配参数为指定类型的方法
args(param-pattern)
param-pattern
:方法的参数类型。
定义切面
@Aspect
@Component
public class ArgsAop {
// 只使用args(java.lang.Integer),条件太广泛会报错
// 匹配com.shpun.service包及子包下的所有类的,有任意参数的方法 并且 只有一个参数类型为Integer的方法
@Pointcut("execution(* com.shpun.service..*(..)) && args(java.lang.Integer)")
public void argsAopPointcut() {
}
...
// 和 ExecutionAop 中类似,注意修改切点
...
}
3.4 bean
@Aspect
@Component
public class BeanAop {
/**
* 匹配bean name
*/
@Pointcut("bean(aopService)")
public void beanAopPointcut() {
}
}
3.5 this 与 target
匹配代理对象 this(declaring-type-pattern)
匹配目标对象 target(declaring-type-pattern)
declaring-type-pattern
:方法所在类的全路径名,只能指定类或接口。
this 和 target 区别:
- 纯接口:this 与 target 表现一致
- 纯类:this 与 target 表现一致
- 实现某个接口的类:this 与 target 不一致
下面以实现某个接口的类为例:
定义切面
@Aspect
@Component
public class ThisAop {
@Pointcut("this(com.shpun.service.impl.AopServiceImpl)")
public void thisAopPointcut() {
}
...
// 和 ExecutionAop 中类似,注意修改切点
...
}
定义切面
@Aspect
@Component
public class TargetAop {
@Pointcut("target(com.shpun.service.impl.AopServiceImpl)")
public void targetAopPointcut() {
}
...
// 和 ExecutionAop 中类似,注意修改切点
...
}
修改AopService,添加带默认实现的方法second()
public interface AopService {
void first(Integer i);
default void second() {
System.out.println("AopService second()");
}
}
测试
@SpringBootTest
public class ThisAndTargetAopTest {
@Autowired
private AopServiceImpl aopServiceImpl;
@Test
public void aopServiceImplTest() {
aopServiceImpl.first(1);
System.out.println("------");
aopServiceImpl.second();
}
}
结果打印
TargetAop Around before
TargetAop Before
ThisAop Around before
ThisAop Before
AopServiceImpl first(1)
ThisAop AfterReturning
ThisAop After
ThisAop Around after
TargetAop AfterReturning
TargetAop After
TargetAop Around after
------
TargetAop Around before
TargetAop Before
AopService second()
TargetAop AfterReturning
TargetAop After
TargetAop Around after
3.6 @within
匹配带有指定注解的类
@within(annotation-type)
annotation-type
:注解的全路径。
添加注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetType {
}
定义切面
@Aspect
@Component
public class AnnotationAop {
// 匹配 类有注解@TargetType
@Pointcut("@within(com.shpun.annotaion.TargetType)")
public void annotationAopPointcut() {
}
...
// 和 ExecutionAop 中类似,注意修改切点
...
}
修改AopServiceImpl,类上添加注解@TargetType
@Service("aopService")
@TargetType
public class AopServiceImpl implements AopService {
@Override
public void first(Integer i) {
if (i == null) {
System.out.println("AopServiceImpl first(" + i + ") throw new NullPointerException()");
throw new NullPointerException();
}
System.out.println("AopServiceImpl first(" + i + ")");
}
}
测试
@SpringBootTest
public class AnnotationAopTest {
@Autowired
private AopService aopService;
@Test
public void withinTest() {
aopService.first(1);
System.out.println("------");
aopService.second();
}
}
结果打印
AnnotationAop Around before
AnnotationAop Before
AopServiceImpl first(1)
AnnotationAop AfterReturning
AnnotationAop After
AnnotationAop Around after
------
AopService second()
3.7 @annotaion
匹配带有指定注解的方法
@annotation(annotation-type)
annotation-type
:注解的全路径。
添加注解
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetMethod {
}
定义切面
@Aspect
@Component
public class AnnotationAop {
// 匹配 方法有注解@TargetMethod
@Pointcut("@annotation(com.shpun.annotaion.TargetMethod)")
public void annotationAopPointcut() {
}
...
// 和 ExecutionAop 中类似,注意修改切点
...
}
修改AopServiceImpl,给方法first()
添加注解@TargetMethod
@Service("aopService")
public class AopServiceImpl implements AopService {
@TargetMethod
@Override
public void first(Integer i) {
if (i == null) {
System.out.println("AopServiceImpl first(" + i + ") throw new NullPointerException()");
throw new NullPointerException();
}
System.out.println("AopServiceImpl first(" + i + ")");
}
}
测试
@SpringBootTest
public class AnnotationAopTest {
@Autowired
private AopService aopService;
@Test
public void annotationTest() {
aopService.first(1);
System.out.println("------");
aopService.second();
}
}
打印结果
AnnotationAop Around before
AnnotationAop Before
AopServiceImpl first(1)
AnnotationAop AfterReturning
AnnotationAop After
AnnotationAop Around after
------
AopService second()
3.8 @args
匹配带有指定注解的类作为参数的方法
@args(annotation-type)
annotation-type
:注解的全路径。
定义注解
@Target({ElementType.TYPE, ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface TargetParameter {
}
定义切面
@Aspect
@Component
public class AnnotationAop {
// 匹配 com.shpun.service包及子包下的所有类的,有任意参数的方法 并且 类有注解@TargetParameter作为方法的唯一参数
@Pointcut("execution(* com.shpun.service..*(..)) && @args(com.shpun.annotaion.TargetParameter)")
public void annotationAopPointcut() {
}
...
// 和 ExecutionAop 中类似,注意修改切点
...
}
添加User类,用于当作方法的参数
@TargetParameter
public class User {
}
修改AopService,添加带默认实现的方法third()
public interface AopService {
void first(Integer i);
default void second() {
System.out.println("AopService second()");
}
default void third(User user) {
System.out.println("AopService third()");
}
}
测试
@SpringBootTest
public class AnnotationAopTest {
@Autowired
private AopService aopService;
@Test
public void argsTest() {
aopService.first(1);
System.out.println("------");
aopService.second();
System.out.println("------");
aopService.third(new User());
}
}
结果打印
AopServiceImpl first(1)
------
AopService second()
------
AnnotationAop Around before
AnnotationAop Before
AopService third()
AnnotationAop AfterReturning
AnnotationAop After
AnnotationAop Around after
3.9 @DeclareParents
添加待织入的接口和实现
public interface TestService {
void test();
}
@Service("testService")
public class TestServiceImpl implements TestService {
@Override
public void test() {
System.out.println("TestServiceImpl test()");
}
}
定义切面
@Aspect
@Component
public class IntroductionAop {
/**
* value 要织入的目标类,全路径,+表示及其子类
* defaultImpl 为待织入的实现类
*/
@DeclareParents(value = "com.shpun.service.AopService+", defaultImpl = TestServiceImpl.class)
public TestService testService;
}
测试
@SpringBootTest
public class IntroductionAopTest {
@Autowired
private AopService aopService;
@Test
public void test() {
aopService.first(1);
TestService testService = (TestService) aopService;
testService.test();
}
}
打印结果
AopServiceImpl first(1)
TestServiceImpl test()
4. 总结
- 方法被多个切面环绕时,多个切面像栈一样,按后入先出的顺序执行。
- 后置通知(after)像finally,不管是否报错,都会执行。
参考:
Spring AOP 官方文档
Spring AOP 面向切面编程入门
Spring AOP SpringBoot集成
Spring AOP切点表达式用法总结
Spring AOP术语:连接点和切点的区别