Mybatis映射器动态代理实现分析

44次阅读

共计 10539 个字符,预计需要花费 27 分钟才能阅读完成。

mybatis 核心组件介绍

  • SqlSessionFactoryBuilder(构造器):它可以通过 xml,注解或者手动配置来创建 SqlSessionFacotry
  • SqlSessionFactory:用来创建 SqlSession(会话)的工厂
  • SqlSession:SqlSession 是 mybatis 最核心的类,可以用来执行语句,提交或者回滚事务以及获取映射器 Mapper 的接口
  • SQL Mapper:它是由一个接口,一个 xml 配置文件或者注解构成,需要给出对应的 SQL 和映射规则,它负责发送 SQL 去执行,并发挥结果
  • 组件使用案例:

    public class MybatisTest {
        private static SqlSessionFactory sqlSessionFactory;
        static {
            try {sqlSessionFactory = new SqlSessionFactoryBuilder()
                        .build(Resources.getResourceAsStream("mybatis-config.xml"));
            } catch (IOException e) {e.printStackTrace();
            }
        }
        public static void main(String[] args) {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
                User user = userMapper.selectById(1);
                System.out.println("User :" + user);
            }
        }
    }
    // 结果:User : User{id=1, age=21, name='pjmike'}

mybatis 动态代理实现

public static void main(String[] args) {try (SqlSession sqlSession = sqlSessionFactory.openSession()) {UserMapper userMapper = sqlSession.getMapper(UserMapper.class);// <1>
        User user = userMapper.selectById(1);
        System.out.println("User :" + user);
    }
}

​ 在前面的例子中,我们使用 sqlSession 的 getMapper 方法获取了 UserMapper 对象,实际上我们获取的是 UserMapper 接口的代理类,然后由代理类来执行方法。在探索动态代理类实现之前,我们需要先明确 sqlSessionFactory 工厂的创建过程做了哪些准备工作。

mybatis 全局配置文件解析

private static SqlSessionFactory sqlSessionFactory;
static {
    try {sqlSessionFactory = new SqlSessionFactoryBuilder()
                .build(Resources.getResourceAsStream("mybatis-config.xml"));
    } catch (IOException e) {e.printStackTrace();
    }
}

​ 我们使用 new SqlSessionFactoryBuilder().build() 方法创建 SqlSessionFactory 工厂

public SqlSessionFactory build(InputStream inputStream, Properties properties) {return build(inputStream, null, properties);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {ErrorContext.instance().reset();
      try {inputStream.close();
      } catch (IOException e) {// Intentionally ignore. Prefer previous error.}
    }
  }

​ 对于 mybatis 的全局配置文件解析,相关的解析代码主要在 XMLConfigBuilderparse()方法中

public Configuration parse() {if (this.parsed) {throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        } else {
            this.parsed = true;
            // 解析全局配置文件
            this.parseConfiguration(this.parser.evalNode("/configuration"));
            return this.configuration;
        }
    }

  private void parseConfiguration(XNode root) {
        try {this.propertiesElement(root.evalNode("properties"));
            Properties settings = this.settingsAsProperties(root.evalNode("settings"));
            this.loadCustomVfs(settings);
            this.typeAliasesElement(root.evalNode("typeAliases"));
            this.pluginElement(root.evalNode("plugins"));
            this.objectFactoryElement(root.evalNode("objectFactory"));
            this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
            this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
            this.settingsElement(settings);
            this.environmentsElement(root.evalNode("environments"));
            this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
           this.typeHandlerElement(root.evalNode("typeHandlers"));
            // 解析映射器配置文件
            this.mapperElement(root.evalNode("mappers"));
        } catch (Exception var3) {throw new BuilderException("Error parsing SQL Mapper Configuration. Cause:" + var3, var3);
        }
    }

​ 从 parseConfiguration 方法的源码中可以看出,XmlConfigBuilder读取 mybatis-config.xml 中的配置信息,然后将信息保存到 configuration 类中

映射器 Mapper 文件解析

// 解析映射器配置文件
this.mapperElement(root.evalNode("mappers"));

​ 该方法是对全局配置文件中 mappers 属性的解析

private void mapperElement(XNode parent) throws Exception {if (parent != null) {Iterator var2 = parent.getChildren().iterator();

            while(true) {while(var2.hasNext()) {XNode child = (XNode)var2.next();
                    String resource;
                    if ("package".equals(child.getName())) {resource = child.getStringAttribute("name");
                        this.configuration.addMappers(resource);
                    } else {resource = child.getStringAttribute("resource");
                        String url = child.getStringAttribute("url");
                        String mapperClass = child.getStringAttribute("class");
                        XMLMapperBuilder mapperParser;
                        InputStream inputStream;
                        if (resource != null && url == null && mapperClass == null) {ErrorContext.instance().resource(resource);
                            inputStream = Resources.getResourceAsStream(resource);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, resource, this.configuration.getSqlFragments());
                            mapperParser.parse();} else if (resource == null && url != null && mapperClass == null) {ErrorContext.instance().resource(url);
                            inputStream = Resources.getUrlAsStream(url);
                            mapperParser = new XMLMapperBuilder(inputStream, this.configuration, url, this.configuration.getSqlFragments());
                            mapperParser.parse();} else {if (resource != null || url != null || mapperClass == null) {throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
                            }

                            Class<?> mapperInterface = Resources.classForName(mapperClass);
                            this.configuration.addMapper(mapperInterface);
                        }
                    }
                }

                return;
            }
        }
    }

​ 其中的 mapperParser.parse() 方法就是 XmlMapperBuilder 对映射器文件的解析

public void parse() {if (!this.configuration.isResourceLoaded(this.resource)) {
            // 解析映射文件的 mapper 节点,该方法主要用于将 mapper 文件中的元素信息解析到 MappedStatement 对象,并保存到 configuration 类的 mappedStatements 属性中
            this.configurationElement(this.parser.evalNode("/mapper"));    
            this.configuration.addLoadedResource(this.resource);
            // 重点方法,这个方法内部会根据 namespace 属性值,生成动态代理类
            this.bindMapperForNamespace();}

        this.parsePendingResultMaps();
        this.parsePendingCacheRefs();
        this.parsePendingStatements();}

​ 核心方法:bindMapperForNamespace()方法,该方法会根据 mapper 文件中的 namespace 属性值,为接口类生成动态代理类

动态代理类的生成

private void bindMapperForNamespace() {
        // 获取 mapper 元素的 namespace 的元素值
        String namespace = this.builderAssistant.getCurrentNamespace();
        if (namespace != null) {
            Class boundType = null;
            try {boundType = Resources.classForName(namespace);
            } catch (ClassNotFoundException var4) {// 如果没有这个类,可以直接忽略,这是因为 namespace 属性值只需要保持唯一就可以了,并不一定对应一个 XXXMapper 接口,没有 XXXMapper 接口时,我们可以直接使用 SqlSession 来进行增删改查}

            if (boundType != null && !this.configuration.hasMapper(boundType)) {this.configuration.addLoadedResource("namespace:" + namespace);
                // 如果 namespace 属性值有对应的 java 类,调用 configuration 中的 addMapper 方法,将其添加到 MapperRegistry 中
                this.configuration.addMapper(boundType);
            }
        }

    }
public <T> void addMapper(Class<T> type) {
    // 这个类必须是一个 class 接口,因为使用的是 JDK 动态代理,所以需要接口,否则不会针对其生成动态代理
        if (type.isInterface()) {if (this.hasMapper(type)) {throw new BindingException("Type" + type + "is already known to the MapperRegistry.");
            }
            boolean loadCompleted = false;
            try {
                // 用于生成一个 MapperProxyFacotry, 用于后面生成动态代理类
                this.knownMappers.put(type, new MapperProxyFactory(type));
                // 以下代码块主要用于解析我们定义的 XXXMapper 接口里面使用的注解,这里主要处理不使用 xml 映射文件的情况
                MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
                parser.parse();
                loadCompleted = true;
            } finally {if (!loadCompleted) {this.knownMappers.remove(type);
                }

            }
        }

    }

MapperRegistry内部维护了一个映射关系,每个接口对应一个 MapperProxyFactory(生成动态代理工厂类)

private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();

​ 这样便于在后面调用 MapperRegistrygetMapper()时,直接从 Map 中获取某一个接口对应的动态代理工厂类,然后再利用工厂类对其接口生成真正的动态代理类

Configuration 的 getMapper()方法

​ 开始我们通过 sqlsession 的 getMapper() 方法调用获取到动态代理类

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;
  @Override
  public <T> T getMapper(Class<T> type) {return configuration.getMapper(type, this);
  }
  ...
}

Configuration中的 getMapper() 方法内部其实是使用的 MapperRegistrygetMapper()方法

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
        if (mapperProxyFactory == null) {throw new BindingException("Type" + type + "is not known to the MapperRegistry.");
        } else {
            try {
                // 这里可以看到,每次调用都会生成一个新的代理对象返回
                return mapperProxyFactory.newInstance(sqlSession);
            } catch (Exception var5) {throw new BindingException("Error getting mapper instance. Cause:" + var5, var5);
            }
        }
    }
protected T newInstance(MapperProxy<T> mapperProxy) {
    // 这里使用 JDK 动态代理,通过 Proxy.newProxyInstance 生成动态代理类
    //newProxyInstance 的参数:类加载器,接口类,InvocationHandler 接口实现类
    // 动态代理可以将所有的接口的调用重定向到调用处理器 InvocationHandler,调用它的 invoke 方法
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

    public T newInstance(SqlSession sqlSession) {MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
        return this.newInstance(mapperProxy);
    }

​ 这里就是 InvocationHandler 接口的实现类 MapperProxy

public class MapperProxy<T> implements InvocationHandler, Serializable {
    private static final long serialVersionUID = -6424540398559729838L;
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
    private final Map<Method, MapperMethod> methodCache;

    public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
        this.methodCache = methodCache;
    }

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        try {
            // 如果调用的是 Object 类中定义的方法,直接通过反射调用即可
            if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);
            }

            if (this.isDefaultMethod(method)) {return this.invokeDefaultMethod(proxy, method, args);
            }
        } catch (Throwable var5) {throw ExceptionUtil.unwrapThrowable(var5);
        }
        // 调用 XXXMapper 接口自定义的方法,进行代理
        // 首先将当前被调用的方法构造成一个 MapperMethod 对象,然后调用其 excute 方法真正开始执行
        MapperMethod mapperMethod = this.cachedMapperMethod(method);
        return mapperMethod.execute(this.sqlSession, args);
    }
       ...
}
public Object execute(SqlSession sqlSession, Object[] args) {
        Object param;
        Object result;
        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);
            }
            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;}
    }

​ 在 MapperMethod 中还有两个内部类,SqlCommand 和 MethodSignature 类,在 excute 方法中首先使用 swithc case 语句根据 SqlCommandgetType()方法,判断要执行的 sql 类型,然后调用 SqlSession 的增删改查方法

getMapper()方法流程图

参考资料 & 鸣谢


  • https://pjmike.github.io/2018…

正文完
 0