Spring(6)AOP的相关概念(一)
如果说IoC是Spring的核心,那么面向切面编程就是Spring最为重要的功能之一,在数据库事务中切面编程被广泛的应用。接下来我们从基础开始学习AOP编程。
一、动态代理
1、一个简单的约定游戏
(1)约定规则
首先我们创建一个Intercept接口,代码如下:
package com.xhbjava.Inc; /** * 接口Intercept * @author mr.wang * */ public interface Interceptor { public void berfor(Object obj); public void after(Object obj); public void afterReturning(Object obj); public void afterThrowing(Object obj); }
这是一个拦截接口,我们可以对它创建实现类。如果使用过Spring Aop会发现这个定义的接口和Spring Aop定义的消息十分相似。
此时我们在生成对象的时候我们要求用这样的一个类去生成相应的对象,代码如下:
package com.xhbjava.factory; import com.xhbjava.Inc.Interceptor; public class ProxyBeanFactory { public static<T> T getBean(T obj,Interceptor interceptor){ return (T) ProxyBeanUtil.getBean(obj,interceptor); } }
具体类ProxyBeanUtil的getBean方法逻辑我们先不予理会,现在只需了解使用了这个方法后,存在约定(obj和interceptor为空的情况先忽略),当一个对象通过ProxyBeanFactory 的getBean方法定义后,约定如下:
1)Bean必须是一个实现了某一个接口的对象。
2)最先会执行拦截器的befor方法。
3)其次执行Bean的方法(通过反射的形式)。
4)执行Bean方法时,无论是否产生异常,都会执行after方法。
5)执行Bean方法时,如果不产生异常,则执行afterReturning方法;如果产生异常,则会执行afterThrowing方法。
这个约定已经十分接近SpringAOP对我们的约定,所以该约定十分重要,其流程图如下:
(2)根据规则编写代码
上面给出了接口和获取Bean的方法,同时也给出了具体的约定,现在根据预定编写代码,打印账户信息,由于约定服务对象必须实现接口,因此我们自定义一个CountService接口。
package com.xhbjava.service; import com.xhbjava.pojo.Account; /** * 账户的业务层接口 * * @author mr.wang * */ public interface IAccountService { /** * 打印方法 * * @param account */ public void printAccount(Account account); }
编写实现类:
package com.xhbjava.service.impl; import com.xhbjava.pojo.Account; import com.xhbjava.service.IAccountService; /** * accountService接口实现类 * @author mr.wang * */ public class AccountServiceImpl implements IAccountService { @Override public void printAccount(Account account) { System.out.println( "{id=" + account.getId() + ",name=" + account.getName() + ",money=" + account.getMmoney() + "}"); } }
实现拦截器:
package com.xhbjava.Inc; public class AccountInterceptor implements Interceptor { @Override public void berfor(Object obj) { System.out.println("准备打印账户信息。"); } @Override public void after(Object obj) { System.out.println("已完成打印账户信息处理。"); } @Override public void afterReturning(Object obj) { System.out.println("刚刚完成打印账户信息。"); } @Override public void afterThrowing(Object obj) { System.out.println("打印账户信息功能异常!"); } }
编写测试类并运行测试:
package com.xhbjava.test; import com.xhbjava.Inc.AccountInterceptor; import com.xhbjava.Inc.Interceptor; import com.xhbjava.factory.ProxyBeanFactory; import com.xhbjava.pojo.Account; import com.xhbjava.service.IAccountService; import com.xhbjava.service.impl.AccountServiceImpl; public class SpringGameMain { public static void main(String[] args) { IAccountService accountService = new AccountServiceImpl(); Interceptor interceptor = new AccountInterceptor(); IAccountService proxy = ProxyBeanFactory.getBean(accountService, interceptor); Account account = new Account(1,"张三",(float) 1234.5); proxy.printAccount(account); System.out.println("--------------------------测试afterthrowing方法------------------------------------------------"); account = null; proxy.printAccount(account); } }
(3)核心代码ProxyBeanUtil
上面的代码我们是基于动态代理模式,该模式是SpringAOP的基础,代码如下:
package com.xhbjava.utils; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import com.xhbjava.Inc.Interceptor; public class ProxyBeanUtil implements InvocationHandler { //被代理对象 private Object obj; //拦截器 private Interceptor interceptor = null; /** * 获取动态代理对象 * @param obj * @param interceptor * @return 动态代理对象 */ public static Object getBean(Object obj,Interceptor interceptor) { ProxyBeanUtil _this = new ProxyBeanUtil(); _this.obj = obj; _this.interceptor = interceptor; return Proxy.newProxyInstance(obj.getClass().getClassLoader(), obj.getClass().getInterfaces(), _this); } /** * 代理方法 * 代理对象 * 当前调度方法 * 参数 * 方法返回 */ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { Object retObj = null; boolean exceptionFlag= false; interceptor.berfore(obj); try { //反射原有方法 retObj = method.invoke(obj, args); }catch(Exception e) { exceptionFlag = true; } if(exceptionFlag) { interceptor.afterThrowing(obj); }else { interceptor.afterReturning(obj); } return retObj; } }
上面的代码使用了动态代理,首先通过getBean方法保存了代理对象、拦截器(interceptor )和参数(args),然后生成JDK动态代理对象(proxy),同时绑定了ProxyBeanUtil返回对象作为其代理类,这样代理对象调用方法的时候会进入到ProxyBeanUtil的invoke方法中。在invoke方法中,我们按照流程图实现了拦截器的方法,通过exceptionFlag标志判断反射原有对象方法的是否发生异常。
关于动态代理详细介绍可以查看链接:
二、Spring AOP的基本概念
1.什么是AOP
AOP:全称是 Aspect Oriented Programming 即:面向切面编程。
在软件业,AOP为Aspect Oriented Programming的缩写,意为:面向切面编程,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
简单来说,就是把重复的代码抽取出来,在需要执行的时候使用动态代理技术,在不修改源码的基础上对我们需要的方法进行增强。
2.AOP的作用及优势
作用:在OOP中,正是这种分散在各处且与对象核心功能无关的代码(横切代码)的存在,使得模块复用难度增加。AOP则将封装好的对象剖开,找出其中对多个对象产生影响的公共行为,并将其封装为一个可重用的模块,这个模块被命名为“切面”(Aspect),切面将那些与业务无关,却被业务模块共同调用的逻辑提取并封装起来,减少了系统中的重复代码,降低了模块间的耦合度,同时提高了系统的可维护性。
优势:(1)减少重复代码(2)提高开发效率(3)维护方便
3.AOP的实现方式
使用动态代理技术
三、AOP的具体应用
1.案例问题所在
在前面我们IAccount增删改查例子中,我们的业务层中可以看出我们的事务被自动动控制了,就是使用了 connection 对象的 setAutoCommit(true),此方式控制事务,如果每次都执行一条 sql 语句,没有问题,但是如果业务方法一次要执行多条 sql语句,这种方式就无法实现功能了。
2.例子
(1)我们在业务层加入一个方法
业务层接口:
/** * 转账操作 * @param sourceName * @param targetName * @param money */ void transfer(String sourceName,String targetName,Float money);
业务层实现类:
@Override public void transfer(String sourceName, String targetName, Float money) { //根据名称查询两个账户信息 Account source = accountDao.findByName(sourceName); Account target = accountDao.findByName(targetName); //转出账户减钱,转入账户加钱 source.setMoney(source.getMoney()-money); target.setMoney(target.getMoney()+money); //更新两个账户 accountDao.updateAccount(source); int i=1/0; //模拟转账异常 accountDao.updateAccount(target); }
(2)问题
我们执行该方法时,由于异常,转账失败。但是我们每次执行持久层的方法是独立事务所以我们无法实现事务控制。这不符合事务的一致性。
(3)问题的解决
我们在业务层控制事务的提交和回滚。
代码如下:
utils工具类:
package com.xhbjava.utils; import java.sql.Connection; import javax.sql.DataSource; /** * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定 */ public class ConnectionUtils { private ThreadLocal<Connection> tl = new ThreadLocal<Connection>(); private DataSource dataSource; public void setDataSource(DataSource dataSource) { this.dataSource = dataSource; } /** * 获取当前线程上的连接 * @return */ public Connection getThreadConnection() { try{ //1.先从ThreadLocal上获取 Connection conn = tl.get(); //2.判断当前线程上是否有连接 if (conn == null) { //3.从数据源中获取一个连接,并且存入ThreadLocal中 conn = dataSource.getConnection(); tl.set(conn); } //4.返回当前线程上的连接 return conn; }catch (Exception e){ throw new RuntimeException(e); } } /** * 把连接和线程解绑 */ public void removeConnection(){ tl.remove(); } }
package com.xhbjava.utils; /** * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接 */ public class TransactionManager { private ConnectionUtils connectionUtils; public void setConnectionUtils(ConnectionUtils connectionUtils) { this.connectionUtils = connectionUtils; } /** * 开启事务 */ public void beginTransaction(){ try { connectionUtils.getThreadConnection().setAutoCommit(false); }catch (Exception e){ e.printStackTrace(); } } /** * 提交事务 */ public void commit(){ try { connectionUtils.getThreadConnection().commit(); }catch (Exception e){ e.printStackTrace(); } } /** * 回滚事务 */ public void rollback(){ try { connectionUtils.getThreadConnection().rollback(); }catch (Exception e){ e.printStackTrace(); } } /** * 释放连接 */ public void release(){ try { connectionUtils.getThreadConnection().close();//还回连接池中 connectionUtils.removeConnection(); }catch (Exception e){ e.printStackTrace(); } } }
业务层实现类:
package com.xhbjava.service.impl; import java.util.List; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import com.xhbjava.dao.IAccountDao; import com.xhbjava.pojo.Account; import com.xhbjava.service.IAccountService; import com.xhbjava.utils.TransactionManager; /** * 账户业务层接口实现类 * * @author mr.wang * */ @Component("accountService") public class AccountServiceImpl implements IAccountService { @Autowired private IAccountDao accountDao; @Autowired private TransactionManager txManager; @Override public void saveAccount(Account account) { try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 accountDao.saveAccount(account); //3.提交事务 txManager.commit(); }catch (Exception e){ //4.回滚操作 txManager.rollback(); throw new RuntimeException(e); }finally { //5.释放连接 txManager.release(); } } @Override public void updateAccount(Account account) { try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 accountDao.updateAccount(account); //3.提交事务 txManager.commit(); }catch (Exception e){ //4.回滚操作 txManager.rollback(); }finally { //5.释放连接 txManager.release(); } } @Override public void deleteAccount(Integer accountId) { try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 accountDao.deleteAccount(accountId); //3.提交事务 txManager.commit(); }catch (Exception e){ //4.回滚操作 txManager.rollback(); }finally { //5.释放连接 txManager.release(); } } @Override public Account findById(Integer accountId) { try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 Account account = accountDao.findById(accountId); //3.提交事务 txManager.commit(); //4.返回结果 return account; }catch (Exception e){ //5.回滚操作 txManager.rollback(); throw new RuntimeException(e); }finally { //6.释放连接 txManager.release(); } } @Override public List<Account> findAll() { try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 List<Account> accounts = accountDao.findAll(); //3.提交事务 txManager.commit(); //4.返回结果 return accounts; }catch (Exception e){ //5.回滚操作 txManager.rollback(); throw new RuntimeException(e); }finally { //6.释放连接 txManager.release(); } } @Override public void transfer(String sourceName, String targetName, Float money) { try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 //2.1根据名称查询转出账户 Account source = accountDao.findByName(sourceName); //2.2根据名称查询转入账户 Account target = accountDao.findByName(targetName); //2.3转出账户减钱 source.setMoney(source.getMoney()-money); //2.4转入账户加钱 target.setMoney(target.getMoney()+money); //2.5更新转出账户 accountDao.updateAccount(source); int i=1/0; //2.6更新转入账户 accountDao.updateAccount(target); //3.提交事务 txManager.commit(); }catch (Exception e){ //4.回滚操作 txManager.rollback(); e.printStackTrace(); }finally { //5.释放连接 txManager.release(); } } }
上面我们对代码进行了修改,实现了事务的控制,但是这样做使得我们业务层逻辑代码重复,并且业务层的方法和事务控制逻辑耦合性增强。一旦提交,回滚,资源释放中任何一个方法名称变动我们都需要修改很多业务层的代码。这些问题有待解决。
(4)解决案例中业务层逻辑代码重复等问题
上面我们已经对动态代理有了进一步了解。我们可以通过动态代理解决我们改造代码时发现的问题。
package com.xhbjava.factory; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; import com.xhbjava.service.IAccountService; import com.xhbjava.service.impl.AccountServiceImpl; import com.xhbjava.utils.TransactionManager; public class BeanFactory { /** * 创建账户业务层实现类的代理对象 * * @return */ public static IAccountService getAccountService() { // 1.定义被代理对象 final IAccountService accountService = new AccountServiceImpl(); // 2.创建代理对象 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 rtValue = null; try { // 开启事务 TransactionManager.beginTransaction(); // 执行业务层方法 rtValue = method.invoke(accountService, args); // 提交事务 TransactionManager.commit(); } catch (Exception e) { // 回滚事务 TransactionManager.rollback(); e.printStackTrace(); } finally { // 释放资源 TransactionManager.release(); } return rtValue; } }); return proxyAccountService; } }