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

Spring框架(2)---AOP

一、AOP概念引入

编写入门案例

创建maven的项目,引入开发的坐标

<dependencies><!--spring的核心依赖jar包--><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version></dependency><!--日志相关--><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><!--有单元测试的环境,Spring5版本,Junit4.12版本--><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test</scope></dependency><!--连接池  阿里巴巴  第三方--><dependency><groupId>com.alibaba</groupId><artifactId>druid</artifactId><version>1.1.10</version></dependency><!--mysql驱动包--><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>8.0.27</version></dependency><!--Spring整合Junit测试的jar包--><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.2.RELEASE</version><scope>test</scope></dependency></dependencies>

 传统方式

1、service业务层实现(包含事务管理)

package com.qcby.service;import com.qcby.model.Account;/*** AOP概念引入需要的业务层接口* 模拟转账业务*/
public interface AccountService {//转账逻辑方法   account1 扣款   account2 增款public void saveAll(Account account1,Account account2);}@Service("accountService")
public class AccountServiceImpl implements AccountService {private AccountDao accountDao;public void setAccountDao(AccountDao accountDao) {this.accountDao = accountDao;}@Overridepublic void saveAll(Account account1, Account account2) {try {// 开启事务TxUtils.startTransaction();// 保存1账号accountDao.save(account1);// 模拟异常// int a = 1/0;// 保存2账号accountDao.save(account2);// 提交事务TxUtils.commit();} catch (Exception e) {// 打印异常信息e.printStackTrace();// 回滚事务TxUtils.rollback();} finally {// 关闭资源TxUtils.close();}}
}

 2、Dao持久层实现

package com.qcby.dao;import com.qcby.model.Account;//持久层  account转账业务
//转账逻辑操作数据库
public interface AccountDao {public void save(Account account);}public class AccountDaoImpl implements AccountDao {@Overridepublic void save(Account account) throws SQLException {Connection conn = TxUtils.getConnection();String sql = "insert into account values(null,?,?)";PreparedStatement stmt = conn.prepareStatement(sql);stmt.setString(1, account.getName());stmt.setDouble(2, account.getMoney());stmt.executeUpdate();stmt.close();}
}

问题分析

  • 代码重复:每个Service方法都需要写相同的事务管理代码

  • 耦合度高:业务逻辑和事务管理代码混合在一起

  • 维护困难:修改事务逻辑需要修改所有相关方法

使用动态代理的AOP方式 

service实现(无事务代码)

package com.qcby.service.impl;import com.qcby.dao.AccountDao;
import com.qcby.model.Account;
import com.qcby.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;/*** AOP概念引入需要的业务层实现类* 模拟转账业务*/
@Service("accountService")
public class AccountServiceImpl implements AccountService {@Autowired@Qualifier("accountDao")private AccountDao accountDao;//转账逻辑方法   account1 扣款   account2 增款  实现public void saveAll(Account account1, Account account2) {try {//开启事务
//            TxUtils.startTransaction();//保存账号1accountDao.save(account1);//模拟异常//int a=1/0;//保存账号二accountDao.save(account2);//提交事务/回滚事务
//            TxUtils.commit();}catch (Exception e){//打印异常信息System.out.println("回滚");e.printStackTrace();
//            TxUtils.rollback();}finally {//关闭资源System.out.println("结束");
//            TxUtils.close();}}
}

 JDK动态代理实现

package com.qcby.JDKUtils;import com.qcby.service.AccountService;
import com.qcby.utils.TxUtils;import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;//JDK代理对象
public class JdkProxy {public static Object getPoxy(final AccountService accountService){/***使用Jdk的动态代理生成代理对象*/Object proxy=Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() {/***调用代理对象的方法,invoke方法就会去执行*@paramproxy*@parammethod*@paramargs*@return*@throwsThrowable* 用到反射*/public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {Object result= null;try {//开启事务TxUtils.startTransaction();//对象目标对象的方法进行增强result =method.invoke(accountService,args);//提交事务TxUtils.commit();}catch (Exception e){e.printStackTrace();TxUtils.rollback();}finally {TxUtils.close();}return result;}});return proxy;}}

测试代码

package com.qcby.test;import com.qcby.JDKUtils.JdkProxy;
import com.qcby.model.Account;
import com.qcby.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;//整合junit文件
@RunWith(SpringJUnit4ClassRunner.class) // 使用Spring的测试运行器
@ContextConfiguration(locations = "classpath:applicationContext.xml") // 加载Spring配置文件
public class AccountTest {@Autowired@Qualifier("accountService")private AccountService accountService;@Testpublic void testSaveAll(){//        ApplicationContext ac =new ClassPathXmlApplicationContext("applicationContext.xml");
//        AccountService accountService=(AccountService) ac.getBean("accountService");// 模拟两个账户Account account1 = new Account();account1.setName("abc");account1.setMoney(1000.0);Account account2 = new Account();account2.setName("qqq");account2.setMoney(2000.0);//生成代理对象Object proxyobj = JdkProxy.getPoxy(accountService);//强转AccountService proxy =(AccountService)proxyobj;//调用代理对象的方法proxy.saveAll(account1,account2);// 调用转账方法
//        accountService.saveAll(account1, account2);System.out.println("转账成功!");}}

AOP方式的优势

  1. 业务解耦:业务代码不再包含事务管理逻辑

  2. 代码复用:事务管理逻辑集中处理

  3. 维护方便:修改事务逻辑只需修改代理类

  4. 灵活性强:可以动态为不同方法添加不同增强

二、Spring的AOP相关概念

AOP的概述

通俗理解:AOP(面向切面编程)就像给程序"打补丁",可以在不修改原有代码的情况下,给程序添加新功能。

生活比喻

  • 就像给手机贴膜:不需要改变手机本身,就能增加防刮功能

  • 类似快递包装:在原有商品外增加保护层,不影响商品本身

解决了什么问题

  1. 传统OOP(面向对象)中,像日志记录、权限检查这些通用功能需要在每个方法里重复写

  2. AOP把这些"横切关注点"抽离出来,实现"一次编写,多处使用"

核心特点

  • 横向抽取:不同于继承的纵向扩展

  • 非侵入式:不改动原有代码

  • 动态增强:运行时给方法添加功能 

AOP的优势 

三大好处

  1. 消灭重复代码

    • 把日志、事务等通用功能集中管理

    • 示例:不用在每个支付方法里写日志代码

  2. 提升开发效率

    • 专注业务逻辑开发

    • 通用功能通过配置实现

  3. 维护更方便

    • 修改日志格式只需改一处

    • 不影响业务代码

AOP底层原理 

两种代理方式

  1. JDK动态代理(适合接口)

    • 过程:创建接口的代理类 → 加载到JVM → 调用代理方法

    • 特点:要求目标类必须实现接口

  2. CGLIB代理(适合类)

    • 过程:生成目标类的子类作为代理

    • 特点:通过继承实现,无需接口 

工作流程:原始方法 → [代理拦截] → 添加增强功能 → 执行原始方法 → [代理拦截] → 返回结果 

 AOP相关的术语

Joinpoint(连接点) 所谓连接点是指那些被拦截到的点。在spring中,这些点指的 是方法,因为spring只支持方法类型的连接点

Pointcut(切入点)-- 所谓切入点是指我们要对哪些Joinpoint进行拦截的定义

Advice(通知/增强)-- 所谓通知是指拦截到Joinpoint之后所要做的事情就是通知. 通知分为前置通知,后置通知,异常通知,最终通知,环绕通知(切面要完成的功能)

Target(目标对象)-- 代理的目标对象

Weaving(织入)-- 是指把增强应用到目标对象来创建新的代理对象的过程

Proxy(代理)--一个类被AOP织入增强后,就产生一个结果代理类

Aspect(切面)--是切入点和通知的结合,以后咱们自己来编写和配置的 

 

三、Spring的AOP技术--配置文件方式

AOP配置文件方式入门程序

基本实现步骤:

1、创建Maven工程并导入坐标依赖

<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><!--AOP联盟--><dependency><groupId>aopalliance</groupId><artifactId>aopalliance</artifactId><version>1.0</version></dependency><!--SpringAspects--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.0.2.RELEASE</version></dependency><!--aspectj--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.3</version></dependency></dependencies>

2、创建Spring配置文件

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"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/aophttp://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>

3、定义目标类

public class UserServiceImpl implements UserService {@Overridepublic void save() {System.out.println("业务层:保存用户...");}
}

4、配置目标类

<bean id="userService" class="com.qcbyjy.demo2.UserServiceImpl"/>

5、定义切面类

public class MyXmlAspect {public void log() {System.out.println("增强的方法执行了...");}
}

6、配置AOP

<aop:config><aop:aspect ref="myXmlAspect"><aop:before method="log" pointcut="execution(public void com.qcbyjy.demo2.UserServiceImpl.save())"/></aop:aspect>
</aop:config>

7、编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext_demo2.xml")
public class Demo2 {@Autowiredprivate UserService userService;@Testpublic void run1() {userService.save();}
}

切入点表达式 

表达式语法:基本格式:execution([修饰符] 返回值类型 包名.类名.方法名(参数))

  • 修饰符可以省略不写,不是必须要出现的。
  • 返回值类型是不能省略不写的,根据你的方法来编写返回值。可以使用*代替。
  • 包名例如:com.tx.demo3.BookDaoImpl
  • 首先com是不能省略不写的,但是可以使用*代替
  • 中间的包名可以使用*号代替
  • 如果想省略中间的包名可以使用
  • 类名也可以使用*号代替,也有类似的写法:*DaoImpl
  • 方法也可以使用*号代替 参数如果是一个参数可以使用*号代替,如果想代表任意参数使用

通用表达式:

<aop:before method="log" pointcut="execution(* com.qcbyjy.*.*ServiceImpl.save*(..))"/>

 AOP通知类型

五种通知类型配置

<aop:config><aop:aspect ref="myXmlAspect"><!-- 1. 前置通知 --><aop:before method="beforeAdvice" pointcut="execution(* com.qcbyjy..*.*(..))"/><!-- 2. 最终通知 --><aop:after method="afterAdvice" pointcut="execution(* com.qcbyjy..*.*(..))"/><!-- 3. 后置通知 --><aop:after-returning method="afterReturningAdvice" pointcut="execution(* com.qcbyjy..*.*(..))"/><!-- 4. 异常通知 --><aop:after-throwing method="afterThrowingAdvice" pointcut="execution(* com.qcbyjy..*.*(..))"/><!-- 5. 环绕通知 --><aop:around method="aroundAdvice" pointcut="execution(* com.qcbyjy..*.*(..))"/></aop:aspect>
</aop:config>

切面类实现

public class MyXmlAspect {// 前置通知public void beforeAdvice() {System.out.println("前置通知:方法执行前调用");}// 最终通知public void afterAdvice() {System.out.println("最终通知:无论是否异常都会执行");}// 后置通知public void afterReturningAdvice(Object result) {System.out.println("后置通知:方法正常返回,返回值: " + result);}// 异常通知public void afterThrowingAdvice(Exception ex) {System.out.println("异常通知:方法抛出异常: " + ex.getMessage());}// 环绕通知public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {System.out.println("环绕通知-前");Object result = pjp.proceed();  // 必须手动调用目标方法System.out.println("环绕通知-后");return result;}
}

关键点总结

  1. 执行顺序

    • 环绕通知前 → 前置通知 → 目标方法 → 环绕通知后 → 后置通知/异常通知 → 最终通知

  2. XML配置要点

    • 必须引入AOP命名空间

    • 使用<aop:config>作为根标签

    • 每个切面使用<aop:aspect>配置

    • 通知类型通过不同子标签配置

  3. 通知选择

    • 需要获取返回值 → 后置通知

    • 需要处理异常 → 异常通知

    • 需要方法前后都处理 → 环绕通知

    • 无论成功失败都要执行 → 最终通知

四、Spring的AOP技术--注解方式

AOP注解方式入门程序

基本实现步骤: 

1、创建Maven工程并导入坐标

<dependencies><dependency><groupId>org.springframework</groupId><artifactId>spring-context</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>commons-logging</groupId><artifactId>commons-logging</artifactId><version>1.2</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.12</version></dependency><dependency><groupId>org.springframework</groupId><artifactId>spring-test</artifactId><version>5.0.2.RELEASE</version></dependency><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version></dependency><!--AOP联盟--><dependency><groupId>aopalliance</groupId><artifactId>aopalliance</artifactId><version>1.0</version></dependency><!--SpringAspects--><dependency><groupId>org.springframework</groupId><artifactId>spring-aspects</artifactId><version>5.0.2.RELEASE</version></dependency><!--aspectj--><dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId><version>1.8.3</version></dependency></dependencies>

2、编写接口和实现类

public interface OrderService {void save();
}@Service
public class OrderServiceImpl implements OrderService {@Overridepublic void save() {System.out.println("订单保存操作...");}
}

3、编写切面类

package com.qcbyjy.demo3;import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;@Component  // 把该类交给IOC管理
@Aspect     // 声明是切面类
public class MyAnnoAspect {// 前置通知@Before("execution(public * com.qcbyjy.demo3.OrderServiceImpl.save(..))")public void log() {System.out.println("增强了...");}
}

4、配置自动代理(XML)

<!-- applicationContext2.xml -->
<context:component-scan base-package="com.qcbyjy.demo3"/>
<aop:aspectj-autoproxy/>

 5、编写测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext2.xml")
public class Demo3 {@Autowiredprivate OrderService orderService;@Testpublic void run1() {orderService.save();}
}

 通知类型的注解

五种通知类型

1、@Before - 前置通知

@Before("execution(* com.qcbyjy.demo3.*.*(..))")
public void beforeAdvice() {System.out.println("前置通知:方法执行前调用");
}

 2、@AfterReturning - 后置通知

@AfterReturning(pointcut="execution(* com.qcbyjy.demo3.*.*(..))", returning="result")
public void afterReturningAdvice(Object result) {System.out.println("后置通知:方法正常返回,返回值: " + result);
}

3、@Around - 环绕通知

@Around("execution(* com.qcbyjy.demo3.*.*(..))")
public Object aroundAdvice(ProceedingJoinPoint pjp) throws Throwable {System.out.println("环绕通知-前");Object result = pjp.proceed();  // 必须手动调用目标方法System.out.println("环绕通知-后");return result;
}

4、@After - 最终通知

@After("execution(* com.qcbyjy.demo3.*.*(..))")
public void afterAdvice() {System.out.println("最终通知:无论是否异常都会执行");
}

5、@AfterThrowing - 异常抛出通知

@AfterThrowing(pointcut="execution(* com.qcbyjy.demo3.*.*(..))", throwing="ex")
public void afterThrowingAdvice(Exception ex) {System.out.println("异常通知:方法抛出异常: " + ex.getMessage());
}

纯注解的方式

 完全使用Java配置替代XML,使用配置类

package com.qcbyjy.demo3;import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.EnableAspectJAutoProxy;@Configuration  // 声明为配置类
@ComponentScan("com.qcbyjy.demo3")  // 组件扫描
@EnableAspectJAutoProxy  // 等价于XML中的<aop:aspectj-autoproxy/>
public class SpringConfig {
}

测试类调整

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)  // 使用配置类
public class Demo3 {@Autowiredprivate OrderService orderService;@Testpublic void run1() {orderService.save();}
}

关键点总结

  1. 切面类必须使用@Aspect@Component注解标记

  2. 通知注解需要指定切入点表达式

  3. 纯注解配置核心是@EnableAspectJAutoProxy注解

  4. 执行顺序:Around前→Before→目标方法→Around后→AfterReturning/AfterThrowing→After

  5. XML与注解对比

    • XML配置更集中,适合大型项目

    • 注解更简洁,适合中小型项目

相关文章:

  • STC-ISP烧录过程中显示“正在检测目标单片机...”
  • 互联网大厂Java求职面试实战:Spring Boot到微服务的技术问答解析
  • 视频编解码学习10之成像技术原理
  • ActiveMQ 生产环境问题排查与调优指南(二)
  • Java面试常见技术问题解析
  • 自然语言处理与BI融合实战:ChatBI动态语义解析技术架构剖析
  • 计算机网络——以太网交换机
  • xilinx QDMA开发调试记录
  • 云计算中的虚拟化:成本节省、可扩展性与灾难恢复的完美结合
  • 【android bluetooth 框架分析 02】【Module详解 5】【HciLayer 模块介绍】
  • 【工作记录】crmeb后端项目打开、运行
  • .Net HttpClient 使用请求数据
  • Python并发编程:开启性能优化的大门(7/10)
  • Redis从基础到高阶应用:核心命令解析与延迟队列、事务消息实战设计
  • 【JavaWeb+后端常用部件】
  • LeetCode面试题 01.03 URL化
  • [Java][Leetcode middle] 80. 删除有序数组中的重复项 II
  • 【Linux网络】HTTP
  • 嵌入式硬件篇---无线通信模块
  • 游戏引擎学习第272天:显式移动转换
  • “远践”项目启动公益生态圈,上海青少年公益力量蓬勃生长
  • 继71路之后,上海中心城区将迎来第二条中运量公交
  • 高波︱忆陈昊:在中年之前离去
  • 沙县小吃中东首店在沙特首都利雅得开业,首天营业额5万元
  • 27岁杨阳拟任苏木镇党委副职,系2020年内蒙古自治区选调生
  • 多元史料下的“西狩”叙事——《“庚子西狩”中外资料六种》解题