https://www.bilibili.com/video/av47952931
p46~55
文章目錄
Account案例中轉賬方法的事務問題
事務控制應該都在業務層,之前的案例中都在持久層,需要修改
寫兩個工具類
2個工具類
ConnectionUtils
/**
* 連接的工具類,它用於從數據源中獲取一個連接,並且實現和線程的綁定
*/
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();
}
}
TransactionManager
/**
* 和事務管理相關的工具類,它包含了,開啓事務,提交事務,回滾事務和釋放連接
*/
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();
}
}
}
連接還回連接池中後,還需再把連接和線程解綁,否則下次ConnectionUtils中判斷是否有連接是true,但這個連接是已經關閉的錯誤的連接
注入
<!-- 配置Connection的工具類 ConnectionUtils -->
<bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
<!-- 注入數據源-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事務管理器-->
<bean id="txManager" class="com.itheima.utils.TransactionManager">
<!-- 注入ConnectionUtils -->
<property name="connectionUtils" ref="connectionUtils"></property>
</bean>
代碼改造
修改之後不需要在beans.xml中注入dataSource了
<!-- 配置Connection的工具類 ConnectionUtils -->
<bean id="connectionUtils" class="com.itheima.utils.ConnectionUtils">
<!-- 注入數據源-->
<!-- property name="dataSource" ref="dataSource"></property-->
</bean>
在AccountDaoImpl中加一個ConnectionUtils
private ConnectionUtils connectionUtils;
public void setConnectionUtils(ConnectionUtils connectionUtils) {
this.connectionUtils = connectionUtils;
}
並且runner獲取連接改爲
runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
此時,AccountServiceImpl中一個完整的事務流程是
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();
}
}
但是每個方法都要這樣寫,很臃腫
而且方法的依賴很嚴重(如果TransactionManager中beginTransaction方法名改成beginTransaction1,AccountServiceImpl中每一處用到的都要改)
進一步改造:代理
& 現在的依賴有些亂七八糟,在後面Spring的事務控制中解決
動態代理
描述
特點:字節碼隨用隨創建,隨用隨加載
作用:不修改源碼的基礎上對方法增強
分類:
- 基於接口的動態代理
- 基於子類的動態代理
用處如:
連接池close方法關閉時不能真正關閉,還要還回池中。可以使用動態代理對其進行增強,把它還回池裏
解決中文亂碼,request對象的方法增強,用裝飾者模式可以實現,也可以用動態代理實現
基於接口的動態代理
涉及的類:Proxy
提供者:JDK官方
如何創建代理對象:使用Proxy類中的newProxyInstance方法
創建代理對象的要求:被代理類最少實現一個接口,如果沒有則不能使用
newProxyInstance方法的參數:
- ClassLoader:類加載器
它是用於加載代理對象字節碼的。和被代理對象使用相同的類加載器。固定寫法 - Class[]:字節碼數組
它是用於讓代理對象和被代理對象有相同方法。固定寫法 - InvocationHandler:用於提供增強的代碼
寫如何代理。一般都是寫一個該接口的實現類,通常情況下都是匿名內部類,但不是必須的。此接口的實現類都是誰用誰寫
示例
/**
* 對生產廠家要求的接口
*/
public interface IProducer {
/**
* 銷售
* @param money
*/
public void saleProduct(float money);
/**
* 售後
* @param money
*/
public void afterService(float money);
}
/**
* 一個生產者
*/
public class Producer implements IProducer{
public void saleProduct(float money){
System.out.println("銷售產品,並拿到錢:"+money);
}
public void afterService(float money){
System.out.println("提供售後服務,並拿到錢:"+money);
}
}
/**
* 模擬一個消費者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
IProducer proxyProducer = (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(),
producer.getClass().getInterfaces(),
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);
}
}
使用代理後,消費者付10000,代理提成20%,生產者拿到8000
並沒有對生產者的代碼做任何修改,但是實現了增強
此處即爲基於接口的動態代理
但是有一個問題
如果生產者沒有實現接口,就不能這樣用了,會報代理異常
基於子類的動態代理
要求有第三方jar包的支持
<dependencies>
<dependency>
<groupId>cglib</groupId>
<artifactId>cglib</artifactId>
<version>2.1_3</version>
</dependency>
</dependencies>
涉及的類:Enhancer
提供者:第三方cglib庫
如何創建代理對象:使用Enhancer類中的create方法
創建代理對象的要求:被代理類不能是最終類
create方法的參數:
- Class:字節碼
用於指定被代理對象的字節碼 - Callback:用於提供增強的代碼
寫如何代理。一般是寫一個該接口的實現類,通常情況下都是匿名內部類,但不是必須的
此接口的實現類都是誰用誰寫
一般寫的都是該接口的子接口實現類:MethodInterceptor
示例
/**
* 一個生產者
*/
public class Producer {
/**
* 銷售
* @param money
*/
public void saleProduct(float money){
System.out.println("銷售產品,並拿到錢:"+money);
}
/**
* 售後
* @param money
*/
public void afterService(float money){
System.out.println("提供售後服務,並拿到錢:"+money);
}
}
package com.itheima.cglib;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import java.lang.reflect.Method;
/**
* 模擬一個消費者
*/
public class Client {
public static void main(String[] args) {
final Producer producer = new Producer();
Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
/**
* 執行被代理對象的任何方法都會經過該方法
* @param proxy
* @param method
* @param args
* 以上三個參數和基於接口的動態代理中invoke方法的參數是一樣的
* @param methodProxy :當前執行方法的代理對象
* @return
* @throws Throwable
*/
@Override
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) 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;
}
});
cglibProducer.saleProduct(12000f);
}
}
使用動態代理實現事務控制
/**
* 用於創建Service的代理對象的工廠
*/
public class BeanFactory {
private IAccountService accountService;
private TransactionManager txManager;
public final void setAccountService(IAccountService accountService) {
this.accountService = accountService;
}
public void setTxManager(TransactionManager txManager) {
this.txManager = txManager;
}
/**
* 獲取Service代理對象
* @return
*/
public IAccountService getAccountService() {
return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
accountService.getClass().getInterfaces(),
new InvocationHandler() {
/**
* 添加事務的支持
*
* @param proxy
* @param method
* @param args
* @return
* @throws Throwable
*/
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();
}
}
});
}
}
並對beans.xml做相應的修改
測試中IAccountService只用Autowird不夠了,還需@Qualifier(“proxyAccountService”)
使用動態代理後,消除了重複代碼,解除了方法的依賴
但是配置變得繁瑣了
更好的方式?——>AOP