spring - aop
本文就介绍一下 AOP 在 spring 环境下的应用。
专有名词
编码过程中,会遇到很多类似的名词。
-
concerns:关注点,就是字面意思,一个我们感兴趣的点;
-
cross-cutting concerns:横切关注点,跨越多个模块的关注点称之为横切关注点。例:日志就是许多模块共同的关注点。日常交流也称为“横向切面”;
-
Aspect:切面,切面就像一个平面,在业务流中插入新的功能。代码上,就是一个类,内部包含切入点、植入的新代码等内容;
-
JoinPoint:连接点,程序执行过程中的任意一点。代码上,就是对拦截函数的二次封装,如下列案例中ProceedingJoinPoint;
-
Pointcut:切入点,对拦截方式的定义,对应于 spring 中的 @Pointcut 注解;
-
Advice:通知,程序执行到关注点要做某些事,通知就是将要做的事情封装成代码。常用的有:前置、后置、异常、返回、环绕通知五类;
-
Target:目标对象:对方法进行拦截时,方法的所属对象;
-
Weave:织入,将通知应用到目标对象上,并导致代理对象被创建的过程;
@Aspect
在 springboot 环境下,手写 aop 的次数非常少,大部分的需求,都有专门的接口可用。
能想到的也只有写日志了,下面提供一个案例:
/**
* 声明一个注解,如果一个方法上有这个注解,说明要记录日志
*/
@Documented
@Target(value = ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ActionLogger {
}
/**
* 类上面要写 @Aspect,表示当前类是一个 AOP 切面
*/
@Aspect
@Configuration
public class ServiceLogAspect {
/**
* 方法上 @Around 注解,声明我们要做一个环绕切面,
* 其中 @annotation(cn.seaboot.admin.logger.manager.ActionLogger),指的是拦截所有带 @ActionLogger 的函数
*
* ProceedingJoinPoint 连接点,包含我们编码所需的各种参数。
*
* @param joinPoint 连接点,包含很多反射所需的对象
* @return 程序运行结果
* @throws Throwable 异常
*/
@Around("@annotation(cn.seaboot.admin.logger.manager.ActionLogger)")
public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {
// TODO: write logs before invoke method
Object ret = joinPoint.proceed();
// TODO: write logs after invoke method
return ret;
}
}
其它常用的切面:
- @Before 前置切面
- @After 后置切面
- @AfterReturning 方法返回结果之后
- @AfterThrowing 抛出异常之后
- @Around 环绕切面
古老的案例
非常古老的案例,用主函数就能调用。
能用主函数调用,意味着拥有超高的代码自由度,你可以发挥自己的想象,封装出各种自己所需的代码。
spring 中有无数个切面,但是学会这 5 个,基本就能满足所有需求了。
前置切面,准确地翻译叫 “前置通知”,也有地叫做 “前置增强”,不确定为什么叫 “增强”,日常沟通的时候,这些名词都可能出现。
- 前置切面:org.springframework.aop.BeforeAdvice,空的接口,可以用实现类 MethodBeforeAdvice;
- 后置切面:org.springframework.aop.AfterReturningAdvice;
- 异常切面:org.springframework.aop.ThrowsAdvice;
- 方法拦截(环绕切面):org.aopalliance.intercept.MethodInterceptor,方法拦截;
- 调用拦截:org.springframework.aop.IntroductionInterceptor,Java反射相关拦截。
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.jetbrains.annotations.NotNull;
import org.springframework.aop.AfterReturningAdvice;
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.aop.ThrowsAdvice;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.aop.support.DelegatingIntroductionInterceptor;
import java.lang.reflect.Method;
/**
* 前置切面
*
* @author Mr.css on 2017-01-01
*/
class MyMethodBeforeAdvice implements MethodBeforeAdvice {
@Override
public void before(Method method, Object @NotNull [] args, Object target) throws Throwable {
System.out.println(method.getName() + "方法执行之前");
}
}
/**
* 后置切面
*
* @author Mr.css on 2017-01-01
*/
class MyAfterReturningAdvice implements AfterReturningAdvice {
@Override
public void afterReturning(Object returnValue, Method method, Object @NotNull [] args, Object target) throws Throwable {
System.out.println(method.getName() + "方法执行结束");
}
}
/**
* 异常切面,接口是空的,方法要按照规范写
*
* @author Mr.css on 2017-01-01
*/
class MyThrowsAdvice implements ThrowsAdvice {
/**
* 笼统地使用了Exception,可以做具体的异常捕捉
*/
public void afterThrowing(Method method, Object[] args, Object target, Exception ex) throws Throwable {
System.out.println(method.getName() + "出现异常");
}
}
/**
* 调用拦截,可以控制 Java 反射相关的内容
*
* @author Mr.css on 2017-01-01
*/
class MyIntroductionInterceptor extends DelegatingIntroductionInterceptor {
private static final long serialVersionUID = 2582891122340903718L;
public Object invoke(@NotNull MethodInvocation methodInvocation) throws Throwable {
System.out.println("引介增强启动拦截");
return super.invoke(methodInvocation);
}
}
/**
* 方法拦截,代码中很常见
*
* @author Mr.css on 2017-01-01
*/
class MyMethodInterceptor implements MethodInterceptor {
@Override
public Object invoke(MethodInvocation methodInvocation) throws Throwable {
System.out.println("MyMethodInterceptor执行");
// TODO 方法执行前要做的事情
//如果说,不执行下面这一行代码,则目标方法将不执行,其他的增强效果也全部失效
Object result = methodInvocation.proceed();
// TODO 方法执行后要做的事情
return result;
}
}
/**
* 测试用接口
*
* @author Mr.css on 2017-01-01
*/
interface BaseDao {
long queryId();
}
/**
* 测试用Dao
*
* @author Mr.css on 2017-01-01
*/
class UserDao implements BaseDao {
public long queryId() {
System.out.println("query complete!");
// 下面这一行肯定会报错
return 2 / 0;
}
}
public class Test {
public static void main(String[] args) {
// 用主函数就能调用,超高的自由度,意味着能写任何你想写的内容
ProxyFactory factory = new ProxyFactory();
// 把我们写的四种代理全部放进去
factory.addAdvice(new MyMethodInterceptor());
factory.addAdvice(new MyMethodBeforeAdvice());
factory.addAdvice(new MyAfterReturningAdvice());
factory.addAdvice(new MyThrowsAdvice());
// 调用拦截比较特殊,需要提供接口
factory.addAdvice(new MyIntroductionInterceptor());
factory.setInterfaces(BaseDao.class.getInterfaces());
// 需要代理的那个类
factory.setTarget(new UserDao());
try {
BaseDao dao = (UserDao) factory.getProxy();
System.out.println(dao.queryId());
} catch (Exception ignore) {
}
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY