一、基于注解的 AOP 步骤总结
1、将目标类、切面类加入到IOC容器中===>@Component
2、告诉Spring哪个是切面类===>@Aspect
3、在切面类中使用5个通知注解,来配置切面中的这些通知方法都何时何地运行
4、在配置文件中开启基于注解的AOP功能 <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
二、以 XML 方式配置切面
除了使用 AspectJ 注解声明切面,Spring 也支持在 bean 配置文件中声明切面,这种声明是通过 aop 名称空间的 XML 元素完成的。
正常情况下,基于注解的声明要优先于基于 XML 的声明。通过 AspectJ 注解,切面可以与 AspectJ 兼容,而基于 XML 的配置则是 Spring 专有的。
由于 AspectJ 得到越来越多的 AOP 框架支持,所以以注解风格编写的切面将会有更多重用的机会。
配置细节:
在 bean 配置文件中,所有的 Spring AOP 配置都必须定义在 <aop:config> 元素内部。对于每个切面而言,都要创建一个 <aop:aspect> 元素来为具体的切面实现引用的 bean 实例。
切面 bean 必须有一个标识符,供 <aop:aspect> 元素引用。

除了在 IOC 容器中声明 bean ,还可以在 Java 类上面加上 @Component 注解,然后让 IOC 容器自动扫描。
@Component
public class Logger {
public void before() {
System.out.println("前置通知");
}
}
配置文件:
<!-- 配置扫描器 -->
<context:component-scan base-package="com.spring.aopxml"></context:component-scan>
<aop:config>
<!-- 指定切面,使用 ref 关联 IOC 容器中的 bean -->
<aop:aspect ref="logger"></aop:aspect>
</aop:config>
三、声明切入点
(1)切入点使用 <aop:pointcut> 元素声明
(2)切入点必须定义在 <aop:aspect> 元素下,或者直接定义在 <aop:config> 元素下;
① 定义在 <aop:aspect> 元素下,只对当前切面有效;
② 定义在 <aop:config> 元素下,对所有切面都有效;
(3)基于 XML 的 AOP 配置不允许在切入点表达式中用名称引用其他切入点

配置文件:
<!-- 配置 aop -->
<aop:config>
<!-- 指定切面,使用 ref 关联 IOC 容器中的 bean -->
<aop:aspect id="" ref="logger">
<!-- 声明切入点表达式 -->
<aop:pointcut expression="execution(* com.spring.aopxml.*.*(..))" id="cut"/>
</aop:aspect>
</aop:config>
四、声明通知
(1)在 aop 名称空间中,每种通知类型都对应一个特定的 XML 元素;
(2)通知元素需要使用 <pointcut-ref> 来引用切入点,或用 <pointcut> 直接嵌入切入点表达式;
(3)method 属性 指定切面类中通知方法的名称;

代码示例:
<!-- 配置 aop -->
<aop:config>
<!-- 指定切面,使用 ref 关联 IOC 容器中的 bean -->
<aop:aspect id="" ref="logger">
<!-- 声明切入点表达式 -->
<aop:pointcut expression="execution(* com.spring.aopxml.*.*(..))" id="cut"/>
<!-- 声明通知方法,并引入切入点 -->
<aop:before method="before" pointcut-ref="cut"/>
</aop:aspect>
</aop:config>
<!-- 配置 aop -->
<aop:config>
<aop:aspect id="" ref="logger">
<!-- 使用 pointcut 属性来配置切入点表达式-->
<aop:before method="before" pointcut="execution(* com.spring.aopxml.*.*(..))"/>
</aop:aspect>
</aop:config>
五、完整案例
声明 LogUtils 切面类:
public class LogUtils {
public static void logStart(JoinPoint joinpoint){
Object[] args = joinpoint.getArgs();
String methodName = joinpoint.getSignature().getName();
System.out.println("[LogUtils前置通知]【"+ methodName +"】方法执行了,参数为【"+ Arrays.toString(args) +"】");
}
public static void logReturn(JoinPoint joinpoint, Object result){
String methodName = joinpoint.getSignature().getName();
System.out.println("[LogUtils返回通知]【"+ methodName +"】方法执行完成,他的结果为是:" + result);
}
public static void logException(JoinPoint joinpoint, Exception exception){
String methodName = joinpoint.getSignature().getName();
System.out.println("[LogUtils异常通知]【"+ methodName +"】方法出现了异常,异常为: " + exception.getCause());
}
public static void logEnd(JoinPoint joinpoint){
String methodName = joinpoint.getSignature().getName();
System.out.println("[LogUtils后置通知]【"+ methodName +"】方法执行最终完成");
}
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
String methodName = pjp.getSignature().getName();
Object[] args = pjp.getArgs();
Object proceed = null;
try {
//@Before
System.out.println("【环绕前置通知】---"+ methodName +"---【方法开始】");
//就是利用反射调用目标方法即可,就是 method.invoke(obj, args)
proceed = pjp.proceed(args);
//@AfterReturning
System.out.println("【环绕返回通知】---"+ methodName +"---【方法返回,返回值 "+ proceed +"】");
} catch (Exception e) {
//@AfterThrowing
System.out.println("【环绕异常通知】---"+ methodName +"---【方法出现异常】,异常信息:" + e);
//为了让外界能知道这个异常,这个异常一定要抛出去
throw new RuntimeException(e);
} finally {
//@After
System.out.println("【环绕后置通知】---"+ methodName +"---【方法结束】");
}
//反射调用后的返回值也一定返回出去,不返回会空指针
return proceed;
}
}
声明 ValidataAspect 切面类:
public class ValidataAspect {
public static void logStart(JoinPoint joinpoint){
Object[] args = joinpoint.getArgs();
String methodName = joinpoint.getSignature().getName();
System.out.println("[Validata前置通知]【"+ methodName +"】方法执行了,参数为【"+ Arrays.toString(args) +"】");
}
public static void logReturn(JoinPoint joinpoint, Object result){
String methodName = joinpoint.getSignature().getName();
System.out.println("[Validata返回通知]【"+ methodName +"】方法执行完成,他的结果为是:" + result);
}
public static void logException(JoinPoint joinpoint, Exception exception){
String methodName = joinpoint.getSignature().getName();
System.out.println("[Validata异常通知]【"+ methodName +"】方法出现了异常,异常为: " + exception.getCause());
}
public static void logEnd(JoinPoint joinpoint){
String methodName = joinpoint.getSignature().getName();
System.out.println("[Validata后置通知]【"+ methodName +"】方法执行最终完成");
}
}
配置文件:
<!--基于配置的AOP @Component-->
<bean id="myMathCalculator" class="com.njf.aop.calc.MyMathCalculator"></bean>
<bean id="logUtils" class="com.njf.aop.utils.LogUtils"></bean>
<bean id="validataAspect" class="com.njf.aop.utils.ValidataAspect"></bean>
<!--使用AOP名称空间-->
<aop:config>
<!--配置通用的切入点表达式,所有的切面都能使用-->
<aop:pointcut id="globalPointCut" expression="execution(* com.njf.aop.calc.*.*(..))"/>
<!--
普通前置 === 目标方法 === (环绕执行后置/返回) === 普通后置 === 普通返回
-->
<!-- 指定切面 -->
<aop:aspect ref="logUtils" order="3">
<!--配置通用的切入点表达式,当前切面能使用的-->
<aop:pointcut id="myPointCut" expression="execution(* com.njf.aop.calc.*.*(..))"/>
<!-- 在切面类中使用五个通知注解来配置切面中的这些方法都何时何地运行 -->
<!-- 配置那个方法是前置通知:method 指定方法 -->
<!-- 方式一:
<aop:before method="logStart" pointcut="execution(* com.njf.aop.calc.*.*(..))"></aop:before>
-->
<!-- 方式二:-->
<aop:before method="logStart" pointcut-ref="myPointCut"></aop:before>
<aop:after-returning method="logReturn" pointcut-ref="myPointCut" returning="result" />
<aop:after-throwing method="logException" pointcut-ref="myPointCut" throwing="exception" />
<aop:after method="logEnd" pointcut-ref="myPointCut"/>
<aop:around method="myAround" pointcut-ref="myPointCut"></aop:around>
</aop:aspect>
<aop:aspect ref="validataAspect" order="1">
<aop:before method="logStart" pointcut-ref="globalPointCut" />
<aop:after-returning method="logReturn" pointcut-ref="globalPointCut" returning="result"/>
<aop:after-throwing method="logException" pointcut-ref="globalPointCut" throwing="exception"/>
<aop:after method="logEnd" pointcut-ref="globalPointCut"/>
</aop:aspect>
</aop:config>
注意:
1、如果没有给切面设置 order 属性指定顺序,默认按照切面配置的先后顺序执行;
2、可以使用 order 属性来指定切面的运行顺序
3、注解配置:快速方便;XMl 配置:功能完善;重要的内容用 XML 配置,不重要的使用注解。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· DeepSeek R1 简明指南:架构、训练、本地部署及硬件要求
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· 面试官:你是如何进行SQL调优的?