• mybatis——Spring中的接口映射器


    一、接口映射器的配置

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    
        <!-- 自动扫描package下bean -->
        <context:component-scan base-package="org.study.*"/>
    
        <!-- aop代理 -->
        <aop:aspectj-autoproxy/>
    
        <!-- 属性文件 -->
        <context:property-placeholder location="classpath*:db.properties"/>
    
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${mysql.driver}"/>
            <property name="url" value="${mysql.url}"/>
            <property name="username" value="${mysql.username}"/>
            <property name="password" value="${mysql.password}"/>
            <property name="initialSize" value="${mysql.initialSize}"/>
            <property name="minIdle" value="${mysql.minIdle}"/>
            <property name="maxActive" value="${mysql.maxActive}"/>
            <property name="maxWait" value="${mysql.maxWait}"/>
         </bean>
        <!-- mybatis的SQLSessionFactory -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <property name="dataSource" ref="dataSource"/>
            <!--扫描配置sql的xml文件-->
            <property name="mapperLocations" value="classpath:mapper/*.xml"/>
        </bean>
    
        <!-- mybatis的mapper接口映射器 -->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
            <!--扫描package下的接口-->
            <property name="basePackage" value="org.study.mapper"/>
        </bean>
    
        <!-- 事务管理 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
        <!-- 启用声明式事务管理 支持注解@Transaction-->
        <tx:annotation-driven/>
    
    </beans>

    前文说明了:sql的xml解析和注解解析,所以省去sqlSessionFactory根据mapperLocations="classpath:mapper/*.xml "匹配对应sql.xml,然后解析。直接看接口映射器实现

    二、MapperScannerConfigurer

    MapperScannerConfigurer是mybatis-spring.jar中的类,不是mybatis.jar中的类,

    主要目的:将mapper接口放入到SpringIOC容器中,并与sql文件映射。跟踪源码前需要要先了解SpringIOC——scan,疑问:

    • 有@Component注解的class才会生成的BeanDefinition,然后初始化到SpringIOC容器中,因此接口需要生成BeanDefinition必须重写判断方法,在哪里重写的?
    • 生成了BeanDefinition后,由于mapper.class是接口,IOC容器是怎样实例化的?

    1、扫描mapper接口生成BeanDefinition实例

    MapperScannerConfigurer只有两个方法

    • postProcessBeanDefinitionRegistry:扫描mapper接口生成BeanDefinition的主要实现方法。
    • processPropertyPlaceHolders:根据properties文件获取MapperScannerConfigurer的属性参数。

    重点跟踪postProcessBeanDefinitionRegistry方法

       /* org.mybatis.spring.mapper.MapperScannerConfigurer#postProcessBeanDefinitionRegistry 
        * postProcessBeanDefinitionRegistry这个方法的执行时机:
        * IOC初始化-refresh()中的⑥ invokeBeanFactoryPostProcessors(beanfactory)时执行的
        * 即方法执行时。容器(registry)已经创建并且已经执行过一次scan,这里是另外附加一次scan
        */
      public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
        if (this.processPropertyPlaceHolders) { 
          //propertis中获取basePackage参数
          //应该是适配springboot无xml方式实现
          processPropertyPlaceHolders();
        }
    
        //mybatis-spring.jar自实现的scanner
        ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
        scanner.setAddToConfig(this.addToConfig);
        //includeFilters.add(annotationClass)
        scanner.setAnnotationClass(this.annotationClass);
        //includerFilters.add(markerInterface)
        scanner.setMarkerInterface(this.markerInterface);
        scanner.setSqlSessionFactory(this.sqlSessionFactory);
        scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
        scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
        scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
        scanner.setResourceLoader(this.applicationContext);
        scanner.setBeanNameGenerator(this.nameGenerator);
        //mapper接口的实际类型MapperFactoryBean.class
        scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
        if (StringUtils.hasText(lazyInitialization)) {
          scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
        }
        //最重要的实现之一:解决为什么接口没有@Component也会被扫描到
        //回忆一下spring中scan阶段includeFilters的作用:默认@Component注解的class才能被IOC扫描到并生成BeanDefinition实例(注意只是生成实例,还没有加入到容器中)
        //registerFIlters:修改了includeFilters范围
        //若配置了annotationClass:includeFilter.add(annotationClass):例如mapper接口使用@Mapper注解。
        //若配置了markerInterface:includeFilter.add(markerInterface)
        //都没有配置默认所有的class都可以生成BeanDefinition
        scanner.registerFilters();
        //解析basePackage下的class生成BeanDefinition(根据上面的includeFilters)
        scanner.scan(
            StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
      }

    scanner.registerFilters(): includeFilters修改

    /* org.mybatis.spring.mapper.ClassPathMapperScanner#registerFilters */
      public void registerFilters() {
        boolean acceptAllInterfaces = true;
    
        // 配置注解,includeFilters添加注解类,basePackage下使用注解的类会被扫描到
        if (this.annotationClass != null) {
          addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
          acceptAllInterfaces = false;
        }
    
        // 配置接口,includeFilters添加接口,basePackage下实现接口的class会被扫描到
        if (this.markerInterface != null) {
          addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
            @Override
            protected boolean matchClassName(String className) {
              return false;
            }
          });
          acceptAllInterfaces = false;
        }
        //若没有配置注解和接口,默认扫描所有的class
        if (acceptAllInterfaces) {
          addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
        }
    
        // 不扫描 *package-info.java
        addExcludeFilter((metadataReader, metadataReaderFactory) -> {
          String className = metadataReader.getClassMetadata().getClassName();
          return className.endsWith("package-info");
        });
      }
    scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)):扫描basePackage下的class
    /* org.mybatis.spring.mapper.ClassPathMapperScanner#doScan */
      public Set<BeanDefinitionHolder> doScan(String... basePackages) {
       //org.springframework.context.annotation.ClassPathBeanDefinitionScanner#doScan 
        //前面springioc-scan()中的doScan()方法
        //ClassPathMapperScanner重写了isCandidateComponent()
        Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
    
        if (beanDefinitions.isEmpty()) {
          LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
              + "' package. Please check your configuration.");
        } else {
          processBeanDefinitions(beanDefinitions);
        }
    
        return beanDefinitions;
      }
    调用super.doScan(basePackages)时: 由于重写isCandidateComponent():接口生成的BeanDefinition也会加入到容器中。
    原方法:includeFilters扫描到class文件并生成BeanDefinition实例,isCandidateComponent()会判断不是接口、抽象类,才会将BeanDefinition放入到IOC容器中
    重写后:接口的BeanDefinition也会放入到IOC容器中(仅限basePackage下)
    /* org.mybatis.spring.mapper.ClassPathMapperScanner#isCandidateComponent */
      protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        //是接口返回true,原判断是metadata.isConcrete():!(isInterface() || isAbstract())
        return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
      }

    processBeanDefinitions():初始化BeanDefinition的属性:

    /* org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions */
      private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
        GenericBeanDefinition definition;
        for (BeanDefinitionHolder holder : beanDefinitions) {
          definition = (GenericBeanDefinition) holder.getBeanDefinition();
          String beanClassName = definition.getBeanClassName();
          LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
              + "' mapperInterface");
    
          //解决:接口为什么能实例化成一个Bean
          //原beanClassName设置为构造方法的参数
          //beanClassName = MapperFactoryBean.class
          //经过上面配置,SpringIOC容器生成接口的实例实际是MapperFactoryBean类型的 
          //最后beanName(userMapper) - singletonObject(new MapperFactoryBean(UserMapper.class))     
          definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
          definition.setBeanClass(this.mapperFactoryBeanClass);
    
          definition.getPropertyValues().add("addToConfig", this.addToConfig);
    
          boolean explicitFactoryUsed = false;
          if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
            //新增属性:definition.sqlSessionFactory根据BeanName注入
            definition.getPropertyValues().add("sqlSessionFactory",
                new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
            explicitFactoryUsed = true;
          } else if (this.sqlSessionFactory != null) {
            //新增属性:definition.sqlSessionFactory直接ref注入
            definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
            explicitFactoryUsed = true;
          }
    
          if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
            if (explicitFactoryUsed) {
              LOGGER.warn(
                  () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
            }
            //新增属性:definition.sqlSessionTemplate根据BeanName注入
            definition.getPropertyValues().add("sqlSessionTemplate",
                new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
            explicitFactoryUsed = true;
          } else if (this.sqlSessionTemplate != null) {
            if (explicitFactoryUsed) {
              LOGGER.warn(
                  () -> "Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
            }
            //新增属性:definition.sqlSessionTemplate直接ref注入
            definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
            explicitFactoryUsed = true;
          }
    
          if (!explicitFactoryUsed) {
            LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
            definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
          }
          //延迟实例化  默认false
          definition.setLazyInit(lazyInitialization);
        }
      }

    到这里mapper接口的BeanDefinition实例已经全部初始化完成了,接下来就是Bean实例化了。Bean实例前先猜想一下这个实例是什么样的。

        // 例如一个UserMapper.class,
        MapperFactoryBean factoryBean = new MapperFactoryBean(UserMapper.class);
        factoryBean.setSqlSessionFactory(sqlSessionFactory);
        factoryBean.setSqlSessionTemplate(sqlSessionTemplate);

    2、MapperFactoryBean初始化

     SpringIOC——refresh初始化中漏掉的实现FactoryBean接口的Bean的特殊处理,除beanName= "&" + beanName,其他初始化逻辑与Bean一模一样。

    这里结合MapperFactoryBean实现,发现了FactoryBean作用:提供接口实例化的机会,接口绑定技术。接口是肯定不能实例化的,但是可以实例化一个FactoryBean实例代替接口。

    例如:UserMapper接口的BeanDefinition实例化出来就是一个MapperFactoryBean实例,

    断点发现,确实是一个MapperFactoryBean实例,其中记录了UserMapper.class信息。

     

     接口映射器实例化已经完成了,但是实际代码一般是下面这样的

    @Autowired
    private UserMapper userMapper;//MapperFactoryBean没有实现UserMaper,所以依赖注入时,注入的肯定不是MapperFactoryBean实例。
    ...
    userMapper.seletUserById(1L);

    断点发现依赖注入时,userMapper确实不是MapperFactoryBean实例,而是一个代理实例

     根据FactoryBean接口,猜想可能是在依赖注入时,依赖注入的是FactoryBean.getObject()实例

    public interface FactoryBean<T> {
    
        String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
        T getObject() throws Exception;
        @Nullable
        Class<?> getObjectType();
        default boolean isSingleton() {
            return true;
        }
    
    }
    最终发现Bean初始化时有一个了解不够全面的地方:org.springframework.beans.factory.support.AbstractBeanFactory#getObjectForBeanInstance,
    也是实现FactoryBean接口的Bean与其他类型Bean的区别所在。
    ① !(Bean instanceof FactoryBean) 直接返回Bean实例
    ②(Bean instanceof FactoryBean) 初始化时返回FactoryBean实例,其他情况下(例如DI)时返回的是FactoryBean.getObject()实例
        protected Object getObjectForBeanInstance(
                Object beanInstance, String name, String beanName, @Nullable RootBeanDefinition mbd) {
    
            //UserMapper初始化时由于MapperFactoryBean instanceof FactoryBean,传入的name = "&userMapper"
            //依赖注入时传入的是name = "userMapper"
            if (BeanFactoryUtils.isFactoryDereference(name)) {
                //Bean instanceof FactoryBean的Bean初始化时,name = "&userMapper"直接返回FactoryBean实例
                if (beanInstance instanceof NullBean) {
                    return beanInstance;
                }
                if (!(beanInstance instanceof FactoryBean)) {
                    throw new BeanIsNotAFactoryException(beanName, beanInstance.getClass());
                }
                if (mbd != null) {
                    mbd.isFactoryBean = true;
                }
                return beanInstance;
            }
    
            // 没有实现FactoryBean的Bean直接返回Bean实例
            if (!(beanInstance instanceof FactoryBean)) {
                return beanInstance;
            }
            //依赖注入时name = "userMapper"
            //返回的是FactoryBean.getObject()实例,
            Object object = null;
            if (mbd != null) {
                mbd.isFactoryBean = true;
            }
            else {
                object = getCachedObjectForFactoryBean(beanName);
            }
            if (object == null) {
                // Return bean instance from factory.
                FactoryBean<?> factory = (FactoryBean<?>) beanInstance;
                // Caches object obtained from FactoryBean if it is a singleton.
                if (mbd == null && containsBeanDefinition(beanName)) {
                    mbd = getMergedLocalBeanDefinition(beanName);
                }
                boolean synthetic = (mbd != null && mbd.isSynthetic());
                object = getObjectFromFactoryBean(factory, beanName, !synthetic);
            }
            return object;
        }

    到这里初始化就剩下一个MapperFactoryBean.getObject():先小结一下:

    ① SpringIOC容器中userMapper是MapperFactoryBean类型的

    ② SpringIOC容器中userService中userMapper是MapperFactoryBean.getObject()类型的(UserMapper.class)

    3、MapperFactoryBean.getObject()

    /* org.mybatis.spring.mapper.MapperFactoryBean#getObject */
      public T getObject() throws Exception {
        //这里sqlSession是SqlSessionTemplate类型的
        return getSqlSession().getMapper(this.mapperInterface);
      }
    
    /* org.mybatis.spring.SqlSessionTemplate#getMapper */
      public <T> T getMapper(Class<T> type) {
        return getConfiguration().getMapper(type, this);
      }
    
    /* 
     * 下面就是mybatis.jar中的逻辑了,也就回到了前文的逻辑了
     */
    
    /* org.apache.ibatis.session.Configuration#getMapper */
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        return mapperRegistry.getMapper(type, sqlSession);
      }
    
    /* org.apache.ibatis.binding.MapperRegistry#getMapper */
      public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
        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);
        }
      }
    
    /* org.apache.ibatis.binding.MapperProxyFactory*/
      public T newInstance(SqlSession sqlSession) {
        final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
        return newInstance(mapperProxy);
      }
    
      protected T newInstance(MapperProxy<T> mapperProxy) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
      }
  • 相关阅读:
    Java 技术笔记
    idea启动TOMCAT html 乱码
    IntelliJ IDEA 导入新项目
    InterlliJ Debug方式启动:method breakpoints may dramatically show down debugging
    intelliJ idea #region 代码折叠
    Console 程序在任务计划程序无法读写文件
    Java 发送邮件
    MySQL 索引
    MySQL 临时表
    11 帧差法获取运动
  • 原文地址:https://www.cnblogs.com/wqff-biubiu/p/12445853.html
Copyright © 2020-2023  润新知