Spring AOP
目录
前言
Spring AOP是一种面向切面编程的实现,通过切面(Aspect)对特定方法进行统一处理,如日志、事务管理等。AOP的核心包括切点(Pointcut)和通知(Advice),支持多种通知类型(@Around、@Before等)。Spring AOP基于动态代理实现,包括JDK动态代理和CGLIB代理。AOP的优势在于减少冗余代码,增强功能而不侵入原有逻辑。
一、AOP 概述
Spring 框架的两大核心:IoC 和 AOP;
AOP 是 Aspect Oriented Programming(面向切面编程);
切面指的是一类特定的问题,比如使用拦截器实现网站用户的强制登录,统一数据返回格式,统一异常处理等,都是 AOP 思想的应用;
AOP 是一种思想,用于对某一类事情进行集中处理;这种思想的方法有多种实现,Spring AOP 是其中一种实现,初次之外还有 AspectJ,CGLIB等;
AOP 的优势:
减少了冗余代码,提高了效率;可以在不侵入原有方法的基础上,对原有功能实现增强;
例如:针对某个复杂的接口,记录接口中每个方法的运行时间:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class RuntimeAspect {
/**
* 记录方法耗时
*/
@Around("execution(* com.example.demo.controller.*.*(..))")
public Object getRuntime(ProceedingJoinPoint joinPoint) throws Throwable {
// 1. 方法执行前
long start = System.currentTimeMillis();
// 2. 方法执行
Object object = joinPoint.proceed();
// 3. 方法执行后
long end = System.currentTimeMillis();
log.info("方法运行时间:{}ms", end - start);
return object;
}
}
并没有修改原有的代码,就能实现统计所有接口运行的时间;
二、Spring AOP 介绍
1. 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
2. 编写切面类
1. 切面类
切面(Aspect) = 切点(Pointcut) + 通知(Advice);
通过切面可以描述 AOP 程序,需要针对哪些方法,在什么时候执行什么操作;
切面所在的类就是切面类,也是被 @Aspect 注解修饰的类;
切面中定义的方法就是一个切面,方法体是通知,通知注解中的切点表达式是切点;
切面类示例:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class AspectDemo1 {
@Around("execution(* com.example.aop.controller.*.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint){
log.info("do around before...");
Object object;
try {
object = joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
log.info("do around after");
return object;
}
}
2. 通知类型
@Around 注解,表示环绕通知,在目标方法(ProceedingJointPoint.proceed())执行前和执行后,都会执行,对原有方法增强;
ProceedingJointPoint.proceed():表示执行原始方法;
环绕通知的返回值必须是 Object 类型,否则目标方法执行完,接收不到返回值;
@Before 注解,表示前置通知,目标方法执行前被执行;
@After 注解:表示后置通知,目标方法执行后被执行,无论是否有异常,都会被执行;
@AfterReturning:返回后通知,目标方法执行后被执行,有异常不会被执行;
@AfterThrowing:异常后通知,发生异常后执行;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class AspectDemo1 {
@Before("execution(* com.example.aop.controller.*.*(..))")
public void doBefore(){
log.info("do before...");
}
@Around("execution(* com.example.aop.controller.*.*(..))")
public Object doAround(ProceedingJoinPoint joinPoint){
log.info("do around before...");
Object object;
try {
object = joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
log.info("do around after");
return object;
}
@After("execution(* com.example.aop.controller.*.*(..))")
public void doAfter(){
log.info("do after...");
}
@AfterReturning("execution(* com.example.aop.controller.*.*(..))")
public void doAfterReturning(){
log.info("do after returning...");
}
@AfterThrowing("execution(* com.example.aop.controller.*.*(..))")
public void doAfterThrowing(){
log.info("do after throwing...");
}
}
定义测试类:
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping("/t1")
public String t1(){
log.info("执行t1方法...");
return "t1";
}
@RequestMapping("/t2")
public Integer t2(){
log.info("执行t2方法...");
Integer n = 10 / 0;
return 1;
}
}
执行 t1:

执行 t2:

3. 切点
切点可以使用 @Pointcut 注解进行定义;
上述代码存在大量重复的切点表达式,可以单独定义一个公共的切点解决重复问题:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class AspectDemo1 {
@Pointcut("execution(* com.example.aop.controller.*.*(..))")
public void pt(){}
@Before("pt()")
public void doBefore(){
log.info("do before...");
}
@Around("pt()")
public Object doAround(ProceedingJoinPoint joinPoint){
log.info("do around before...");
Object object;
try {
object = joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
log.info("do around after");
return object;
}
@After("pt()")
public void doAfter(){
log.info("do after...");
}
@AfterReturning("pt()")
public void doAfterReturning(){
log.info("do after returning...");
}
@AfterThrowing("pt()")
public void doAfterThrowing(){
log.info("do after throwing...");
}
}
public 修饰的切点也可以在其它的切面类中使用(注意写全限定类名加切点名):
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class AspectDemo2 {
@Before("com.example.aop.aspect.AspectDemo1.pt()")
public void doBefore(){
log.info("aspect2 do before...");
}
@Around("com.example.aop.aspect.AspectDemo1.pt()")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
log.info("aspect2 do around before...");
Object object = joinPoint.proceed();
log.info("aspect2 do around after...");
return object;
}
@After("com.example.aop.aspect.AspectDemo1.pt()")
public void doAfter(){
log.info("aspect2 do after...");
}
}
4. 切面优先级
不同切面的优先级可以使用 @Order 注解定义,括号里写整数,数值越大,优先级越低;
定义优先级示例:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
@Order(4)
@Slf4j
@Aspect
@Component
public class AspectDemo1 {
@Pointcut("execution(* com.example.aop.controller.*.*(..))")
public void pt(){}
@Before("pt()")
public void doBefore(){
log.info("do before...");
}
}
运行测试类中的 t1 方法,测试上述两个切面的优先级:

AspectDemo1 的默认优先级更高;
更改 AspectDemo1 的优先级为 4,AspectDemo2 的优先级为 2,运行 t1 方法:

5. 切点表达式
使用切点表达式来描述切点,常用的有两种方式:
- execution(...):根据方法签名来匹配;
- @annotation(...):根据注解匹配;
execution 表达式:
execution(< 访问修饰符 > < 返回类型 > < 包名 . 类名 . ⽅法 ( ⽅法参数 )> < 异常 >)
以 execution(* com.example.aop.controller.*.*(..)) 为例:
* 表示通配符;
访问修饰限定符省略,返回类型为 * ,表示匹配所有返回类型;
匹配 com.example.aop.controller 这个包;
.* 表示匹配所有类;
.*(..) 表示匹配所有方法;
@annotation 注解:
execution 适用于定义有规则的切点,如果想定义多个无规则的切点,使用 @annotation 更合适;
实现步骤:
- 1. 编写自定义注解;
- 2. 使用 @annotation 表达式描述切点;
- 3. 在连接点的方法上添加自定义注解;
编写自定义注解:
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface MyAspect {
}
@Retention 表示注解的生命周期:
- RententionPolicy.SOURCE:表示注解存在于源代码阶段;
- RententionPolicy.CLASS:表示注解存在于源代码和字节码文件阶段;
- RententionPolicy.RUNTIME 表示注解存在于源代码,字节码和运行阶段;
@Target 表示注解的作用范围:
- ElementType.TYPE:表示用于描述类,接口或者枚举类型;
- ElementType.METHOD:表示用于描述方法;
- ElementType.PARAMETER:用于描述参数;
- ElementType.TYPE_USE:用于描述任意类型;
使用 @annotation 表达式描述切点:
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;
@Slf4j
@Aspect
@Component
public class MyAspectDemo {
@Around("@annotation(com.example.aop.config.MyAspect)")
public Object doAround(ProceedingJoinPoint joinPoint){
log.info("my aspect do around before...");
Object object = null;
try {
object = joinPoint.proceed();
} catch (Throwable e) {
throw new RuntimeException(e);
}
log.info("my aspect do around after...");
return object;
}
}
@annotation(com.example.aop.config.MyAspect) 切点表达式定义的切点,只对 @MyAspect 生效;
测试:
import com.example.aop.config.MyAspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/test")
public class TestController {
@MyAspect
@RequestMapping("/t1")
public String t1(){
log.info("执行t1方法...");
return "t1";
}
@RequestMapping("/t2")
public Integer t2(){
log.info("执行t2方法...");
Integer n = 10 / 0;
return 1;
}
}
import com.example.aop.config.MyAspect;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
@Slf4j
@RestController
@RequestMapping("/user")
public class UserController {
@MyAspect
@RequestMapping("/u1")
public String u1(){
log.info("执行u1方法...");
return "u1";
}
@RequestMapping("/u2")
public String u2(){
log.info("执行u2方法...");
Integer i = 10 / 0;
return "u2";
}
}
测试 t1 和 u1 方法:

测试 t2 方法:

三、Spring AOP 原理
Spring AOP 是基于动态代理实现的;
1. 代理模式
代理模式也叫做委托模式;
通过为某个对象提供一种代理以控制这个对象的访问;
通过提供一个代理类,调用目标对象的方法的时候,不直接调用,而是通过代理对象间接调用;
代理的作用:目标对象提供的功能不够,通过代理对原有功能进行增强;
代理模式的主要角色:
- Subject:业务接口类;
- RealSubject:业务实现类;
- Proxy:代理类;
2. 静态代理
程序运行之前,代理类的 .class 文件已经存在了;
业务接口类:
package com.example.aop.proxy;
public interface HouseSubject {
void rentHouse();
void saleHouse();
}
业务实现类:
package com.example.aop.proxy;
public class RealHouseSubject implements HouseSubject{
public void rentHouse(){
System.out.println("我是房东,出租房屋");
}
public void saleHouse(){
System.out.println("我是房东,出售房屋");
}
}
代理类:
import com.example.aop.proxy.RealHouseSubject;
public class HouseProxy implements HouseSubject {
// 静态代理
private RealHouseSubject realHouseSubject;
public HouseProxy(RealHouseSubject realHouseSubject) {
this.realHouseSubject = realHouseSubject;
}
@Override
public void rentHouse() {
System.out.println("我是代理,开始代理");
realHouseSubject.rentHouse();
System.out.println("我是代理,结束代理");
}
@Override
public void saleHouse() {
System.out.println("我是代理,开始代理");
realHouseSubject.saleHouse();
System.out.println("我是代理,结束代理");
}
}
测试:
package com.example.aop.proxy.staticc;
import com.example.aop.proxy.RealHouseSubject;
public class Main {
public static void main(String[] args) {
HouseProxy proxy = new HouseProxy(new RealHouseSubject());
proxy.rentHouse();
proxy.saleHouse();
}
}

通过上面的例子可以看出,静态代理的方式不够灵活,假如业务实现类发生变化,代理类就需要发生变化,否则就没办法实现对新增业务的增强;
3. 动态代理
动态代理相对于静态代理,更加灵活;
不需要针对每一个目标对象都创建一个代理对象,而是把这个创建代理对象的时机推迟到程序运行时,由 JVM 来实现;程序在运行时,根据需要,动态创建;
1. JDK 动态代理
JDK 动态代理类,需要实现一个 InvocationHandler 接口,通过调用 invoke() 方法实现功能增强;
invoke() 方法类似Spring AOP 中通知(Advice)的实现方式,通过连接点,调用 invoke() 方法,将目标对象和参数作为参数传入,返回结果;
package com.example.aop.proxy.dynamic;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
public class HouseInvocationHandler implements InvocationHandler {
private Object target;
public HouseInvocationHandler(Object target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我是代理,开始代理");
Object o = method.invoke(target, args);
System.out.println("我是代理,结束代理");
return o;
}
}
测试:
创建代理对象的时候,通过 Proxy.newInstance() 方法创建;
分别传入类加载器,接口类型和代理类实例作为参数;
使用 JDK 代理,目标对象必须要实现接口,否则接口类型参数将不匹配,出现报错;
import com.example.aop.proxy.HouseSubject;
import com.example.aop.proxy.RealHouseSubject;
import org.springframework.cglib.proxy.Enhancer;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
HouseSubject houseSubject = new RealHouseSubject();
// 使用 JDK 动态代理
HouseSubject proxy = (HouseSubject) Proxy.newProxyInstance(
houseSubject.getClass().getClassLoader(),
new Class[]{HouseSubject.class},
new HouseInvocationHandler(houseSubject));
proxy.rentHouse();
proxy.saleHouse();
}

相比静态代理,业务发生变化后,也可以使用动态代理调用对象的业务,而不需要修改代理类;
2. CGLIB 代理
需要实现 MethodInterceptor 接口,重写 interceptor() 方法;
通过调用 invoke() 方法实现功能增强;
类似 Spring AOP 通知的实现,传入目标对象和参数,返回结果;
package com.example.aop.proxy.dynamic;
import org.springframework.cglib.proxy.MethodInterceptor;
import org.springframework.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
public class CGLIBMethodInterceptor implements MethodInterceptor {
private Object target;
public CGLIBMethodInterceptor(Object target) {
this.target = target;
}
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy proxy) throws Throwable {
System.out.println("我是中介,开始代理");
Object object = method.invoke(target, args);
System.out.println("我是中介,结束代理");
return object;
}
}
测试:
创建代理对象的时候,通过Enhancer.create() 创建;
传入目标对象类型以及代理类实例作为参数;
package com.example.aop.proxy.dynamic;
import com.example.aop.proxy.HouseSubject;
import com.example.aop.proxy.RealHouseSubject;
import org.springframework.cglib.proxy.Enhancer;
import java.lang.reflect.Proxy;
public class Main {
public static void main(String[] args) {
HouseSubject houseSubject = new RealHouseSubject();
// 使用 CGLIB 动态代理 - 代理接口
HouseSubject cglibProxy = (HouseSubject) Enhancer.create(
houseSubject.getClass(), new CGLIBMethodInterceptor(houseSubject));
cglibProxy.rentHouse();
cglibProxy.saleHouse();
// 使用 CGLIB 动态代理 - 代理实体类
RealHouseSubject realHouseSubject = new RealHouseSubject();
RealHouseSubject cglibProxy2 = (RealHouseSubject) Enhancer.create(
realHouseSubject.getClass(), new CGLIBMethodInterceptor(realHouseSubject));
cglibProxy2.rentHouse();
cglibProxy2.saleHouse();
}

可以看到,CGLIB 代理可以代理实现接口的类,也可以代理不实现接口的类;
3. 代理工厂
Spring 源码中是通过工厂模式创建代理,代理工厂有一个重要的属性:proxyTargetClass,可以通过 application.yml 文件配置;
proxyTargetClass 为 false,如果业务实现类实现了接口,使用 JDK 动态代理,没实现接口,使用 CGLIB 代理;
proxyTargetClass 为 true,使用 CGLIB 代理;
Spring 默认 proxyTargetClass 是 false,默认使用 JDK 代理,当业务实现类没实现接口的情况,使用 CGLIB代理;
SpringBoot 从 2.x 开始,默认 proxyTargetClass 是 true,默认使用 CGLIB 代理;
原文地址:https://blog.csdn.net/weixin_41965831/article/details/152791140
免责声明:本站文章内容转载自网络资源,如侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!
