一直以来都在使用MyBatis作持久化框架,也知道当咱们定义XXXMapper接口类并利用它来作CRUD操做时,Mybatis是利用了动态代理的技术帮咱们生成代理类。那么动态代理内部的实现细节究竟是怎么的呀?XXXMapper.java类和XXXMapper.xml究竟是如何关联起来的呀?本篇文章就来详细剖析下MyBatis的动态代理的具体实现机制。java
在详细探究MyBatis中动态代理机制以前,先来补充一下基础知识,认识一下MyBatis的核心组件。mysql
注意: 如今咱们使用Mybatis,通常都是和Spring框架整合在一块儿使用,这种状况下,SqlSession将被Spring框架所建立,因此每每不须要咱们使用SqlSessionFactoryBuilder或者SqlSessionFactory去建立SqlSession
下面展现一下如何使用MyBatis的这些组件,或者如何快速使用MyBatis:git
CREATE TABLE user( id int, name VARCHAR(255) not NULL , age int , PRIMARY KEY (id) )ENGINE =INNODB DEFAULT CHARSET=utf8;
@Data public class User { private int id; private int age; private String name; @Override public String toString() { return "User{" + "id=" + id + ", age=" + age + ", name='" + name + '\'' + '}'; } }
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <!--全局配置文件的根元素--> <configuration> <!--enviroments表示环境配置,能够配置成开发环境(development)、测试环境(test)、生产环境(production)等--> <environments default="development"> <environment id="development"> <!--transactionManager: 事务管理器,属性type只有两个取值:JDBC和MANAGED--> <transactionManager type="MANAGED" /> <!--dataSource: 数据源配置--> <dataSource type="POOLED"> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/test"/> <property name="username" value="root" /> <property name="password" value="root" /> </dataSource> </environment> </environments> <!--mappers文件路径配置--> <mappers> <mapper resource="mapper/UserMapper.xml"/> </mappers> </configuration>
public interface UserMapper { User selectById(int id); }
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!--namespace属性表示命令空间,不一样xml映射文件namespace必须不一样--> <mapper namespace="com.pjmike.mybatis.UserMapper"> <select id="selectById" parameterType="int" resultType="com.pjmike.mybatis.User"> SELECT id,name,age FROM user where id= #{id} </select> </mapper>
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,与此同时,我也将用这个例子来进一步探究MyBatis动态原理的实现。github
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-config配置文件是如何读取的,映射器文件是如何读取的?sql
private static SqlSessionFactory sqlSessionFactory; static { try { sqlSessionFactory = new SqlSessionFactoryBuilder() .build(Resources.getResourceAsStream("mybatis-config.xml")); } catch (IOException e) { e.printStackTrace(); } }
咱们使用new SqlSessionFactoryBuilder().build()的方式建立SqlSessionFactory工厂,走进build方法数据库
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的全局配置文件的解析,相关解析代码位于XMLConfigBuilder的parse()方法中:api
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; //解析全局配置文件 parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); loadCustomLogImpl(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")); //解析mapper映射器文件 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
从parseConfiguration方法的源代码中很容易就能够看出它对mybatis全局配置文件中各个元素属性的解析。固然最终解析后返回一个Configuration对象,Configuration是一个很重要的类,它包含了Mybatis的全部配置信息,它是经过XMLConfigBuilder取钱构建的,Mybatis经过XMLConfigBuilder读取mybatis-config.xml中配置的信息,而后将这些信息保存到Configuration中mybatis
//解析mapper映射器文件 mapperElement(root.evalNode("mappers"));
该方法是对全局配置文件中mappers属性的解析,走进去:app
mapperParser.parse()
方法就是XMLMapperBuilder对Mapper映射器文件进行解析,可与XMLConfigBuilder进行类比框架
public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); //解析映射文件的根节点mapper元素 configuration.addLoadedResource(resource); bindMapperForNamespace(); //重点方法,这个方法内部会根据namespace属性值,生成动态代理类 } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
该方法主要用于将mapper文件中的元素信息,好比insert
、select
这等信息解析到MappedStatement对象,并保存到Configuration类中的mappedStatements属性中,以便于后续动态代理类执行CRUD操做时可以获取真正的Sql语句信息
buildStatementFromContext方法就用于解析insert、select
这类元素信息,并将其封装成MappedStatement对象,具体的实现细节这里就不细说了。
该方法是核心方法,它会根据mapper文件中的namespace属性值,为接口生成动态代理类,这就来到了咱们的主题内容——动态代理类是如何生成的。
bindMapperForNamespace方法源码以下所示:
private void bindMapperForNamespace() { //获取mapper元素的namespace属性值 String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { // 获取namespace属性值对应的Class对象 boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { //若是没有这个类,则直接忽略,这是由于namespace属性值只须要惟一便可,并不必定对应一个XXXMapper接口 //没有XXXMapper接口的时候,咱们能够直接使用SqlSession来进行增删改查 } 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); //若是namespace属性值有对应的Java类,调用Configuration的addMapper方法,将其添加到MapperRegistry中 configuration.addMapper(boundType); } } } }
这里提到了Configuration的addMapper方法,实际上Configuration类里面经过MapperRegistry对象维护了全部要生成动态代理类的XxxMapper接口信息,可见Configuration类确实是至关重要一类
public class Configuration { ... protected MapperRegistry mapperRegistry = new MapperRegistry(this); ... public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); } public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } ... }
其中两个重要的方法:getMapper()和addMapper()
Configuration将addMapper方法委托给MapperRegistry的addMapper进行的,源码以下:
public <T> void addMapper(Class<T> type) { // 这个class必须是一个接口,由于是使用JDK动态代理,因此须要是接口,不然不会针对其生成动态代理 if (type.isInterface()) { if (hasMapper(type)) { throw new BindingException("Type " + type + " is already known to the MapperRegistry."); } boolean loadCompleted = false; try { // 生成一个MapperProxyFactory,用于以后生成动态代理类 knownMappers.put(type, new MapperProxyFactory<>(type)); //如下代码片断用于解析咱们定义的XxxMapper接口里面使用的注解,这主要是处理不使用xml映射文件的状况 MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
MapperRegistry内部维护一个映射关系,每一个接口对应一个MapperProxyFactory(生成动态代理工厂类)
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
这样便于在后面调用MapperRegistry的getMapper()时,直接从Map中获取某个接口对应的动态代理工厂类,而后再利用工厂类针对其接口生成真正的动态代理类。
Configuration的getMapper()方法内部就是调用MapperRegistry的getMapper()方法,源代码以下:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { //根据Class对象获取建立动态代理的工厂对象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 { //这里能够看到每次调用都会建立一个新的代理对象返回 return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } }
从上面能够看出,建立动态代理类的核心代码就是在MapperProxyFactory.newInstance方法中,源码以下:
protected T newInstance(MapperProxy<T> mapperProxy) { //这里使用JDK动态代理,经过Proxy.newProxyInstance生成动态代理类 // newProxyInstance的参数:类加载器、接口类、InvocationHandler接口实现类 // 动态代理能够将全部接口的调用重定向到调用处理器InvocationHandler,调用它的invoke方法 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); }
PS: 关于JDK动态代理的详细介绍这里就再也不细说了,有兴趣的能够参阅我以前写的文章: 动态代理的原理及其应用
这里的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; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { //若是调用的是Object类中定义的方法,直接经过反射调用便可 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } //调用XxxMapper接口自定义的方法,进行代理 //首先将当前被调用的方法Method构形成一个MapperMethod对象,而后掉用其execute方法真正的开始执行。 final MapperMethod mapperMethod = cachedMapperMethod(method); return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); } ... }
最终的执行逻辑在于MapperMethod类的execute方法,源码以下:
public class MapperMethod { private final SqlCommand command; private final MethodSignature method; public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) { this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); } public Object execute(SqlSession sqlSession, Object[] args) { Object result; switch (command.getType()) { //insert语句的处理逻辑 case INSERT: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.insert(command.getName(), param)); break; } //update语句的处理逻辑 case UPDATE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.update(command.getName(), param)); break; } //delete语句的处理逻辑 case DELETE: { Object param = method.convertArgsToSqlCommandParam(args); result = rowCountResult(sqlSession.delete(command.getName(), param)); break; } //select语句的处理逻辑 case SELECT: if (method.returnsVoid() && method.hasResultHandler()) { executeWithResultHandler(sqlSession, args); result = null; } else if (method.returnsMany()) { result = executeForMany(sqlSession, args); } else if (method.returnsMap()) { result = executeForMap(sqlSession, args); } else if (method.returnsCursor()) { result = executeForCursor(sqlSession, args); } else { Object param = method.convertArgsToSqlCommandParam(args); //调用sqlSession的selectOne方法 result = sqlSession.selectOne(command.getName(), param); if (method.returnsOptional() && (result == null || !method.getReturnType().equals(result.getClass()))) { result = Optional.ofNullable(result); } } break; case FLUSH: result = sqlSession.flushStatements(); break; default: throw new BindingException("Unknown execution method for: " + command.getName()); } if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) { throw new BindingException("Mapper method '" + command.getName() + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ")."); } return result; } ... }
在MapperMethod中还有两个内部类,SqlCommand和MethodSignature类,在execute方法中首先用switch case语句根据SqlCommand的getType()方法,判断要执行的sql类型,好比INSET、UPDATE、DELETE、SELECT和FLUSH,而后分别调用SqlSession的增删改查等方法。
慢着,说了这么多,那么这个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); } ... }
因此getMapper方法的大体调用逻辑链是:
SqlSession#getMapper() ——> Configuration#getMapper() ——> MapperRegistry#getMapper() ——> MapperProxyFactory#newInstance() ——> Proxy#newProxyInstance()
还有一点咱们须要注意:咱们经过SqlSession的getMapper方法得到接口代理来进行CRUD操做,其底层仍是依靠的是SqlSession的使用方法。
根据上面的探究过程,简单画了一个逻辑图(不必定准确):
本篇文章主要介绍了MyBatis的动态原理,回过头来,咱们须要知道咱们使用UserMapper的动态代理类进行CRUD操做,本质上仍是经过SqlSession这个关键类执行增删改查操做,可是对于SqlSession如何具体执行CRUD的操做并无仔细阐述,有兴趣的同窗能够查阅相关资料。