springboot整合mybatis源码分析

本文主要讲述mybatis在springboot中是如何被加载执行的,由于涉及的内容会比较多,所以这次只会对调用关系及关键代码点进行讲解,为了避免文章太长,读起来昏昏欲睡,一些不影响整体流程的细节就不涉及了。

源码位置https://github.com/wbo112/blogdemo/tree/main/springbootdemo/springboot-mybatis

1、预备知识

  • FactoryBean

    什么是FactoryBean?

    我们先看看FactoryBean的源码

  1. //由 BeanFactory 中使用的对象实现的接口,这些对象本身是单个对象的工厂。如果一个 bean 实现了这个接口,它就被用作一个对象暴露的工厂,而不是直接作为一个将暴露自己的 bean 实例。
  2. //注意:实现此接口的 bean 不能用作普通 bean。 FactoryBean 以 bean 样式定义,但为 bean 引用公开的对象 (getObject()) 始终是它创建的对象。
  3. //FactoryBeans 可以支持单例和原型,并且可以根据需要懒惰地或在启动时急切地创建对象。 SmartFactoryBean 接口允许公开更细粒度的行为元数据。
  4. //该接口在框架本身中被大量使用,例如用于 AOP org.springframework.aop.framework.ProxyFactoryBean 或 org.springframework.jndi.JndiObjectFactoryBean。它也可以用于自定义组件;然而,这仅适用于基础设施代码。
  5. //FactoryBean 是一个程序化契约。实现不应该依赖于注释驱动的注入或其他反射设施。 getObjectType() getObject() 调用可能会在引导过程的早期到达,甚至在任何后处理器设置之前。如果您需要访问其他 bean,请实现 BeanFactoryAware 并以编程方式获取它们。
  6. //容器只负责管理FactoryBean 实例的生命周期,而不负责管理FactoryBean 创建的对象的生命周期。因此,暴露的 bean 对象(例如 java.io.Closeable.close() 上的 destroy 方法不会被自动调用。相反,FactoryBean 应该实现 DisposableBean 并将任何此类关闭调用委托给底层对象。
  7. //最后,FactoryBean 对象参与包含 BeanFactory 的 bean 创建同步。除了 FactoryBean 本身(或类似的)内部的延迟初始化之外,通常不需要内部同步。
  8. package org.springframework.beans.factory;
  9. import org.springframework.lang.Nullable;
  10. public interface FactoryBean<T> {
  11. String OBJECT_TYPE_ATTRIBUTE = "factoryBeanObjectType";
  12. //返回真正的beanFacotry中的bean对象
  13. @Nullable
  14. T getObject() throws Exception;
  15. //返回真正的beanFacotry中的bean对象的类型
  16. @Nullable
  17. Class<?> getObjectType();
  18. //是否单例
  19. default boolean isSingleton() {
  20. return true;
  21. }
  22. }

上面就是FactoryBean的源码了,源码中的注释我都删除掉了。类上的中文注释是翻译的源码上的,方法上的注释是我自己加的。简单来说就是时间这个接口的类是作为对象暴漏的工厂,真正调用getObject()才会得到实际的bean对象。

2、springboot集成mybatis

  • 之前的文章简单说到springboot启动的时候会读取META-INF\spring.factories文件,把key=org.springframework.boot.autoconfigure.EnableAutoConfiguration的字符串作为类名去加载(启动会配合META-INF\spring-autoconfigure-metadata.properties中的内容过滤掉不符合当前场景的)

    springboot集成mybatis也是这样实现的。

  • 是由谁来上面的文件的呢

    我们的main方法上都会有@SpringBootApplication注解

在SpringBootApplication这个上面会有个@EnableAutoConfiguration注解

在这个上面会有import注解,参数是AutoConfigurationImportSelector.class。真正读取上面文件的类就是AutoConfigurationImportSelector。

AutoConfigurationImportSelector.java

  1. //真正的读取代码是在这里
  2. protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
  3. if (!isEnabled(annotationMetadata)) {
  4. return EMPTY_ENTRY;
  5. }
  6. AnnotationAttributes attributes = getAttributes(annotationMetadata);
  7. //在这里读取META-INF\spring.factories文件中key=org.springframework.boot.autoconfigure.EnableAutoConfiguration的值
  8. List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
  9. configurations = removeDuplicates(configurations);
  10. Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  11. checkExcludedClasses(configurations, exclusions);
  12. configurations.removeAll(exclusions);
  13. //在这里读取META-INF\spring.factories文件中key=org.springframework.boot.autoconfigure.AutoConfigurationImportFilter的值根据META-INF\spring-autoconfigure-metadata.properties进行过滤
  14. configurations = getConfigurationClassFilter().filter(configurations);
  15. fireAutoConfigurationImportEvents(configurations, exclusions);
  16. return new AutoConfigurationEntry(configurations, exclusions);
  17. }

读取META-INF\spring-autoconfigure-metadata.properties文件是在AutoConfigurationImportSelector的内部类ConfigurationClassFilter的构造方法中,真正的过滤也是在这个内部类中

  1. ConfigurationClassFilter(ClassLoader classLoader, List<AutoConfigurationImportFilter> filters) {
  2. //在这里读取的META-INF\spring-autoconfigure-metadata.properties
  3. this.autoConfigurationMetadata = AutoConfigurationMetadataLoader.loadMetadata(classLoader);
  4. this.filters = filters;
  5. }
  1. //这个也是ConfigurationClassFilter的方法
  2. List<String> filter(List<String> configurations) {
  3. long startTime = System.nanoTime();
  4. String[] candidates = StringUtils.toStringArray(configurations);
  5. boolean skipped = false;
  6. for (AutoConfigurationImportFilter filter : this.filters) {
  7. //执行过滤
  8. boolean[] match = filter.match(candidates, this.autoConfigurationMetadata);
  9. for (int i = 0; i < match.length; i++) {
  10. if (!match[i]) {
  11. candidates[i] = null;
  12. skipped = true;
  13. }
  14. }
  15. }
  16. + TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - startTime) + " ms");
  17. }
  18. return result;
  19. }

默认的过滤器是有3个,是在这里

在读取过程中就会读取mybatis-spring-boot-autoconfigure-2.2.0.jar中的META-INF\spring.factories配置(本文第一个图),加载下面两个类

  1. org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  2. org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
  3. org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

同样的也会用mybatis-spring-boot-autoconfigure-2.2.0.jar中的META-INF\spring-autoconfigure-metadata.properties文件进行过滤。

这里的过滤其实就是用类名+.+Conditional*来作为过滤的

  1. org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.ConditionalOnClass=org.apache.ibatis.session.SqlSessionFactory,org.mybatis.spring.SqlSessionFactoryBean
  2. org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.ConditionalOnSingleCandidate=javax.sql.DataSource

比如上面两行org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.ConditionalOnClass是根据等号后面的类是否存在来判断是否被过滤掉,org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration.ConditionalOnSingleCandidate看代码也是判断对应类是否存在来判断的,多个条件是and的关系

这两个条件的具体判断代码位置在OnBeanCondition中

  1. protected final ConditionOutcome[] getOutcomes(String[] autoConfigurationClasses,
  2. AutoConfigurationMetadata autoConfigurationMetadata) {
  3. ConditionOutcome[] outcomes = new ConditionOutcome[autoConfigurationClasses.length];
  4. for (int i = 0; i < outcomes.length; i++) {
  5. String autoConfigurationClass = autoConfigurationClasses[i];
  6. if (autoConfigurationClass != null) {
  7. //获取*.ConditionalOnClass等号后面的值
  8. Set<String> onBeanTypes = autoConfigurationMetadata.getSet(autoConfigurationClass, "ConditionalOnBean");
  9. //进行判断,返回null就是OK的,条件不存在也是null
  10. outcomes[i] = getOutcome(onBeanTypes, ConditionalOnBean.class);
  11. if (outcomes[i] == null) {
  12. //获取*.ConditionalOnSingleCandidate等号后面的值
  13. Set<String> onSingleCandidateTypes = autoConfigurationMetadata.getSet(autoConfigurationClass,
  14. "ConditionalOnSingleCandidate");
  15. //进行判断,返回null就是OK的,条件不存在也是null
  16. outcomes[i] = getOutcome(onSingleCandidateTypes, ConditionalOnSingleCandidate.class);
  17. }
  18. }
  19. }
  20. return outcomes;
  21. }

当前的场景这两个类都是符合的不会被过滤掉。这两个类就会被加载。

3、MybatisAutoConfiguration的加载,beanFatory加载@Mapper类

下面具体看下加载的过程,主要是MybatisAutoConfiguration这个类,所以我们这里也就只看这个类了

  1. //这里就把类上的注解粘了出来简单都介绍下
  2. //这个注解大家都比较熟悉,不多说了
  3. @org.springframework.context.annotation.Configuration
  4. //这个还是条件注解,处理的类和上面配置文件中的处理都在同一个类中
  5. //这个是判断对应类是否存在
  6. @ConditionalOnClass({ SqlSessionFactory.class, SqlSessionFactoryBean.class })
  7. //这个就和配置文件中的处理有区别了,这个是判断beanFacotry中是否只有一个类型DataSource.class的bean的定义,或者有多个,但有一个主要的
  8. @ConditionalOnSingleCandidate(DataSource.class)
  9. //这个是去让注入配置文件
  10. @EnableConfigurationProperties(MybatisProperties.class)
  11. //这个是排序的
  12. @AutoConfigureAfter({ DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class })
  13. public class MybatisAutoConfiguration implements InitializingBean {
  14. ......
  15. }

下面这个图是ConditionalOnClass,ConditionalOnSingleCandidate执行处理的位置,还是在OnBeanCondition这个类中

在进行完过滤判断后,确定MybatisAutoConfiguration类要加载之后,会扫描内部类和方法上,符合条件的也都会被加载,主要是找@Configuration,@Bean这两个注解。我们当前这个类中依次会加载如下内容

加载这个内部类,ConditionalOnMissingBean这个条件当前是成立的,关于条件这块都会忽略掉,不多说这块了。同时由于类上有Import注解,也就会继续加载AutoConfiguredMapperScannerRegistrar.class这个类,

类上有方法@Bean注解

这两个类也会被加载

这里就会加载MybatisAutoConfiguration,MapperScannerRegistrarNotFoundConfiguration,AutoConfiguredMapperScannerRegistrar,SqlSessionTemplate,SqlSessionFactory这几个作为bean的定义(后面两个是方法)。

由于AutoConfiguredMapperScannerRegistrar实现了ImportBeanDefinitionRegistrar接口,在加载的过程中,会调用registerBeanDefinitions去注册额外的bean的定义。

这个方法比较重要,我们进去看看

  1. @Override
  2. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  3. //这个是判断beanFactory中是否存在AutoConfigurationPackages的bean,这里是存在的
  4. if (!AutoConfigurationPackages.has(this.beanFactory)) {
  5. logger.debug("Could not determine auto-configuration package, automatic mapper scanning disabled.");
  6. return;
  7. }
  8. logger.debug("Searching for mappers annotated with @Mapper");
  9. //这里获取要扫描的包名,这里会是{“com.example.springbootmybatis”},其实也就是我们在哪里找mapper,后面单独说下这个
  10. List<String> packages = AutoConfigurationPackages.get(this.beanFactory);
  11. if (logger.isDebugEnabled()) {
  12. packages.forEach(pkg -> logger.debug("Using auto-configuration base package '{}'", pkg));
  13. }
  14. //下面这些代码主要就是定义一个bean的定义,添加到BeanFactory中
  15. BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
  16. builder.addPropertyValue("processPropertyPlaceHolders", true);
  17. //这就是要扫描的注解类型,就是@Mapper
  18. builder.addPropertyValue("annotationClass", Mapper.class);
  19. //这里是要扫描的包的路径
  20. builder.addPropertyValue("basePackage", StringUtils.collectionToCommaDelimitedString(packages));
  21. BeanWrapper beanWrapper = new BeanWrapperImpl(MapperScannerConfigurer.class);
  22. Set<String> propertyNames = Stream.of(beanWrapper.getPropertyDescriptors()).map(PropertyDescriptor::getName)
  23. .collect(Collectors.toSet());
  24. if (propertyNames.contains("lazyInitialization")) {
  25. // Need to mybatis-spring 2.0.2+
  26. builder.addPropertyValue("lazyInitialization", "${mybatis.lazy-initialization:false}");
  27. }
  28. if (propertyNames.contains("defaultScope")) {
  29. // Need to mybatis-spring 2.0.6+
  30. builder.addPropertyValue("defaultScope", "${mybatis.mapper-default-scope:}");
  31. }
  32. registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition());
  33. }

这里我们来看下AutoConfigurationPackages.get(this.beanFactory)这个获取的包名是如何的

先看下这个方法的里面调用

  1. public static List<String> get(BeanFactory beanFactory) {
  2. try {
  3. //BEAN = AutoConfigurationPackages.class.getName(),这个方法也就是获取bean的名字是AutoConfigurationPackages.class.getName(),AutoConfigurationPackages.BasePackages.class类型的bean,再调用AutoConfigurationPackages.BasePackages的get方法
  4. //下面我们分析下这个值是怎么来的
  5. return beanFactory.getBean(BEAN, BasePackages.class).get();
  6. }
  7. catch (NoSuchBeanDefinitionException ex) {
  8. throw new IllegalStateException("Unable to retrieve @EnableAutoConfiguration base packages");
  9. }
  10. }

由于我们main方法的类上有@SpringBootApplication注解,它的注解上有@EnableAutoConfiguration,它的注解上有@AutoConfigurationPackage,它的注解上@Import(AutoConfigurationPackages.Registrar.class),在加载我们的主类SpringbootMybatisApplication时,就会调用到AutoConfigurationPackages.Registrar的registerBeanDefinitions这个方法

  1. static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
  2. @Override
  3. //这里的metadata就是我们的SpringbootMybatisApplication
  4. public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  5. //我们先看看new PackageImports(metadata)这个方法
  6. register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
  7. }
  8. ......
  9. }
  1. //这个比较简单了,就是获取一些包名
  2. PackageImports(AnnotationMetadata metadata) {
  3. AnnotationAttributes attributes = AnnotationAttributes
  4. .fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
  5. //这里是获取我们主类SpringbootMybatisApplication上注解的"basePackages"属性,由于我们没有配置,所以这里就是null
  6. List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
  7. //这里是获取我们主类SpringbootMybatisApplication上注解的"basePackageClasses"属性,由于我们没有配置,所以也不会走到这个for循环
  8. for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
  9. packageNames.add(basePackageClass.getPackage().getName());
  10. }
  11. //这里的packageNames就是空的,会走到这个if分支
  12. if (packageNames.isEmpty()) {
  13. //packageNames增加当前SpringbootMybatisApplication类所在的包名com.example.springbootmybatis
  14. packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
  15. }
  16. //这里的this.packageNames中就只会有com.example.springbootmybatis
  17. this.packageNames = Collections.unmodifiableList(packageNames);
  18. }

我们再回头看上面的registerBeanDefinitions

  1. static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
  2. @Override
  3. public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
  4. //上面new PackageImports(metadata)已经分析过了,这时new PackageImports(metadata).getPackageNames().toArray(new String[0])就是{“com.example.springbootmybatis”}
  5. //这个方法就不点进去了,在这里简单说说
  6. //方法就是在registry(也就是beanFatory)中增加一个bean的定义(BasePackagesBeanDefinition,它的参数就是{“com.example.springbootmybatis”}),所以上面的AutoConfigurationPackages.get(this.beanFactory)这句返回的结果就是{“com.example.springbootmybatis”}
  7. register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
  8. }
  9. ......
  10. }

我们继续看 public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) 中在registry(也就是beanFacotry)中增加的bean的定义registry.registerBeanDefinition(MapperScannerConfigurer.class.getName(), builder.getBeanDefinition())

由于MapperScannerConfigurer这个类实现了BeanDefinitionRegistryPostProcessor,所以它就会被生成bean之前加载,调用它的postProcessBeanDefinitionRegistry方法


  1. public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
  2. if (this.processPropertyPlaceHolders) {
  3. //这个是主要设置一些属性,比如上面包名,要扫描的注解类名称等等
  4. processPropertyPlaceHolders();
  5. }
  6. //这个类看名字,大家都知道是干什么的了。主要就是扫描mapper注解的类
  7. ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
  8. scanner.setAddToConfig(this.addToConfig);
  9. scanner.setAnnotationClass(this.annotationClass);
  10. scanner.setMarkerInterface(this.markerInterface);
  11. scanner.setSqlSessionFactory(this.sqlSessionFactory);
  12. scanner.setSqlSessionTemplate(this.sqlSessionTemplate);
  13. scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName);
  14. scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName);
  15. scanner.setResourceLoader(this.applicationContext);
  16. scanner.setBeanNameGenerator(this.nameGenerator);
  17. scanner.setMapperFactoryBeanClass(this.mapperFactoryBeanClass);
  18. if (StringUtils.hasText(lazyInitialization)) {
  19. scanner.setLazyInitialization(Boolean.valueOf(lazyInitialization));
  20. }
  21. if (StringUtils.hasText(defaultScope)) {
  22. scanner.setDefaultScope(defaultScope);
  23. }
  24. //这里是设置要扫描的注解类,这里会设置@Mapper
  25. scanner.registerFilters();
  26. //这里就是要根据传入的包名去做扫描了,这里的this.basePackage就是上面说的com.example.springbootmybatis
  27. scanner.scan(
  28. StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
  29. }
  1. public int scan(String... basePackages) {
  2. ......
  3. //在这里进行mapper的扫描
  4. doScan(basePackages);
  5. ......
  6. }
  1. @Override
  2. public Set<BeanDefinitionHolder> doScan(String... basePackages) {
  3. //首先会进入这里,我们进去看看
  4. Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
  5. if (beanDefinitions.isEmpty()) {
  6. LOGGER.warn(() -> "No MyBatis mapper was found in '" + Arrays.toString(basePackages)
  7. + "' package. Please check your configuration.");
  8. } else {
  9. //我们会走到这里,这个方法也比较重要,我们进去看看
  10. processBeanDefinitions(beanDefinitions);
  11. }
  12. return beanDefinitions;
  13. }
  1. protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
  2. Assert.notEmpty(basePackages, "At least one base package must be specified");
  3. Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();
  4. //根据传入的包名遍历
  5. for (String basePackage : basePackages) {
  6. //这里就是扫描类路径下的mapper注解类了。
  7. //比如我这里的传入的包名是com.example.springbootmybatis,就会被转换成classpath*:com/example/springbootmybatis/**/*.class这个路径进行解析查找,将找到的类作为BeanDefinition的定义,返回
  8. Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
  9. for (BeanDefinition candidate : candidates) {
  10. ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
  11. candidate.setScope(scopeMetadata.getScopeName());
  12. //获取bean的名字
  13. String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
  14. //这里candidate的类型是ScannedGenericBeanDefinition,所以会进入这个if分支,这个没啥,就是设置一些bean初始化相关属性,不关注了
  15. if (candidate instanceof AbstractBeanDefinition) {
  16. postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
  17. }
  18. //也会进入这个if分支,这个也不进去看了
  19. if (candidate instanceof AnnotatedBeanDefinition) {
  20. AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
  21. }
  22. //这个是判断beanFactory是否包含beanName的bean的定义,不包含就会进入分支,这个分支也没啥特殊的,就是把bean的定义添加到beanFactory中
  23. if (checkCandidate(beanName, candidate)) {
  24. BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
  25. definitionHolder =
  26. AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
  27. beanDefinitions.add(definitionHolder);
  28. registerBeanDefinition(definitionHolder, this.registry);
  29. }
  30. }
  31. }
  32. return beanDefinitions;
  33. }

继续会进入这个方法,这个方法比较长,不过比较重要,大家一起跟我看吧,非关键代码我都省略掉吧

  1. private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
  2. ......
  3. //这里是给bean定义的添加一个构造方法参数,就是我们扫描出来mapper注解类的类名,我这里是com.example.springbootmybatis.mapper.UserMapper。这个是为后续选择哪个构造方法服务的
  4. definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
  5. //这个就是设置对应bean的类的类,这里设置成了org.mybatis.spring.mapper.MapperFactoryBean这个类,这注意这个类实现了FactoryBean接口
  6. definition.setBeanClass(this.mapperFactoryBeanClass);
  7. ......
  8. if (!explicitFactoryUsed) {
  9. LOGGER.debug(() -> "Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
  10. //这句也比较重要,代表属性注入模式
  11. definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
  12. }
  13. ......
  14. }
  15. }

到这里,对于@Mapper类的加载就完成了,后面的都是在生成对应bean的时候完成的

4.beanFatory生成对应@Mapper类的bean对象

创建bean对象实例会调用到AbstractBeanFactory的doGetBean这个方法

  1. protected <T> T doGetBean(
  2. String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
  3. throws BeansException {
  4. ......
  5. // Create bean instance.
  6. //由于我们的是单例对象,会走到这个分支
  7. if (mbd.isSingleton()) {
  8. sharedInstance = getSingleton(beanName, () -> {
  9. try {
  10. //在这个方法中会创建bean对象,我们下面看看这个方法
  11. return createBean(beanName, mbd, args);
  12. }
  13. catch (BeansException ex) {
  14. // Explicitly remove instance from singleton cache: It might have been put there
  15. // eagerly by the creation process, to allow for circular reference resolution.
  16. // Also remove any beans that received a temporary reference to the bean.
  17. destroySingleton(beanName);
  18. throw ex;
  19. }
  20. });
  21. //由于我们的sharedInstance对象是,所以在这里最终会调用到FactoryBeanRegistrySupport的doGetObjectFromFactoryBean方法,返回真正的userMapper的bean对象,也就是调用MapperFactoryBean的getObject()方法
  22. beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
  23. }
  24. }

创建bean对象实例最终都会走到AbstractAutowireCapableBeanFactory类的doCreateBean这个方法

  1. //我们来分析下这块代码,不相关的代码我都省略掉
  2. protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args)
  3. throws BeanCreationException {
  4. // Instantiate the bean.
  5. BeanWrapper instanceWrapper = null;
  6. if (mbd.isSingleton()) {
  7. //看this.factoryBeanInstanceCache这个名字就知道是factoryBean实例的缓存,其实我们当前的userMapper创建的实例已经缓存到这里了,不过无所谓,就算之前没有创建缓存到这里,下面12行就会去创建。所以我们这里就认为之前没有创建过,去看看13行的代码具体是如何创建userMapper的factoryBean实例的
  8. instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
  9. }
  10. if (instanceWrapper == null) {
  11. //正常创建userMapper的factoryBean对象是走不到这里的,是在这之前创建的,不过创建方法也是调用的这个
  12. //这个方法的所用就是根据RootBeanDefinition的getBeanClass()找到对应的类,再查找所有构造方法,根据 RootBeanDefinition.getConstructorArgumentValues()构造方法的参数选择合适的构造方法创建类对象,返回BeanWrapperImpl包装的对象
  13. //上面的processBeanDefinitions的方法中对RootBeanDefinition的beanClass和constructorArgumentValues都做过了专门的设置。
  14. //所以我们这里其实是调用的MapperFactoryBean(Class<T> mapperInterface)这个构造方法,里面的参数mapperInterface就是我们的mapper类com.example.springbootmybatis.mapper.UserMapper
  15. instanceWrapper = createBeanInstance(beanName, mbd, args);
  16. }
  17. //这个就是获取创建出来的MapperFactoryBean对象
  18. Object bean = instanceWrapper.getWrappedInstance();
  19. //这个是创建创建出来的对象的类型,也就是org.mybatis.spring.mapper.MapperFactoryBean
  20. Class<?> beanType = instanceWrapper.getWrappedClass();
  21. if (beanType != NullBean.class) {
  22. mbd.resolvedTargetType = beanType;
  23. }
  24. ......
  25. // Initialize the bean instance.
  26. Object exposedObject = bean;
  27. try {
  28. //这里就是属性填充了,我们去这里看看
  29. populateBean(beanName, mbd, instanceWrapper);
  30. //由于我们的MapperFactoryBean继承了SqlSessionDaoSupport,它继承了DaoSupport,它实现了InitializingBean这个接口,所以在这里也会调用到DaoSupport的afterPropertiesSet方法
  31. exposedObject = initializeBean(beanName, exposedObject, mbd);
  32. }
  33. return exposedObject;
  34. }
  1. protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
  2. ......
  3. //这个是获取RootBeanDefinition属性注入模式,我们的是在上面processBeanDefinitions这个方法中设置过的
  4. //definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);就是这句
  5. int resolvedAutowireMode = mbd.getResolvedAutowireMode();
  6. if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
  7. ......
  8. if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
  9. //最终会走到这里,遍历类的所有属性,使用unsatisfiedNonSimpleProperties方法进行过滤,对属性进行注入。我们这里会对sqlSessionFactory,sqlSessionTemplate这两个属性进行注入(这两个注入的属性都是在MybatisAutoConfiguration的类中,通过方法定义的bean对象,上面也说过了)
  10. autowireByType(beanName, mbd, bw, newPvs);
  11. }
  12. pvs = newPvs;
  13. }
  14. ......
  15. }

DaoSupport的afterPropertiesSet方法


  1. public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
  2. // Let abstract subclasses check their configuration.
  3. //这个方法是由子类MapperFactoryBean实现的,我们进去看看
  4. checkDaoConfig();
  5. // Let concrete implementations initialize themselves.
  6. try {
  7. initDao();
  8. }
  9. catch (Exception ex) {
  10. throw new BeanInitializationException("Initialization of DAO failed", ex);
  11. }
  12. }

MapperFactoryBean的checkDaoConfig方法

  1. protected void checkDaoConfig() {
  2. ......
  3. //这个是获取之前注入的SqlSessionTemplate的Configuration
  4. Configuration configuration = getSqlSession().getConfiguration();
  5. if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
  6. try {
  7. //会在这里添加我们的mapper类,这里的this.mapperInterface就是com.example.springbootmybatis.mapper.UserMapper,我们进到这里去看看
  8. configuration.addMapper(this.mapperInterface);
  9. } catch (Exception e) {
  10. logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
  11. throw new IllegalArgumentException(e);
  12. } finally {
  13. ErrorContext.instance().reset();
  14. }
  15. }
  16. }

Configuration的addMapper方法

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

MapperRegistry的addMapper方法

  1. public <T> void addMapper(Class<T> type) {
  2. ......
  3. try {
  4. //在knownMappers中添加一个key=com.example.springbootmybatis.mapper.UserMapper的MapperProxyFactory对象
  5. knownMappers.put(type, new MapperProxyFactory<>(type));
  6. // It's important that the type is added before the parser is run
  7. // otherwise the binding may automatically be attempted by the
  8. // mapper parser. If the type is already known, it won't try.
  9. //在这里就是去查找mapper.xml文件了,同样的如果我们不是通过xml配置的sql,而是用注解的方式实现的,具体的查找都是通过下面的parse方法来实现,我们进去parse方法看看
  10. MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
  11. parser.parse();
  12. loadCompleted = true;
  13. } finally {
  14. if (!loadCompleted) {
  15. knownMappers.remove(type);
  16. }
  17. }
  18. }
  19. }

MapperAnnotationBuilder的parse方法

  1. public void parse() {
  2. String resource = type.toString();
  3. if (!configuration.isResourceLoaded(resource)) {
  4. //这行就是去从类路径加载mapper的xml文件了,具体的路径规则是这样的type.getName().replace('.', '/') + ".xml"。所以如果我们的mapper的xml文件是按照这种规则指定的,就不需要单独通过mybatis.mapper-locations去单独指定mapper.xml的路径了
  5. loadXmlResource();
  6. configuration.addLoadedResource(resource);
  7. assistant.setCurrentNamespace(type.getName());
  8. parseCache();
  9. parseCacheRef();
  10. //下面这块就是去扫描方法上的注解去生成sql配置了,这里就不进去看了
  11. for (Method method : type.getMethods()) {
  12. if (!canHaveStatement(method)) {
  13. continue;
  14. }
  15. if (getAnnotationWrapper(method, false, Select.class, SelectProvider.class).isPresent()
  16. && method.getAnnotation(ResultMap.class) == null) {
  17. parseResultMap(method);
  18. }
  19. try {
  20. parseStatement(method);
  21. } catch (IncompleteElementException e) {
  22. configuration.addIncompleteMethod(new MethodResolver(this, method));
  23. }
  24. }
  25. }
  26. parsePendingMethods();
  27. }

我们再回到doGetBean方法看后面的

  1. protected <T> T doGetBean(
  2. String name, @Nullable Class<T> requiredType, @Nullable Object[] args, boolean typeCheckOnly)
  3. throws BeansException {
  4. ......
  5. //由于我们的sharedInstance对象是,所以在这里最终会调用到FactoryBeanRegistrySupport的doGetObjectFromFactoryBean方法,返回真正的userMapper的bean对象,也就是调用MapperFactoryBean的getObject()方法
  6. beanInstance = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
  7. }
  8. }

MapperFactoryBean的getObject方法

  1. public T getObject() throws Exception {
  2. //这个getSqlSession()就是我们上面属性注入的。org.mybatis.spring.SqlSessionTemplate的对象,
  3. return getSqlSession().getMapper(this.mapperInterface);
  4. }

最终会调用到MapperRegistry的getMapper方法


  1. public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
  2. // 我们在上面addMapper的方法中讲过knownMappers已经添加了key=com.example.springbootmybatis.mapper.UserMapper的MapperProxyFactory对象,
  3. final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
  4. if (mapperProxyFactory == null) {
  5. throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
  6. }
  7. try {
  8. //这里就是调用MapperProxyFactory的newInstance的方法了
  9. return mapperProxyFactory.newInstance(sqlSession);
  10. } catch (Exception e) {
  11. throw new BindingException("Error getting mapper instance. Cause: " + e, e);
  12. }
  13. }

MapperProxyFactory的newInstance方法

  1. public T newInstance(SqlSession sqlSession) {
  2. final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
  3. return newInstance(mapperProxy);
  4. }
  1. protected T newInstance(MapperProxy<T> mapperProxy) {
  2. //最终调用到这里,创建一个MapperProxy的代理对象,这个也就是真正的创建的bean对象
  3. return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  4. }

到这里,springboot整合mybatis到创建出来mapper对象,整个流程就到这里了,后面调用mapper的方法其实也就是通过MapperProxy代理来实现的。具体springboot中调用mybatis的执行的流程将在接下来的一篇给大家讲解。

5.关于@MapperScan

  1. @MapperScan(basePackages = "com.example.springbootmybatis",annotationClass = Mapper.class)

basePackages是指定扫描的包名

annotationClass是查找对应包名下类上有个Mapper注解的接口,如果没有指定这个参数,会将获取到的所有的类都当作Mapper去处理,这时后面执行sql操作就会出错了不知道是不是mybatis的版本问题,之前记得是不需要指定这个参数的

具体的代码位置是在ClassPathMapperScanner类的registerFilters方法中

  1. public void registerFilters() {
  2. boolean acceptAllInterfaces = true;
  3. // if specified, use the given annotation and / or marker interface
  4. //如果设置了annotationClass = Mapper.class就会走到这里,在这里会过滤class上是否有Mapper注解
  5. if (this.annotationClass != null) {
  6. addIncludeFilter(new AnnotationTypeFilter(this.annotationClass));
  7. acceptAllInterfaces = false;
  8. }
  9. // override AssignableTypeFilter to ignore matches on the actual marker interface
  10. if (this.markerInterface != null) {
  11. addIncludeFilter(new AssignableTypeFilter(this.markerInterface) {
  12. @Override
  13. protected boolean matchClassName(String className) {
  14. return false;
  15. }
  16. });
  17. acceptAllInterfaces = false;
  18. }
  19. //没有设置了annotationClass = Mapper.class,就会走这里,直接返回true
  20. if (acceptAllInterfaces) {
  21. // default include filter that accepts all classes
  22. addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
  23. }
  24. // exclude package-info.java
  25. addExcludeFilter((metadataReader, metadataReaderFactory) -> {
  26. String className = metadataReader.getClassMetadata().getClassName();
  27. return className.endsWith("package-info");
  28. });
  29. }

大家看到我的demo中是没有使用@MapperScan这个注解的,那什么时候使用这个注解呢,下面我们从源码来看看

MapperScan注解上面会有@Import(MapperScannerRegistrar.class),@Repeatable(MapperScans.class)这两个注解,MapperScannerRegistrar这个注解实现了ImportBeanDefinitionRegistrar,加载主类的过程中会调用registerBeanDefinitions这个方法,

  1. public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
  2. //这个是获取主类上MapperScan注解的相关属性比如我们的配置是(@MapperScan(basePackages = "com.example.springbootmybatis")),比如basePackages属性等等都是这个注解上的
  3. AnnotationAttributes mapperScanAttrs = AnnotationAttributes
  4. .fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
  5. if (mapperScanAttrs != null) {
  6. //这里就会根据这些属性创建一个MapperScannerConfigurer类的bean的定义,添加到beanFatory中
  7. registerBeanDefinitions(importingClassMetadata, mapperScanAttrs, registry,
  8. generateBaseBeanName(importingClassMetadata, 0));
  9. }
  10. }

我们再看看之前说的MybatisAutoConfiguration.MapperScannerRegistrarNotFoundConfiguration这个内部类

  1. @org.springframework.context.annotation.Configuration
  2. @Import(AutoConfiguredMapperScannerRegistrar.class)
  3. //由于我们上面已经在beanFactory中添加了MapperScannerConfigurer这个类型的bean的定义,所以这个条件就不会成立,上面的import注解中导入AutoConfiguredMapperScannerRegistrar类也就不会执行
  4. @ConditionalOnMissingBean({ MapperFactoryBean.class, MapperScannerConfigurer.class })
  5. public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean {
  6. @Override
  7. public void afterPropertiesSet() {
  8. logger.debug(
  9. "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer.");
  10. }
  11. }

所以@MapperScan的区别主要就在于MapperScannerConfigurer这个bean定义的生成位置不一样


整个的内容比较多,如果大家觉的哪里讲的不清晰或不清楚的,欢迎评论区留言。

springboot整合mybatis源码分析的更多相关文章

  1. tomcat源码--springboot整合tomcat源码分析

    1.测试代码,一个简单的springboot web项目:地址:https://gitee.com/yangxioahui/demo_mybatis.git 一:tomcat的主要架构:1.如果我们下 ...

  2. springboot集成mybatis源码分析(一)

    本篇文章只是简单接受使用,具体源码解析请看后续文章 1.新建springboot项目,并导入mybatis的pom配置 配置数据库驱动和mybatis dependency <dependenc ...

  3. springboot集成mybatis源码分析-mybatis的mapper执行查询时的流程(三)

    例: package com.example.demo.service; import com.example.demo.dao.UserDao; import com.example.demo.do ...

  4. springboot集成mybatis源码分析-启动加载mybatis过程(二)

    1.springboot项目最核心的就是自动加载配置,该功能则依赖的是一个注解@SpringBootApplication中的@EnableAutoConfiguration 2.EnableAuto ...

  5. MyBatis源码分析之环境准备篇

    前言 之前一段时间写了[Spring源码分析]系列的文章,感觉对Spring的原理及使用各方面都掌握了不少,趁热打铁,开始下一个系列的文章[MyBatis源码分析],在[MyBatis源码分析]文章的 ...

  6. 【MyBatis源码分析】环境准备

    前言 之前一段时间写了[Spring源码分析]系列的文章,感觉对Spring的原理及使用各方面都掌握了不少,趁热打铁,开始下一个系列的文章[MyBatis源码分析],在[MyBatis源码分析]文章的 ...

  7. MyBatis 源码分析 - 映射文件解析过程

    1.简介 在上一篇文章中,我详细分析了 MyBatis 配置文件的解析过程.由于上一篇文章的篇幅比较大,加之映射文件解析过程也比较复杂的原因.所以我将映射文件解析过程的分析内容从上一篇文章中抽取出来, ...

  8. MyBatis 源码分析系列文章导读

    1.本文速览 本篇文章是我为接下来的 MyBatis 源码分析系列文章写的一个导读文章.本篇文章从 MyBatis 是什么(what),为什么要使用(why),以及如何使用(how)等三个角度进行了说 ...

  9. mybatis源码分析之06二级缓存

    上一篇整合redis框架作为mybatis的二级缓存, 该篇从源码角度去分析mybatis是如何做到的. 通过上一篇文章知道,整合redis时需要在FemaleMapper.xml中添加如下配置 &l ...

随机推荐

  1. 使用mockjs模拟分页请求

    首先安装mockjs npm install mockjs --save-dev 创建mock.js //mock.js const Mock = require("mockjs" ...

  2. Redis面试连环问,快看看你能走到哪一步

    今天,我不自量力的面试了某大厂的java开发岗位,迎面走来一位风尘仆仆的中年男子,手里拿着屏幕还亮着的mac,他冲着我礼貌的笑了笑,然后说了句"不好意思,让你久等了",然后示意我坐 ...

  3. 数据泵导出报错ORA-31693 ORA-02354 ORA-01466

    1.Oracle数据泵导出schema时有报错: Connected to: Oracle Database 11g Enterprise Edition Release 11.2.0.3.0 - P ...

  4. JAVA设计模式(6:单例模式详解)

    单例模式作为一种创建型模式,在日常开发中用处极广,我们先来看一一段代码: // 构造函数 protected Calendar(TimeZone var1, Locale var2) { this.l ...

  5. 【.NET 与树莓派】LED 数码管驱动模块——TM1638

    LED 数码管,你可以将它看做是 N 个发光二级管的组合,一个灯负责显示一个段,七个段组合一位数字,再加一个小数点,这么一来,一位数码管就有八段.一般,按照顺时针的方向给每个段编号. 上图中的 h 就 ...

  6. theUnforgiven-冲刺第一天

    每天的冲刺博客分为scrum和PM报告两部分 吴邦天 负责项目整体构思以及对任务安排,承担项目具体设计,编码: 唐嘉诚 负责项目前端页面设计,承担整个项目的后端数据库搭建以及编码 周游 项目美化以及细 ...

  7. html中各种标签和属性(最基础的基本都有)

    1.标题标签: h1~h6  ctrl+1~6 2.段落标签: p      ctrl+shift+p 3.换行标签: br     shift+回车 4.水平线:   hr 5.加粗标签: stro ...

  8. webapi发布在iis之后报错Http 403.14 error

    服务器是 Windows Server 2008 R2 Enterprise 网上找了很多说是修改webconfig.试过之后没有效果,另外报错了. 最后才找到是因为webapi发布时选择的应用程序的 ...

  9. 『动善时』JMeter基础 — 53、JMeter集合点功能的使用

    目录 1.集合点介绍 2.同步定时器界面介绍 3.集合点的使用 (1)测试计划内包含的元件 (2)线程组元件内容 (3)HTTP请求组件内容 (4)同步定时器内容 (5)运行脚本查看结果 4.集合点设 ...

  10. AcWing 1141. 局域网

    某个局域网内有n台计算机和m条 双向 网线,计算机的编号是1~n由于搭建局域网时工作人员的疏忽, 现在局域网内的连接形成了回路,我们知道如果局域网形成回路那么数据将不停的在回路内传输,造成网络卡的现象 ...