主流架構二:Spring(4)動态代理實作事務控制
- 一、事務控制
-
- 1.ConnectionUtils
- 2.TransactionManager
- 3.新的問題
- 二、動态代理回顧
-
- 2.動态代理的作用
- 3.動态代理的分類:
-
- (1)基于接口的動态代理
- (2)基于子類的動态代理
- 三、動态代理實作事務控制
-
- 1.用于建立service代理對象的factory
- 2.配置依賴注入
- 3.service層與測試類
一、事務控制
事務我們在java web中有涉及。Spring中 我們使用了 connection 對象的 setAutoCommit(true)此方式控制事務,如果我們每次都執行一條 sql 語句,沒有問題,但是如果業務方法一次要執行多條 sql語句,這種方式就無法實作功能了。
![](https://img.laitimes.com/img/__Qf2AjLwojIjJCLyojI0JCLiAzNfRHLGZkRGZkRfJ3bs92YsYTMfVmepNHLxcGRNFTQq5keRpHW4Z0MMBjVtJWd0ckW65UbM5WOHJWa5kHT20ESjBjUIF2X0hXZ0xCMx81dvRWYoNHLrdEZwZ1Rh5WNXp1bwNjW1ZUba9VZwlHdssmch1mclRXY39CXldWYtlWPzNXZj9mcw1ycz9WL49zZuBnL5IDNwMDNxUTM1IDOwkTMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
解決辦法:
讓業務層來控制事務的送出和復原。(這個我們之前已經在 web 階段講過了)
改造後的業務層實作類:
用 注:此處沒有使用 spring 的 的IOC.
/**
* 賬戶的業務層實作類
*/
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao = new AccountDaoImpl();
@Override
public List<Account> findAllAccount() {
List<Account> accounts = null;
try {
TransactionManager.beginTransaction();
accounts = accountDao.findAll();
TransactionManager.commit();
return accounts;
} catch (Exception e) {
TransactionManager.rollback();
e.printStackTrace();
}finally {
TransactionManager.release();
}
return null;
}
......
}
下面來看一下這兩個工具類 ConnectionUtils 和 TransactionManager
1.ConnectionUtils
連接配接的工具類,它用于從資料源中擷取一個連接配接,并且實作和線程綁定,是以之後我們擷取目前線程的連接配接便可以直接connectionUtils.getThreadConnection()
/**
* 連接配接的工具類,它用于從資料源中擷取一個連接配接,并且實作和線程綁定
*/
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 connection = tl.get();
//2.判斷目前線程上是否有連接配接
if (connection == null) {
//3.從資料源中擷取一個連接配接,并且存入ThreadLocal中
connection = dataSource.getConnection();
tl.set(connection);
}
//4.傳回目前線程上的連接配接
return connection;
}catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* 把連結和線程解綁
*/
public void removeConnection() {
tl.remove();
}
}
2.TransactionManager
和事務管理相關的工具類,它包含了:開啟事務,送出事務,復原事務和釋放連接配接(總管理)
例如:connectionUtils.getThreadConnection().setAutoCommit(false); 就等價于connection.setAutoCommit(false);
**
* 和事務管理相關的工具類,它包含了:開啟事務,送出事務,復原事務和釋放連接配接
* @author Mango
*/
public class TransactionManager {
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
/**
* 開啟事務
*/
public void beginTransaction() {
try {
connectionUtils.getThreadConnection().setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 送出事務
*/
public void commit() {
try {
connectionUtils.getThreadConnection().commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 復原事務
*/
public void rollback() {
try {
connectionUtils.getThreadConnection().rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
/**
* 釋放連接配接
*/
public void release() {
try {
connectionUtils.getThreadConnection().close();//還回連接配接池中
connectionUtils.removeConnection();//連接配接與線程解綁
} catch (SQLException e) {
e.printStackTrace();
}
}
}
3.新的問題
上一小節的代碼,通過對業務層改造,已經可以實作事務控制了,但是由于我們添加了事務控制,也産生了一
個新的問題:
@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 Account findAccountById(Integer accountId) {
try {
//1.開啟事務
txManager.beginTransaction();
//2.執行操作
Account account = accountDao.findAccountById(accountId);
//3.送出事務
txManager.commit();
//4.傳回結果
return account;
}catch (Exception e){
//5.復原操作
txManager.rollback();
throw new RuntimeException(e);
}finally {
//6.釋放連接配接
txManager.release();
}
}
業務層方法變得臃腫了,裡面充斥着很多重複代碼。并且業務層方法和事務控制方法耦合了。
試想一下,如果我們此時送出,復原,釋放資源中任何一個方法名變更,都需要修改業務層的代碼,況且這還隻是一個業務層實作類,而實際的項目中這種業務層實作類可能有十幾個甚至幾十個。
二、動态代理回顧
1、 動态代理的特點
(1)位元組碼随用随建立,随用随加載。
(2)它與靜态代理的差別也在于此。因為靜态代理是位元組碼一上來就建立好,并完成加載。
(3)裝飾者模式就是靜态代理的一種展現。
2.動态代理的作用
**不修改源碼的基礎上對方法增強**
3.動态代理的分類:
(1)基于接口的動态代理
(1)基于接口的動态代理:
涉及的類:Proxy
提供者:JDK官方
如何建立代理對象:
使用proxy類中的newProxyInstance方法(要求:被代理的類至少實作其一個接口(例如IProducer,而不能用Producer來實作),如果沒有就不能使用)
newProxyInstance方法
參數:1.ClassLoader:類加載器
它是用于加載 代理對象位元組碼的。被代理對象的類加載器(固定寫法)。
2Interfaces:位元組碼數組
它是用于讓代理對象和被代理對象有相同的方法,實作相同的接口。(固定寫法)
3.InvocationHandler:用于提供增強的代碼
它是讓我們寫如何代理。我們一般都是寫一個該接口的實作類,通常情況下都是匿名内部類,但不是必須
此接口的實作類都是誰用誰寫。
/**
* 模拟一個service
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();//必須是最終的
IProducer proxyProducer =(IProducer) Proxy.newProxyInstance(
參數(1)producer.getClass().getClassLoader(),
參數(2)producer.getClass().getInterfaces(),
參數(3)new InvocationHandler() {
/**
* 作用:執行被代理對象的任何接口的方法都會經過該方法
* 方法參數的含義:
* @param proxy 代理對象的引用
* @param method 目前執行的方法
* @param args 目前執行方法所需的參數
* @return 和被代理對象方法有相同的傳回值
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//提供增強方法的代碼
Object returnValue = null;
//1.擷取方法執行的參數
Float money = (Float) args[0];
//2.判斷目前方法是不是銷售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money * 0.8f);
}
return returnValue;//方法執行一遍便有相同傳回值
}
});
proxyProducer.saleProduct(10000f);
}
}
(2)基于子類的動态代理
基于子類的動态代理
提供者:第三方的 CGLib,如果報 asmxxxx 異常,需要導入 asm.jar。需要導入jar包
要求:被代理類不能用 final 修飾的類(最終類)。
使用 CGLib 的 的 Enhancer 類建立代理對象
(2)基于子類的動态代理:
涉及的類:Enhancer
提供者:第三方cglib庫
如何建立代理對象:
使用Enhancer中的create方法(要求:被代理的類不能是最終類)
create中的參數:
1.class:位元組碼
用于指定被代理對象的位元組碼(這裡Producer被代理)
2.callback:用于提供增強的代碼
一般寫的都是該接口的子接口實作類:MenthInterceptor
/**
* 模拟一個消費者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
Producer cglibProducer =(Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
*
* @param o
* @param method
* @param objects
以上三個參數和基于接口的動态代理中的invoke方法的參數時一樣的
* @param methodProxy 目前執行方法的代理對象
* @return
* @throws Throwable
*/
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
//提供增強方法的代碼
Object returnValue = null;
//1.擷取方法執行的參數
Float money = (Float) objects[0];
//2.判斷目前方法是不是銷售
if("saleProduct".equals(method.getName())) {
returnValue = method.invoke(producer, money * 0.8f);
}
return returnValue;//方法執行一遍便有相同傳回值
}
});
cglibProducer.saleProduct(10000f);
}
}
三、動态代理實作事務控制
1.用于建立service代理對象的factory
解決案例中的問題:
建立客戶業務層對象工廠(當然也可以建立其他業務層對象,隻不過我們此處不做那麼繁瑣)
/**
* 用于建立service代理對象的factory
* @author Mango
*/
public class BeanFactory {
private IAccountService accountService;
public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
private TransactionManager transactionManager;
public void setTransactionManager(TransactionManager transactionManager) {
this.transactionManager = transactionManager;
}
public IAccountService getAccountService() {
return (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事務的支援
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object returnValue = null;
try {
//1.開啟事務
transactionManager.beginTransaction();
//2.執行操作
returnValue = method.invoke(accountService, args);
//3.送出事務
transactionManager.commit();
//4.傳回結果
return returnValue;
} catch (Exception e) {
//5.復原
transactionManager.rollback();
throw new RuntimeException(e);
} finally {
//6.釋放連接配接
transactionManager.release();
}
}
});
}
}
2.配置依賴注入
<!--配置代理的service對象-->
<bean id="proxyAccountService" factory-bean="beanfactory" factory-method="getAccountService"></bean>
<!--配置beanfactory-->
<bean id="beanfactory" class="com.itheima.factory.BeanFactory">
<!--注入service-->
<property name="accountService" ref="accountService"></property>
<!--注入事務管理器-->
<property name="transactionManager" ref="transactionManger"></property>
</bean>
3.service層與測試類
當我們改造完成之後,業務層用于控制事務的重複代碼就都可以删掉了。
/**
* 賬戶的業務層實作類
*/
public class AccountServiceImpl implements IAccountService{
private IAccountDao accountDao;
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String souceName, String targetName, Float money) {
System.out.println("transfer......");
//2.執行操作
//1.根據名稱查詢轉出賬戶
Account source = accountDao.findAccountByName(souceName);
//2.根據名稱查詢轉入賬戶
Account target = accountDao.findAccountByName(targetName);
//3.轉出賬戶減錢
source.setMoney(source.getMoney() - money);
//4.轉入賬戶加錢
target.setMoney(target.getMoney() + money);
//5.更新轉出賬戶
accountDao.updateAccount(source);
//6.更新轉入賬戶
accountDao.updateAccount(target);
}
}
是以測試類中:
/**
* 使用Junit單元測試:測試我們的配置
*/
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {
@Autowired
@Qualifier("proxyAccountService")//使用BeanFactory中的代理AccountService來執行
private IAccountService as;
@Test
public void testTransfer() {
as.transfer("aaa","bbb", 100f);
}
}