上一章和大家分享了Mybatis一级缓存和二级缓存,本章将继续和大家分享Mapper接口动态代理实现原理,按照国际惯例,先看源码,然后结合原理,写一个自己的小demo,从理论到实战,真正掌握面向接口编程的思想。
还是使用上一章的工程,这里就不占用篇幅了,可以访问下面的网址访问。
https://blog.csdn.net/CSDN_LICY/article/details/108764141
一、原理篇
(首先说明下我的idea快捷方式设置的是和eclipse一样,F5步进,F6下一步,F7步出,F8到下一断点。)
首先从Mybatis加载config.xml配置文件并解析mapper开始讲解。
private void bindMapperForNamespace() {
//获取namespace,xml里面的namespace一定要和mapper接口的全路径名一致
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
//interface com.study.mybatisdemo.mapper.CountryMapper
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
//将CountryMapper添加到configuration
configuration.addMapper(boundType);
}
}
}
}
此时位于MapperRegistry的addMapper方法,
public <T> void addMapper(Class<T> type) {
//必须是接口类型,因为使用的是jdk动态代理
if (type.isInterface()) {
//如果knownMappers已经存在了此类型的MapperProxyFactory,则抛出异常。
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
//首先根据Type构建一个MapperProxyFactory,并按照type放到knownMappers
knownMappers.put(type, new MapperProxyFactory<>(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);
}
}
}
}
到此,已经将Country的工厂类放到了knownMappers,接下来看是如何获取的mapper
此时位于MapperRegistry的getMapper方法,可以看到从knownMappers取出来了mapperProxyFactory,然后利用mapperProxyFactory.newInstance获取一个实例,继续走,看newInstance怎么做的。
此时位于MapperProxyFactory的newInstance方法,将sqlSession传递进去构建一个MapperProxy。
此时此时位于MapperProxyFactory的newInstance方法,可以看到使用Proxy返回一个代理对象。
再次回到测试案例,发现此时countryMapper就是一个代理对象。那这个代理对象是怎么执行的呢?继续往下走。
MapperProxy实现了InvocationHandler,因此走到invoke方法,首先获取PlainMethodInvoker,然后调用PlainMethodInvoker的invoke方法。
PlainMethodInvoker的invoke方法如下:
最终来到MapperMethod的execute方法,可以看到此时已经将mapper方法转换成了sqlSession方法的调用。此时的command.getName()返回的是com.study.mybatisdemo.mapper.CountryMapper.selectById,就是XML文件中namespace和具体方法id的组合。
总结如下:当调用一个接口的方法时,会先通过接口的全限定名称和当前调用的方法名的组合得到一个方法id,这个id就是映射xml中namespace和具体方法id的组合。所以可以在代理方法中使用sqlsession以命名空间的方式调用方法。通过这种方式可以将接口和XML文件中的方法关联起来。这种代理方式和常规的不同之处在于,这里没有对某个具体类进行代理,而是通过代理转化成了对其他代码的调用。
哈哈,通过上面的原理讲解,大家是不是对动态代理更加感兴趣了,下面通过一个简单的例子,来实战一下。
二、实战篇
(1)首先准备一个接口,用于模仿mapper
public interface Sell {
public void sellSomeThing(String kind);
public void sellOnline();
}
(2)准备两个实现类,实现sell接口,这是常规的动态代理实现
public class SellApple implements Sell{
public SellApple() {
System.out.println("苹果商铺");
}
@Override
public void sellSomeThing(String kind) {
System.out.println("开始卖苹果,种类:" + kind + "=========");
}
@Override
public void sellOnline() {
}
}
public class SellJuzhi implements Sell{
public SellJuzhi() {
System.out.println("橘子商铺");
}
@Override
public void sellSomeThing(String kind) {
System.out.println("开始卖橘子,种类:" + kind + "=========");
}
@Override
public void sellOnline() {
}
}
(3)准备一个普通类,用于模仿sqlsession,通过动态代理这个桥梁将对接口方法的调用转换为对其他方法的调用
public class OnLineShopping {
public String sellOnLine() {
System.out.println("我是电子商铺,不需要店铺,哈哈哈");
return "电子商铺";
}
}
(4)准备一个普通的动态代理类增强器InvocationHandler
public class MyInvocationHandler implements InvocationHandler {
private Sell sell;
public MyInvocationHandler(Sell sell) {
this.sell = sell;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("开始卖东西喽。。。。。。。。。。。");
Object invoke = method.invoke(sell, args);
System.out.println("卖完了,好开心。。。。。");
return invoke;
}
}
(5)准备一个MybatisInvocationHandler 用于模仿mybatis的mapper调用流程
public class MybatisInvocationHandler implements InvocationHandler {
private OnLineShopping onLineShopping;
public MybatisInvocationHandler(OnLineShopping onLineShopping) {
this.onLineShopping = onLineShopping;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//将sell接口中sellOnLine的方法,转换为onLineShopping中sellOnLine方法的调用
return onLineShopping.sellOnLine();
}
}
(6)主测试案例
public class SellMain {
public static void main(String[] args) {
//这是将代理类生成到本地,可以通过看代理类源码的方式,更加了解代理类是如何执行的。
System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles", "true");
//普通的代理类,是有实现类的
Sell sellApple = (Sell) Proxy.newProxyInstance(SellApple.class.getClassLoader(), SellApple.class.getInterfaces(), new MyInvocationHandler(new SellApple()));
Sell sellJuzhi = (Sell) Proxy.newProxyInstance(SellJuzhi.class.getClassLoader(), SellJuzhi.class.getInterfaces(), new MyInvocationHandler(new SellJuzhi()));
sellApple.sellSomeThing("红富士");
System.out.println("--------------------------------------------");
sellJuzhi.sellSomeThing("贡菊");
System.out.println("---------------------面向接口编程-----------------------");
//将sell的sellOnline转换为OnLineShopping的sellOnline
Sell sell = (Sell) Proxy.newProxyInstance(OnLineShopping.class.getClassLoader(), new Class[] { Sell.class }, new MybatisInvocationHandler(new OnLineShopping()));
sell.sellOnline();
}
}
结果如下:
至此细说Mybatis一级缓存、二级缓存以及mybatis获取mapper的面向接口编程思想(Mapper接口动态代理实现原理)终于讲完了,感谢大家的阅读,有问题欢迎留言,指正。
路漫漫其修远兮,吾将上下而求索