【源碼分析設計模式 8】mybatis中的代理模式

一、基本概念

代理模式給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。通俗的來講代理模式就是中介。

想象一下我們生活中購買火車票的情節,我們可以通過飛豬購買,也可以到窗口購買,飛豬就相當於代理模式,秒懂吧?

二、代理模式的結構

代理是英文proxy翻譯過來的,我們生活中最常見的代理,代購,不需解釋,都懂。

上圖就是代理模式的UML類圖。

1、用戶只關心接口功能,而不在乎是誰提供了功能,上圖中接口subject;

2、接口真正實現者是RealSubject,但是它不與用戶直接接觸,而是通過代理;

3、代理就是上圖中的 Proxy,由於它實現了 Subject 接口,所以它能夠直接與用戶接觸;

4、用戶調用 Proxy 的時候,Proxy 內部調用了 RealSubject。所以,Proxy 是中介者,它可以增強 RealSubject 操作。

三、代理模式的優缺點

1、優點

(1)被代理類可以更加專注於主要功能的實現,在一定程度上降低了系統的耦合度。

還是賣火車票的例子,當火車站的火車票交給代理商去做的時候,他們就可以更加專注於完成其它業務,買票的效率也上去了,還可以提前訂票,退票,好處簡直太多太多。

(2)代理類可以提供額外的功能。

代理商代理了售賣火車票的事情後,顧客可以搶票了,在火車站你怎麼搶?還可以預定座位,我記得火車站買票好像不能訂座。。。

(3)代理對象可以在客戶端和目標對象之間起到中介的作用,這樣起到了保護目標對象的作用。

2、缺點

(1)由於客戶端和對象之間增加了代理對象,因此有些類型的代理模式可能會造成請求處理速度變慢;

(2)實現代理模式需要額外的工作,有些代理模式的實現非常複雜;

四、代理模式的使用場景

1、虛代理

根據需要創建開銷很大的對象時,只有用到才創建;

2、保護代理

控制對原始對象的訪問,比如過濾器;

3、智能指引

在訪問對象時附加一些操作,比如對象沒有引用時釋放資源;

4、遠程代理

爲一個對象在不同的地址空間提供局部代理;

五、靜態代理

靜態代理在使用時,需要定義接口或者父類,被代理對象與代理對象一起實現相同的接口或者繼承相同的父類。

我們經常去看電影,在電影的開始和結束階段都會播放廣告,這個可以給電影院帶來經濟效益,不同的電影可能對應不同的廣告,但是同一個影片的廣告基本上都是固定的,這就是靜態代理,固定的,額外的;

1、接口IMovie

package designMode.advance.proxy;

public interface IMovie {
    void play();
}

2、實現類Movie  

package designMode.advance.proxy;

public class Movie implements IMovie {
    @Override
    public void play() {
        System.out.println("您正在觀看電影《速度與激情8》");
    }
}

3、代理類MovieProxy   

package designMode.advance.proxy;

public class MovieProxy implements IMovie {
    Movie movie;

    public MovieProxy(Movie movie) {
        this.movie = movie;
    }

    @Override
    public void play() {
        advertising(true);
        movie.play();
        advertising(false);
    }

    private void advertising(boolean isBoforMovie){
        if(isBoforMovie){
            System.out.println("影片馬上開始,素小暖入駐CSDN啦,快來關注我啊");
        }else{
            System.out.println("影片正片已經結束,馬上彩蛋環節,不要離開哦,素小暖入駐CSDN啦,快來關注我啊");
        }
    }

    public static void main(String[] args) {
        Movie movie = new Movie();
        IMovie movieProxy = new MovieProxy(movie);
        movieProxy.play();
    }
}

4、控制檯輸出 

六、動態代理

1、動態代理特點

  • 代理對象不需要實現接口,但是目標對象要實現接口;
  • 代理對象的生成,是利用JDK的API,動態的在內存中構建代理對象;
  • 動態代理也叫JDK代理或接口代理;

2、使用JDK實現動態代理

jdk實現動態代理必須有實現接口InvocationHandler的處理類,用於執行被代理類的方法。

(1)接口IMovie

package designMode.advance.proxy.dynamic;

public interface IMovie {
    void play(String movieName);
    void advertising(Boolean isBoforMovie,String txt);
}

(2)實現類Movie

package designMode.advance.proxy.dynamic;

public class Movie implements IMovie {

    @Override
    public void play(String movieName) {
        System.out.println("您正在觀看電影《"+movieName+"》");
    }

    @Override
    public void advertising(Boolean isBoforMovie, String txt) {
        if(isBoforMovie){
            System.out.println("影片馬上開始,"+txt);
        }else{
            System.out.println("影片正片已經結束,馬上彩蛋環節,不要離開哦,"+txt);
        }
    }
}

(3)代理類MovieProxy

package designMode.advance.proxy.dynamic;

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

public class MovieProxy {
    private Object target;

    public MovieProxy(Object target) {
        this.target = target;
    }

    /*
    *
    * public static Object newProxyInstance(ClassLoader loader,
                                          Class<?>[] interfaces,
                                          InvocationHandler h)
    * 1、ClassLoader loader:指定當前目標對象使用的類加載器,獲取加載器的方法;
    * 2、Class<?>[] interfaces:目標對象實現的接口類型,使用泛型方式確認類型;
    * 3、InvocationHandler h:事情處理,執行目標對象的方法時,會觸發事情處理器方法,會吧當前
    *
    * */
    public Object getProxyInstance(){
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        System.out.println("JDK代理開始~~");
                        //反射機制調用目標對象的方法
                        Object ret = method.invoke(target,args);
                        System.out.println("JDK代理結束~~");
                        return ret;
                    }
                });
    }
}

(4)測試類

package designMode.advance.proxy.dynamic;

public class Client {
    public static void main(String[] args) {
        IMovie target = new Movie();
        IMovie proxyInstance = (IMovie) new MovieProxy(target).getProxyInstance();
        System.out.println("proxyInstance="+proxyInstance.getClass());
        proxyInstance.advertising(true,"素小暖入駐CSDN啦,快來關注我啊");
        proxyInstance.play(" 速度與激情8 ");
        proxyInstance.advertising(false,"素小暖入駐CSDN啦,快來關注我啊");
    }
}

(5)控制檯輸出

3、使用JDK實現動態代理源碼分析

(1)代理對象會在內部緩存,如果沒有緩存則會由ProxyClassFactory生成。

首先會做接口校驗,比如是否可以從提供的classLoader獲取接口

(2) invoke方法的具體實現類,DynamicProxy

 (3)DynamicProxy類,調用invoke方法,生成代理對象,實現動態代理,並存入緩存

 七、cglib代理

JDK實現動態代理需要實現類通過接口定義業務方法,對於沒有接口的類,如何實現動態代理呢,這就需要CGLib了。CGLib採用了非常底層的字節碼技術,其原理是通過字節碼技術爲一個類創建子類,並在子類中採用方法攔截的技術攔截所有父類方法的調用,順勢織入橫切邏輯。但因爲採用的是繼承,所以不能對final修飾的類進行代理。JDK動態代理與CGLib動態代理均是實現Spring AOP的基礎。

1、引入jar包

2、普通類Movie

package designMode.advance.proxy.cglib;

public class Movie {
    public void play(String movieName) {
        System.out.println("我是cglib代理,不需要實現接口,您正在觀看電影《"+movieName+"》");
    }

    public void advertising(Boolean isBoforMovie, String txt) {
        if(isBoforMovie){
            System.out.println("影片馬上開始,"+txt);
        }else{
            System.out.println("影片正片已經結束,馬上彩蛋環節,不要離開哦,"+txt);
        }
    }
}

3、代理類MovieProxy

package designMode.advance.proxy.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 MovieProxy implements MethodInterceptor {
    //維護一個目標對象
    private Object target;

    //構造器,傳入一個被代理的對象
    public MovieProxy(Object target) {
        this.target = target;
    }

    //返回一個代理對象:  是 target 對象的代理對象
    public Object getProxyInstance() {
        //1. 創建一個工具類
        Enhancer enhancer = new Enhancer();
        //2. 設置父類
        enhancer.setSuperclass(target.getClass());
        //3. 設置回調函數
        enhancer.setCallback(this);
        //4. 創建子類對象,即代理對象
        return enhancer.create();
    }


    @Override
    public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
        System.out.println("Cglib代理開始~~");
        Object returnVal = method.invoke(target,objects);
        System.out.println("Cglib代理結束~~");
        return returnVal;
    }
}

4、測試類

package designMode.advance.proxy.cglib;

public class Client {
    public static void main(String[] args) {
        //創建目標對象
        Movie target = new Movie();
        //獲取到代理對象,並且將目標對象傳遞給代理對象
        Movie proxyInstance = (Movie)new MovieProxy(target).getProxyInstance();

        //執行代理對象的方法,觸發intecept 方法,從而實現 對目標對象的調用
        proxyInstance.advertising(true,"素小暖入駐CSDN啦,快來關注我啊");
        proxyInstance.play(" 速度與激情8 ");
        proxyInstance.advertising(false,"素小暖入駐CSDN啦,快來關注我啊");
    }
}

5、控制檯輸出

八、幾種常見的代理模式介紹

1、防火牆代理

內網通過代理穿透防火牆,實現對公網的訪問。

2、緩存代理

當請求圖片文件等資源時,先到緩存中去,如果沒有再到數據庫中取,然後緩存。

3、遠程代理

遠程對象的本地代表,通過它可以把遠程對象當做本地對象來調用。

4、同步代理

主要在多線程編程中使用,完成多線程間的同步工作。

九、mybatis中的代理模式

下面我們進入configuration.addMapper(mapperInterface);這個方法看看源碼如何實現

public <T> void addMapper(Class<T> type) {
    //點進入看具體實現源碼
    mapperRegistry.addMapper(type);
}

1、首先看看mapperRegistry是什麼東西,點進去看看

protected MapperRegistry mapperRegistry = new MapperRegistry(this);

2、原來是MapperRegistry 類,再點進去看看MapperRegistry 類裏面怎麼寫的

public class MapperRegistry {
    private final Configuration config;
    //定義了一個map接口
    private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
}
//Mybatis掃包方式有兩種一種是 寫package、和resource 
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        //重點來了,看看knownMappers是什麼,就是一個map集合
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

通過上面的源碼分析我們可以知道,使用map集合來裝接口:再用configuration來接受配置文件所有信息

configuration.addMapper(mapperInterface);

configuration完成後。回到build(..)

進入build(..)方法,來看源碼

 public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
 }

可以知道,最後通過sqlSession拿到Configuration對象

最後我們看看源碼是如何獲取mapper,入口代碼如下

UserMapper mapper = sqlSession.getMapper(UserMapper.class);

點進getMapper(..)方法看源碼如何實現的(ctrl+alt+B)選擇DefaultSqlSession

public <T> T getMapper(Class<T> type) {
    //點這裏進入具體實現源碼
    return configuration.<T>getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
}

上面源碼分析了mapRegistry對象裏面封裝了一個map集合,用來存放mappers接口,我們點進去getMapper(..)看源碼如何實現的

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    //從map集合中獲取接口
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      //使用工廠初始化,進入源碼
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
 }

點進newInstance(..)看源碼

public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
    //點進去看看
    return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}

通過上述源碼分析,我們知道了Mapper接口綁定原理(代理設計模式)。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章