一、基本概念
代理模式給某一個對象提供一個代理對象,並由代理對象控制對原對象的引用。通俗的來講代理模式就是中介。
想象一下我們生活中購買火車票的情節,我們可以通過飛豬購買,也可以到窗口購買,飛豬就相當於代理模式,秒懂吧?
二、代理模式的結構
代理是英文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接口綁定原理(代理設計模式)。