此部落格用于個人學習,來源于ssm架構的書籍,對知識點進行一個整理。
2.2 動态代理模式和責任鍊模式
動态代理的意義在于生成一個占位(又稱代理對象),來代理真實對象,進而控制真實對象的通路。
先舉個例子,能更好的了解代理模式。你的公司是一家軟體公司,你作為一名軟體工程師,平時的工作肯定是跟代碼打交道。客戶來你們公司,肯定不是直接找你談,而是去找商務談。此時,對于客戶來說,商務就是代表整個公司。
![](https://img.laitimes.com/img/_0nNw4CM6IyYiwiM6ICdiwiIyVGduV2YfNWawNyZuBnL5MDN3AjM1ATM3EjNwAjMwIzLc52YucWbp5GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.png)
客戶是通過商務與軟體工程師進行溝通,那麼商務(代理對象)的作用是什麼呢?商務可以進行談判,比如項目啟動前的商務談判,軟體的價格等等,商務也有可能談判失敗,此時就可以結束與客戶的合作關系,這些都不需要軟體工程師來處理。是以,代理的作用就是,在真實對象通路之前或者之後加入對應的邏輯,或者根據其他的規則控制是否使用真實對象。
我們需要在調用者調用對象之前産生一個代理對象,而這個代理對象需要和真實對象建立代理關系,是以代理分為兩個步驟:
- 代理對象和真實對象建立代理關系;
- 實作代理對象的代理邏輯方法。
Java中,代理技術由許多種,本篇主要讨論 Spring 常用的 JDK 代理技術和 CGLIB 代理技術。
2.2.1 JDK 動态代理
需要借助接口才能建立代理對象,首先定義一個 HelloWorld 接口:
public interface HelloWorld {
public void sayHelloWorld();
}
然後通過 HelloWorldImpl 這個類來實作接口:
public class HelloWorldImpl implements HelloWorld{
@Override
public void sayHelloWorld() {
System.out.println("Hello World!");
}
}
進行動态代理:
public class JdkProxyExample implements InvocationHandler {
//真實對象
private Object target = null;
/**
* 建立代理對象與真實對象的代理關系
* @param target 真實對象
* @return 傳回代理對象
*/
public Object bind(Object target){
this.target = target;
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
}
/**
* 代理方法邏輯
* @param proxy 代理對象
* @param method 目前排程方法
* @param args 目前方法參數
* @return 代理結果傳回
* @throws Throwable 異常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("進入代理邏輯方法");
System.out.println("在排程真實對象之前的服務");
//相當于調用sayHelloWorld方法
Object obj = method.invoke(target,args);
System.out.println("在排程真實對象之後的服務");
return obj;
}
}
第一步,建立代理對象與真實對象的關系,這裡通過 bind 方法去完成,先是通過類的屬性 target 儲存了真實對象,然後通過下面的代碼建立并生成代理對象。
對 newProxyInstance 方法進行解釋:
- 第一個是類加載器,我們采用了 target 本身的類加載器。
- 第二個是把生成的動态代理對象下挂在哪些接口下,這個寫法就是放在 target 實作的接口下。
- 第三個是定義實作方法邏輯的代理類,this 表示目前對象,它必須實作 InvocationHandler 接口的 invoke 方法,它就是代理邏輯方法的現實方法。
第二步,實作代理邏輯方法。通過 invoke 函數實作:
- proxy,代理對象,就是 bind 方法生成的對象。
- method,目前排程的方法。
- args,排程方法的參數。
這行代碼相當于排程真實對象的方法,隻是通過反射實作而已。
進行測試:
JdkProxyExample jdk = new JdkProxyExample();
//由于是挂在接口HelloWorld下,是以聲明代理對象HelloWorld proxy
HelloWorld proxy = (HelloWorld) jdk.bind(new HelloWorldImpl());
proxy.sayHelloWorld();
輸出結果:
進入代理邏輯方法
在排程真實對象之前的服務
Hello World!
在排程真實對象之後的服務
當我們建立 HelloWorld 對象的時候,就會進入代理的邏輯方法 invoke 中,因為 JDK 生成的代理類,它繼承自 Proxy 實作我們定義的 HelloWorld 接口,在實作 HelloWorld 接口方法的内部,通過反射調用了 InvocationHandlerImpl 的 invoke 方法。
2.2.2 CGLIB 動态代理
對于 JDK 動态代理技術來說,必須提供接口才能使用,在一些不能提供接口的環境中,隻能采用其他的第三方技術,比如 CGLIB 動态代理。它的優勢在于不需要提供接口,隻要一個非抽象類就能實作動态代理。
public class CglibProxyExample implements MethodInterceptor{
/**
* 生成 CGLIB 代理對象
* @param cls class類
* @return Class類的 CGLIB 代理對象
*/
public Object getProxy(Class cls){
//CGLIB enhancer 增強類對象
Enhancer enhancer = new Enhancer();
//設定增強類型
enhancer.setSuperclass(cls);
//定義代理邏輯對象為目前對象,要求目前對象實作 MethodInterception 方法
enhancer.setCallback(this);
//生成并傳回代理對象
return enhancer.create();
}
/**
* 代理邏輯方法
* @param proxy 代理對象
* @param method 方法
* @param args 方法參數
* @param methodProxy 方法代理
* @return 代理邏輯傳回
* @throws Throwable 異常
*/
public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy)throws Throwable{
System.out.println("調用真實對象前");
//CGLIB 反射調用真實對象方法
Object result = methodProxy.invokeSuper(proxy,args);
System.out.println("調用真實對象後");
return result;
}
}
這裡用了 CGLIB 的加強者 Enhancer ,通過設定超類的方法(setSuperclass),然後通過 setCallback 方法設定哪個類成為它的代理類。其中,參數為 this 就意味着是目前對象,那就要求用這個 this 這個對象實作接口 MethodInterceptor 的方法——intercept,然後傳回代理對象。在反射真實對象方法前後進行了列印,是通過如下代碼完成的。
進行測試:
public void testCGLIBProxy(){
CglibProxyExample cpe = new CglibProxyExample();
ReflectServiceImpl obj = (ReflectServiceImpl)cpe.getProxy(ReflectServiceImpl.class);
obj.sayHello("張三");
}
CGLIB 動态代理和 JDK 動态代理是相似的,它們都是用 getProxy 方法生成代理對象,制定代理的邏輯類,而代理邏輯類要實作一個接口的一個方法,那麼這個接口定義的方法就是代理對象的邏輯方法,它可以控制真實對象的方法。
2.2.3 攔截器
設計者會為開發者設計一個攔截器供其使用,開發者隻需要知道攔截器接口發方法,含義和作用即可,無須知道動态代理是怎麼實作的。用 JDK 動态代理來實作一個攔截器的邏輯,先定義攔截器接口 Interceptor,代碼如下:
public interface Interceptor {
public boolean before(Object proxy, Object target, Method method,Object[] args);
public void around(Object proxy, Object target, Method method,Object[] args);
public void after(Object proxy, Object target, Method method,Object[] args);
}
其中要注意的是:
- before 方法傳回一個 boolean 值,它在真實對象前調用,當傳回為 true 時,則反射真實對象的方法;當傳回為 false 時,則調用 around 方法。
- 在 before 方法傳回為 false 的情況下,調用 around 方法。
- 在反射真實對象方法或者是 around 方法執行之後,調用 after 方法。
實作這個接口——MyInterceptor,代碼如下:
public class MyInterceptor implements Interceptor{
@Override
public boolean before(Object proxy, Object target, Method method, Object[] args) {
System.out.println("反射方法前邏輯");
//不反射被代理對象的原有方法
return false;
}
@Override
public void around(Object proxy, Object target, Method method, Object[] args) {
System.out.println("反射方法後邏輯");
}
@Override
public void after(Object proxy, Object target, Method method, Object[] args) {
System.out.println("取代了被代理對象的方法");
}
}
接下來,使用 JDK 動态代理,就可以去實作這些方法在适當時的調用邏輯,此時,在 JDK 動态代理中使用攔截器。
public class InterceptorJdkProxy implements InvocationHandler {
//真實對象
private Object target;
//攔截器的全限定名
private String interceptorClass = null;
public InterceptorJdkProxy(Object target,String interceptorClass){
this.target = target;
this.interceptorClass = interceptorClass;
}
/**
* 綁定委托對象并傳回一個【代理占位】
* @param target 真實對象
* @param interceptorClass 攔截器全限定名
* @return 代理對象【占位】
*/
public static Object bind(Object target,String interceptorClass){
//取得代理對象
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new InterceptorJdkProxy(target,interceptorClass));
}
/**
* 通過代理對象調用方法,首先進入這個方法
* @param proxy 代理對象
* @param method 方法,被調用的方法
* @param args 方法的參數
* @return 代理結果傳回
* @throws Throwable 異常
*/
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if(interceptorClass == null){
//沒有設定攔截器則直接反射原有方法
return method.invoke(target,args);
}
Object result = null;
//通過反射生成攔截器
Interceptor interceptor = (Interceptor) Class.forName(interceptorClass).newInstance();
//調用前置方法
if (interceptor.before(proxy,target,method,args)){
//反射原有對象方法
result = method.invoke(target,args);
}else{
//傳回false執行around方法
interceptor.around(proxy,target,method,args);
}
//調用後置方法
interceptor.after(proxy,target,method,args);
return result;
}
}
代碼執行步驟:
- 在 bind 方法中使用 JDK 動态代理綁定了一個對象,然後傳回代理對象。
- 如果沒有設定攔截器,則直接反射真實對象的方法,然後結束,否則進行第三步。
- 通過反射生成攔截器,并準備使用它。
- 調用攔截器的 before 方法,如果傳回為 true ,反射原來的方法;否則運作攔截器的 around 方法。
- 調用攔截器的 after 方法。
- 傳回結果。 攔截器可以進一步簡化動态代理的使用方法,使程式變得更簡單,進行測試:
Spring架構學習第二節:Java設計模式(二)2.2 動态代理模式和責任鍊模式
public static void main(String[] args) {
HelloWorld proxy = (HelloWorld) InterceptorJdkProxy.bind(new HelloWorldImpl(),"ssm.learn.chapter2.interceptor.MyInterceptor");
proxy.sayHelloWorld();
}
得到結果:
反射方法前邏輯
反射方法後邏輯
取代了被代理對象的方法
由此可見,攔截器已經生效。
2.2.4 責任鍊模式
設計者往往會用攔截器去替代動态代理,然後将攔截器的接口提供給開發者,進而簡化開發者的開發難度,但是攔截器可能有多個。舉個例子,一個程式員需要請假一周,這個時候把請假申請單看成一個對象,那麼這個對象會經過項目經理,部門經理,人事部等多個角色的審批,這個時候,每個角色都有機會可以通過攔截這個申請單進行審批或者修改。如圖所示:
我們稱這種模式為責任鍊模式,它用于一個對象在多個角色中傳遞的場景,例如上面這個例子,申請單到了項目經理這裡,可以修改申請時間等資訊,然後進而影響後面的審批,即,後面的審批需要根據前面的結果進行。這個時候就可以考慮用層層代理來實作,當申請單到項目經理處,使用第一個動态代理 proxy1;當它走到部門經理處,項目經理會得到一個在項目經理的代理 proxy1 基礎上生成的 proxy2 來處理部門經理的邏輯;當它走到人事處,會在 proxy2 的基礎上生成 proxy3。如果還有其他角色,以此類推即可。
首先,定義三個攔截器:
public class Interceptor1 implements Interceptor {
@Override
public boolean before(Object proxy, Object target, Method method, Object[] args) {
System.out.println("【攔截器1】的 before 方法");
return true;
}
@Override
public void around(Object proxy, Object target, Method method, Object[] args) {
}
@Override
public void after(Object proxy, Object target, Method method, Object[] args) {
System.out.println("【攔截器1】的 after 方法");
}
}
public class Interceptor2 implements Interceptor {
@Override
public boolean before(Object proxy, Object target, Method method, Object[] args) {
System.out.println("【攔截器2】的 before 方法");
return true;
}
@Override
public void around(Object proxy, Object target, Method method, Object[] args) {
}
@Override
public void after(Object proxy, Object target, Method method, Object[] args) {
System.out.println("【攔截器2】的 after 方法");
}
}
public class Interceptor3 implements Interceptor {
@Override
public boolean before(Object proxy, Object target, Method method, Object[] args) {
System.out.println("【攔截器3】的 before 方法");
return true;
}
@Override
public void around(Object proxy, Object target, Method method, Object[] args) {
}
@Override
public void after(Object proxy, Object target, Method method, Object[] args) {
System.out.println("【攔截器3】的 after 方法");
}
}
對攔截器進行測試:
public static void main(String[] args) {
HelloWorld proxy1 = (HelloWorld) InterceptorJdkProxy.bind(new HelloWorldImpl(),"ssm.learn.chapter2.responsibility.Interceptor1");
HelloWorld proxy2 = (HelloWorld) InterceptorJdkProxy.bind(proxy1,"ssm.learn.chapter2.responsibility.Interceptor2");
HelloWorld proxy3 = (HelloWorld) InterceptorJdkProxy.bind(proxy2,"ssm.learn.chapter2.responsibility.Interceptor3");
proxy3.sayHelloWorld();
}
然後就可以得到以下結果,注意觀察方法的執行順序。
【攔截器3】的 before 方法
【攔截器2】的 before 方法
【攔截器1】的 before 方法
Hello World!
【攔截器1】的 after 方法
【攔截器2】的 after 方法
【攔截器3】的 after 方法
可以看出,before 方法是按照從最後一個攔截器到第一個攔截器的加載順序運作,而 after 方法正好相反。
從代碼中可見,責任鍊模式的優點在于我們可以在傳遞鍊上加入新的攔截器,增加攔截邏輯,其缺點是會增加代理和反射,而代理和反射的性能不高。