当前位置: 首页 > news >正文

Java-Spring入门指南(十二)SpringAop的三种实现方式

Java-Spring入门指南(十二)SpringAOP的三种实现方式

  • 前言
  • 一、AOP是什么,有什么用,作用是什么?
  • 二、AOP的三种实现方式
    • 方式一:基于Spring Advice接口实现
      • 1. 项目目录结构
      • 2. 核心代码实现
        • (1)业务接口与实现类
        • (2)切面类(实现Advice接口)
        • (3)Spring配置文件(applicationContext.xml)
      • 核心知识点总结
      • 3. 测试代码与结果
      • 方式一总结
    • 方式二:自定义切面实现(不依赖Spring接口)
      • 1. 项目目录结构
      • 2. 核心代码实现
        • (1)业务接口与实现类
        • (2)自定义切面类(DiyPointCut)
        • (3)Spring配置文件(applicationContext.xml)
      • 3. 测试代码与结果
    • 方式三:注解驱动实现(@Aspect注解)
      • 1. 项目目录结构
      • 2. 核心代码实现
        • (1)业务接口与实现类
        • (2)注解式切面类(AnnotationPointCut)
        • (3)Spring配置文件(applicationContext.xml)
      • 3. 测试代码与结果
  • 三种实现方式对比


前言

  • 在前一篇博客中,我们已经掌握了代理模式的核心逻辑,并初步认识了Spring AOP的概念——通过“切面”为业务方法动态添加增强功能,解决横切关注点(如日志、事务)与核心业务的耦合问题。

  • 而在实际开发中,Spring AOP提供了多种灵活的实现方式,适配不同的场景需求。

  • 本篇将聚焦Spring AOP的三种核心实现方式,从基于接口的通知实现、自定义切面实现,到注解驱动实现,逐步拆解代码逻辑,帮助你掌握不同方式的配置细节与适用场景,真正将AOP落地到项目开发中。

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Java-Spring入门指南知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_13040333.html?spm=1001.2014.3001.5482

在这里插入图片描述


Spring的官方AOP讲解网站
https://docs.spring.io/spring-framework/docs/current/spring-framework-reference/core.html#aop

一、AOP是什么,有什么用,作用是什么?

在正式讲解实现方式前,我们先快速回顾上一篇的核心内容

  • AOP是什么:全称Aspect Oriented Programming(面向切面编程),是一种编程思想。它将分散在多个业务方法中的“通用逻辑”(如日志、权限校验)抽取为“切面(Aspect)”,在指定时机(如方法执行前/后)动态“织入”到目标方法中,无需修改业务代码。

  • AOP有什么用:解决“横切关注点”问题。横切关注点是指与核心业务无关,但需重复出现在多个方法中的逻辑(如记录每个业务方法的执行日志)。AOP让这些逻辑只需编写一次,即可作用于多个方法,减少代码冗余、降低耦合、便于统一维护

  • AOP的核心作用场景

    • 日志记录:自动记录方法调用信息(参数、返回值、执行时间);
    • 事务管理:方法执行前开启事务,执行成功提交、失败回滚;
    • 权限校验:方法执行前校验用户是否有权限操作;
    • 异常处理:统一捕获方法执行中的异常并处理。
  • AOP核心组件回顾

    • 切面(Aspect):封装横切逻辑的类(如“日志切面”);
    • 通知(Advice):切面中的具体增强逻辑(如“方法执行前打印日志”);
    • 切入点(Pointcut):通过表达式指定“哪些方法需要被增强”;
    • 织入(Weaving):将通知动态植入目标方法的过程(Spring在运行时完成)。

二、AOP的三种实现方式

Spring AOP的实现方式围绕“如何定义切面与通知”展开,核心分为基于Spring接口的实现、自定义切面实现、注解驱动实现三种。

使用Spring AOP需添加aspectjweaver依赖:

<!-- AspectJ织入依赖:支持AOP功能的核心依赖 -->
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.9.9.1</version>
</dependency>

方式一:基于Spring Advice接口实现

这种方式需让“切面类”实现Spring提供的Advice系列接口(如MethodBeforeAdvice前置通知、AfterReturningAdvice后置通知),通过接口方法定义增强逻辑。Spring会自动识别实现类为“通知类”,再通过XML配置绑定“通知”与“切入点”。

1. 项目目录结构

先明确代码组织,将该方式的类放在com.niit.aop1包下:
在这里插入图片描述

spring_aop
└── src└── main├── java│   └── com.niit│       └── aop1          # 方式一的代码包│           ├── LogBefore.java       # 切面类(实现MethodBeforeAdvice)│           ├── StudentService.java  # 业务接口│           └── StudentServiceImpl.java # 业务实现类└── resources└── applicationContext.xml       # Spring核心配置文件

2. 核心代码实现

(1)业务接口与实现类

首先定义业务逻辑(学生管理的增删改查),这是需要被AOP增强的“目标对象”:

  • StudentService.java(业务接口)
    在这里插入图片描述
// 学生业务接口:定义核心业务方法
public interface StudentService {void add();    // 添加学生void del();    // 删除学生void update(); // 修改学生void query();  // 查询学生
}
  • StudentServiceImpl.java(业务实现类)
import org.springframework.stereotype.Component;// 注册为Spring Bean,id为"ssi"(便于后续从容器中获取)
@Component("ssi")
public class StudentServiceImpl implements StudentService {// 核心业务逻辑:仅关注“做什么”,不关心日志等增强逻辑@Overridepublic void add() {System.out.println("【核心业务】添加学生");}@Overridepublic void del() {System.out.println("【核心业务】删除学生");}@Overridepublic void update() {System.out.println("【核心业务】修改学生");}@Overridepublic void query() {System.out.println("【核心业务】查询学生");}
}
(2)切面类(实现Advice接口)

创建LogBefore类,实现MethodBeforeAdvice接口(Spring提供的“前置通知”接口),在before方法中定义“方法执行前”的增强逻辑(打印日志):

  • LogBefore.java(切面类)
import org.springframework.aop.MethodBeforeAdvice;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;// 注册为Spring Bean(让Spring管理切面类)
@Component
// 实现MethodBeforeAdvice:代表“目标方法执行前”的增强
public class LogBefore implements MethodBeforeAdvice {/*** 前置通知的核心逻辑:目标方法执行前会自动调用此方法* @param method 被增强的目标方法(如add()、del())* @param args   目标方法的参数(本例中无参数,为null)* @param target 被增强的目标对象(如StudentServiceImpl实例)*/@Overridepublic void before(Method method, Object[] args, Object target) throws Throwable {// 增强逻辑:打印“哪个类的哪个方法即将执行”System.out.println("【AOP前置增强】" + "类:" + target.getClass().getName() + ",方法:" + method.getName() + " 即将执行");}
}
(3)Spring配置文件(applicationContext.xml)

通过XML配置“切入点”(指定增强哪些方法)和“通知绑定”(将LogBefore的增强逻辑织入切入点):

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:context="http://www.springframework.org/schema/context"xmlns:aop="http://www.springframework.org/schema/aop"xsi:schemaLocation="http://www.springframework.org/schema/beanshttp://www.springframework.org/schema/beans/spring-beans.xsdhttp://www.springframework.org/schema/contexthttp://www.springframework.org/schema/context/spring-context.xsdhttp://www.springframework.org/schema/aophttp://www.springframework.org/schema/aop/spring-aop.xsd"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><!-- 1. 组件扫描:扫描com.niit包下的@Component注解,注册Bean --><context:component-scan base-package="com.niit"/><!-- 2. AOP核心配置:方式一(基于Advice接口) --><aop:config proxy-target-class="false"><aop:pointcut id="pointcut" expression="execution(* com.niit.aop1.*.add())"/><aop:advisor advice-ref="logBefore" pointcut-ref="pointcut"></aop:advisor></aop:config></beans>
  • 核心代码解析

1. 根标签 aop:config

  • 作用:是Spring AOP XML配置的根标签,所有AOP相关的配置(切入点、通知、切面关联等)都需要定义在该标签内部。
  • 属性 proxy-target-class
    用于指定Spring AOP生成代理的方式:
    • proxy-target-class="false"(默认值):使用JDK动态代理,要求目标类必须实现接口,代理对象是接口的实现类。
    • proxy-target-class="true":使用CGLIB代理,可以代理没有实现接口的类(通过继承目标类生成代理子类)。

2. 切入点配置 aop:pointcut

  • 作用:定义"切入点"(Pointcut),即AOP要拦截的方法(哪些方法需要被增强)。
  • 属性详解
    • id="pointcut":给切入点起一个唯一标识,方便后续引用(如在<aop:advisor>中关联)。
    • expression:切入点表达式,用于精确匹配需要拦截的方法,核心语法是execution()(最常用的表达式类型)。

切入点表达式 execution(* com.niit.aop1.*.add()) 解析

execution() 用于匹配方法执行的连接点,语法结构为:
execution(修饰符 返回值类型 包名.类名.方法名(参数列表))

  • *:第一个*表示"任意返回值类型"(如void、int、Object等)。
  • com.niit.aop1.*com.niit.aop1是包名,后面的*表示"该包下的所有类"。
  • .add():表示匹配类中的add()方法,且无参数(如果有参数,需写成add(参数类型),如add(int))。

整体含义:拦截com.niit.aop1包下所有类中的add()无参方法。

3. 切面关联 aop:advisor

  • 作用:将"通知(Advice)"和"切入点(Pointcut)“关联起来,形成一个完整的"切面(Aspect)”。
    (这里的advisor是Spring AOP中基于Advice接口的特殊切面形式,一个advisor只能关联一个通知和一个切入点)

  • 属性详解

    • advice-ref="logBefore":引用一个"通知Bean"(id为logBefore),该Bean需实现Spring的Advice接口(如MethodBeforeAdvice前置通知、AfterReturningAdvice后置通知等)。
    • pointcut-ref="pointcut":引用前面定义的切入点(id为pointcut),表示"通知"要作用在这个切入点匹配的方法上。

核心知识点总结

  1. AOP核心概念

    • 切入点(Pointcut):要拦截的方法(通过表达式定义)。
    • 通知(Advice):拦截方法后要执行的逻辑(如日志、事务、权限校验等),需实现Advice接口。
    • 切面(Aspect):切入点 + 通知的组合(这里通过<aop:advisor>实现)。
  2. 基于Advice接口的特点
    这是Spring早期的AOP配置方式,通知必须严格实现Spring提供的Advice相关接口(如MethodBeforeAdviceAfterReturningAdvice),灵活性较低。现在更多使用基于AspectJ的注解方式(如@Before@After)。

  3. 代理方式选择

    • JDK代理(默认):依赖接口,性能较好。
    • CGLIB代理:不依赖接口,可代理任意类,但会生成子类,性能略低。

3. 测试代码与结果

编写Junit测试类,从Spring容器中获取业务Bean,调用方法验证AOP是否生效:

  • 测试类代码
import org.junit.Test;
import org.springframework.context.support.ClassPathXmlApplicationContext;public class AopTest {@Testpublic void test1() {// 1. 加载Spring配置文件,初始化容器ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");// 2. 获取业务Bean:注意!若proxy-target-class=false(JDK代理),需按接口获取StudentService studentService = (StudentService) ac.getBean("ssi");// 3. 调用方法:验证增强效果System.out.println("-----调用add()方法(会被增强)-----");studentService.add();   // add()在切入点中,会触发前置通知System.out.println("-----调用del()方法(不被增强)-----");studentService.del();   // del()不在切入点中,无增强逻辑System.out.println("-----调用query()方法(不被增强)-----");studentService.query(); // query()不在切入点中,无增强逻辑// 4. 关闭容器ac.close();}
}
  • 测试结果

在这里插入图片描述

结果分析:仅add()方法触发了前置增强(打印日志),del()query()未被增强,符合切入点表达式的配置,说明AOP生效。

方式一总结

  • 核心逻辑:切面类实现Spring的Advice接口,通过<aop:advisor>绑定通知与切入点;
  • 代理方式:支持JDK(基于接口)和CGLIB(基于子类),由proxy-target-class控制;
  • 优缺点:实现简单,适合简单的通知场景;但需依赖Spring接口,灵活性较低(若需多个通知,需实现多个接口)。

方式二:自定义切面实现(不依赖Spring接口)

这种方式无需实现Spring的Advice接口,而是自定义切面类(包含before、after等增强方法),再通过XML配置将自定义方法指定为“通知”,灵活性更高。

1. 项目目录结构

将该方式的类放在com.niit.aop2包下,与方式一隔离:
在这里插入图片描述

spring_aop
└── src└── main├── java│   └── com.niit│       └── aop2          # 方式二的代码包│           ├── DiyPointCut.java     # 自定义切面类(含增强方法)│           ├── StudentService.java  # 业务接口(与方式一一致)│           └── StudentServiceImpl.java # 业务实现类└── resources└── applicationContext.xml       # Spring配置文件

2. 核心代码实现

(1)业务接口与实现类

与方式一完全一致,仅Bean id改为“ssi2”(避免与方式一的Bean重名):

  • StudentServiceImpl.java
package com.niit.aop2;import org.springframework.stereotype.Component;// Bean id改为"ssi2",与方式一的"ssi"区分
@Component("ssi2")
public class StudentServiceImpl implements StudentService {@Overridepublic void add() {System.out.println("【核心业务】添加学生");}@Overridepublic void del() {System.out.println("【核心业务】删除学生");}@Overridepublic void update() {System.out.println("【核心业务】修改学生");}@Overridepublic void query() {System.out.println("【核心业务】查询学生");}
}

在这里插入图片描述
在这里插入图片描述

(2)自定义切面类(DiyPointCut)

自定义类,直接编写before(前置增强)、after(后置增强)方法,无需实现任何接口:

  • DiyPointCut.java
import org.springframework.stereotype.Component;// 注册为Spring Bean
@Component
// 自定义切面类:包含多个增强方法(before、after)
public class DiyPointCut {// 前置增强方法:目标方法执行前调用public void before() {System.out.println("【自定义前置增强】方法即将执行,准备参数校验...");}// 后置增强方法:目标方法执行后调用(无论是否抛出异常都会执行)public void after() {System.out.println("【自定义后置增强】方法执行完成,记录操作日志...");}// 环绕增强方法(可选):包裹目标方法,可在执行前后添加逻辑public void around() {System.out.println("【自定义环绕增强】方法执行前的准备工作...");// 注意:环绕增强若需执行目标方法,需配合ProceedingJoinPoint(后续方式三会演示)System.out.println("【自定义环绕增强】方法执行后的清理工作...");}
}

在这里插入图片描述

(3)Spring配置文件(applicationContext.xml)

重点使用<aop:aspect>标签配置自定义切面,将切面中的方法绑定为“通知”:

<!-- 继续在applicationContext.xml中添加方式二的配置 -->
<!--    方式二--><aop:config proxy-target-class="true"><!-- 这里用CGLIB代理(true),可直接按类获取Bean,false默认接口 --><aop:aspect ref="diyPointCut"> <!-- 1. 定义切面:ref指向自定义切面类(DiyPointCut的Bean id)diyPointCut是DiyPointCut类的默认Bean id(首字母小写) --><!-- 2. 定义切入点:增强com.niit.aop2包下的del()方法 --><aop:pointcut id="mydiyPointCut" expression="execution(* com.niit.aop2.*.del())"/><aop:after method="after" pointcut-ref="mydiyPointCut"></aop:after><aop:before method="before" pointcut-ref="mydiyPointCut"></aop:before> <!-- 3. 绑定增强方法与切入点:指定切面中的方法作为通知 --></aop:aspect></aop:config>

在这里插入图片描述

3. 测试代码与结果

  • 测试类代码
@Test
public void test2() {ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");// 因proxy-target-class=true(CGLIB代理),可直接按类获取BeanStudentServiceImpl studentService = 	ac.getBean(StudentServiceImpl.class);System.out.println("-----调用del()方法(会被增强)-----");studentService.del();   // del()在切入点中,触发前置+后置增强System.out.println("-----调用add()方法(不被增强)-----");studentService.add();   // add()不在切入点中,无增强ac.close();
}
  • 测试结果

在这里插入图片描述

方式三:注解驱动实现(@Aspect注解)

这是实际开发中最常用的方式——通过@Aspect@Before@After等注解直接在切面类中定义“切面”和“通知”,无需复杂的XML配置,仅需开启注解驱动即可。

1. 项目目录结构

将该方式的类放在com.niit.aop3包下:

spring_aop
└── src└── main├── java│   └── com.niit│       └── aop3          # 方式三的代码包│           ├── AnnotationPointCut.java # 注解式切面类│           ├── StudentService.java     # 业务接口│           └── StudentServiceImpl.java  # 业务实现类└── resources└── applicationContext.xml          # Spring配置文件

2. 核心代码实现

(1)业务接口与实现类

Bean id改为“ssi3”,避免重名:
在这里插入图片描述

  • StudentServiceImpl.java
package com.niit.aop3;import org.springframework.stereotype.Component;@Component("ssi3")
public class StudentServiceImpl implements StudentService {@Overridepublic void add() {System.out.println("【核心业务】添加学生");}@Overridepublic void del() {System.out.println("【核心业务】删除学生");}@Overridepublic void update() {System.out.println("【核心业务】修改学生");}@Overridepublic void query() {System.out.println("【核心业务】查询学生");}
}
(2)注解式切面类(AnnotationPointCut)

使用@Aspect声明切面,@Before@After@Around等注解定义通知,execution表达式直接写在注解中:

  • AnnotationPointCut.java
package com.niit.aop3;import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;// 1. 注册为Spring Bean
@Component
// 2. @Aspect:声明当前类是“切面类”
@Aspect
public class AnnotationPointCut {// 3. @Before:前置通知,增强com.niit.aop3包下的add()方法@Before(value = "execution(* com.niit.aop3.*.add())")public void before() {System.out.println("【注解前置通知】add()方法即将执行");}// 4. @After:后置通知(目标方法执行后,无论是否异常)@After(value = "execution(* com.niit.aop3.*.add())")public void after() {System.out.println("【注解后置通知】add()方法执行完成");}// 5. @Around:环绕通知(最灵活,包裹目标方法,需手动执行目标方法)@Around(value = "execution(* com.niit.aop3.*.add())")public void around(ProceedingJoinPoint point) throws Throwable {System.out.println("【注解环绕通知】方法执行前:开启事务");// 关键:调用point.proceed()执行目标方法(add())point.proceed();System.out.println("【注解环绕通知】方法执行后:提交事务");}// 6. @AfterReturning:返回通知(目标方法正常执行完成后触发)@AfterReturning(value = "execution(* com.niit.aop3.*.add())")public void afterReturning() {System.out.println("【注解返回通知】add()方法正常返回,无异常");}
}

关键说明

  • @Around通知必须传入ProceedingJoinPoint参数,通过point.proceed()手动触发目标方法执行;
  • 通知执行顺序:环绕前 → 前置 → 目标方法 → 后置 → 环绕后 → 返回通知(若无异常)。
(3)Spring配置文件(applicationContext.xml)

无需复杂的<aop:config>配置,仅需开启注解驱动的AOP即可:

<!-- 方式三:注解驱动AOP -->
<!-- 1. 组件扫描:扫描com.niit包下的@Component和@Aspect注解 -->
<context:component-scan base-package="com.niit"/><!-- 2. 开启AspectJ注解驱动:让Spring识别@Aspect、@Before等注解 -->
<aop:aspectj-autoproxy  proxy-target-class="true"/> <!-- 可选,默认false(JDK代理),true(CGLIB代理) -->

在这里插入图片描述

3. 测试代码与结果

  • 测试类代码
    在这里插入图片描述
import com.niit.aop3.StudentService;
@Test
public void test3() {ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");StudentService studentService = (StudentService) ac.getBean("ssi3");System.out.println("-----调用add()方法(触发所有注解通知)-----");studentService.add();ac.close();
}
  • 测试结果

在这里插入图片描述


三种实现方式对比

为了更清晰地选择合适的方式,我们通过表格总结核心差异:

实现方式依赖接口配置方式灵活性推荐度适用场景
基于Spring Advice接口需实现Advice系列接口XML配置★★☆☆☆简单通知场景(仅需单一通知)
自定义切面实现无需接口XML配置★★★☆☆需多个通知,但不想用注解
注解驱动实现(@Aspect)无需接口注解+少量XML★★★★★企业级开发(推荐首选)

我的个人主页,欢迎来阅读我的其他文章
https://blog.csdn.net/2402_83322742?spm=1011.2415.3001.5343
我的Java-Spring入门指南知识文章专栏
欢迎来阅读指出不足
https://blog.csdn.net/2402_83322742/category_13040333.html?spm=1001.2014.3001.5482

非常感谢您的阅读,喜欢的话记得三连哦

在这里插入图片描述

http://www.dtcms.com/a/428061.html

相关文章:

  • 网站在线统计代码cms开发框架
  • CometD 长轮询协议及在Salesforce中的应用
  • 企术建站网站收录查询主要由哪几个网站
  • 中小型网站建设与网络搭建教育机构加盟
  • 重庆职业能力建设投稿网站网站正能量免费推广软件晚上
  • LeetCode 114. 二叉树展开为链表
  • 网站性能优化电子商务网站建设与管理期末答案
  • 橙色网站设计手机社区网站模板
  • ns3 配置 Ubuntu × CLion
  • 大模型——长文拆解上下文工程落地策略与实践
  • 网站免费建站pixiv appdw如何在网站做弹窗
  • 分身宝 1.0.8 | 无限多开系统级分身~更稳定安全支持同时登录多个社交软件、游戏账号,互不干扰,操作简单便捷,一键切换
  • 网站服务器租用价格 贴吧磁力神器
  • 山东seo推广网站建设个人网站推广方案
  • 商务网站建设工程师是做网站找俊义 合优
  • 简要介绍IDM(Internet Download Manager)的功能及其在下载管理领域的地位
  • 杭州网站开发设计购物网站开发计划书
  • Javascript常量介绍
  • 从 Vercel 构建失败谈 Git 大小写敏感性问题:一个容易被忽视的跨平台陷阱
  • 门户网站有哪些品牌推广理论
  • wordpress 电商网站政务网站建设发言材料
  • 自己做的网站提示不安全企业做网站可以带中国吗
  • thumbnail(资源管理器 缩略图)
  • Java 25 新特性解析与代码示例
  • 新天力:以绿色创新重塑食品容器行业新生态
  • 做个购物网站多少钱关于做电影的网站设计
  • 基于SWAT模型的香溪河流域面源污染模拟
  • wordpress微信网站模板中铁二局深圳公司官网
  • Pytorch强化学习demo
  • Python连接MinIO的参数详解