Mybatis 源码解析二、Mapper接口的代理实现过程 MapperScannerConfigurer 解析

时间:2021-06-17 17:18:08
一、使用包扫描的配置方式生成Mapper的动态代理对象 上一篇文章中介绍了SqlSessionFactoryBean 通过Mybatis主配置文件生成Mapper接口的代理对象,并将其保存到 Configuration 中,但是一般在开发中我们并不是采用这种方式配置Mapper接口,因为这种方式是针对单个接口进行配置的,如果有大量的Mapper接口,这样配置就很麻烦。开发中我们通常使用包扫描的方式进行配置。例如下面的配置:
   
   
  1. <!-- mybatis操作接口扫描 -->
  2. <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  3. <property name="annotationClass" value="javax.annotation.Resource" />
  4. <property name="basePackage" value="com.xxx.md.xxx.xxx.dao" />
  5. <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
  6. </bean>
   其中basePackage就是我们配置的Mapper文件所在的包,我们追踪下代码来看一下,MapperScannerConfigurer是如何通过包扫描的方式将我们的Mapper保存到Configuration。
1.1 MapperScannerConfigurer 类     该类的主要作用是初始化我们配置的参数,并调用方法扫描配置的包。
    
   
   
  1. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
  2. if (this.processPropertyPlaceHolders) {
  3. processPropertyPlaceHolders();
  4. }
  5. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  6. scanner.setAddToConfig(this.addToConfig);
  7. scanner.setAnnotationClass(this.annotationClass);
  8. scanner.setMarkerInterface(this.markerInterface);
  9. scanner.setSqlSessionFactory(this.sqlSessionFactory);
  10. scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  11. scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  12. scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  13. scanner.setResourceLoader(this.applicationContext);
  14. scanner.setBeanNameGenerator(this.nameGenerator);
  15. scanner.registerFilters();
  16. scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  17. }
    从上面代码我们可以看到真正执行扫描的是ClassPathMapperScanner类的Scan方法。
   1.2 ClassPathMapperScanner
        ClassPathMapperScanner类继承了Spring提供的ClassPathBeanDefinitionScanner类。Scan方法的实现在父类中。
        
   
   
  1. public int scan(String... basePackages) {
  2. int beanCountAtScanStart = this.registry.getBeanDefinitionCount();
  3. doScan(basePackages);
  4. // Register annotation config processors, if necessary.
  5. if (this.includeAnnotationConfig) {
  6. AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
  7. }
  8. return (this.registry.getBeanDefinitionCount() - beanCountAtScanStart);
  9. }
        在执行scan()方法时,会执行doScan()方法,这个方法在ClassPathMapperScanner类中有具体实现。
        
    
    
  1. public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  2. Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
  3. if (beanDefinitions.isEmpty()) {
  4. logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
  5. } else {
  6. for (BeanDefinitionHolder holder : beanDefinitions) {
  7. GenericBeanDefinition definition = (GenericBeanDefinition) holder.getBeanDefinition();
  8. if (logger.isDebugEnabled()) {
  9. logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
  10. + "' and '" + definition.getBeanClassName() + "' mapperInterface");
  11. }
  12. // the mapper interface is the original class of the bean
  13. // but, the actual class of the bean is MapperFactoryBean
  14. definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
  15. definition.setBeanClass(MapperFactoryBean.class);
  16. definition.getPropertyValues().add("addToConfig", this.addToConfig);
  17. boolean explicitFactoryUsed = false;
  18. if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
  19. definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
  20. explicitFactoryUsed = true;
  21. } else if (this.sqlSessionFactory != null) {
  22. definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
  23. explicitFactoryUsed = true;
  24. }
  25. if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
  26. if (explicitFactoryUsed) {
  27. logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
  28. }
  29. definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
  30. explicitFactoryUsed = true;
  31. } else if (this.sqlSessionTemplate != null) {
  32. if (explicitFactoryUsed) {
  33. logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
  34. }
  35. definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
  36. explicitFactoryUsed = true;
  37. }
  38. if (!explicitFactoryUsed) {
  39. if (logger.isDebugEnabled()) {
  40. logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
  41. }
  42. definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  43. }
  44. }
  45. }
  46. return beanDefinitions;
  47. }
        该方法首先会执行父类中的doScan()方法,父类中的doScan()方法也即Spring的包扫描,根据注解生成对象的方法,从源码中我们也可以看到在MapperScannerConfigure中配置的annotationClass是如何起到过滤作用的,如果不配置这个参数,会根据Spring中的默认注解生成对象,配置这个参数,我们可以指定我们自定义的注解。简单来说Spring在生成对象的时候会有一个注解过滤器进行过滤,使用特定注解的类,才能由Spring生成对象,这也解释了我们常用的注解起作用的过程。
        
     
     
  1. definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
  2. definition.setBeanClass(MapperFactoryBean.class);
  3. definition.getPropertyValues().add("addToConfig", this.addToConfig);
      我们关键看一下这三行代码,很明显由Spring包扫描的Mapper接口添加到了MapperFactoryBean中。 1.3  MapperFactoryBean类    MapperFactoryBean类实现了FactroyBean类,其中主要起作用的方法是cheakDaoConfig()。
      
      
  1. @Override
  2. protected void checkDaoConfig() {
  3. super.checkDaoConfig();
  4. notNull(this.mapperInterface, "Property 'mapperInterface' is required");
  5. Configuration configuration = getSqlSession().getConfiguration();
  6. if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
  7. try {
  8. configuration.addMapper(this.mapperInterface);
  9. } catch (Throwable t) {
  10. logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
  11. throw new IllegalArgumentException(t);
  12. } finally {
  13. ErrorContext.instance().reset();
  14. }
  15. }
  16. }
    在这个方法中我们看到一行熟悉的代码,configuration.addMapper(this.mapperInterface);从源码解析一中我们知道这行代码的实际作用就是将Mapper接口使用JDK动态代理技术生成代理对象,并添加到Configuration中。
 根据以上三个类的源码解析,我们已经看到Mybatis已经实现了通过包扫描的方式生成Mapper接口的代理对象。下面我们再了解下Mapper接口动态代理对象起作用的过程。

二、Mapper动态代理对象 MapperProxy的方法执行过程
   我们知道使用JDK动态代理技术生成的动态代理对象,方法在执行时,会执行代理的invoke()方法。
   
       
       
  1. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  2. if (Object.class.equals(method.getDeclaringClass())) {
  3. try {
  4. return method.invoke(this, args);
  5. } catch (Throwable t) {
  6. throw ExceptionUtil.unwrapThrowable(t);
  7. }
  8. }
  9. final MapperMethod mapperMethod = cachedMapperMethod(method);
  10. return mapperMethod.execute(sqlSession, args);
  11. }

   从代码中我们可以看到如果mapper是一个接口实现对象的话会执行method.invoke()方法,但我们使用的mapper实际是一个接口,所以会执行生成MapperMethod对象,并执行excute()方法。简单理解为,我们执行方法时,会生成代理方法,并执行代理方法。
   研究MapperMethod源码我们可以发现,Mybatis会根据我们传递的参数,分析方法的签名、返回值、参数名、参数类型、参数顺序以及获取绑定的mapper.xml文件中对应的SQL(mapper.xml文件与Mapper接口的绑定,会在第三篇博客中介绍)。
   我们看下MapperMethod中的excute()方法。
       
       
  1. public Object execute(SqlSession sqlSession, Object[] args) {
  2. Object result;
  3. if (SqlCommandType.INSERT == command.getType()) {
  4. Object param = method.convertArgsToSqlCommandParam(args);
  5. result = rowCountResult(sqlSession.insert(command.getName(), param));
  6. } else if (SqlCommandType.UPDATE == command.getType()) {
  7. Object param = method.convertArgsToSqlCommandParam(args);
  8. result = rowCountResult(sqlSession.update(command.getName(), param));
  9. } else if (SqlCommandType.DELETE == command.getType()) {
  10. Object param = method.convertArgsToSqlCommandParam(args);
  11. result = rowCountResult(sqlSession.delete(command.getName(), param));
  12. } else if (SqlCommandType.SELECT == command.getType()) {
  13. if (method.returnsVoid() && method.hasResultHandler()) {
  14. executeWithResultHandler(sqlSession, args);
  15. result = null;
  16. } else if (method.returnsMany()) {
  17. result = executeForMany(sqlSession, args);
  18. } else if (method.returnsMap()) {
  19. result = executeForMap(sqlSession, args);
  20. } else {
  21. Object param = method.convertArgsToSqlCommandParam(args);
  22. result = sqlSession.selectOne(command.getName(), param);
  23. }
  24. } else {
  25. throw new BindingException("Unknown execution method for: " + command.getName());
  26. }
  27. if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
  28. throw new BindingException("Mapper method '" + command.getName()
  29. + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
  30. }
  31. return result;
  32. }
   上面代码很清晰的显示,根据绑定的不同的sql,执行不同的方法,我们简单看一个查询多个结果的方法,excuteForMany();
   
       
       
       
  1. private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
  2. List<E> result;
  3. Object param = method.convertArgsToSqlCommandParam(args);
  4. if (method.hasRowBounds()) {
  5. RowBounds rowBounds = method.extractRowBounds(args);
  6. result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
  7. } else {
  8. result = sqlSession.<E>selectList(command.getName(), param);
  9. }
  10. // issue #510 Collections & arrays support
  11. if (!method.getReturnType().isAssignableFrom(result.getClass())) {
  12. if (method.getReturnType().isArray()) {
  13. return convertToArray(result);
  14. } else {
  15. return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
  16. }
  17. }
  18. return result;
  19. }

   上述代码很清晰的表明最后执行查询的依然是我们熟悉的SqlSession对象