SSM涉及到的設計模式與動態代理技術


《JavaEE 互聯網輕量級框架整合開發》第 2 章講解的是SSM框架的前置知識:設計模式

本章重點包含:

1.反射技術 之前寫過一篇:博客鏈接 包含了:

  • Class類的使用
  • 動態加載類
  • 方法信息的反射
  • 獲取成員變量&構造函數
  • 方法反射的基本操作
  • 通過反射了解集合泛型的本質

2.動態代理、責任鏈模式 以及攔截器的概念

3.觀察者模式

4.工廠模式和抽象工廠模式

5.Builder(構建)模式


​ 反射技術之前已經總結過了。現在學習設計模式。《JavaEE 互聯網輕量級框架整合開發》這本書中只是一筆帶過,講的不夠通俗,這里我們結合《圖解設計模式》這本書來學習設計模式,更好的理解SSM。

動態代理模式

動態代理和責任鏈模式在Spring的AOP以及MyBatis中都有涉及,學好這些就能更好的探究Spring以及MyBatis的原理。

proxy模式

在了解代理模式前,先了解下什么是proxy模式:

《圖解設計模式》中將Proxy歸為“避免浪費”類別中,用一句話說明“只在必要的時候生成實例”來概括。

代理人以本人的身份出現,去完成工作,只有在自己無法解決的時候就會找本人來解決。

代理模式的優點,例如:

一個文檔里面有一個圖片,圖片有標題、像素等信息,以及真實的大小。如果圖片打開很慢,我們在打開文檔就會很慢。那么我們就可以使用代理技術,使代理圖片的標題、像素信息的“代理圖”放入文檔。這樣我們的文檔就會打開的很快,等真正瀏覽到圖片的時候,再去加載這張圖片。

代理的兩個步驟:

  1. 代理對象和真實對象建立代理關系
  2. 實現代理對象的代理邏輯方法

靜態代理和動態代理:

1、靜態代理:代理類由程序員創建的然后編譯成.class文件。但是其中缺點是,具有重復代碼,靈活性不好,例如在執行接口A中所有方法之前加上日志邏輯,那么使用靜態代理的話,在代理類中每個方法都得加,如果我想add* 開頭方法加上一種邏輯,select* 開頭方法加上另一種邏輯,那么就很難去實現和維護了,想解決以上困惑就要使用動態代理了。

2、動態代理:是在運行的時候,通過jvm中的反射進行動態創建對象,生成字節碼對象(構造方法參數 InvocationHandler h類型),傳入由我們實現InvocationHandler接口的對象,通過反射創建代理對象。 然后當調用代理對象的任何方法都會調用h中的 invoke(Object proxy,Method method,Object[] args)傳入當前代理對象、當前調用的方法、方法參數值。

在Java中有多種動態代理技術,比如JDK、CGLIB、Javassist、ASM等,其中最常用的是JDK和CGLIB。

JDK動態代理:

JDK動態代理是 Java.lang.reflect .*包提供的方式,但是它必須借助一個接口才能實現。

代碼演示:

package jdkproxy;
/**
 * 接口 代理類和真實對象共同的接口
 */
public interface Real {
    public String print(String x,String y);
}    
/**
 * 真實對象
 */
package jdkproxy;

public class RealImpl implements Real {
    @Override
    public String print(String x, String y) {
        return x+"愛"+y;
    }
}
package jdkproxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class ProxyTest {
    public static void main(String[] args) {
        Real proxy = null;
        try {
            proxy = (Real)Proxy.newProxyInstance(
                    Class.forName("jdkproxy.RealImpl").getClassLoader(),
                    Class.forName("jdkproxy.RealImpl").getInterfaces(),
                    new InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            return method.invoke(
                                    Class.forName("jdkproxy.RealImpl").newInstance(),
                                    args
                                    );
                        }
                    }
            );
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        System.out.println(proxy.print("牛牛","小豬"));
    }

}

上述代碼的關鍵是Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler handler)方法,該方法會根據指定的參數動態創建代理對象。三個參數的意義如下:

  1. loader,指定代理對象的類加載器;
  2. interfaces,代理對象需要實現的接口,可以同時指定多個接口;
  3. handler,方法調用的實際處理者,代理對象的方法調用都會轉發到這里(*注意)。

newProxyInstance()會返回一個實現了指定接口的代理對象,對該對象的所有方法調用都會轉發給InvocationHandler.invoke()方法。理解上述代碼需要對Java反射機制有一定了解。動態代理神奇的地方就是:

  1. 代理對象是在程序運行時產生的,而不是編譯期;
  2. 對代理對象的所有接口方法調用都會轉發到InvocationHandler.invoke()方法,在invoke()方法里我們可以加入任何邏輯,比如修改方法參數,加入日志功能、安全檢查功能等;之后我們通過某種方式執行真正的方法體,示例中通過反射調用了Hello對象的相應方法,還可以通過RPC調用遠程方法。

注意:對於從Object中繼承的方法,JDK Proxy會把hashCode()、equals()、toString()這三個非接口方法轉發給InvocationHandler,其余的Object方法則不會轉發。詳見JDK Proxy官方文檔。

CGLIB動態代理:

CGLIB(Code Generation Library)是一個基於ASM的字節碼生成庫,它允許我們在運行時對字節碼進行修改和動態生成。CGLIB通過繼承(非抽象類)方式實現代理。沒有實現接口的話,JDK動態代理不可使用,我們就可以使用CGLIB動態代理。

代碼演示:

package cglibproxy;

/**
 * 真實對象類
 */
public class Real {
    public String getString(String x,String y){
        return x+"喜歡"+y;
    }
}
public class ProxyExample implements MethodInterceptor {

    /**
     * 生成代理對象
     * @param cls ---Class類  真實對象的類類型
     * @return Class類的CGLIB代理對象
     */
    public Object getProxy(Class cls){
        Enhancer enhancer = new Enhancer();//CGLIBde 增強類對象
        enhancer.setSuperclass(cls);//設置增強類型,也就是需要代理的類
        enhancer.setCallback(this);//設置代理邏輯對象為實現了MethodInterceptor的本來對象
        return enhancer.create();//創建代理類
    }

    /**
     * 代理邏輯方法
     * @param proxy 代理對象
     * @param method 方法
     * @param args 參數
     * @param methodProxy  方法代理
     * @return
     * @throws Throwable
     */
    @Override
    public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
        return methodProxy.invokeSuper(proxy,args);
    }
}
package cglibproxy;

public class ProxyTest {
    public static void main(String[] args) {
        ProxyExample proxyExample = new ProxyExample();
        Real proxy = (Real)proxyExample.getProxy(Real.class);
        System.out.println(proxy.getString("牛牛","小豬"));
    }
}

上述代碼中,我們通過CGLIB的Enhancer來指定要代理的目標對象、實際處理代理邏輯的對象,最終通過調用create()方法得到代理對象,對這個對象所有非final方法的調用都會轉發給MethodInterceptor.intercept()方法,在intercept()方法里我們可以加入任何邏輯,比如修改方法參數,加入日志功能、安全檢查功能等;通過調用MethodProxy.invokeSuper()方法,我們將調用轉發給原始對象,具體到本例,就是HelloConcrete的具體方法。CGLIG中MethodInterceptor的作用跟JDK代理中的InvocationHandler很類似,都是方法調用的中轉站。

注意:對於從Object中繼承的方法,CGLIB代理也會進行代理,如hashCode()、equals()、toString()等,但是getClass()、wait()等方法不會,因為它是final方法,CGLIB無法代理。

兩種代理方式的總結:

  1. JDK動態代理是Java原生的實現方式,而CGLIB是第三方工具包提供的方式。
  2. JDK動態代理需實現接口代理,而CGLIB是通過繼承的方式進行。
  3. JDK動態代理除了代理所有的接口方法還會代理hashCode()、equals()、toString()這三個方法,因為這個非接口的方法會轉發給InvocationHandler。而CGLIB除了如getClass()、wait()等final的方法不能代理之外,所有的方法你無需指定都能代理。

攔截器

攔截器是什么?我覺得百度百科上說的很清楚:攔截器-百度百科 但是這里還是粘出來說一下:

java里的攔截器是動態攔截Action調用的對象。它提供了一種機制可以使開發者可以定義在一個action執行的前后執行的代碼,也可以在一個action執行前阻止其執行,同時也提供了一種可以提取action中可重用部分的方式。在AOP(Aspect-Oriented Programming)中攔截器用於在某個方法或字段被訪問之前,進行攔截然后在之前或之后加入某些操作。

攔截器與過濾器的區別

過濾器可以簡單理解為“取你所想取”,忽視掉那些你不想要的東西;攔截器可以簡單理解為“拒你所想拒”,關心你想要拒絕掉哪些東西,比如一個BBS論壇上攔截掉敏感詞匯。

1.攔截器是基於java反射機制的,而過濾器是基於函數回調的。

2.過濾器依賴於servlet容器,而攔截器不依賴於servlet容器。

3.攔截器只對action起作用,而過濾器幾乎可以對所有請求起作用。

4.攔截器可以訪問action上下文、值棧里的對象,而過濾器不能。

5.在action的生命周期里,攔截器可以多起調用,而過濾器只能在容器初始化時調用一次。

攔截器的實現代碼:

(攔截器接口)

package interceptor;

import java.lang.reflect.Method;

public interface Interceptor {
    /**
     * 調用真實對象前執行的方法
     * @param proxy     代理對象
     * @param target    真實對象
     * @param method    需要真實對象執行的方法
     * @param args      方法參數
     * @return
     */
    public boolean before(Object proxy, Object target, Method method, Object[] args);

    /**
     * 調用真實對象
     * @param proxy
     * @param target
     * @param method
     * @param args
     */
    public void around(Object proxy, Object target, Method method, Object[] args);

    /**
     * 調用真實對象后的方法
     * @param proxy
     * @param target
     * @param method
     * @param args
     */
    public void after(Object proxy, Object target, Method method, Object[] args);
}

(攔截器實現類)

package interceptor;

import java.lang.reflect.Method;

public class InterceptorImpl 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("反射方法后邏輯");
    }
}

(綁定攔截器與真實對象)

package interceptor;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

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));
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if(interceptorClass==null){ //如果沒有攔截器的話直接反射真實對象方法
            return method.invoke(target,args);
        }
        Object result = null;
        //通過反射生成攔截器
        Interceptor intercepter = (Interceptor)Class.forName(interceptorClass).newInstance();
        //調用前置方法決定是否調用真實對象的方法
        if(intercepter.before(intercepter,target,method,args)){
            result = method.invoke(target,args);//調用真實對象方法
        }else {
            intercepter.around(intercepter,target,method,args);//不調用真實對象的方法,調用攔截器的方法
        }
        intercepter.after(intercepter,target,method,args);//后置方法
        return result;
    }
}

(測試類)

package interceptor;

import jdkproxy.Real;
import jdkproxy.RealImpl;

public class InterceptorTest {
    public static void main(String[] args) {
        //我們 借用jdkproxy中用到的Real接口以及它的實現類作為真實對象
        Real proxy= (Real)InterceptorJdkProxy.bind(new RealImpl(), "interceptor.InterceptorImpl");
        proxy.print("小豬","牛牛");
    }
}

責任鏈(Chain of Responsibility)模式

對於《JavaEE 互聯網輕量級框架整合開發》“請假流程審批“的例子講解責任鏈模式,我更傾向於《圖解設計模式》中所說的“推卸責任”的理解,將一系列對象組成鏈表一樣的責任鏈,當一個問題需要處理,先到第一個人,能處理就處理,如果不能處理就轉發給下一個人。

”請假流程審批“是責任鏈模式結合攔截器的這樣一個例子。對於攔截器我們可以使用層級代理的形式給各個攔截器之間形成一條責任鏈。

如下例代碼所示:

Real proxy1= (Real)InterceptorJdkProxy.bind(new RealImpl(), "interceptor.Interceptor1");
Real proxy2= (Real)InterceptorJdkProxy.bind(proxy1, "interceptor.Interceptor2");
Real proxy3= (Real)InterceptorJdkProxy.bind(proxy2, "interceptor.Interceptor3");
proxy3.print("牛牛","小豬");

觀察者(Observer)模式

觀察者模式又稱為“發布訂閱模式”,所以這兩個是同一個。

重點:

  • 一對多,一個被觀察者可以同時被多個觀察者監視
  • 被觀察者發生變化,會通知所有的觀察者。

這樣一個機制反而說“發布訂閱模式”就能更好的理解。

例子:

假設有一家品牌化妝品供應商,淘寶和京東都已授權並關注這家的產品,一旦有新的產品推出。這兩家電商平台就會在自家的平台上上架這款產品。接下來我們就用Java自帶的java.util.Observable類去實現這樣的場景;

(某化妝品廠商產品庫)

package observer;

import java.util.ArrayList;
import java.util.List;
import java.util.Observable;
import java.util.Observer;

public class ProductList extends Observable {
    private List<String> productList = null;
    private static ProductList instance;
    private ProductList(){}
    public static ProductList getInstance(){
        if(instance == null){
            instance = new ProductList();
            instance.productList = new ArrayList<String>();
        }
        return instance;
    }
    //截止到這 以上都是以單例模式窗機商品庫

    //添加觀察者【給京東、淘寶授權】
    public void addProductListObserver(Observer observer){
        this.addObserver(observer);
    }
    //添加新產品
    public void addProduct(String newProduct){
        productList.add(newProduct);
        System.out.println("新推出["+newProduct+"]產品入庫");
        this.setChanged();//設置被觀察者發生了變化
        this.notifyObservers(newProduct);//通知觀察者
    }
}

(京東和淘寶)

package observer;

import java.util.Observable;
import java.util.Observer;

public class JingDongObserver implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        String newProduct = (String)arg;
        System.out.println("[京東商城]上架新款化妝品:"+newProduct);
    }
}
-------------------------------------------------------------------
package observer;

import java.util.Observable;
import java.util.Observer;

public class TaoBaoObserver implements Observer {
    @Override
    public void update(Observable o, Object arg) {
        String newProduct = (String)arg;
        System.out.println("【淘寶商城】上架新款化妝品:"+newProduct);
    }
}

(測試類)

package observer;

public class ObserverTest {
    public static void main(String[] args) {
        ProductList productList = ProductList.getInstance();
        JingDongObserver jingDongObserver = new JingDongObserver();
        TaoBaoObserver taoBaoObserver = new TaoBaoObserver();
        productList.addProductListObserver(jingDongObserver);
        productList.addProductListObserver(taoBaoObserver);
        productList.addProduct("SK III 新一代神仙水");
    }
}

工廠模式與抽象工廠模式

建造者(Builder)模式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM