文章目录
转账操作的问题
假设我们想实现一个转账操作,我们在IAccountService(账户的业务层接口)中添加转账方法,
/** * 转账 * @param sourceName 转出商户名称 * @param targetName 转入账户名 * @param money 转账金额 */ void transfer(String sourceName,String targetName,float money);
然后在其实现类中添加方法的实现
@Override public void transfer(String sourceName, String targetName, float money) { //1.根据名称查询转出账户 Account source = dao.findAccountByName(sourceName); //2.根据名称查询转入账户 Account target = dao.findAccountByName(targetName); //3.转出账户减钱 source.setMoney(source.getMoney()-money); //4.转入账户加钱 target.setMoney(target.getMoney()+money); //5.更新转出账户 dao.updateAccount(source); //6.更新转入账户 dao.updateAccount(target); }
添加测试方法
package com.spring.test; import com.spring.service.IAccountService; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:bean.xml") public class AccountservicImpl { @Autowired private IAccountService service; /** * 测试转账 */ @Test public void testTransfer() { service.transfer("aaa","bbb",100); } }
此时运行可以正常执行
但此时我们在方法的实现中动点手脚
@Override public void transfer(String sourceName, String targetName, float money) { //1.根据名称查询转出账户 Account source = dao.findAccountByName(sourceName); //2.根据名称查询转入账户 Account target = dao.findAccountByName(targetName); //3.转出账户减钱 source.setMoney(source.getMoney()-money); //4.转入账户加钱 target.setMoney(target.getMoney()+money); //5.更新转出账户 dao.updateAccount(source); //捣乱 int a=1/0; //6.更新转入账户 dao.updateAccount(target); }
此时运行会发生报错,并且查看数据库会发现钱被扣掉了,但转入的账户却没有收到。
经过老师的分析:
我们需要将这些操作封装到一个事务中(如果语句成功便修改,不成功则回滚)
初步解决问题
下面我们就来解决这个问题:
首先我们要创建一个ConnectionUtils来实现线程与连接的绑定:
import javax.sql.DataSource; import java.sql.Connection; /** * 连接工具类 * 实现线程绑定 * @author 28985 */ public class ConnectionUtils { private ThreadLocal<Connection> t1 = new ThreadLocal<Connection>(); private DataSource source; public void setSource(DataSource source) { this.source = source; } public Connection getThreadConnection(){ try { //从threadlocal上获取连接 Connection connection = t1.get(); //判断线程上是否有连接 if (connection==null){ //从数据源中获取连接,并和线程绑定存入ThreadLocal中 connection=source.getConnection(); t1.set(connection); } return connection; } catch (Exception e){ throw new RuntimeException(e); } } public void removeConnection(){ t1.remove(); } }
其中关于ThreadLocal的详解在:https://www.jianshu.com/p/6fc3bba12f38
之后我们需要创建一个事务相关类为我们提供各种操作事务的方法TransactionManage
/** * 事务相关 * @author 28985 */ public class TransactionManage { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /** * 开启事务 */ public void openTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); } catch (Exception e){ throw new RuntimeException(e); } } /** * 提交事务 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); } catch (Exception e){ throw new RuntimeException(e); } } /** * 回滚事务 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); } catch (Exception e){ throw new RuntimeException(e); } } /** * 关闭事务 */ public void release(){ try { connectionUtils.getThreadConnection().close();//将连接还回连接池 connectionUtils.removeConnection();//将线程和连接解绑 } catch (Exception e){ throw new RuntimeException(e); } } }
之后就可以对业务层做出调整
其基本结构为:
try { //1 开启事务 //2 执行操作 //3 提交事务 //4 返回结果 } catch (Exception e){ //5 回滚操作 } finally { //6 释放连接 }
于是便有了以下代码
/** * 账户的业务层实现类 * 事务控制都应该在业务层 * @author 28985 */ public class IAccountServiceImpl implements IAccountService { private IAccountDao dao; private TransactionManage manage; public void setManage(TransactionManage manage) { this.manage = manage; } public IAccountDao getDao() { return dao; } public void setDao(IAccountDao dao) { this.dao = dao; } public IAccountServiceImpl(IAccountDao dao) { this.dao = dao; } public IAccountServiceImpl() { } @Override public List<Account> findAllAccount() { try { //1 开启事务 manage.openTransaction(); //2 执行操作 List<Account> allAccount = dao.findAllAccount(); //3 提交事务 manage.commit(); //4 返回结果 return allAccount; } catch (Exception e){ //5 回滚操作 manage.rollback(); throw new RuntimeException(e); } finally { //6 释放连接 manage.release(); } } @Override public Account findAccountById(Integer id) { try { //1 开启事务 manage.openTransaction(); //2 执行操作 Account account = dao.findAccountById(id); //3 提交事务 manage.commit(); //4 返回结果 return account; } catch (Exception e){ //5 回滚操作 manage.rollback(); throw new RuntimeException(e); } finally { //6 释放连接 manage.release(); } } @Override public void saveAccount(Account account) { try { //1 开启事务 manage.openTransaction(); //2 执行操作 dao.saveAccount(account); //3 提交事务 manage.commit(); } catch (Exception e){ //5 回滚操作 manage.rollback(); } finally { //6 释放连接 manage.release(); } } @Override public void updateAccount(Account account) { try { //1 开启事务 manage.openTransaction(); //2 执行操作 dao.updateAccount(account); //3 提交事务 manage.commit(); } catch (Exception e){ //5 回滚操作 manage.rollback(); throw new RuntimeException(e); } finally { //6 释放连接 manage.release(); } } @Override public void delAccount(int id) { try { //1 开启事务 manage.openTransaction(); //2 执行操作 dao.delAccount(id); //3 提交事务 manage.commit(); } catch (Exception e){ //5 回滚操作 manage.rollback(); throw new RuntimeException(e); } finally { //6 释放连接 manage.release(); } } @Override public void transfer(String sourceName, String targetName, float money) { try { //1 开启事务 manage.openTransaction(); //2 执行操作 //2.1.根据名称查询转出账户 Account source = dao.findAccountByName(sourceName); //2.2.根据名称查询转入账户 Account target = dao.findAccountByName(targetName); //2.3.转出账户减钱 source.setMoney(source.getMoney()-money); //2.4.转入账户加钱 target.setMoney(target.getMoney()+money); //2.5.更新转出账户 dao.updateAccount(source); //2.捣乱 int a=1/0; //2.6.更新转入账户 dao.updateAccount(target); //3 提交事务 manage.commit(); } catch (Exception e){ //5 回滚操作 manage.rollback(); throw new RuntimeException(e); } finally { //6 释放连接 manage.release(); } } }
于是我们可以发现Connection已经由QueryRunner转移到ConnectionUtils里
所以我们对每个runner.query("select * from account",new BeanListHandler<Account>(Account.class));
更改为runner.query(connectionUtil.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
即:
/** * 账户的持久层实现类 * @author 28985 */ public class AccountDaoImpl implements IAccountDao { private QueryRunner runner; private ConnectionUtils connectionUtil; public void setConnectionUtil(ConnectionUtils connectionUtil) { this.connectionUtil = connectionUtil; } public void setRunner(QueryRunner runner) { this.runner = runner; } @Override public List<Account> findAllAccount() { try { return runner.query(connectionUtil.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class)); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public Account findAccountById(Integer id) { try { return runner.query(connectionUtil.getThreadConnection(),"select * from account where id ="+id,new BeanHandler<Account>(Account.class)); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public void saveAccount(Account account) { try { runner.update(connectionUtil.getThreadConnection(),"insert into account(name,money) value(?,?)",account.getName(),account.getMoney()); } catch (SQLException e) { throw new RuntimeException(e); } } @Override public void updateAccount(Account account) { try { runner.update(connectionUtil.getThreadConnection(),"update account set name=?,money=? where id = ?",account.getName(),account.getMoney(),account.getId()); } catch (SQLException e) { e.printStackTrace(); } } @Override public void delAccount(int id) { try { runner.update(connectionUtil.getThreadConnection(),"delete from account where id = ?",id); } catch (SQLException e) { e.printStackTrace(); } } @Override public Account findAccountByName(String name) { try { List<Account> accounts=runner.query(connectionUtil.getThreadConnection(),"select * from account where name = ?",new BeanListHandler<Account>(Account.class),name); if (accounts.size()==0||accounts == null){ return null; } if (accounts.size()>1){ throw new RuntimeException("结果集出现问题"); } return accounts.get(0); } catch (SQLException e) { throw new RuntimeException(e); } } }
之后就是spring’配置文件了
注意这里已经不需要给QueryRunner注入datasource了
<?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="accountService" class="com.spring.service.impl.IAccountServiceImpl"> <property name="dao" ref="accountDao"/> <property name="manage" ref="transactionManage"/> </bean> <!--配置持久层--> <bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl"> <property name="runner" ref="runner"/> <property name="connectionUtil" ref="connectionUtils"></property> </bean> <!--配置runner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!-- <constructor-arg name="ds" ref="ds"/> 此时已经不需要通过QueryRunner的连接了,连接已经交给Utils来管理--> </bean> <!--配置DataSource(数据源)--> <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="root"/> <property name="password" value="adminadmin"/> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_test"/> </bean> <!-- 配置连接工具类连接与线程绑定实现类--> <bean id="connectionUtils" class="com.spring.utils.ConnectionUtils"> <property name="source" ref="ds"></property> </bean> <!-- 配置事务相关--> <bean id="transactionManage" class="com.spring.utils.TransactionManage"> <property name="connectionUtils" ref="connectionUtils"></property> </bean> </beans>
然后运行之前的转账方法就会发现事务起到作用,回滚成功了
但这里有一个问题,代码现在非常的臃肿,复用率很低所以接下来会采用动态代理的方式来解决这些问题
动态代理
特点:字节码(class)文件随用随创建,随用随加载
作用:不修改实现类源码的基础上对方法进行增强
或者没有实现类时对方法实现
分类:
基于接口的动态代理
基于子类的动态代理
https://www.liaoxuefeng.com/wiki/1252599548343744/1264804593397984#0
基于接口的动态代理
涉及类:Proxy
提供者:jdk官方
如何创建代理对象:
Proxy类中的newProxyInstance方法
创建代理对象的要求:
被代理对象至少实现一个接口,没有则不能使用
newProxyInstance方法的参数:
ClassLoader 类加载器
加载代理对象字节码,和被代理对象使用相同的类加载器
Class[] 字节码数组
让代理对象和被代理对象有相同的方法(被代理对象的接口)
InvocationHandler 它是让我们写如何代理,一般写一个该接口的实现类 通常为内部类
示例:
制造商接口:
package com.spring.proxy.producers; /** * @author GeGeGe * @version 1.0 * @date 2021/10/2 9:56 */ public interface IProducer { /** * 购买 * @param money 制造商收到的钱 */ public void buy(float money); /** * 售后 * @param money 制造商收到的钱s */ public void shouHou(float money); }
制造商实现类
package com.spring.proxy.producers; /** * @author GeGeGe * @version 1.0 * @date 2021/10/2 10:00 */ public class ProducerImpl implements IProducer{ @Override public void buy(float money) { System.out.println("商品已经售出收到"+money+"元"); } @Override public void shouHou(float money) { System.out.println("商品已售后收到"+money+"元"); } }
主函数
public static void main(String[] args) { ProducerImpl producer = new ProducerImpl(); IProducer producer1 = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(), new InvocationHandler() { /** *执行被代理对象的任何接口方法都会经过该方法 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { float money = (float) args[0]; if ("buy".equals(method.getName())) { money *= 0.8f;//制造商抽成0.2 } return method.invoke(producer,money); } }); producer1.buy(10000); }
此时运行结果为
这表示producer1对象已经经过代理了
基于子类的动态代理
涉及类:Enhancer
提供者:cglib
如何创建代理对象:
Enhancer类中的creat方法
创建代理对象的要求:
被代理对象不能是最终类
newProxyInstance方法的参数:
ClassLoader 类加载器
加载代理对象字节码,和被代理对象使用相同的类加载器
Class[] 字节码数组
让代理对象和被代理对象有相同的方法(被代理对象的接口)
InvocationHandler 它是让我们写如何代理,一般写一个该接口的实现类 通常为内部类
因为使用了cglib 所以我们先导入cglib的依赖
<dependencies> <dependency> <groupId>cglib</groupId> <artifactId>cglib</artifactId> <version>3.3.0</version> </dependency> </dependencies>
更改main代码
public class Client { public static void main(String[] args) { ProducerImpl producer = new ProducerImpl(); IProducer cglibproducer = (IProducer) Enhancer.create(ProducerImpl.class, new MethodInterceptor() { @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { float money = (float) objects[0]; if ("buy".equals(method.getName())) { money *= 0.8f;//制造商抽成0.2 } return method.invoke(producer,money); } }); cglibproducer.buy(1000); } }
此时便可以运行得到结果:
使用动态代理解决事务控制代码臃肿的问题
我们先将原来service中所有关于事务控制的代码删除,将其变为原来的样子
package com.spring.service.impl; import com.spring.dao.IAccountDao; import com.spring.domain.Account; import com.spring.service.IAccountService; import com.spring.utils.TransactionManage; import java.util.List; /** * 账户的业务层实现类 * 事务控制都应该在业务层 * @author 28985 */ public class IAccountServiceImpl implements IAccountService { private IAccountDao dao; public IAccountDao getDao() { return dao; } public void setDao(IAccountDao dao) { this.dao = dao; } public IAccountServiceImpl(IAccountDao dao) { this.dao = dao; } public IAccountServiceImpl() { } @Override public List<Account> findAllAccount() { return dao.findAllAccount(); } @Override public Account findAccountById(Integer id) { return dao.findAccountById(id); } @Override public void saveAccount(Account account) { dao.saveAccount(account); } @Override public void updateAccount(Account account) { dao.updateAccount(account); } @Override public void delAccount(int id) { dao.delAccount(id); } @Override public void transfer(String sourceName, String targetName, float money) { //2.1.根据名称查询转出账户 Account source = dao.findAccountByName(sourceName); //2.2.根据名称查询转入账户 Account target = dao.findAccountByName(targetName); //2.3.转出账户减钱 source.setMoney(source.getMoney()-money); //2.4.转入账户加钱 target.setMoney(target.getMoney()+money); //2.5.更新转出账户 dao.updateAccount(source); //2.捣乱 // int a=1/0; //2.6.更新转入账户 dao.updateAccount(target); } }
此时再创建一个BeanFactory工厂类
里面用来创建AccountService的代理对象
/** * @author GeGeGe * @version 1.0 * @date 2021/10/2 12:19 */ public class BeanFactory { private IAccountService accountService; private TransactionManage manage; public void setManage(TransactionManage manage) { this.manage = manage; } public void setAccountService(IAccountService accountService) { this.accountService = accountService; } /** * 获取service的代理对象 * @return */ public IAccountService getAccountService() { IAccountService proxyAccountService=(IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(), new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object rtObject; try { //1 开启事务 manage.openTransaction(); //2 执行操作 rtObject = method.invoke(accountService,args); //3 提交事务 manage.commit(); //4 返回结果 return rtObject; } catch (Exception e){ //5 回滚操作 manage.rollback(); throw new RuntimeException(e); } finally { //6 释放连接 manage.release(); } } }); return proxyAccountService; } }
代理中增强了事务相关的的代码
然后在bean.xml中为beanfactory注入manage和accountService
并生产一个添加了代理的accountService
<?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"> <!-- 工厂生产的代理AccountService--> <bean id="proxyService" factory-bean="beanFactory" factory-method="getAccountService"/> <!-- 配置工厂--> <bean id="beanFactory" class="com.spring.factory.BeanFactory"> <property name="accountService" ref="accountService"/> <property name="manage" ref="transactionManage"/> </bean> <!--配置业务层--> <bean id="accountService" class="com.spring.service.impl.IAccountServiceImpl"> <property name="dao" ref="accountDao"/> </bean> <!--配置持久层--> <bean id="accountDao" class="com.spring.dao.impl.AccountDaoImpl"> <property name="runner" ref="runner"/> <property name="connectionUtil" ref="connectionUtils"></property> </bean> <!--配置runner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"> <!-- <constructor-arg name="ds" ref="ds"/> 此时已经不需要通过QueryRunner的连接了,连接已经交给Utils来管理--> </bean> <!--配置DataSource(数据源)--> <bean id="ds" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="user" value="root"/> <property name="password" value="adminadmin"/> <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_test"/> </bean> <!-- 配置连接工具类连接与线程绑定实现类--> <bean id="connectionUtils" class="com.spring.utils.ConnectionUtils"> <property name="source" ref="ds"></property> </bean> <!-- 配置事务相关--> <bean id="transactionManage" class="com.spring.utils.TransactionManage"> <property name="connectionUtils" ref="connectionUtils"></property> </bean> </beans>
运行测试:
@RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = "classpath:bean.xml") public class AccountservicImpl { @Qualifier("proxyService") @Autowired private IAccountService service; /** * 测试转账 */ @Test public void testTransfer() { service.transfer("aaa","bbb",100); } }
注意这里添加了注解:@Qualifier("proxyService")
是因为工厂生产的代理对象和配置业务层的beanaccountService
都符合Autowired的条件,这时需要我们进行指定
至此使用动态代理解决事务控制的问题就结束了,但是我们发现配置文件很繁琐,那么就需要我们spring里的AOP登场了
AOP
什么是AOP
aop的中文含义是面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。--------百度百科
AOP的作用及优势
作用:在程序运行期间,不修改源码,对已有方法进行增强
优势:
减少重复代码
提高开发效率
维护方便
spring中的AOP
AOP 相关术语(了解)
Joinpoint(连接点):
所谓连接点是指那些被拦截的点,在spring中,这些点指的是方法,因为spring只支持方法类型的连接点
pointout(切入点):
所谓切入点是指我们要对哪些Joinpoint进行拦截的定义(本案例中指需要进行事务控制的方法,而不需要事务控制的连接点不能叫切入点)
Advice(通知/增强):
通知是指拦截到Joinpoint之后要做的事情
通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知
Intorduction(引介):
特殊的一种通知,在不修改代码的前提下,可以在运行期间动态的为类添加一些方法
Target(目标对象)
代理的目标对象(被代理对象)
Weaving(织入):
指吧增强应用到目标对象来创建新的代理对象的过程
srping采用动态代理织入,AspectJ采用编译期织入和类装载期织入
Proxy(代理):
一个类被AOP织入增强后,就产生一个结果代理类
Aspect(切面):
是切入点和通知的结合
基于xml的AOP
创建业务层接口及实现类
package com.spring.services; /** * 账户的业务层接口 * @author GeGeGe * @version 1.0 * @date 2021/10/5 20:13 */ public interface IAccountService { /** * 模拟保存账户 */ public void save(); /** * 模拟更新账户 * @param i */ public void update(int i); /** * 模拟删除账户 * @return */ public int delete(); }
import com.spring.services.IAccountService; /** * @author GeGeGe * @version 1.0 * @date 2021/10/5 20:27 */ public class AccountServiceImpl implements IAccountService { @Override public void save() { System.out.println("保存"); } @Override public void update(int i) { System.out.println("更新"); } @Override public int delete() { System.out.println("删除"); return 0; } }
在创建一个logger类提供公用方法
package com.spring.utils; /**记录日志工具,里面提供公共的代码 * @author GeGeGe * @version 1.0 * @date 2021/10/5 20:29 */ public class Logger { /** * 前置消息 */ public void beforePrint(){ System.out.println("前置消息"); } /** * 后置消息 */ public void afterPrint(){ System.out.println("后置消息"); } /** * 异常消息 */ public void tryPrint(){ System.out.println("异常消息"); } /** * 消息 */ public void finallyPrint(){ System.out.println("最终消息"); } }
配置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"> <!-- 配置srping的Ioc,把service对象配置进来--> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean> <!-- 配置Logger类 --> <bean id="logger" class="com.itheima.utils.Logger"></bean> <!--配置AOP--> <aop:config> <!-- 配置切入点表达式 id属性用于指定表达式的唯一标识。expression属性用于指定表达式内容 此标签写在aop:aspect标签内部只能当前切面使用。 它还可以写在aop:aspect外面,此时就变成了所有切面可用 --> <aop:pointcut id="pt1" expression="execution(* com.itheima.service.impl.*.*(..))"></aop:pointcut> <!--配置切面 --> <aop:aspect id="logAdvice" ref="logger"> <!-- 配置前置通知:在切入点方法执行之前执行 <aop:before method="beforePrint" pointcut-ref="pt1" ></aop:before>--> <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个 <aop:after-returning method="afterPrint" pointcut-ref="pt1"></aop:after-returning>--> <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个 <aop:after-throwing method="tryPrint" pointcut-ref="pt1"></aop:after-throwing>--> <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行 <aop:after method="finallyPrint" pointcut-ref="pt1"></aop:after>--> </aop:aspect> </aop:config> </beans>
编写main方法:
import com.spring.services.IAccountService; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; /** * @author GeGeGe * @version 1.0 * @date 2021/10/6 15:50 */ public class AopTest { public static void main(String[] args) { ApplicationContext appletContext = new ClassPathXmlApplicationContext("bean.xml"); IAccountService as = (IAccountService)appletContext.getBean("accountService"); as.save(); as.update(1); as.delete(); } }
运行结果:
前置消息 保存 后置消息 最终消息 前置消息 更新 后置消息 最终消息 前置消息 删除 后置消息 最终消息
我们更改下delete方法
@Override public int delete() { System.out.println("删除"); int k = 5/0; return 0; }
结果为:
前置消息 保存 后置消息 最终消息 前置消息 更新 后置消息 最终消息 前置消息 删除 异常消息 最终消息 Exception in thread "main" java.lang.ArithmeticException: / by zero
此时我们可以得出结论 异常消息和后置消息是冲突的,只能存在一个
基于注解的AOP
基于注解的aop的xml配置很简单
只需要指定要扫描的包和开启spring对aop注解的支持
配置文件如下
<?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" xmlns:context="http://www.springframework.org/schema/context" 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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 指定要扫描的包--> <context:component-scan base-package="com.spring"/> <!-- 开启spring对aop注解的支持--> <aop:aspectj-autoproxy/> </beans>
首先要实现之前在xml中实现的
<!-- 配置srping的Ioc,把service对象配置进来--> <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean> <!-- 配置Logger类 --> <bean id="logger" class="com.itheima.utils.Logger"></bean>
这两行代码
我们用注解来代替
AccountServiceImpl和Logger中添加注解@Service和@Component即可
接下来就是aop部分
第一句没有实际作用这里无需进行配置
第二句pointcut这里 我们改为使用pointcut注解,定义了一个空函数
@Pointcut("execution(* com.spring.services.impl.*.*(..))") public void pointcut(){}
第三句为声明切面
我们需要为类添加@Aspect注解
@Aspect
下面这些对应注解
@Before("pointcut()") @AfterReturning("pointcut()") @AfterThrowing("pointcut()") @After("pointcut()") @Around("pointcut()")
所以logger更改后为
package com.spring.utils; import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.*; import org.springframework.stereotype.Component; /**记录日志工具,里面提供公共的代码 * @author GeGeGe * @version 1.0 * @date 2021/10/5 20:29 */ @Component @Aspect//表示当前类是一个切面类 public class Logger { @Pointcut("execution(* com.spring.services.impl.*.*(..))") public void pointcut(){} /** * 前置消息 */ @Before("pointcut()") public void beforePrint(){ System.out.println("前置消息"); } /** * 后置消息 */ @AfterReturning("pointcut()") public void afterPrint(){ System.out.println("后置消息"); } /** * 异常消息 */ @AfterThrowing("pointcut()") public void tryPrint(){ System.out.println("异常消息"); } /** * 消息 */ @After("pointcut()") public void finallyPrint(){ System.out.println("最终消息"); } /** * 环绕通知 * @param pjp */ // @Around("pointcut()") public void aroundPrint(ProceedingJoinPoint pjp){ try { System.out.println("前置"); Object proceed = pjp.proceed(pjp.getArgs()); System.out.println("后置"); } catch (Throwable e) { System.out.println("异常"); throw new RuntimeException(e); }finally { System.out.println("最终"); } } }
最后执行便可以得到xml配置相同的结果 注意在spring5.0.x某些版本中 后置消息和最终消息是有顺序问题的bug
完全不使用xml
@Configuration @ComponentScan(basePackages="com.spring") @EnableAspectJAutoProxy public class SpringConfiguration { }