美文网首页
Mybatis 动态代理源码解析

Mybatis 动态代理源码解析

作者: 进击的三文鱼 | 来源:发表于2021-05-06 14:02 被阅读0次

    代理模式在设计模式中还是很常见的,因为他可以动态生成一个类,所以非常灵活,接手一个需求,可以不通过修改之前的代码就完成逻辑的修改,那么代理模式就派上用场了,jdk代理模式又分动态和静态,除了jdk动态代理外还有cglib等代理,现在就来说说Mybatic中对jdk动态代理的使用。

    public void test_query() {
        String resource = "spring/mybatis-config-datasource.xml";
        Reader reader;
        try {
            //1 将资源文件写入到字符流中
            reader = Resources.getResourceAsReader(resource);
            //2 通过SqlSessionFactoryBuilder创建SqlSessionFactory
            SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(reader);
    
            SqlSession session = sqlMapper.openSession();
            try {
                User user = session.selectOne("com.didspace.queryUserInfoById", 1L);
    
            } finally {
                session.close();
                reader.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    

    1 将资源文件写入到字符流中

    2 通过SqlSessionFactoryBuilder创建SqlSessionFactory,如果了解Mybatis的人,估计大部分都会知道SqlSessionFactory是Mybatis的核心,那下面我们就具体分析一下

    new SqlSessionFactoryBuilder().build(reader); 看下这段代码的调用链路

    image.png

    再看下SqlSessionFactory的实现

    image.png

    从实现上看SqlSessionFactory和DefaultSqlSessionFactory有很大联系,先不管,我们继续看
    new SqlSessionFactoryBuilder().build(reader)

      //这个方法是我们调用的
      public SqlSessionFactory build(Reader reader) {
        return this.build((Reader)reader, (String)null, (Properties)null);
    }
    
    public SqlSessionFactory build(Reader reader, String environment) {
        return this.build((Reader)reader, environment, (Properties)null);
    }
    
    public SqlSessionFactory build(Reader reader, Properties properties) {
        return this.build((Reader)reader, (String)null, properties);
    }
    
    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
            XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
            var5 = this.build(parser.parse());
        } catch (Exception var14) {
            throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
        } finally {
            ErrorContext.instance().reset();
    
            try {
                reader.close();
            } catch (IOException var13) {
                ;
            }
    
        }
    
        return var5;
    }
    

    我们看到builer是四个的,但是是用了方法的重载,方法名称相同,但是参数不同,最终调用的是

    public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
        SqlSessionFactory var5;
        try {
        XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
        var5 = this.build(parser.parse());
    } catch (Exception var14) {
        throw ExceptionFactory.wrapException("Error building SqlSession.", var14);
    } finally {
        ErrorContext.instance().reset();
    
        try {
            reader.close();
        } catch (IOException var13) {
            ;
        }
    
    }
    

    这个不是设计模式,但是这种写法,是很不错的,值得借鉴,然后我们看下代码
    先说new XMLConfigBuilder(reader, environment, properties);

    这是一个构造方法,源码中很多都是通过构造方法来初始化对象

         public XMLConfigBuilder(Reader reader, String environment, Properties props) {
        this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
      }
    

    这里面又套了一层,也是和上面的处理方式类似,一个构造方法又去调用另外一个构造方法,另一个构造方法如下

       private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
      }
    

    这里我们看到初始化了Configuration对象,最终我们创建了XMLConfigBuilder对象,然后我们执行下一步

      public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
        try {
          XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
          return build(parser.parse());
    

    build(parser.parse()) 方法,先执行parser.parse(),我们会调用到下面的方法

       private void parseConfiguration(XNode root) {
        try {
          //issue #117 read properties first
          propertiesElement(root.evalNode("properties"));
          Properties settings = settingsAsProperties(root.evalNode("settings"));
          loadCustomVfs(settings);
          typeAliasesElement(root.evalNode("typeAliases"));
          pluginElement(root.evalNode("plugins"));
          objectFactoryElement(root.evalNode("objectFactory"));
          objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
          reflectorFactoryElement(root.evalNode("reflectorFactory"));
          settingsElement(settings);
          // read it after objectFactory and objectWrapperFactory issue #631
          environmentsElement(root.evalNode("environments"));
          databaseIdProviderElement(root.evalNode("databaseIdProvider"));
          typeHandlerElement(root.evalNode("typeHandlers"));
          mapperElement(root.evalNode("mappers"));
        } catch (Exception e) {
          throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
      }
    

    这里调动了 mapperElement(root.evalNode("mappers")),看下代码,就是在这里将Mapperxml文件中内容放到Configuration对象中,也就是之前初始化的对象并返回

       private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
            if ("package".equals(child.getName())) {
              String mapperPackage = child.getStringAttribute("name");
              configuration.addMappers(mapperPackage);
            } else {
              String resource = child.getStringAttribute("resource");
              String url = child.getStringAttribute("url");
              String mapperClass = child.getStringAttribute("class");
              if (resource != null && url == null && mapperClass == null) {
    

    现在我们看看 build(parser.parse())的build方法代码

      public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
      }
    

    这里面最终我们返回了DefaultSqlSessionFactory对象,而在最开始时的类关系,我们知道DefaultSqlSessionFactory实现了SqlSessionFactory,所以也就等于返回了一个SqlSessionFactory,这时候我们的SqlSessionFactory初始化也就完成了。

    那Mybatis既然使用了动态代理,那是在哪里使用了呢,回到之前提到的方法,这个方法就包含了代理类的生成以及mapper的加载

       //   通过package方式自动搜索加载,生成对应的mapper代理类
       private void mapperElement(XNode parent) throws Exception {
        if (parent != null) {
          for (XNode child : parent.getChildren()) {
          //mybatis框架提供的搜索classpath下指定package以及子package中符合条件(注解或者继承于某个类/接口)的类,默认使用Thread.currentThread().getContextClassLoader()返回的加载器,和spring的工具类殊途同归。
            if ("package".equals(child.getName())) {
              String mapperPackage = child.getStringAttribute("name");
              configuration.addMappers(mapperPackage);
            } else {
            ......
    

    我们看下调用链路

    image.png

    从这个链路中我们看到addMapper方法,看他做了什么

    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 {
       // 为mapper接口创建一个MapperProxyFactory代理
        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);
        }
      }
    }
      }
    

    knownMappers.put(type, new MapperProxyFactory<T>(type)),这里就创建了代理,注意他是没有生成代理类的,他做了一个接口类和代理类的映射,MapperProxyFactory就是动态代理的核心类,看下他的内容

    public class MapperProxyFactory<T> {
    
      private final Class<T> mapperInterface;
      private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
    
      public MapperProxyFactory(Class<T> mapperInterface) {
        this.mapperInterface = mapperInterface;
      }
    
      public Class<T> getMapperInterface() {
        return mapperInterface;
      }
    
      public Map<Method, MapperMethod> getMethodCache() {
        return methodCache;
      }
    
      @SuppressWarnings("unchecked")
      protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
    

    注意这个newInstance方法,我们看他的参数是MapperProxy<T> 对象,看下他的源码

      public class MapperProxy<T> implements InvocationHandler, Serializable { 
    

    他实现了InvocationHandler,也就是动态代理一定要实现的类,说明newInstance这个方法就是创建动态代理类的方法的,通过查看他的调用会发现,他不是在初始化容器的时候被调用,而是在查询方法执行时被调用。

     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);
    }
    

    }

    通过查看调用,我们发现是由这个方法调用,在查询时,mybatis会根据mapper.xml文件中配置的nameSpace中得到类路径找到类,然后进行动态代理。

    相关文章

      网友评论

          本文标题:Mybatis 动态代理源码解析

          本文链接:https://www.haomeiwen.com/subject/xucddltx.html