1、基于一个用户account转账带来的思考
-
domain层
public class Account implements Serializable { private Integer id; private String name; private Float money; public Integer getId() {return id;} public void setId(Integer id) {this.id = id;} public String getName() {return name;} public void setName(String name) {this.name = name;} public Float getMoney() {return money;} public void setMoney(Float money) {this.money = money;} @Override public String toString() { return "Account{" + "id=" + id + ", name='" + name + '\'' + ", money=" + money + '}'; } }
-
service层
public interface IAccountService { /** * 查询所有 * @return */ List<Account> findAllAccount(); /** * 转账 * @param sourceName 转出账户名称 * @param targetName 转入账户名称 * @param money 转账金额 */ void transfer(String sourceName,String targetName,Float money); }
/** * 账户的业务层实现类 * 事务控制应该都是在业务层 */ public class AccountServiceImpl implements IAccountService{ private IAccountDao accountDao; private TransactionManager txManager; //set方法,使用容器导入 public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } //set方法,使用容器导入 public void setAccountDao(IAccountDao accountDao) { this.accountDao = accountDao; } @Override public List<Account> findAllAccount() { try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 List<Account> accounts = accountDao.findAllAccount(); //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.findAccountByName(sourceName); //2.2根据名称查询转入账户 Account target = accountDao.findAccountByName(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(); } } }
-
utils层
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(); } }
/** * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接 */ 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(); } } }
-
配置文件
<?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: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/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 配置Service --> <bean id="accountService" class="com.chen.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> <!-- 注入事务管理器 --> <property name="txManager" ref="txManager"></property> </bean> <!--配置Dao对象--> <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"></property> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/nba"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <!-- 配置Connection的工具类 ConnectionUtils --> <bean id="connectionUtils" class="com.chen.utils.ConnectionUtils"> <!-- 注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务管理器--> <bean id="txManager" class="com.chen.utils.TransactionManager"> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property> </bean> </beans>
-
测试类
package com.itheima.test; import com.itheima.service.IAccountService; 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) @ContextConfiguration(locations = "classpath:bean.xml") public class AccountServiceTest { @Autowired private IAccountService as; @Test public void testTransfer(){ as.transfer("aaa","bbb",100f); } }
2、以上案例带来的问题
以上代码已经用事务完成了代码的提交和回滚,但是与之同时,我们发现代码之中有大量的开启事务、提交事务、回滚操作、释放连接等操作。且业务层的方法于事务层的方法过度耦合,试想一下,若要以上代码再描述account的增、删、改,是否又需要进行大量重复代码的描写。又或者,当你事务类的方法有一个命名不是特别规范,那你是不是又要回头改所有业务层中引用事务的方法名。
因此这个问题便需要动态代理来实现
3、动态代理
字节码随用随创建,随用随加载。这是一个动态的过程。
而静态代理是字节码一上来便创建,并完成了加载。
-
基于接口的动态代理
提供者:JDK 官方的 Proxy 类。
要求:被代理类最少实现一个接口。
-
基于子类的动态代理
提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。 要求:被代理类不能用 final 修饰的类(最终类)。
3.1、基于接口动态代理的Proxy类
- 创建的方式是Proxy.newProxyInstance(三个参数)
- ClassLoader:和被代理对象使用相同的类加载器
- Interfaces:和被代理对象具有相同的行为。实现相同的接口。
- InvocationHandler:如何代理。
- InvocationHandler的三个参数(Object proxy, Method method, Object[] args)
- proxy:代理对象的引用
- method:当前执行的方法对象
- args:执行方法所需的参数
- 需要注意的是
- Proxy.newProxyInstance的返回值应该为目标对象的接口对象
- final修饰符
3.2、基于动态代理的修改
-
创建Service的代理对象的工厂
package com.chen.factory; import com.chen.service.IAccountService; import com.chen.utils.TransactionManager; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; import java.lang.reflect.Proxy; /** * 用于创建Service的代理对象的工厂 */ public class BeanFactory { private IAccountService accountService; private TransactionManager txManager; public void setTxManager(TransactionManager txManager) { this.txManager = txManager; } public final void setAccountService(IAccountService accountService) { this.accountService = accountService; } /** * 获取Service代理对象 * @return */ public IAccountService getAccountService() { return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),//被代理对象使用相同的类加载器 accountService.getClass().getInterfaces(),//和被代理对象具有相同的行为 new InvocationHandler() { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if("test".equals(method.getName())){ return method.invoke(accountService,args); } Object rtValue = null; try { //1.开启事务 txManager.beginTransaction(); //2.执行操作 rtValue = method.invoke(accountService, args); //3.提交事务 txManager.commit(); //4.返回结果 return rtValue; } catch (Exception e) { //5.回滚操作 txManager.rollback(); throw new RuntimeException(e); } finally { //6.释放连接 txManager.release(); } } }); } }
-
service的修改
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public List<Account> findAllAccount() {
return accountDao.findAllAccount();
}
@Override
public void transfer(String sourceName, String targetName, Float money) {
System.out.println("transfer....");
//2.1根据名称查询转出账户
Account source = accountDao.findAccountByName(sourceName);
//2.2根据名称查询转入账户
Account target = accountDao.findAccountByName(targetName);
//2.3转出账户减钱
source.setMoney(source.getMoney()-money);
//2.4转入账户加钱
target.setMoney(target.getMoney()+money);
//2.5更新转出账户
accountDao.updateAccount(source);
//2.6更新转入账户
accountDao.updateAccount(target);
}
}
3. 测试类的修改
```java
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
//因为此时有两个Service的bean在容器,Autowired无法指向,所以需要再加一个Qualifier注解
@Autowired
@Qualifier("proxyAccountService")
private IAccountService as;
@Test
public void testTransfer(){
as.transfer("aaa","bbb",100f);
}
}
-
bean方法的配置文件修改
<?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: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/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 告知spring在创建容器时要扫描的包 --> <!--<context:component-scan base-package="com.itheima"></context:component-scan>--> <!--配置代理的service--> <bean id="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean> <!--配置beanfactory--> <bean id="beanFactory" class="com.chen.factory.BeanFactory"> <!-- 注入service --> <property name="accountService" ref="accountService"></property> <!-- 注入事务管理器 --> <property name="txManager" ref="txManager"></property> </bean> <!-- 配置Service --> <bean id="accountService" class="com.chen.service.impl.AccountServiceImpl"> <!-- 注入dao --> <property name="accountDao" ref="accountDao"></property> </bean> <!--配置Dao对象--> <bean id="accountDao" class="com.chen.dao.impl.AccountDaoImpl"> <!-- 注入QueryRunner --> <property name="runner" ref="runner"></property> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property> </bean> <!--配置QueryRunner--> <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean> <!-- 配置数据源 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <!--连接数据库的必备信息--> <property name="driverClass" value="com.mysql.jdbc.Driver"></property> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/nba"></property> <property name="user" value="root"></property> <property name="password" value="root"></property> </bean> <!-- 配置Connection的工具类 ConnectionUtils --> <bean id="connectionUtils" class="com.chen.utils.ConnectionUtils"> <!-- 注入数据源--> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 配置事务管理器--> <bean id="txManager" class="com.chen.utils.TransactionManager"> <!-- 注入ConnectionUtils --> <property name="connectionUtils" ref="connectionUtils"></property> </bean> </beans>