spring boot基础 - Mybatis的Mapper实例化原理

我们在使用Mybatis的时候,通常会在Mapper接口上添加@Mapper注解,或者为了方便而使用@MapperScan注解。接下来分别看看这两种注解是如何实现相关mapper的bean注册的。

@Mapper注解

Mapper bean的注册

我们在业务代码中可以通过@Autowired注解注入mybatis 的 mapper,那么这个mapper一定是由spring容器来管理的一个bean definition。
问题是@Mapper是 mybatis的注解,spring是如何知道要把它注册为bean的呢?

首先定位到org.mybatis.spring.boot.autoconfigure包中的顶层配置类 MybatisAutoConfiguration,其中有几个关键信息:

1)@Bean方法 public SqlSessionFactory sqlSessionFactory(DataSource dataSource),生成SqlSessionFactory;

2)@Bean方法 public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory),生成SqlSessionTemplate;

3)静态内部类public static class AutoConfiguredMapperScannerRegistrar implements BeanFactoryAware, ImportBeanDefinitionRegistrar
注意它实现了ImportBeanDefinitionRegistrar接口,重写了registerBeanDefinitions方法,也就意味着应该会有一个配置类通过@Import的方法引入这个类,使这个类得到解析。

另外,它的registerBeanDefinitions方法中,注册的bean definition的类型是 MapperScannerConfigurer,并且设置了PropertyValue,比如要扫描的注解是@Mapper,扫描的basePackage默认与启动类的@ComponentScan相同。

再看看MapperScannerConfigurer这个类,它实现了BeanDefinitionRegistryPostProcessor,大概就可以猜到,在注册bean definition的过程中,会使用到这个PostProcessor来进行mapper扫描和bean注册。

4)静态内部类public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean,
这是个配置类,并且使用了注解@Import(AutoConfiguredMapperScannerRegistrar.class),果然这里就引入了刚才的AutoConfiguredMapperScannerRegistrar这个注册器。

到这里,mapper bean注册的初步工作就比较明朗了:

1)解析配置类MybatisAutoConfiguration -> 解析内部配置类MapperScannerRegistrarNotFoundConfiguration -> 解析注册器AutoConfiguredMapperScannerRegistrar,实例化并绑定到引入它的配置类;

2)处理配置类MybatisAutoConfiguration,解析@Bean方法,注册两个bean definition:sqlSessionFactory 和 sqlSessionTemplate;

3)处理配置类MapperScannerRegistrarNotFoundConfiguration,使用引入的注册器AutoConfiguredMapperScannerRegistrar,调用重写方法,注册bean difinition:mapperScannerConfigurer;

4)在后续调用BeanDefinitionRegistryPostProcessor过程中,实例化了mapperScannerConfigurer,调用其postProcessBeanDefinitionRegistry方法。

接下来看看postProcessBeanDefinitionRegistry方法:

  @Override
  public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  	//通常加载属性配置所使用的的PropertyResourceConfigurer,实现的是BeanFactoryPostProcessor,而BeanFactoryPostProcessor的处理在BeanDefinitionRegistryPostProcessor之后,所以这里需要手动地进行一次提前处理使其生效。
    if (this.processPropertyPlaceHolders) {
      processPropertyPlaceHolders();
    }

    ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
    scanner.setAddToConfig(this.addToConfig);
    scanner.setAnnotationClass(this.annotationClass);
    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);
    scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
    if (StringUtils.hasText(lazyInitialization)) {
      scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
    }
    if (StringUtils.hasText(defaultScope)) {
      scanner.setDefaultScope(defaultScope);
    }
    scanner.registerFilters();
    //创建一个ClassPathMapperScanner,继承自spring的ClassPathBeanDefinitionScanner类,调用scan方法扫描@Mapper注解并注册bean definition,然后设置beanClass属性为org.mybatis.spring.mapper.MapperFactoryBean,设置bean definition的constructorArgumentValues为当前接口名。
    scanner.scan(
        StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  }

这里使用mapperScannerConfigurer,完成了@Mapper注解的扫描和mapper bean的注册,并且设置beanClass属性为org.mybatis.spring.mapper.MapperFactoryBean(和MapperFactoryBean关联起来),设置bean definition的constructorArgumentValues为当前接口名(后续实例化的时候用),设置自动注入类型为AUTOWIRE_BY_TYPE(后续初始化的时候用)。

到这里mapper bean的注册就完成了。

Mapper bean的实例化

我们业务中的mapper是接口类型,要将它实例化然后调用其中的方法,需要找到一个实现类。
目前我们知道的,与具体类有关联的地方,就是前面在mapper bean注册的时候,将beanClass属性为org.mybatis.spring.mapper.MapperFactoryBean。

MapperFactoryBean是一个泛型类,实现了 FactoryBean<T>接口,表示它是一个工厂bean,它的作用是创建mapper的实现类。
源码如下:

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;

  public MapperFactoryBean() {
    // intentionally empty
  }

  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

  @Override
  public Class<T> getObjectType() {
    return this.mapperInterface;
  }

  @Override
  public boolean isSingleton() {
    return true;
  }
  
  public void setMapperInterface(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public void setAddToConfig(boolean addToConfig) {
    this.addToConfig = addToConfig;
  }

  public boolean isAddToConfig() {
    return addToConfig;
  }
}

MapperFactoryBean类里面有构造方法,有重写的getObject()、getObjectType()等方法。
spring在实例化mapper bean 或者获取类型的时候,一旦发现它的beanClass是MapperFactoryBean这个工厂bean,就会先完成MapperFactoryBean的实例化和初始化,然后通过 getObject()来获取mapper bean对象,或者通过getObjectType()来获取mapper bean的类型。

1、MapperFactoryBean的实例化
MapperFactoryBean有两个构造函数:

  public MapperFactoryBean() {
    // intentionally empty
  }

  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

由于之前mapper bean definition中设置了constructorArgumentValues,将当前mapper接口的Class对象作为参数值添加了进去;所以在使用构造函数实例化的时候,会根据设置的构造函数参数,选择下面这个含参的构造函数,完成工厂bean的实例化。

2、MapperFactoryBean的初始化
初始化的第一个重要方法,populateBean,属性填充。
之前mapper bean definition中设置了自动注入类型为AUTOWIRE_BY_TYPE,在初始化的时候,会走到autowireByType方法中对其PropertyValues进行一次填充。

autowireByType方法中主要做了两件事情:
1)使用java的内省机制(基于反射),也就是java.beans包下的Introspector类的getBeanInfo方法,获取到MapperFactoryBean的 BeanInfo。
关键方法调用链:
unsatisfiedNonSimpleProperties -> getPropertyDescriptors -> new CachedIntrospectionResults(beanClass) -> getBeanInfo(beanClass) -> Introspector.getBeanInfo

BeanInfo包含了java bean的基本信息,包括PropertyDescriptor(属性描述符)、BeanDescriptor(bean描述符)、MethodDescriptor(方法描述符)、EventSetDescriptor(事件描述符)等。

其中我们进行属性填充,需要拿到的是PropertyDescriptor,具体逻辑是:

  • 如果有父类,先拿到父类BeanInfo中的PropertyDescriptor,放进结果列表中;
  • 遍历MapperFactoryBean类的所有public方法(包括继承的和自己定义的),判断方法名是否是 getXXX、isXXX 或 setXXX,然后将XXX作为属性,组装PropertyDescriptor,放进结果列表中;
  • 处理结果列表,进行合并。

利用Introspector内省器我们拿到了初步结果:
在这里插入图片描述
这里拿到MapperFactoryBean类的PropertyDescriptor之后,为了保险起见,比如接口的default方法也可能用到setter/getter等名称,又对MapperFactoryBean类所实现的接口、以及其所有父类实现的接口都进行了内省。

可以看到现在拿到的所有PropertyDescriptor,有些明显不是我们属性注入所需要的,这里spring对其进行了过滤,最终只剩下两个:sqlSessionFactory 和 sqlSessionTemplate。

2)遍历PropertyDescriptor,调用bean factory的resolveDependency方法,解决setter方法的参数依赖。
这里其实就是实例化了sqlSessionFactory 和 sqlSessionTemplate,并将他们添加到MapperFactoryBean的PropertyValues中。

初始化的第二个重要方法,initializeBean,完成初始化,得到bean对象。
这里包含四个步骤:
invokeAwareMethods、applyBeanPostProcessorsBeforeInitialization、invokeInitMethods、applyBeanPostProcessorsAfterInitialization

其中我们本次要关注的是invokeInitMethods,它里面检测了当前bean是否实现了InitializingBean接口,如果是则调用其afterPropertiesSet方法。
显然,MapperFactoryBean实现了该接口,并且在它的afterPropertiesSet方法中,又调用checkDaoConfig,将mapperInterface成员变量,也就是我们业务中使用的mapper接口添加到了sqlSessionFactory.Configuration.MapperRegistry.knownMappers属性中,knownMappers的结构为 Map<Class<?>, MapperProxyFactory<?>>。
代码如下:

  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");
	//拿到内部sqlSessionFactory的Configuration对象
    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
      	//把当前接口放到configuration的mapperRegistry属性中
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }

到这里,MapperFactoryBean这个工厂bean的实例化和初始化就完成了,而且现在工厂bean、mapper接口、sqlSessionFactory、
sqlSessionTemplate之间已经建立了联系,最后就是通过MapperFactoryBean对象的getObject()方法拿到mapper接口的实现类对象了。

3、生成mapper bean实例

到现在为止,我们要创建的mapper bean对象,实际是创建了它对应的工厂bean实例,但是工厂bean并不是给我们最终需要的,因为它的作用是为我们生成各种不同mapper接口的实现类对象,这个生成的对象才是我们最终使用的。
所以在createBean创建完工厂bean实例后,会调用getObjectForBeanInstance方法,最终调用的是工厂bean的getObject()方法,拿到mapper bean对象,如下图:
在这里插入图片描述
getObject():

  @Override
  public T getObject() throws Exception {
    return getSqlSession().getMapper(this.mapperInterface);
  }

该方法的调用链拆解如下:
1)MapperFactoryBean.getSqlSessionTemplate.getSqlSessionFactory.getConfiguration.getMapperRegistry.getMapperProxyFactory(MapperFactoryBean.mapperInterface)
2)MapperProxyFactory.newInstance(MapperFactoryBean.sqlSessionTemplate)

简单来说就是在MapperFactoryBean的内部拿到当前mapper接口对应的MapperProxyFactory类对象,然后调用newInstance方法创建mapper接口的代理类对象。

接着看newInstance方法:

  protected T newInstance(MapperProxy<T> mapperProxy) {
  	//参数:代理类的类加载器、要实现的接口、调用处理器。
    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);
  }

这里首先创建了一个MapperProxy<T>调用处理器类对象,这个类实现了InvocationHandler接口,重写了invoke方法实现代理功能;
然后使用Proxy.newProxyInstance方法,创建了最终的代理对象实例。

由于涉及到的类比较多,这里统一梳理一下各个类的功能:

SqlSession,接口,可以理解为与数据库的连接,定义了select、insert等各种执行sql语句的方法,以及getConfiguration、getMapper等用于内部协作的get方法。

DefaultSqlSession,类,实现SqlSession接口,内部属性包含Configuration(配置)、Executor(执行器,执行具体sql)。

SqlSessionFactory,接口,用来创建SqlSession。

DefaultSqlSessionFactory,类,实现SqlSessionFactory接口,内部属性包含Configuration类对象。

SqlSessionTemplate,类,实现SqlSession接口,同时封装SqlSessionFactory及其他关联对象。

MapperFactoryBean,类,工厂bean,重写getObject()方法创建mapper代理对象;内部属性包含SqlSessionTemplate类对象、表示对应mapper接口的Class对象。

MapperProxyFactory,mapper代理对象工厂类,承接具体的mapper代理对象创建工作(使用Proxy类的动态代理机制)。

MapperProxy,调用处理器类,实现InvocationHandler接口,重写invoke方法,完成mapper功能的具体实现,作为Proxy.newProxyInstance方法入参。

到这里,mapper bean的实例化就完成了,实际是创建了一个代理类对象。

当业务代码走到类似 bookMapper.selectByExample(bookExample); 的语句时,就进入了代理对象的invoke方法,然后由sqlSessionFactory创建一个sqlSession,通过sqlSession中的executor来执行sql语句(最终还是构建了JDBC的PreparedStatement,调用了execute()方法)。

@MapperScan注解

除了在mapper接口上添加@Mapper注解,还可以在启动类(主配置类)上添加@MapperScan(value = “org.example.dao”)注解,让程序在指定路径下自动扫描。

点开@MapperScan注解,可以看到内部包含了@Import(MapperScannerRegistrar.class)注解,它引入了一个MapperScanner注册器。

再看下MapperScannerRegistrar这个类,它实现了ImportBeanDefinitionRegistrar接口,也就意味着解析到MapperScannerRegistrar时,会将这个注册器绑定到启动类,后面处理启动类时再通过该注册器的registerBeanDefinitions方法注册一个bean definition。

再看下registerBeanDefinitions方法,注册的bean类型正是MapperScannerConfigurer,和之前处理@Mapper注解时注册的一样,只不过这里少了像annotationClass = Mapper.class 这样的PropertyValue。

后面和@Mapper注解的处理类似,调用MapperScannerConfigurer 的 postProcessBeanDefinitionRegistry方法,创建ClassPathMapperScanner,扫描、解析路径下的接口,注册mapper bean definition。

参考文档:http://www.mybatis.cn/2182.html(Mybatis中文官网)

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 可以回答这个问题。MyBatis-Spring-Boot-Starter 是一个 MyBatisSpring Boot 集成的插件,可以方便地在 Spring Boot 项目中使用 MyBatis 进行数据库操作。它提供了自动配置和一些常用的配置选项,使得使用 MyBatis 变得更加简单和便捷。 ### 回答2: MyBatis-Spring-Boot-Starter是一个用于整合MyBatisSpring Boot的工具。它提供了一种方便的方式来配置和使用MyBatis持久化框架,并简化了与Spring Boot的集成过程。 首先,MyBatis-Spring-Boot-Starter支持自动配置。它会根据项目的依赖和配置文件来自动初始化和配置MyBatis,大大减少了手动配置的工作量。只需简单地在配置文件中指定数据库的连接信息和MyBatis的相关配置,就可以轻松地集成MyBatis框架到Spring Boot应用中。 其次,MyBatis-Spring-Boot-Starter还支持事务管理。通过注解的方式,可以很方便地对数据库操作进行事务管理。开发者可以使用@Transactional注解来标记需要进行事务管理的方法,MyBatis-Spring-Boot-Starter会自动为其开启事务并处理事务的提交和回滚。 此外,MyBatis-Spring-Boot-Starter还提供了一些额外的功能,如分页插件、缓存管理等。分页插件可以简化分页查询操作,缓存管理可以提高查询效率。这些功能的开启和配置也是非常简单的,只需在配置文件中进行相应的配置即可。 总而言之,MyBatis-Spring-Boot-Starter是一个极大简化了整合MyBatisSpring Boot的工具,它提供了自动配置、事务管理和其他辅助功能,使开发者可以更加轻松地使用MyBatis作为数据持久化框架,并结合Spring Boot快速构建稳定高效的应用程序。 ### 回答3: mybatis-spring-boot-starter是一个用于集成MyBatisSpring Boot的工具包。它可以极大地简化在Spring Boot项目中使用MyBatis的配置和使用过程。 使用mybatis-spring-boot-starter,我们不再需要手动配置MyBatis的配置文件和数据源等信息。只需要在项目的application.properties(或application.yml)文件中简单配置几个参数,如数据库连接信息、MyBatisMapper接口所在的包路径等,就可以自动完成MyBatis的初始化工作。 另外,mybatis-spring-boot-starter还集成了一些常用的功能,方便我们在Spring Boot项目中使用MyBatis。例如,它可以自动扫描并注册Mapper接口,并将其注入到Spring容器中。我们可以直接使用@Autowired注解将Mapper接口注入到我们的服务类中,无需手动实例化。 此外,mybatis-spring-boot-starter还提供了一些常见的插件和功能扩展。例如,它支持分页插件、动态SQL插件等,可以方便地对数据库进行操作。同时,它还支持事务管理,保证了数据库操作的一致性和完整性。 总之,mybatis-spring-boot-starter为我们提供了一种更加便捷和高效的方式来集成MyBatisSpring Boot。它减少了我们的配置工作,提高了开发效率,并且提供了一些常用的功能扩展。使用mybatis-spring-boot-starter,我们可以更加专注于业务逻辑的开发,而无需过多关注底层的配置和细节。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值