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方式的优势
-
业务解耦:业务代码不再包含事务管理逻辑
-
代码复用:事务管理逻辑集中处理
-
维护方便:修改事务逻辑只需修改代理类
-
灵活性强:可以动态为不同方法添加不同增强
二、Spring的AOP相关概念
AOP的概述
通俗理解:AOP(面向切面编程)就像给程序"打补丁",可以在不修改原有代码的情况下,给程序添加新功能。
生活比喻:
-
就像给手机贴膜:不需要改变手机本身,就能增加防刮功能
-
类似快递包装:在原有商品外增加保护层,不影响商品本身
解决了什么问题:
-
传统OOP(面向对象)中,像日志记录、权限检查这些通用功能需要在每个方法里重复写
-
AOP把这些"横切关注点"抽离出来,实现"一次编写,多处使用"
核心特点:
-
横向抽取:不同于继承的纵向扩展
-
非侵入式:不改动原有代码
-
动态增强:运行时给方法添加功能
AOP的优势
三大好处:
-
消灭重复代码
-
把日志、事务等通用功能集中管理
-
示例:不用在每个支付方法里写日志代码
-
-
提升开发效率
-
专注业务逻辑开发
-
通用功能通过配置实现
-
-
维护更方便
-
修改日志格式只需改一处
-
不影响业务代码
-
AOP底层原理
两种代理方式:
-
JDK动态代理(适合接口)
-
过程:创建接口的代理类 → 加载到JVM → 调用代理方法
-
特点:要求目标类必须实现接口
-
-
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;}
}
关键点总结
-
执行顺序:
-
环绕通知前 → 前置通知 → 目标方法 → 环绕通知后 → 后置通知/异常通知 → 最终通知
-
-
XML配置要点:
-
必须引入AOP命名空间
-
使用
<aop:config>
作为根标签 -
每个切面使用
<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、编写接口和实现类
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();}
}
关键点总结
-
切面类必须使用
@Aspect
和@Component
注解标记 -
通知注解需要指定切入点表达式
-
纯注解配置核心是
@EnableAspectJAutoProxy
注解 -
执行顺序:Around前→Before→目标方法→Around后→AfterReturning/AfterThrowing→After
-
XML与注解对比:
-
XML配置更集中,适合大型项目
-
注解更简洁,适合中小型项目
-