mybatis源码之mapper接口扫描原理分析

2022-08-12 12:29:43

本文将通过阅读源码的方法分析session.getMapper(Class)的实现原理。

系列文档:


示例代码

mapper接口

publicinterfaceBlogMapper{BlogselectBlogById(Integer id);intinsertBlog(Blog blog);List<Blog>selectBlogByParameter(BlogSearchParameter parameter);}

xml mapper配置文件

就是一个普通的配置文件,有两点需要注意:

  • namespace与接口的全类名对应
  • SQL的id与接口的方法名对应。
<mappernamespace="org.net5ijy.mybatis.test.mapper.BlogMapper"><resultMapid="BlogResultMap"type="org.net5ijy.mybatis.test.entity.Blog"><idcolumn="id"property="id"/><idcolumn="title"property="title"/><idcolumn="content"property="content"/><resultcolumn="create_time"property="createTime"/><resultcolumn="update_time"property="updateTime"/></resultMap><insertid="insertBlog"parameterType="org.net5ijy.mybatis.test.entity.Blog"useGeneratedKeys="true"keyProperty="id">
    insert into blog
    (title, content, create_time, update_time)
    values
    (#{title}, #{content}, now(), now())</insert><selectid="selectBlogById"resultMap="BlogResultMap"parameterType="java.lang.Integer">
    select * from blog where id = #{id}</select><!-- other sql configuration --></mapper>

主配置文件

<configuration><propertiesresource="jdbc.properties"/><environmentsdefault="development"><environmentid="development"><transactionManagertype="JDBC"/><dataSourcetype="POOLED"><propertyname="driver"value="${driver}"/><propertyname="url"value="${url}"/><propertyname="username"value="${username}"/><propertyname="password"value="${password}"/></dataSource></environment></environments><mappers><!-- 这里还是引入xml mapper配置文件 --><mapperresource="mapper/BlogMapper.xml"/></mappers></configuration>

测试类

publicclassBlogMapperTest2{privateSqlSession session;privateBlogMapper blogMapper;@Beforepublicvoidbefore()throwsIOException{String resource="mybatis-config.xml";InputStream inputStream=Resources.getResourceAsStream(resource);SqlSessionFactory sessionFactory=newSqlSessionFactoryBuilder().build(inputStream);// 注意这里的session还是非自动提交事务的this.session= sessionFactory.openSession();this.blogMapper=this.session.getMapper(BlogMapper.class);}@Afterpublicvoidafter(){this.session.close();}@TestpublicvoidtestInsertBlog(){Blog blog=newBlog();
    blog.setTitle("spring学习");
    blog.setContent("spring深入 - 源码分析");int rows=this.blogMapper.insertBlog(blog);System.out.println(rows);System.out.println(blog.getId());}@TestpublicvoidtestSelectBlogById(){Blog blog=this.blogMapper.selectBlogById(1);System.out.println(blog);}// other test case}

源码分析

入口

BlogMapper blogMapper=this.session.getMapper(BlogMapper.class);

执行的是DefaultSqlSession的getMapper(Class)方法:

public<T>TgetMapper(Class<T> type){return configuration.getMapper(type,this);}

configuration.getMapper方法

public<T>TgetMapper(Class<T> type,SqlSession sqlSession){return mapperRegistry.getMapper(type, sqlSession);}

mapperRegistry.getMapper方法

public<T>TgetMapper(Class<T> type,SqlSession sqlSession){finalMapperProxyFactory<T> mapperProxyFactory=(MapperProxyFactory<T>) knownMappers.get(type);if(mapperProxyFactory==null){thrownewBindingException("Type "+ type+" is not known to the MapperRegistry.");}try{return mapperProxyFactory.newInstance(sqlSession);}catch(Exception e){thrownewBindingException("Error getting mapper instance. Cause: "+ e, e);}}

方法中的knownMappers是一个成员变量,Map结构保存Class -> MapperProxyFactory映射关系:

privatefinalMap<Class<?>,MapperProxyFactory<?>> knownMappers=newHashMap<>();

此时knownMappers中是存在元素的,就是我们要get的BlogMapper.class与其对应的MapperProxyFactory的映射。

那么这个knownMappers是什么时候put值的呢?

在addMapper(Class)方法中有knownMappers的put操作,只要知道addMapper方法在哪里被调用,就可以知道knownMappers的初始化入口了。

addMapper(Class)方法

public<T>voidaddMapper(Class<T> type){if(type.isInterface()){if(hasMapper(type)){thrownewBindingException("Type "+ type+" is already known to the MapperRegistry.");}boolean loadCompleted=false;try{
      knownMappers.put(type,newMapperProxyFactory<>(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=newMapperAnnotationBuilder(config, type);
      parser.parse();
      loadCompleted=true;}finally{if(!loadCompleted){
        knownMappers.remove(type);}}}}

经过debug知道了addMapper(Class)方法被Configuration的addMapper(Class type)方法调用,而Configuration的addMapper(Class type)方法又被XMLMapperBuilder的bindMapperForNamespace方法和XMLConfigBuilder的mapperElement方法调用,因为我们在主配置文件不是使用mapperClass方式配置的mapper标签,所以在当前示例应该执行的是XMLMapperBuilder的bindMapperForNamespace方法,bindMapperForNamespace方法被XMLMapperBuilder.parse方法调用:

publicvoidparse(){if(!configuration.isResourceLoaded(resource)){configurationElement(parser.evalNode("/mapper"));
    configuration.addLoadedResource(resource);bindMapperForNamespace();}parsePendingResultMaps();parsePendingCacheRefs();parsePendingStatements();}

这段代码很眼熟,就是之前初始化阶段解析主配置文件的逻辑。

解析mapper接口和statement:

addMapper(Class)方法的这两行代码值得注意一下:

MapperAnnotationBuilder parser=newMapperAnnotationBuilder(config, type);
parser.parse();// parser.parse()publicvoidparse(){String resource= type.toString();if(!configuration.isResourceLoaded(resource)){// 此时xml mapper配置文件都解析完成了,这个方法的逻辑就不会执行了loadXmlResource();
    configuration.addLoadedResource(resource);
    assistant.setCurrentNamespace(type.getName());// 缓存,不展开分析parseCache();parseCacheRef();// 遍历所有方法for(Method method: type.getMethods()){// 判断方法是否可以转为statement// 基本都可以的if(!canHaveStatement(method)){continue;}// 我们的接口方法没有使用注解方式,所以这个分支进不来// 后续的注解mapper分析再做展开if(getAnnotationWrapper(method,false,Select.class,SelectProvider.class).isPresent()&& method.getAnnotation(ResultMap.class)==null){parseResultMap(method);}try{// 方法转statement// 核心是根据方法标注的Select、Update、Insert、Delete、SelectProvider、// UpdateProvider、InsertProvider、DeleteProvider等注解创建statement// 我们使用的不是注解方法,不展开分析parseStatement(method);}catch(IncompleteElementException e){
        configuration.addIncompleteMethod(newMethodResolver(this, method));}}}parsePendingMethods();}

回到mapperRegistry.getMapper方法

这段代码很重要,所以此处又一次记录:

public<T>TgetMapper(Class<T> type,SqlSession sqlSession){finalMapperProxyFactory<T> mapperProxyFactory=(MapperProxyFactory<T>) knownMappers.get(type);if(mapperProxyFactory==null){thrownewBindingException("Type "+ type+" is not known to the MapperRegistry.");}try{// 创建代理return mapperProxyFactory.newInstance(sqlSession);}catch(Exception e){thrownewBindingException("Error getting mapper instance. Cause: "+ e, e);}}// mapperProxyFactory.newInstance(sqlSession)publicTnewInstance(SqlSession sqlSession){finalMapperProxy<T> mapperProxy=newMapperProxy<>(sqlSession, mapperInterface, methodCache);returnnewInstance(mapperProxy);}protectedTnewInstance(MapperProxy<T> mapperProxy){// 创建java代理return(T)Proxy.newProxyInstance(
                      mapperInterface.getClassLoader(),newClass[]{ mapperInterface},
                      mapperProxy);}

简单介绍一下Proxy.newProxyInstance方法创建java的参数:

  • ClassLoader - 类加载器对象
  • Class<?>[] - 代理的interface
  • InvocationHandler - 处理器

重点在InvocationHandler参数,这是一个接口:

publicinterfaceInvocationHandler{publicObjectinvoke(Object proxy,Method method,Object[] args)throwsThrowable;}

代理不是我们讨论的内容,我们看一下MapperProxy类的invoke方法实现,核心的代码就在这个方法里面。

MapperProxy类

核心在invoke方法:

publicObjectinvoke(Object proxy,Method method,Object[] args)throwsThrowable{try{if(Object.class.equals(method.getDeclaringClass())){return method.invoke(this, args);}else{// 执行这个分支returncachedInvoker(method).invoke(proxy, method, args, sqlSession);}}catch(Throwable t){throwExceptionUtil.unwrapThrowable(t);}}// cachedInvoker方法privateMapperMethodInvokercachedInvoker(Method method)throwsThrowable{try{MapperMethodInvoker invoker= methodCache.get(method);if(invoker!=null){return invoker;}return methodCache.computeIfAbsent(method, m->{// 因为方法都不是default的,所以执行else分支if(m.isDefault()){try{if(privateLookupInMethod==null){returnnewDefaultMethodInvoker(getMethodHandleJava8(method));}else{returnnewDefaultMethodInvoker(getMethodHandleJava9(method));}}catch(IllegalAccessException|InstantiationException|InvocationTargetException|NoSuchMethodException e){thrownewRuntimeException(e);}}else{// 执行这里,创建PlainMethodInvoker对象返回returnnewPlainMethodInvoker(newMapperMethod(mapperInterface, method, sqlSession.getConfiguration()));}});}catch(RuntimeException re){Throwable cause= re.getCause();throw cause==null? re: cause;}}// MapperMethodInvoker接口interfaceMapperMethodInvoker{Objectinvoke(Object proxy,Method method,Object[] args,SqlSession sqlSession)throwsThrowable;}// PlainMethodInvoker类private
  • 作者:xuguofeng2016
  • 原文链接:https://blog.csdn.net/xuguofeng2016/article/details/123706385
    更新时间:2022-08-12 12:29:43