【Spring Framework】Spring入門教程(四)
本文主要講解內容如下:
- Spring的核心之一 - AOP思想
(1) 代理模式- 動態代理
① JDK的動態代理 (Java官方)
② CGLIB 第三方代理
AOP概述
什麼是AOP(面向切面程式設計)
AOP為Aspect Oriented Programming的縮寫,意為:面向切面程式設計,通過預編譯方式和執行時動態代理實現程式功能的統一維護的一種技術。AOP是OOP的延續,是函數語言程式設計的一種衍生範型。利用AOP可以對業務邏輯的各個部分進行隔離,從而使得業務邏輯各部分之間的耦合度降低,提高程式的可重用性,進而提高開發效率。
傳統Web開發模型: 縱向的程式設計
面向切面程式設計: 縱橫配合的程式設計.
AOP的作用及優勢
作用:在程式執行期間,不修改任何相關原始碼對已有方法進行增強。
優勢:減少重複程式碼、提高開發效率、方便後期維護。
AOP的實現方式
使用動態代理模式來實現
可能通過上面的介紹,我們還是沒有對AOP有一個清晰的認識。沒關係,我們看看下面的具體應用。
案例中問題
通過下面的程式碼,我們能看出什麼問題嗎?
模擬事務管理器
public class TransactionMangerHandler { public void begin() { System.out.println("開啟事務"); } public void commit() { System.out.println("提交事務"); } public void rollback() { System.out.println("回滾事務"); } public void closeSession() { System.out.println("關閉session"); } }
Service層程式碼
public class UserServiceImpl implements UserService { // 引入事務管理器 private TransactionManagerHandler txManager = new TransactionManagerHandler(); @Override public void insert() { try { txManager.begin(); System.out.println("呼叫dao層insert方法"); txManager.commit(); } catch (Exception e) { txManager.rollback(); } finally { txManager.close(); } } @Override public void update() { try { txManager.begin(); System.out.println("呼叫dao層update方法"); txManager.commit(); } catch (Exception e) { txManager.rollback(); } finally { txManager.close(); } } }
存在的問題
上面程式碼的問題就是:我們的事務控制的程式碼是重複性的。這還只是一個業務類,如果有多個業務類,每個業務類中都會有這些重複性的程式碼。是不是重複程式碼太多了?
通過傳統方式來控制業務層的事務,那麼會出現大量的冗餘程式碼,例如開啟事務、提交事務、事務回滾、關閉資源。這些冗餘的程式碼,降低了開發效率,並且不利於程式碼後期的維護。我們可以藉助AOP思想來集中管理冗餘的程式碼。AOP可以理解為在業務方法的執行前後植入一些增強(事務開始、事務提交、事務回滾、資源關閉),到這裡,AOP總的來說就是解決方法內部程式碼冗餘的問題,並且在一定程度上使得方法內部的非業務邏輯程式碼和業務邏輯程式碼解耦,提高開發效率、質量的同時方便程式碼的後期維護!
解決上述問題的方案
-
JDK的動態代理 - Java官方。
-
CGLIB動態代理 -第三方組織開源。
-
Spring的AOP技術(底層就是JDK動態代理和CGLIB代理技術)。
什麼是動態代理技術?
Java中的動態代理,就是使用者使用的不是真實的物件,而是使用一個代理物件,而這個代理物件中包含的是真實的物件,代理物件的作用就是不改變原有物件的功能方法的基礎之上封裝新的功能。可以認為是真實物件的方法在代理物件的某一個制定好規則的方法中執行。如事務管理。
動態代理技術,可以使得業務邏輯程式碼和非業務邏輯程式碼相分離,提高了開發效率,控制事務的程式碼都已經在代理物件的方法中搭建好了,當需要呼叫真實物件的方法時,那麼就會呼叫代理物件中已經搭建好框架的方法,只需將真實物件的方法植入到框架的指定位置,即可完成真實物件方法的訪問。
JDK動態代理
JDK動態代理是Java官方的動態代理技術。
使用JDK官方的Proxy類建立代理物件:
-
需要通過Proxy類建立代理物件。
-
建立代理物件必須要有一個代理處理類(實現介面InvocationHandler的類)。
JDK動態代理API分析
1、java.lang.reflect.Proxy
類:
Java動態代理機制生成的所有動態代理類的父類,它提供了一組靜態方法來為一組介面動態地生成代理類及其物件。
主要方法:
public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces,InvocationHandler hanlder)
方法職責:為指定類載入器、一組介面及呼叫處理器生成動態代理類例項
引數:
- loader: 類載入器
- interfaces : 模擬的介面
- hanlder: 代理執行處理器
返回:動態生成的代理物件
2、java.lang.reflect.InvocationHandler
介面:
public Object invoke(Object proxy, Method method, Object[] args)
方法職責:負責集中處理動態代理類上的所有方法呼叫
引數:
- proxy :生成的代理物件
- method :當前呼叫的真實方法物件
- args :當前呼叫方法的實參
返回: 真實方法的返回結果
3、JDK動態代理操作步驟:
① 實現InvocationHandler介面,建立增強程式碼處理器。
② 給Proxy類提供ClassLoader物件和代理介面型別陣列,建立動態代理物件。
③ 在處理器中實現增強操作。
案例程式碼
在我們的UserServiceImpl實現類中,使用JDK動態代理,進行方法的增強,增強事物的功能。
--UserService&UserServiceImpl
public interface UserService {
void insert();
void update();
}
public class UserServiceImpl implements UserService {
@Override
public void insert() {
System.out.println("執行了service層的insert方法");
}
@Override
public void update() {
System.out.println("執行了service層的update方法");
}
}
--JDK動態代理工具類
/**
* 獲取代理物件的工具類
* 增強功能:事務管理
*/
@Component
public class DynamicProxyUtil {
// 模擬的事務管理器
@Autowired
private TransactionMangerHandler tx;
/**
* 根據類的位元組碼獲取代理物件
* @param cls 被代理類的位元組碼
* @param <T> 被代理類的型別
* @return 代理物件
*/
public <T> T getProxyObject(Class<T> cls) {
/*
Proxy類是JDK動態代理類
靜態方法newProxyInstance用於建立類的代理物件dd
*/
Object proxyObj = Proxy.newProxyInstance(cls.getClassLoader(),
cls.getInterfaces(), new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Object result = null; // 代理方法執行結果
try {
tx.begin(); // 開啟事務
result = method.invoke(cls.newInstance(), args); // 執行被代理方法
tx.commit(); // 提交事務
} catch (Exception e) {
e.printStackTrace();
tx.rollback(); // 回滾事務
} finally{
tx.closeSession(); // 關閉資源
}
return result; // 返回被代理方法的執行結果
}
});
return (T) proxyObj; // 返回代理物件
}
}
--配置類
@Configuration
public class SpringConfig {
@Bean(name = "tx")
public TransactionMangerHandler getTransactionMangerHandler() {
return new TransactionMangerHandler();
}
@Bean(name = "dynamicProxyUtil")
public DynamicProxyUtil getDynamicProxyUtil() {
DynamicProxyUtil proxyUtil = new DynamicProxyUtil();
return proxyUtil;
}
}
--測試程式碼
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class DynamicProxyTest {
@Resource(name = "dynamicProxyUtil")
private DynamicProxyUtil proxyUtil;
@Test
public void testInsert() {
// 通過ProxyUtil工具類獲取某個類的代理物件
UserService userService = proxyUtil.getProxyObject(UserServiceImpl.class);
userService.insert();
}
@Test
public void testUpdate() {
UserService userService = proxyUtil.getProxyObject(UserServiceImpl.class);
userService.update();
}
--測試結果
JDK動態代理的不足
-
JDK動態代理的物件必須要實現介面,因為JDK動態代理是基於介面代理的。
-
需要為每個物件建立代理物件。
-
代理粒度大,動態代理的最小單位是類(所有類中的方法都會被處理),但是查詢方法不需要事務,因此不需要被代理。
CGLIB動態代理(第三方代理)
CGLIB(Code Generation Library)是一個開源專案。
CGLIB和JDK動態代理一樣都是動態代理,但是CGLIB可以代理沒有實現介面的類。
CGLIB代理沒有實現介面的類時,程式在JVM執行過程中動態的為這個類建立一個子類,並重寫父類方法,在呼叫父類方法時,在方法執行之前、之後、異常、最終做增強。
Spring預設已經整合CGLIB代理,直接可以使用即可,不用拷貝任何jar包。
案例程式碼
在我們的UserServiceImpl的實現類中,使用CGLIB的動態代理,進行方法的增強,增強事物的功能。
--UserServiceImpl2
public class UserServiceImpl2 {
public void insert() {
System.out.println("執行了service層的insert方法");
}
public void update() {
System.out.println("執行了service層的update方法");
}
}
--CGLIB代理工具類
@Component("cglibProxyUtil")
public class CglibProxyUtil {
@Autowired
@Qualifier("tx")
private TransactionMangerHandler tx;
/**
* 返回一個代理物件,代理物件就做了方法的增強,(事物管理,日誌控制,許可權管理等等)
* @param cls 被代理類的位元組碼
* @return
*/
public <T> T getProxyObject(Class<T> cls) {
Enhancer enhancer = new Enhancer();
// 設定類載入器
enhancer.setClassLoader(cls.getClassLoader());
// 設定被代理類的位元組碼
enhancer.setSuperclass(cls);
// 設定功能增強
/**
* 代理類的增強方法,正常增強的地方
* @param proxy 代理物件
* @param method 被代理物件的方法
* @param args 被代理物件方法的引數
* @return 被代理物件執行的結果
* @throws Throwable
*/
enhancer.setCallback(new InvocationHandler() {
@Override
public Object invoke(Object o, Method method, Object[] args) throws Throwable {
Object result = null;
try {
tx.begin();
result = method.invoke(cls.newInstance(), args);
tx.commit();
} catch (Exception e) {
e.printStackTrace();
tx.rollback();
} finally {
tx.closeSession();
}
return result;
}
});
// 建立代理物件
Object proxyObj = enhancer.create();
return (T) proxyObj;
}
}
--配置類
@Configuration
public class SpringConfig {
@Bean(name = "tx")
public TransactionMangerHandler getTransactionMangerHandler() {
return new TransactionMangerHandler();
}
@Bean(name = "cglibProxyUtil")
public CglibProxyUtil getDynamicProxyUtil() {
CglibProxyUtil proxyUtil = new CglibProxyUtil();
return proxyUtil;
}
}
--測試程式碼
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)
public class CglibProxyTest {
@Resource(name = "cglibProxyUtil")
private CglibProxyUtil proxyUtil;
@Test
public void testInsert() {
// 通過ProxyUtil工具類獲取某個類的代理物件
UserServiceImpl2 proxyObj = proxyUtil.getProxyObject(UserServiceImpl2.class);
proxyObj.insert();
}
@Test
public void testUpdate() {
UserServiceImpl2 proxyObj = proxyUtil.getProxyObject(UserServiceImpl2.class);
proxyObj.update();
}
}
--測試結果
CGLIB代理的不足
-
CGLIB可以標類的子類,並重寫父類非final修飾符的方法。
-
要求類不能是final的,要代理的方法要是非final、非static、非private的。
-
動態代理的最小單位是類(所有類中的方法都會被處理)。
小結
解決程式碼重複的方案
在Spring中:
-
若目標物件實現了若干介面,Spring就會使用JDK動態代理。
-
若目標物件沒有實現任何介面,Spring就使用CGLIB動態代理。
直接使用代理的缺陷
如果直接使用動態代理技術(JDK代理、CGLIB代理)解決程式碼重複問題,我們會發現,我們每一個類都要配置代理類,非常的麻煩。
動態代理模式的缺陷是什麼
動態代理模式的缺陷是:
-
實現類必須要實現介面 -JDK動態代理
-
無法通過規則制定攔截無需功能增強的方法。
如何解決這個問題?
使用Spring提供了AOP的實現。