一筹吐、引入今天的主題
今天準備寫代理模式的時候猛频,苦思要找什么例子狮崩,剛好今天520就搜了下世界名牌口紅的企業(yè)——YSL(圣羅蘭),就問下了女朋友鹿寻,知道這個嘛睦柴。上圖的回答,簡直讓我懷疑找了個假女朋友()毡熏。
搜YSL也是看見微商在朋友圈發(fā)的廣告坦敌,不知道大家有沒有發(fā)現(xiàn),微商簡直就是代理模式的完美例子痢法,畫個問號狱窘?,向下看
二财搁、正文開始——代理模式
是什么
代理模式是給某一個對象提供一個代理對象蘸炸,并且代理對象持有原對象的引用
在不更改原對象源碼的情況下對原對象的方法進行修改和加強,符合開閉原則
屬于對象的結(jié)構(gòu)型模式
看不太懂妇拯?幻馁,沒有關(guān)系,下面講例子
舉上面的例子——微商
原對象(真實對象):YSL官方商店越锈,買YSL的產(chǎn)品(原對象方法)
代理對象:微商仗嗦,代理YSL官方商店買YSL的產(chǎn)品(原對象方法),為了提高競爭力甘凭,并送一些小禮物(方法修改和加強)
三稀拐、代理模式分類
靜態(tài)代理:指在編譯階段,代理類由程序員寫好丹弱,在程序運行時直接獲取代理對象的源碼進行編譯
動態(tài)代理:編譯階段程序員不寫代理類德撬,而是在程序運行時铲咨,根據(jù)用戶定義的增加規(guī)則來動態(tài)生成原對象的代理對象,(不用想蜓洪,肯定用到了多態(tài))
動態(tài)代理分為面向接口的jdk動態(tài)代理和Cglib動態(tài)代理(暫不做討論纤勒,Mybatis中使用的是jdk動態(tài)代理)。
3.1靜態(tài)代理
3.1.1實現(xiàn)靜態(tài)代理兩個要求
1.原對象和代理對象實現(xiàn)同一個接口
2.代理對象持有原對象的引用隆檀,并在方法中對原對象的方法進行增強
如:
原對象:YSL的官方商店
代理對象:微商摇天,持有YSL的官方商店的引用
實現(xiàn)同一個接口:賣產(chǎn)品
3.1.2代碼實現(xiàn)
/**
* @Author Think-Coder
* @Data 2020/5/14 10:55
* @Version 1.0
*/
//定義一個賣化妝品的接口
public interface MakeUpSeller {
//銷售的方法
//name為化妝品名字,price是價格
void sell(String name,double price);
}
//原對象—————YSL官方商店
public class YSLSeller implements MakeUpSeller {
@Override
public void sell(String name, double price) {
System.out.println("感謝購買"+name+",一共是"+price+"元");
}
}
//代理對象————微商代理YSL官方商店
public class WeiShangProxy implements MakeUpSeller {
//持有YSL官方商店的引用
private YSLSeller yslSeller;
public WeiShangProxy(YSLSeller yslSeller) {
this.yslSeller = yslSeller;
}
//實現(xiàn)接口的sell方法恐仑,并增強原對象YSL官方商店的方法
//增強原對象的方法:兩個輸出方法
@Override
public void sell(String name, double price) {
System.out.println("我要發(fā)朋友圈,介紹商品優(yōu)勢");
//YSL官方商店對象調(diào)用賣產(chǎn)品的接口
yslSeller.sell(name,price);
System.out.println("并送您一瓶卸妝水泉坐,歡迎下次再來");
}
}
測試類ProxyTest
public class ProxyTest {
public static void main(String[] args) {
//將new的YSLSeller官方商店原對象傳入微商代理對象
//微商代理對象實現(xiàn)了客戶對YSL官方商店的訪問控制
WeiShangProxy weiShangProxy = new WeiShangProxy(new YSLSeller());
//微商代理對象調(diào)用賣產(chǎn)品方法
weiShangProxy.sell("YSL口紅",1000);
}
}
看下面的結(jié)果是不是很暖心
我要發(fā)朋友圈,介紹商品優(yōu)勢
感謝購買YSL口紅,一共是1000.0元
并送您一瓶卸妝水,歡迎下次再來
Process finished with exit code 0
用類圖做個總結(jié):
在測試類中最重要的就是將new YSLSeller()對象放入WeiShangProxy構(gòu)造函數(shù)中
也就是說客戶直接訪問了微商代理類裳仆,從而微商代理控制了客戶對YSL官方商店的訪問
靜態(tài)代理缺點:
靜態(tài)代理是面向?qū)崿F(xiàn)編程(YSLSeller實現(xiàn)了MakeUpSeller接口)而不是面向接口編程腕让,就把程序?qū)懰懒耍焕诔绦虻臄U展歧斟,即如果原對象增加或刪除方法纯丸,代理對象也會跟著改變,極大提高代碼維護成本
于是就有了JDK動態(tài)代理
3.2jdk動態(tài)代理
3.2.1定義
在程序運行時构捡,根據(jù)用戶的定義規(guī)則液南,動態(tài)生成原對象的代理對象,
用上邊的例子解釋就是,不寫微商代理類勾徽,而是在程序運行時利用Proxy類及InvocationHandler接口等動態(tài)生成代理類及代理實例滑凉。
3.2.2jdk動態(tài)代理的兩個核心方法
Proxy類的newProxyInstance方法:生成原對象的代理對象
InvocationHandler接口的invoke方法:包裝原對象的方法,并增強
Proxy類的newProxyInstance方法
生成代理對象
/**
* 參數(shù)1:ClassLoader loader,原對象的類加載器
* 參數(shù)2:Class[] interfaces,原對象繼承(實現(xiàn))的類和接口Class類數(shù)組
* 參數(shù)3:InvocationHandler h,用戶自定義增強原對象的方法接口
**/
public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)
//上面省略
/*
* Look up or generate the designated proxy class.
* 查找或生成指定的代理類
*/
Class cl = getProxyClass0(loader, intfs);
//下面省略
}
InvocationHandler接口的invoke方法
用戶自定義的規(guī)則接口需要實現(xiàn)此接口喘帚,invoke方法用于增加原代理對象方法
public interface InvocationHandler {
/**
* 參數(shù)1:Object proxy,代理對象
* 參數(shù)2:Method method,原對象方法對應(yīng)的反射類畅姊,method.invoke反射調(diào)用原對象方法
* 參數(shù)3:Object[] args,傳入方法參數(shù)
**/
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
3.2.3拿上面的例子舉例
微商代理類已經(jīng)不需要了,可以動態(tài)生成
MakeUpSeller接口及YSLSeller官方商店類不發(fā)生變化
加入MakeUpSellerHandler類實現(xiàn)InvocationHandler接口吹由,用于增強原對象方法
完整代碼如下
/**
* @Author Think-Coder
* @Data 2020/5/14 10:55
* @Version 1.0
*/
//定義一個賣化妝品的接口
public interface MakeUpSeller {
//銷售的方法
//name為化妝品名字若未,price是價格
void sell(String name,double price);
}
//原對象—————YSL官方商店
public class YSLSeller implements MakeUpSeller {
@Override
public void sell(String name, double price) {
System.out.println("感謝購買"+name+",一共是"+price+"元");
}
}
//實現(xiàn)InvocationHandler接口
public class MakeUpSellerHandler implements InvocationHandler {
//持有原對象的父類的引用,父類引用指向子類對象倾鲫,多態(tài)的體現(xiàn)
private MakeUpSeller makeUpSeller;
public MakeUpSellerHandler(MakeUpSeller makeUpSeller) {
this.makeUpSeller = makeUpSeller;
}
@Override
//增強原對象的方法
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("我要發(fā)朋友圈,介紹商品優(yōu)勢");
//反射調(diào)用原對象的方法
method.invoke(makeUpSeller,args);
System.out.println("并送您一瓶卸妝水粗合,歡迎下次再來");
return null;
}
}
看下測試類
public class ProxyTest {
public static void main(String[] args) {
/**
* 參數(shù)1:MakeUpSeller.class.getClassLoader(),MakeUpSeller的類加載器
* 參數(shù)2:new Class[]{MakeUpSeller.class},MakeUpSeller繼承(實現(xiàn))的類和接口Class數(shù)組
* 參數(shù)3:new MakeUpSellerHandler(new YSLSeller()),用戶自定義增強原對象的方法接口
**/
MakeUpSeller yslProxy = (MakeUpSeller) Proxy.newProxyInstance(MakeUpSeller.class.getClassLoader(),
new Class[]{MakeUpSeller.class},
new MakeUpSellerHandler(new YSLSeller()));
yslProxy.sell("YSL口紅",1000);
}
}
看測試結(jié)果
我要發(fā)朋友圈,介紹商品優(yōu)勢
感謝購買YSL口紅,一共是1000.0元
并送您一瓶卸妝水,歡迎下次再來
Process finished with exit code 0
至此動態(tài)代理就實現(xiàn)了
不過乌昔,還有兩個疑問沒有解決
1.為什么Proxy.newProxyInstance方法生成的代理對象可以強轉(zhuǎn)成MakeUpSeller接口類型隙疚?
2.為什么代理對象調(diào)用sell方法,會調(diào)用MakeUpSellerHandler的invoke方法磕道?
帶著這兩個疑問供屉,咱們反編譯下生成動態(tài)代理類
編譯是.java文件編譯為.class文件,反編譯為.class文件變?yōu)?java文件的過程
反編譯生成動態(tài)代理類
改下測試類代碼
public static void main(String[] args) throws IOException {
MakeUpSeller yslProxy = (MakeUpSeller) Proxy.newProxyInstance(MakeUpSeller.class.getClassLoader(),new Class[]{MakeUpSeller.class},
new MakeUpSellerHandler(new YSLSeller()));
yslProxy.sell("YSL口紅",1000);
createProxyClass();
}
public static void createProxyClass() throws IOException {
byte[] bytes = ProxyGenerator.generateProxyClass("MakeUpSeller$proxy", new Class[]{MakeUpSeller.class});
Files.write(new File("D:\\ITProject\\javaproj\\selfproj\\ProxyTest\\out\\production\\ProxyTest\\MakeUpSeller$proxy.class").toPath(),bytes);
}
生成的文件如下
代碼如下,做了部分省略伶丐,
//繼承Proxy代理類悼做,實現(xiàn)了MakeUpSeller接口
//這個就可以回答第一個問題,可以轉(zhuǎn)成MakeUpSeller類型
public final class MakeUpSeller$proxy extends Proxy implements MakeUpSeller {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public MakeUpSeller$proxy(InvocationHandler var1) throws? {
super(var1);
}
//實現(xiàn)MakeUpSeller接口sell類
public final void sell(String var1, double var2) throws? {
try {
//這行代碼很重要哗魂,回答了第二個問題
//該類繼承proxy類肛走,h便為InvocationHandler接口,因此可以調(diào)用invoke方法
//而MakeUpSellerHandler實現(xiàn)了InvocationHandler接口录别,因此直接調(diào)用了
//MakeUpSellerHandler類中invoke方法
super.h.invoke(this, m3, new Object[]{var1, var2});
} catch (RuntimeException | Error var5) {
throw var5;
} catch (Throwable var6) {
throw new UndeclaredThrowableException(var6);
}
}
}
如此就可以解釋上面的兩個問題了
最后也用類圖總結(jié)一下
main方法用代理對象調(diào)用sell方法時羹与,其實是動態(tài)生成的MakeUpSeller$proxy類實例調(diào)用的sell方法
根據(jù)上面反編譯類中sell方法中,調(diào)用的是MakeUpSellerHandler接口中invoke方法庶灿,invoke方法中包裝了原對象YSLSeller的sell方法,最后實現(xiàn)了動態(tài)代理吃衅。
接下來看jdk動態(tài)代理在Mybatis中的應(yīng)用往踢,終于到了
四、動態(tài)代理在MyBatis中的應(yīng)用
4.1手寫的MyBtatis框架的測試類
public static void main(String[] args) throws IOException {
//1.讀取配置文件徘层,連接數(shù)據(jù)庫
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.創(chuàng)建SqlSessionFactory工廠
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工廠生產(chǎn)SqlSession對象,用于操作數(shù)據(jù)庫
SqlSession session = factory.openSession();
//4.使用SqlSession創(chuàng)建Dao接口的代理對象峻呕,因為IUserDao接口沒有實現(xiàn)類
IUserDao userDao = session.getMapper(IUserDao.class);
//5.使用代理對象執(zhí)行方法
List users = userDao.findAll();
for (User user:users){
System.out.println(user);
}
//6.釋放資源
session.close();
in.close();
}
在短短的測試類中就使用了三個設(shè)計模式,確實對初學(xué)者不太友好趣效,所以一點一點拆開來看未免不是一個好的學(xué)習(xí)習(xí)慣瘦癌,所以今天主要看兩行代碼
//4.使用SqlSession創(chuàng)建Dao接口的代理對象,因為IUserDao接口沒有實現(xiàn)類
IUserDao userDao = session.getMapper(IUserDao.class);
//5.使用代理對象執(zhí)行方法
List users = userDao.findAll();
看完上面的動態(tài)代理跷敬,再看這兩行代碼就能解開初學(xué)Mybatis時候的疑惑讯私,
為什么只有Dao層接口,沒有Dao層的接口實現(xiàn)類就可以操作數(shù)據(jù)庫西傀?
就是用到了jdk的動態(tài)代理生成了Dao層接口的代理對象userDao
下面從源碼分析一下斤寇,Mybatis底層是怎么創(chuàng)建Dao層接口的代理對象的
4.2MapperProxyFactory類創(chuàng)建Dao層接口代理對象
也就是研究下面的代碼
IUserDao userDao = session.getMapper(IUserDao.class);
當調(diào)用幾個類的getMapper方法后,會調(diào)用下面類第1個newInstance方法
public class MapperProxyFactory {
private final Class mapperInterface;
private final Map methodCache = new ConcurrentHashMap();
//通過構(gòu)造函數(shù)傳入IUerDao接口Class對象
//學(xué)過反射的童鞋應(yīng)該知道拥褂,拿到Class對象娘锁,相當于拿到IUserDao類
public MapperProxyFactory(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class getMapperInterface() {
return this.mapperInterface;
}
public Map getMethodCache() {
return this.methodCache;
}
//先調(diào)用此方法
public T newInstance(SqlSession sqlSession) {
MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
//調(diào)用下面newInstance方法
return this.newInstance(mapperProxy);
}
protected T newInstance(MapperProxy mapperProxy) {
/**
* 有沒有很熟悉!
* mapperInterface就是Dao層接口? IUserDao
* 參數(shù)1:this.mapperInterface.getClassLoader(),IUserDao的類加載器
* 參數(shù)2:new Class[]{this.mapperInterface},IUserDao繼承(實現(xiàn))的類和接口Class數(shù)組
* 參數(shù)3:mapperProxy,上邊的newInstace方法返回的饺鹃,實現(xiàn)了InvocationHandler接口莫秆,用于方法增強
**/
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
}
上面的代碼,寫注釋的地方是重點
MapperProxyFactory類就是創(chuàng)建代理對象的工廠類悔详,自定義Dao層接口傳入構(gòu)造函數(shù)镊屎,通過newInstance方法返回自定義Dao層接口的代理對象
4.3使用代理對象執(zhí)行findAll方法
List<User> users = userDao.findAll();
看到代碼不得不提出兩個問題
1.代理對象userDao是如何執(zhí)行findAll()方法的
2.findAll方法是如何找到對應(yīng)的sql語句進行增刪改查的
4.3.1首先看MapperProxy類
該類實現(xiàn)InvocationHandler接口,重寫的invoke方法包裝了原對象IUserDao接口中findAll方法
也就是說伟端,當執(zhí)行userDao.findAll();時杯道,會調(diào)用該類的invoke方法
invoke方法作用:生成findAll方法對應(yīng)的MapperMethod類實例,MapperMethod類是最重要的,在下面
public class MapperProxy implements InvocationHandler, Serializable {
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class mapperInterface;
private final Map methodCache;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//上面省略
//下面兩行代碼很重要
//method為Dao層自定義接口方法
//調(diào)用下面的cachedMapperMethod找到與要執(zhí)行的Dao層接口方法對應(yīng)的MapperMethod
MapperMethod mapperMethod = this.cachedMapperMethod(method);
//調(diào)用execute方法來執(zhí)行findAll方法
//先把sqlSession傳入到MapperMethod內(nèi)部
//在MapperMethod內(nèi)部將要執(zhí)行的方法名和參數(shù)再傳入sqlSession對應(yīng)方法中去執(zhí)行
return mapperMethod.execute(this.sqlSession, args);
}
//根據(jù)的傳入IUserDao接口自定義方法findAll党巾,生成對應(yīng)的MapperMethod類實例
private MapperMethod cachedMapperMethod(Method method) {
return (MapperMethod)this.methodCache.computeIfAbsent(method, (k) -> {
return new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
});
}
}
4.3.2再看MapperMethod類
該類的兩個作用
1.解析接口自定義的findAll方法
2.并找到執(zhí)行對應(yīng)的sql語句的方法
先看是如何解析的
public class MapperMethod {
//SqlCommand內(nèi)部類解析自定義接口方法的方法名稱和SQL語句類型萎庭,
private final MapperMethod.SqlCommand command;
//MethodSignature內(nèi)部類解析接口方法的簽名,即接口方法和參數(shù)名稱和參數(shù)值映射關(guān)系齿拂,如String a="0"
private final MapperMethod.MethodSignature method;
public MapperMethod(Class mapperInterface, Method method, Configuration config) {
this.command = new MapperMethod.SqlCommand(config, mapperInterface, method);
this.method = new MapperMethod.MethodSignature(config, mapperInterface, method);
}
}
那么問題來了驳规,該類是如何找到findAll方法對應(yīng)的sql語句呢?
答案就是Configuration對象署海,通過MapperMethod構(gòu)造函數(shù)傳進來的
如圖所示Configuration中的mapperedStatements字段中的MapperedStatement對象是一個Map類型
key為findAll方法吗购,value中包含sql語句,可以通過方法名findAll找到對應(yīng)的sql語句(這個就是上面第二個問題的答案)
再看execute方法為findAll方法找到的sql語句類型匹配方法
execute方法源碼
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
//根據(jù)SqlCommand解析出來的sql語句類型砸狞,為增刪改查類型匹配方法
switch(this.command.getType()) {
case INSERT:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.insert(this.command.getName(), param));
break;
case UPDATE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.update(this.command.getName(), param));
break;
case DELETE:
param = this.method.convertArgsToSqlCommandParam(args);
result = this.rowCountResult(sqlSession.delete(this.command.getName(), param));
break;
case SELECT:
if (this.method.returnsVoid() && this.method.hasResultHandler()) {
this.executeWithResultHandler(sqlSession, args);
result = null;
} else if (this.method.returnsMany()) {
result = this.executeForMany(sqlSession, args);
} else if (this.method.returnsMap()) {
result = this.executeForMap(sqlSession, args);
} else if (this.method.returnsCursor()) {
result = this.executeForCursor(sqlSession, args);
} else {
param = this.method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(this.command.getName(), param);
if (this.method.returnsOptional() && (result == null || !this.method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + this.command.getName());
}
if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) {
throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ").");
} else {
return result;
}
}
根據(jù)sql語句類型匹配對應(yīng)的方法后捻勉,其實是調(diào)用SqlSession接口的實現(xiàn)類執(zhí)行sql語句
如根據(jù)查找到executeForMany方法
private Object executeForMany(SqlSession sqlSession, Object[] args) {
Object param = this.method.convertArgsToSqlCommandParam(args);
List result;
if (this.method.hasRowBounds()) {
RowBounds rowBounds = this.method.extractRowBounds(args);
//最后執(zhí)行sqlSession接口中的selectList方法
result = sqlSession.selectList(this.command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(this.command.getName(), param);
}
if (!this.method.getReturnType().isAssignableFrom(result.getClass())) {
return this.method.getReturnType().isArray() ? this.convertToArray(result) : this.convertToDeclaredCollection(sqlSession.getConfiguration(), result);
} else {
return result;
}
}
SqlSession接口
public interface SqlSession extends Closeable {
/**
* var1:Dao層自定義接口的方法名稱,即findAll()
* var2:方法的參數(shù)
* var3:用于分頁查詢
**/
T selectOne(String var1);
T selectOne(String var1, Object var2);
List selectList(String var1, Object var2, RowBounds var3);
....
}
最后交給SqlSession實現(xiàn)類DefaultSqlSession去執(zhí)行findAll方法對應(yīng)sql語句,并返回結(jié)果
這個和我們直接用SqlSession對象調(diào)用DefaultSqlSession的實現(xiàn)類的方法是一樣的刀森,轉(zhuǎn)了一圈回來踱启,就完成了動態(tài)代理
五、圖總結(jié)
當代理對象userDao調(diào)用findAll()執(zhí)行的代碼流程
原文鏈接:https://blog.csdn.net/shang_0122/article/details/106105717