文章目录
- 一、引入今天的主题
- 二、正文开始——代理模式
- 三、代理模式分类
- 3.1静态代理
- 3.1.1实现静态代理两个要求
- 3.1.2代码实现
- 3.2jdk动态代理
- 3.2.1定义
- 3.2.2jdk动态代理的两个核心方法
- 3.2.3拿上面的例子举例
- 四、动态代理在MyBatis中的应用
- 4.1手写的MyBtatis框架的测试类
- 4.2MapperProxyFactory类创建Dao层接口代理对象
- 4.3使用代理对象执行findAll方法
- 4.3.1首先看MapperProxy类
- 4.3.2再看MapperMethod类
- 五、图总结
一、引入今天的主题
今天准备写代理模式的时候,苦思要找什么例子,就搜了下世界名牌口红的企业——YSL(圣罗兰),就问下了女朋友,知道这个嘛。上图的回答,简直让我怀疑找了个假女朋友()。
搜YSL也是看见微商在朋友圈发的广告,不知道大家有没有发现,微商简直就是代理模式的完美例子,画个问号?,向下看
二、正文开始——代理模式
是什么
- 代理模式是给某一个对象提供一个代理对象,并且代理对象持有原对象的引用
- 在不更改原对象源码的情况下对原对象的方法进行修改和加强,符合开闭原则
- 属于对象的结构型模式
看不太懂?,没有关系,下面讲例子
举上面的例子——微商
- 原对象(真实对象):YSL官方商店,买YSL的产品(原对象方法)
- 代理对象:微商,代理YSL官方商店买YSL的产品(原对象方法),为了提高竞争力,并送一些小礼物(方法修改和加强)
三、代理模式分类
- 静态代理:指在编译阶段,代理类由程序员写好,在程序运行时直接获取代理对象的源码进行编译
- 动态代理:编译阶段程序员不写代理类,而是在程序运行时,根据用户定义的增加规则来动态生成原对象的代理对象,(不用想,肯定用到了多态)
动态代理分为面向接口的jdk动态代理和Cglib动态代理(暂不做讨论,Mybatis中使用的是jdk动态代理)。
3.1静态代理
3.1.1实现静态代理两个要求
- 1.原对象和代理对象实现同一个接口
- 2.代理对象持有原对象的引用,并在方法中对原对象的方法进行增强
如:
- 原对象:YSL的官方商店
- 代理对象:微商,持有YSL的官方商店的引用
- 实现同一个接口:卖产品
3.1.2代码实现
//定义一个卖化妆品的接口
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;
}
//实现接口的sell方法,并增强原对象YSL官方商店的方法
//增强原对象的方法:两个输出方法
@Override
public void sell(String name, double price) {
System.out.println("我要发朋友圈,介绍商品优势");
//YSL官方商店对象调用卖产品的接口
yslSeller.sell(name,price);
System.out.println("并送您一瓶卸妆水,欢迎下次再来");
}
}
测试类ProxyTest
public class ProxyTest {
public static void main(String[] args) {
//将new的YSLSeller官方商店原对象传入微商代理对象
//微商代理对象实现了客户对YSL官方商店的访问控制
WeiShangProxy weiShangProxy = new WeiShangProxy(new YSLSeller());
//微商代理对象调用卖产品方法
weiShangProxy.sell("YSL口红",1000);
}
}
看下面的结果是不是很暖心
我要发朋友圈,介绍商品优势
感谢购买YSL口红,一共是1000.0元
并送您一瓶卸妆水,欢迎下次再来
Process finished with exit code 0
用类图做个总结:
在测试类中最重要的就是将new YSLSeller()对象放入WeiShangProxy构造函数中
也就是说客户直接访问了微商代理类,从而微商代理控制了客户对YSL官方商店的访问
静态代理缺点:
静态代理是面向实现编程(YSLSeller实现了MakeUpSeller接口)而不是面向接口编程,就把程序写死了,不利于程序的扩展,即如果原对象增加或删除方法,代理对象也会跟着改变,极大提高代码维护成本
于是就有了JDK动态代理
3.2jdk动态代理
3.2.1定义
- 在程序运行时,根据用户的定义规则,动态生成原对象的代理对象,
- 用上边的例子解释就是,不写微商代理类,而是在程序运行时利用Proxy类及InvocationHandler接口等动态生成代理类及代理实例。
3.2.2jdk动态代理的两个核心方法
- Proxy类的newProxyInstance方法:生成原对象的代理对象
- InvocationHandler接口的invoke方法:包装原对象的方法,并增强
Proxy类的newProxyInstance方法
生成代理对象
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h)
//上面省略
Class<?> cl = getProxyClass0(loader, intfs);
//下面省略
}
InvocationHandler接口的invoke方法
用户自定义的规则接口需要实现此接口,invoke方法用于增加原代理对象方法
public interface InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args)
throws Throwable;
}
3.2.3拿上面的例子举例
微商代理类已经不需要了,可以动态生成
MakeUpSeller接口及YSLSeller官方商店类不发生变化
加入MakeUpSellerHandler类实现InvocationHandler接口,用于增强原对象方法
完整代码如下
//定义一个卖化妆品的接口
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+"元");
}
}
//实现InvocationHandler接口
public class MakeUpSellerHandler implements InvocationHandler {
//持有原对象的父类的引用,父类引用指向子类对象,多态的体现
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("我要发朋友圈,介绍商品优势");
//反射调用原对象的方法
method.invoke(makeUpSeller,args);
System.out.println("并送您一瓶卸妆水,欢迎下次再来");
return null;
}
}
看下测试类
public class ProxyTest {
public static void main(String[] args) {
MakeUpSeller yslProxy = (MakeUpSeller) Proxy.newProxyInstance(MakeUpSeller.class.getClassLoader(),
new Class[]{MakeUpSeller.class},
new MakeUpSellerHandler(new YSLSeller()));
yslProxy.sell("YSL口红",1000);
}
}
看测试结果
我要发朋友圈,介绍商品优势
感谢购买YSL口红,一共是1000.0元
并送您一瓶卸妆水,欢迎下次再来
Process finished with exit code 0
至此动态代理就实现了
不过,还有两个疑问没有解决
- 1.为什么Proxy.newProxyInstance方法生成的代理对象可以强转成MakeUpSeller接口类型?
- 2.为什么代理对象调用sell方法,会调用MakeUpSellerHandler的invoke方法?
带着这两个疑问,咱们反编译下生成动态代理类
编译是.java文件编译为.class文件,反编译为.class文件变为.java文件的过程
反编译生成动态代理类
改下测试类代码
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代理类,实现了MakeUpSeller接口
//这个就可以回答第一个问题,可以转成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);
}
//实现MakeUpSeller接口sell类
public final void sell(String var1, double var2) throws {
try {
//这行代码很重要,回答了第二个问题
//该类继承proxy类,h便为InvocationHandler接口,因此可以调用invoke方法
//而MakeUpSellerHandler实现了InvocationHandler接口,因此直接调用了
//MakeUpSellerHandler类中invoke方法
super.h.invoke(this, m3, new Object[]{var1, var2});
} catch (RuntimeException | Error var5) {
throw var5;
} catch (Throwable var6) {
throw new UndeclaredThrowableException(var6);
}
}
}
如此就可以解释上面的两个问题了
最后也用类图总结一下
main方法用代理对象调用sell方法时,其实是动态生成的MakeUpSeller$proxy类实例调用的sell方法
根据上面反编译类中sell方法中,调用的是MakeUpSellerHandler接口中invoke方法,invoke方法中包装了原对象YSLSeller的sell方法,最后实现了动态代理。
接下来看jdk动态代理在Mybatis中的应用,终于到了
四、动态代理在MyBatis中的应用
4.1手写的MyBtatis框架的测试类
public static void main(String[] args) throws IOException {
//1.读取配置文件,连接数据库
InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
//2.创建SqlSessionFactory工厂
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
//3.使用工厂生产SqlSession对象,用于操作数据库
SqlSession session = factory.openSession();
//4.使用SqlSession创建Dao接口的代理对象,因为IUserDao接口没有实现类
IUserDao userDao = session.getMapper(IUserDao.class);
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
for (User user:users){
System.out.println(user);
}
//6.释放资源
session.close();
in.close();
}
在短短的测试类中就使用了三个设计模式,确实对初学者不太友好,所以一点一点拆开来看未免不是一个好的学习习惯,所以今天主要看两行代码
//4.使用SqlSession创建Dao接口的代理对象,因为IUserDao接口没有实现类
IUserDao userDao = session.getMapper(IUserDao.class);
//5.使用代理对象执行方法
List<User> users = userDao.findAll();
看完上面的动态代理,再看这两行代码就能解开初学Mybatis时候的疑惑,
为什么只有Dao层接口,没有Dao层的接口实现类就可以操作数据库?
就是用到了jdk的动态代理生成了Dao层接口的代理对象userDao
下面从源码分析一下,Mybatis底层是怎么创建Dao层接口的代理对象的
4.2MapperProxyFactory类创建Dao层接口代理对象
也就是研究下面的代码
IUserDao userDao = session.getMapper(IUserDao.class);
当调用几个类的getMapper方法后,会调用下面类第1个newInstance方法
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();
//通过构造函数传入IUerDao接口Class对象
//学过反射的童鞋应该知道,拿到Class对象,相当于拿到IUserDao类
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return this.mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return this.methodCache;
}
//先调用此方法
public T newInstance(SqlSession sqlSession) {
MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
//调用下面newInstance方法
return this.newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
}
上面的代码,写注释的地方是重点
MapperProxyFactory类就是创建代理对象的工厂类,自定义Dao层接口传入构造函数,通过newInstance方法返回自定义Dao层接口的代理对象
4.3使用代理对象执行findAll方法
List<User> users = userDao.findAll();
看到代码不得不提出两个问题
- 1.代理对象userDao是如何执行findAll()方法的
- 2.findAll方法是如何找到对应的sql语句进行增删改查的
4.3.1首先看MapperProxy类
- 该类实现InvocationHandler接口,重写的invoke方法包装了原对象IUserDao接口中findAll方法
- 也就是说,当执行userDao.findAll();时,会调用该类的invoke方法
invoke方法作用:生成findAll方法对应的MapperMethod类实例,MapperMethod类是最重要的,在下面
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final Method privateLookupInMethod;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//上面省略
//下面两行代码很重要
//method为Dao层自定义接口方法
//调用下面的cachedMapperMethod找到与要执行的Dao层接口方法对应的MapperMethod
MapperMethod mapperMethod = this.cachedMapperMethod(method);
//调用execute方法来执行findAll方法
//先把sqlSession传入到MapperMethod内部
//在MapperMethod内部将要执行的方法名和参数再传入sqlSession对应方法中去执行
return mapperMethod.execute(this.sqlSession, args);
}
//根据的传入IUserDao接口自定义方法findAll,生成对应的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.并找到执行对应的sql语句的方法
先看是如何解析的
public class MapperMethod {
//SqlCommand内部类解析自定义接口方法的方法名称和SQL语句类型,
private final MapperMethod.SqlCommand command;
//MethodSignature内部类解析接口方法的签名,即接口方法和参数名称和参数值映射关系,如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方法对应的sql语句呢?
答案就是Configuration对象,通过MapperMethod构造函数传进来的
如图所示Configuration中的mapperedStatements字段中的MapperedStatement对象是一个Map类型
key为findAll方法,value中包含sql语句,可以通过方法名findAll找到对应的sql语句(这个就是上面第二个问题的答案)
再看execute方法为findAll方法找到的sql语句类型匹配方法
execute方法源码
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
Object param;
//根据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;
}
}
根据sql语句类型匹配对应的方法后,其实是调用SqlSession接口的实现类执行sql语句
如根据查找到executeForMany方法
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
Object param = this.method.convertArgsToSqlCommandParam(args);
List result;
if (this.method.hasRowBounds()) {
RowBounds rowBounds = this.method.extractRowBounds(args);
//最后执行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 {
<T> T selectOne(String var1);
<T> T selectOne(String var1, Object var2);
<E> List<E> selectList(String var1, Object var2, RowBounds var3);
....
}
最后交给SqlSession实现类DefaultSqlSession去执行findAll方法对应sql语句,并返回结果
这个和我们直接用SqlSession对象调用DefaultSqlSession的实现类的方法是一样的,转了一圈回来,就完成了动态代理
五、图总结
当代理对象userDao调用findAll()执行的代码流程