重新学习Mybatis(五)

本篇主要分析第二种实现方式(*Mapper mapper = sqlSession.getMapper(*Mapper.class)是如何实现的.

本文主要分析JDK动态代理,有关cglib代理和Byte Buddy代理的使用示例请参考源码仓库

注意cglib不建议使用,因为它不再维护了,并且在JDK17中存储问题,建议使用Byte Buddy

普通的JDK动态代理基本由以下几部分构成

一个接口ProxyService

public interface ProxyService {

    String print(String message);
}

该接口的实现类ProxyServiceImpl

public class ProxyServiceImpl implements ProxyService {
    @Override
    public String print(String message) {
        return message;
    }
}

再加上一个至关重要的动态代理的Handler,CommonProxyHandler

public class CommonProxyHandler implements InvocationHandler {

    private Object target;

    public CommonProxyHandler(Object tarject) {
        this.target = tarject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(target,args);
    }
}

最后就是使用的时候如何使用

@SpringBootTest
public class CommonProxyTest {

    @Test
    public void testCommonProxy(){
        // 利用java的多态,接收其实例化的实例ProxyServiceImpl
        ProxyService proxyService = new ProxyServiceImpl();
        // handler中传入目标接口的实例化的对象ProxyServiceImpl
        CommonProxyHandler commonProxyHandler = new CommonProxyHandler(proxyService);
        // 第一个参数:当前类的类加载器
        // 第二个参数:被代理类的实现的接口
        // 第三个参数:handler
        // 返回:该ProxyServiceImpl的代理类
        ProxyService proxyInstance = (ProxyService) Proxy.newProxyInstance(CommonProxyTest.class.getClassLoader(),
                proxyService.getClass().getInterfaces(), commonProxyHandler);
        String s = proxyInstance.print("我是一个小代理");
        System.out.println(s);
    }
}

以上就是最常见的动态代理的简易实现方式。

因为第四篇里分析里JDK的动态代理,稍微思考一下,我发现了一个问题,既然动态代理的被代理类是接口的实现类,那么按照这个道理来说,我应该对UserMapper接口进行具体的实现才能实现JDK的动态代理。可是Mybatis并没有让我们去实现Mapper接口。

debug开始

在调用接口的具体方法之前可以看到,当前的mapper实例是一个MapperProxy的类型。是通过上一步sqlSession.getMapper(*Mapper.class)获得的,那就在上一步debug,进去。

会调用configuration对象的getMapper方法,参数就是当前UserMapper的完整类名

接下来会进入到mapperRegistry.getMapper中,根据当前的类名,获得对应的mapperProxyFactory对象,再利用mapperProxyFactory产生一个MapperProxy对象

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//在这里获得MapperProxyFactory的一个对象
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
// 在这里开始产生一个MapperProxy对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

mapperProxyFactory.newInstance()方法点进去,MapperProxy是实现了InvocationHandler接口的,他就等价于我们上面提到的CommonProxyHandler

 protected T newInstance(MapperProxy<T> mapperProxy) {
// 这里看到没,是不是特别熟悉
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
// 调用上面的方法
    return newInstance(mapperProxy);
  }

特别注意:

重点在这里.在文章开头给的例子中,CommonPorxyHandler实例化的时候,我们传进去的是一个接口的实现类,Impl类,而这里传进去的当前这个接口,可以点new MapperProxy那里看一下。

这样就产生了UserMapper接口的代理对象,而不是我们给出的一般JDK动态代理的Impl类的代理对象。

PS:这里有细心的人可能会问,这些我都看懂了,可是第一个,configuration.getMapper()、mapperResitry.getMapper、还有那个很关键的knowMppers.get()这里之前,里面的参数是怎么加入进去的,其实是在项目启动的时候,解析Mapper.xml文件和mybatis-config.xml的时候通过bindMapperForNamespace这个方法中调用configuration.addMapper加入进去的第三篇分析的时候特意标红了的,可以看一下。

在获得了UserMapper的代理对象之后,使用代理对象调用addUser方法之前,我又想到了一个问题,第四篇中动态代理分析中,明确指出了最终执行的对象要是接口的具体实现类,否则执行method.invoke()的时候会报错。那么Mybatis是如何跳过这个障碍的?debug进去,

可以清楚的看到,Mybatis通过判断并没有进入method.invoke()方法,这样就不会报错,实例化一个MapperMethod对象

根据传入的method对象那个,实例化了MapperMethod中的command对象,对象的名字就是方法的具体路径。

最终会执行mapperMethod.execute()方法,进入会根据当前的SQL执行类型进行执行,如下:

核心执行方法为sqlSession.insert()到这里就很明了了,可以看到这里传入的参数就是我们第二种方式中sqlSession显示指定的方法名字和参数。接下来的执行过程就和第一种没有任何差别了

Mybatis的精妙之处就在于,把核心功能交给sqlSession对象进行操作,供用户使用的Mapper接口则通过入参方式进行调用,同时利用JDK代理的另外一种方式,即产生接口的代理对象,而不是接口实现类的代理对象的方式,让用户(程序员)使用更少的代码来完成核心功能的调用

说实话看了Mybatis的源码之后我才发现,JDK的动态代理并不局限于接口的实现类,可以另辟蹊跷完成动态代理的使用。建议仔细看一下MapperProxy这个类,看看Mybatis是如何接收接口的

到此为止:Mybatis框架一些粗糙的东西就分析完了,写的一些小的demo放在了GitHub上,如有需要,自取。这里是地址

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值