video-course:https://www.bilibili.com/video/BV1Sb411s7vP
(1/2)内容索引:
--------------------------------------------------------------------------------
1.Demo--问题引入;
2.动态代理-JDK;
3.动态代理-CGLIB;
4.问题解决:(对业务层代码进行动态代理的增强--添加事务操作);
5.AOP概念 && 术语;
6.Spring AOP的XML 和 Annotation配置;
-------------------------------------------------------------------------------
(2/2)详解:
--------------------------------------------------------------------------------
1.Demo--问题引入:
(1)项目结构:
(2)需求:
实现转账操作。
(3)业务逻辑:
1 @Override 2 public void transfer(String sourceName, String targetName) { 3 //1.根据名称查询转出账户 4 Account source = accountDao.findAccountByName(sourceName); 5 //2.根据名称查询转入账户 6 Account target = accountDao.findAccountByName(targetName); 7 //3.转出账户减钱 8 source.setMoney(source.getMoney() - 100); 9 //4.转入账户加钱 10 target.setMoney(target.getMoney() + 100); 11 //5.更新转出账户 12 accountDao.updateAccount(source); 13 //6.更新转入账户 14 accountDao.updateAccount(target); 15 }
(4)数据访问层需要:
1 @Override 2 public Account findAccountByName(String accountName) { 3 try{ 4 List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name=?",new BeanListHandler<>(Account.class),accountName); 5 if(accounts == null || accounts.size() == 0){ 6 return null; 7 } 8 if(accounts.size() > 1){ 9 throw new RuntimeException("结果不唯一"); 10 } 11 return accounts.get(0); 12 } catch (SQLException e) { 13 throw new RuntimeException(e); 14 } 15 } 16 //此方法依赖: 17 1.ConnectionUtils() 18 (1)获取数据源; 19 (2)获取连接; 20 1)连接需要绑定线程,即获取绑定线程的连接--实现同步; 21 private ThreadLocal<Connection> t1 = new ThreadLocal<Connection>(); 22 (3)移除连接;(解绑线程与连接) 23 2.commons-dbutils.jar(QueryRunner对象、结果集操作对象)
..依赖注入--Spring配置;
..测试;
(5)问题:
1)程序发生异常,数据会不同步--需要事务支持;
一改:添加事务支持!
--------------------------------------------------------------------------------
(1)事务管理工具类编写:
1 public class TransactionManager { 2 3 private ConnectionUtils connectionUtils; 4 5 public void setConnectionUtils(ConnectionUtils connectionUtils) { 6 this.connectionUtils = connectionUtils; 7 } 8 9 //开启事务 10 public void beginTransaction(){ 11 try{ 12 connectionUtils.getThreadConnection().setAutoCommit(false); 13 } catch (SQLException e) { 14 e.printStackTrace(); 15 } 16 } 17 //提交事务 18 public void commit() { 19 try{ 20 connectionUtils.getThreadConnection().commit(); 21 } catch (SQLException e) { 22 e.printStackTrace(); 23 } 24 } 25 //回滚事务 26 public void rollback(){ 27 try{ 28 connectionUtils.getThreadConnection().rollback(); 29 } catch (SQLException e) { 30 e.printStackTrace(); 31 } 32 } 33 //释放连接 34 public void release(){ 35 try{ 36 connectionUtils.getThreadConnection().close();//还回线程池 37 connectionUtils.removeConnection();//线程和连接解绑 38 } catch (SQLException e) { 39 e.printStackTrace(); 40 } 41 } 42 }
(2)为业务添加事务管理:
1 @Override 2 public void transfer(String sourceName, String targetName) { 3 try{ 4 //开启事务 5 txManager.beginTransaction(); 6 //执行操作 1-6 7 //1.根据名称查询转出账户 8 Account source = accountDao.findAccountByName(sourceName); 9 //2.根据名称查询转入账户 10 Account target = accountDao.findAccountByName(targetName); 11 //3.转出账户减钱 12 source.setMoney(source.getMoney() - 100); 13 //4.转入账户加钱 14 target.setMoney(target.getMoney() + 100); 15 //5.更新转出账户 16 accountDao.updateAccount(source); 17 //6.更新转入账户 18 accountDao.updateAccount(target); 19 //提交事务 20 txManager.commit(); 21 }catch (Exception e){ 22 //事务回滚 23 txManager.rollback(); 24 throw new RuntimeException(e); 25 }finally { 26 //释放连接 27 txManager.release(); 28 } 29 }
对修改后代码中存在的问题分析:
(1)事务与业务代码纠缠不清,不便于维护;
(2)对于业务操作(需要事务支持的),每次都要写事务操作代码(繁琐,效率低);
--------------------------------------------------------------------------------
二改分析:(根据JDK/CGLIB动态代理技术,对业务层代码(AccountServiceImpl)进行增强!)
2.动态代理-JDK(默认)
(1)接口:
1 public interface Iproducer { 2 //销售 3 public void saleProduct(float money); 4 //售后 5 public void afterService(float money); 6 }
(2)实现类(被代理类):
1 public class Producer implements Iproducer{ 2 //销售 3 public void saleProduct(float money){ 4 System.out.println("=================="); 5 System.out.println("销售产品,回钱! "+money); 6 } 7 //售后 8 public void afterService(float moeny){ 9 System.out.println("=================="); 10 System.out.println("服务,收取费用! "+moeny); 11 } 12 }
(3)JDK动态代理测试(通过反射技术,根据接口,对被代理类进行代码增强):
1 public class Client { 2 public static void main(String[] args){ 3 Producer producer = new Producer(); 4 //不使用动态代理 5 // producer.saleProduct(10000f); 6 // producer.afterService(10000f); 7 //使用动态代理: 8 /*特点:字节码随用随创建,随加载 9 * 作用:不修改源码的基础上对方法进行增强 10 * 分类: 11 * 基于接口(JDK); 涉及类:Proxy; 创建对象:newProxyInstance; 需要:代理类至少实现一个接口 12 * newProxyInstance()方法的参数: 13 * ClassLoader:类加载器,用于加载代理对象字节码,和被代理对象使用相同类加载器:固定写法。 14 * Class[]:用于让代理对象 和 被代理对象具有相同的方法:固定写法。 15 * InvocationHandler:用于提供增强的代码:让写如何代理! 16 * 一般写接口的实现类,一般写内部类,不必须 17 * 基于子类(CGLIB); 18 * 可以没有接口,但必须不是最终类(即可以被继承) 19 * */ 20 Iproducer proxyInstance = (Iproducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), 21 producer.getClass().getInterfaces(), 22 new InvocationHandler() { 23 //方法作用:执行被代理对象的任何接口都会经过该方法 24 /*方法参数的含义: 25 * proxy:代理对象的引用 26 * method:当前执行的方法-被代理对象的方法 27 * args:当前执行方法所需的参数 和 被代理对象有相同的返回值 28 * */ 29 @Override 30 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 31 Object result = null; 32 //提供增强的代码 33 //获取方法参数: 34 Float money = (Float) args[0]; 35 //判断当前方法是不是销售(所需要增强的方法?) 36 if("saleProduct".equals(method.getName())){ 37 result = method.invoke(producer,money*0.8f); 38 } 39 return result; 40 } 41 }); 42 proxyInstance.saleProduct(10000f); 43 } 44 }
3.动态代理-CGLIB;
(1)被代理类(同上):
1 public class Producer implements Iproducer{ 2 //销售 3 public void saleProduct(float money){ 4 System.out.println("=================="); 5 System.out.println("销售产品,回钱! "+money); 6 } 7 //售后 8 public void afterService(float moeny){ 9 System.out.println("=================="); 10 System.out.println("服务,收取费用! "+moeny); 11 } 12 }
(2)CGLIB动态代理测试(通过反射技术,根据接口,对被代理类进行代码增强):
1 public class Client { 2 public static void main(String[] args) { 3 Producer producer = new Producer(); 4 //不使用动态代理 5 // producer.saleProduct(10000f); 6 // producer.afterService(10000f); 7 //使用动态代理: 8 /*特点:字节码随用随创建,随加载 9 * 作用:不修改源码的基础上对方法进行增强 10 * 分类: 11 * 基于子类(第三方cglib库); 涉及类:Enhancer; 创建对象:create方法; 需要:被代理类不能是最终类 12 * create()方法的参数: 13 * Class:字节码,被代理对象的字节码,固定写法。 14 * Callback:用于提供增强的代码:让写如何代理!一般写子接口(MethodInterceptor)的实现类 15 * 基于子类(CGLIB); 16 * 可以没有接口,但必须不是最终类(即可以被继承) 17 * */ 18 Producer cglibProxy = (Producer) Enhancer.create(producer.getClass(), 19 new MethodInterceptor() { 20 /*参数列表: 21 * proxy 22 * method 23 * args 24 * 以上三个参数,和基于接口的方法invoke参数一样 25 * methodProxy:当前执行方法的代理 26 * */ 27 @Override 28 public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable { 29 Object result = null; 30 //提供增强的代码 31 //获取方法参数: 32 Float money = (Float) args[0]; 33 //判断当前方法是不是销售(所需要增强的方法?) 34 if("saleProduct".equals(method.getName())){ 35 result = method.invoke(producer,money*0.8f); 36 } 37 return result; 38 } 39 }); 40 cglibProxy.saleProduct(10000f); 41 } 42 }
4.问题解决:(对业务层代码进行动态代理的增强--添加事务操作)
(1)通过对动态代理技术的分析,可以很容易联想到本案例(Demo)中把事务操作“织入”业务层代码的情形,并且考虑易于后期维护和兼顾开发效率等因素二改:对Demo中的业务层代码,使用动态代理技术,对其进行增强(即添加事务支持)
(2)代码:对于Demo中新建包:factory,用于存放对业务层添加事务管理抽取出的工具类:
1 //Beanfactory.java--用于创建Service对象的代理对象的工厂: 2 public class BeanFactory { 3 //事务管理对象 4 private TransactionManager txManager; 5 //使用Spring注入所需要setter方法 6 public final void setTxManager(TransactionManager txManager) { 7 this.txManager = txManager; 8 } 9 10 //使用JDK动态代理,需要的接口 11 private IAccountService accountService; 12 public void setAccountService(IAccountService accountService) { 13 this.accountService = accountService; 14 } 15 16 //获取Service代理对象(核心--创建该工厂的目的; 方式:动态代理) 17 public IAccountService getAccountService(){ 18 Proxy.newProxyInstance(accountService.getClass().getClassLoader(), 19 accountService.getClass().getInterfaces(), 20 new InvocationHandler() { 21 //添加事务支持(增强) 22 @Override 23 public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { 24 //过滤掉test方法 25 if ("test".equals(method.getName())) { 26 return method.invoke(accountService, args); 27 } 28 Object result = null; 29 try { 30 //开启事务 31 txManager.beginTransaction(); 32 //执行方法 33 result = method.invoke(accountService, args); 34 //提交事务 35 txManager.commit(); 36 return result; 37 } catch (Exception e) { 38 //事务回滚 39 txManager.rollback(); 40 throw new RuntimeException(e); 41 } finally { 42 //释放连接 43 txManager.release(); 44 } 45 } 46 }); 47 return accountService; 48 } 49 }
(3)IoC容器中声明该工厂,就可以使用了;
由于声明的各个对象之间相互引用,故贴出所有配置项:
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd"> <!--代理--> <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean> <!--beanFactory--> <bean id="beanFactory" class="com.spring_02_aop._01_demo.factory.BeanFactory"> <property name="accountService" ref="accountService"/> <property name="txManager" ref="txManager"/> </bean> <!-- 配置Service --> <bean id="accountService" class="com.spring_02_aop._01_demo.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> </bean> <!--事务管理--> <bean id="txManager" class="com.spring_02_aop._01_demo.utils.TransactionManager"> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置Dao对象--> <bean id="accountDao" class="com.spring_02_aop._01_demo.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"></property> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!--注入数据源--> <constructor-arg name="ds" ref="dataSource"></constructor-arg> </bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy?serverTimezone=UTC"></property> <property name="user" value="root"></property> <property name="password" value="921112"></property> </bean> <bean id="connectionUtils" class="com.spring_02_aop._01_demo.utils.ConnectionUtils"> <property name="dataSource" ref="dataSource"/> </bean> </beans>
二改问题总结:
(1)解决了需求 && 解决了以上--一改后代码中存在的问题;
(2)业务层代码 和 事务代码相互分离,一目了然,“又好吃又好听,都好都好~”;
(4)备注(我的开发环境):
IDE:IntelliJ IDEA
Spring.version:Spring 5.2.1
java.version:1.8.0_221
DBMS:MySQL 8.0.19
数据库eesy(取自网上教程资料),建表语句:
create table `account` ( `id` int (11), `name` varchar (120), `money` float );
OS:Windows NT6.1
Demo中涉及的第三方Jar:
cglib-3.2.5.jar(依赖包:asm-5.0.4.jar);
commons-dbutils-1.6.jar
spring核心包
数据库相关:
驱动:
mysql-connector-java-8.0.19.jar
连接池:
mchange-commons-java-0.2.10.jar
c3p0-0.9.5.0.jar
--------------------------------------------------------------------------------
5.AOP(基于动态代理实现)概念 && 术语;
(1)Joinpoint--切入点--业务中的方法(不论是不是要被增强,如上述transfer()及其他所有业务方法)
(2)Pointcut--切入点--业务中可被增强的方法(如上述transfer())
(3)Advice--通知--拦截到Joinpoint或者Pointcut之后要执行的操作(要增强写增强代码,不要增强,不写或者啥也不做)
(4)Aspect--切面--切入点和通知的结合(大概如此),包含多种类型:
1)before
2)after-returning
3)after-throwing
4)after
5)around(若用环绕通知可以实现1-4四种类型能完成的需求,推荐用环绕通知)
(5)其他术语,请移步官网自行查询;
--------------------------------------------------------------------------------
6.Spring AOP的XML 和 Annotation的简单配置;
(0)我用的jar(读者可根据需要版本自行下载,注意不同版本之间可能的异常):
1)aopalliance-1.0.jar
2)aspectjweaver-1.8.7.jar
3)spring-aop-5.2.1.RELEASE.jar
4)spring-aspects-5.2.1.RELEASE.jar
(1)XML配置:
0)需求:业务层方法中添加日志!(..)
1)项目结构:
2)AccountServiceImpl.java
1 //业务层代码 2 public class AccountServiceImpl implements IAccountService { 3 4 @Override 5 public void saveAccount() { 6 System.out.println("执行了保存"); 7 } 8 9 @Override 10 public void updateAccount(int i) { 11 System.out.println("执行了更新 "+i); 12 13 } 14 15 @Override 16 public int deleteAccount() { 17 System.out.println("执行了删除"); 18 return 0; 19 } 20 }
3)Logger.java
1 //待织入日志代码 2 public class Logger { 3 public void printLog(){ 4 System.out.println("Logger记录日志"); 5 } 6 }
4)AOP配置--application-config.xml(作用同bean.xml)
<?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/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd"> <!--配置service对象--> <bean id="accountService" class="com.spring_02_aop._03_spring_aop.service.impl.AccountServiceImpl"></bean> <!--配置Logger--> <bean id="logger" class="com.spring_02_aop._03_spring_aop.utils.Logger"></bean> <!--配置AOP--> <aop:config> <aop:pointcut id="pt1" expression="execution(* com.spring_02_aop._03_spring_aop.service.impl.*.*(..))"/> <!--配置切面--> <aop:aspect id="logAdvice" ref="logger"> <!--配置通知的类型--> <!-- <aop:before method="printLog" pointcut="execution(* com.spring_02_aop._03_spring_aop.service.impl.*.*(..))"></aop:before>--> <!-- <aop:after-returning method="printLog" pointcut="execution(* com.spring_02_aop._03_spring_aop.service.impl.*.*(..))"></aop:after-returning>--> <!-- <aop:after-throwing method="printLog" pointcut="execution(* com.spring_02_aop._03_spring_aop.service.impl.*.*(..))"></aop:after-throwing>--> <!-- <aop:after method="printLog" pointcut="execution(* com.spring_02_aop._03_spring_aop.service.impl.*.*(..))"></aop:after>--> <aop:around method="aroundPrintLog" pointcut-ref="pt1"></aop:around> </aop:aspect> </aop:config> </beans> <!--spring中基于XML的AOP配置步骤 1、把通知Bean也交给spring来管理 2、使用aop:config标签表明开始AOP的配置 3、使用aop:aspect标签表明配置切面 id属性:是给切面提供一个唯一标识 ref属性:是指定通知类bean的Id。 4、在aop:aspect标签的内部使用对应标签来配置通知的类型 我们现在示例是让printLog方法在切入点方法执行之前之前:所以是前置通知 aop:before:表示配置前置通知 method属性:用于指定Logger类中哪个方法是前置通知 pointcut属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强 切入点表达式的写法: 关键字:execution(表达式) 表达式: 访问修饰符 返回值 包名.包名.包名...类名.方法名(参数列表) -->
5)自行测试。
--------------------------------------------------------------------------------
(2)Annotation方式:
1)业务层代码加@Service注解
2)日志类代码加@Component && @Aspect注解
3)日志方法代码可根据AOP配置及通知类型(除环绕通知)加不同注解
4)application-config.xml的配置:
<!--头文件同上--> <!--Spring创建容器要扫描的包--> <context:component-scan base-package="com.spring_02_aop._04_annotationAOP"></context:component-scan> <!--开启注解aop的支持--> <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
5)测试:...
--------------------------------------------------------------------------------
**环绕通知:(如事务、日志、安全等通用且与具体应用无关的代码应用环绕通知注意点)
(1)通用代码内方法,需要添加ProceedingJoinPoint类型参数
(2)基本算法:
//如方法添加ProceedingJoinPoint pjp类型参数 try{ 1.pjp对象获取所有参数(代理方法所需要参数--即业务方法所需参数) //前置通知 2.执行代理方法,获取结果 result = pjp.proceed(args); //返回通知 }catch(){ //异常通知 }finally{ //最终通知 }
(3)Debug截图(底层代码实现复杂,我能力有限,大概流程):
1)通过代理获取被代理的方法,及参数类型
2)通过JDK动态代理,执行代理方法invoke
3)......底层层层的执行、判断、过滤,获取到参数对象列表(这里只有一个参数)
4)用获取的参数执行方法,获取结果:
::以上仅供学习参考,如有错误,非常希望指出!