【微服务专题】SpringBoot自动配置简单源码解析

目录

前言

想要搞懂【自动配置】,需要当前系列前两篇笔记内容打底。不过我上一篇笔记写的不好,没有什么突出的重点,主要也是当时跟着课程看了源码之后,也没发现什么特别的,直到在看自动装配源码的时候,才后知后觉遗漏了一些重要的知识点。但是总体来说不影响这一节课的笔记记录吧。

另外说到【自动配置】,以前我总是掉入一个思维陷阱里面,认为这里说到的配置就是xml配置,这让我在理解【自动配置】的时候总是走了一些弯路,所以这里也是给大家提前提个醒了,这个配置不仅仅是xml配置而已,而是包括任何形式的,对服务接口的配置。

阅读对象

  1. 对自动配置感兴趣的朋友

阅读导航

系列上一篇文章:《【微服务专题】Spring启动过程源码解析

前置知识

什么是自动配置

我们说起SpringBoot,总会不自觉的联想到自动配置约定大于配置等标签,但说实在,我是有一点困惑的,这个【约定和配置】到底是什么意思,或者说,它们具体指的是哪些内容啊?!

我在想,像我这种,只是用过那么一年多SSM/SSH,甚至完全没有经历过的人来说,是很难理解的。虽然我也确实经历过SSM/SSH,但那时候初出茅庐,根本就没在意过这些玩意好吧...

不过话说回来,后面再回来继续做Java的时候,已经是SpringBoot的时代了嘛,所以单从片面印象来说的话:SpringBoot确实方便了很多。最最最直观的就是启动,给我的感觉启动一个web应用跟简单运行了一个main方法一样便捷。而在以前SSM/SSH时代,各种眼花缭乱的xml配置,还有那个令人烦躁的tomcat配置,就已经让我很难受了。

好吧,不懂嘛,于是我就去稍微学习了一下。下面,我就按照我自己的理解,再拼上一些百度的、起码能说服我自己的答案给大家说道说道。另外,也想举一个真正直观的例子给大家说说,【约定和配置】到底使了什么魔法

0.1 基本概念

【约定大于配置】是一种开发思想、设计理念,它强调使用默认约定和规则,减少开发者需要做的【显式】配置。根据这一原则,大部分情况下,开发者只需关注那些【不符合默认约定】的配置,而不需要为每个细节都进行显式配置。。

开天眼给大家说一下:既然结果是减少了需要程序员【显示】配置的工作量,那反过来就证明了,在SpringBoot之前的时代,有一些配置是不得不(必须)需要程序员去配置的!

另外,这里说的【配置】,不仅仅是指xml配置,准确来说是对引入的服务的所有形式的配置,xml只是一种形式而已。我们还可以是txtyml,还有java文件的配置方式。

我认为这个结论很重要!我后面研究案例就是往这方向去看的

综上所述,可以这样简单地理解【约定】和【配置】:

  1. 默认规则
  2. 【显示】配置(不得不设置的配置 / 没办法使用默认规则的配置)。再重申一遍:这里说的【配置】,不仅仅是指xml配置,准确来说是对引入的服务的所有形式的配置,xml只是一种形式而已。我们还可以是txtyml,还有java文件的配置方式。

这个理念,有如下显著的优势:

  1. 提高开发效率: 通过遵循默认约定,开发者可以快速启动项目并进行开发,无需花费大量时间在繁琐的配置上
  2. 减少决策负担: 【约定大于配置】减少了开发者需要做出的决策,使开发过程更加流畅,不需要在每个细节上做出选择
  3. 减少错误: 默认约定可以减少配置错误的机会,因为开发者只需要在特定情况下进行配置,从而降低了出错的可能性

0.2 SpringBoot中的【约定大于配置】

我们都知道,SpringBoot是基于Spring的,严格来说,SpringBoot的出现其实是为了降低Spring的使用门槛。

  1. 使用maven的目录结构。默认有src-main-resources文件夹,存放资源配置文件;src-main-java存放项目java源码;target文件夹存放编译、打包内容(其实,Maven的设计本身就是遵循【约定大于配置】的原则)

这是约定的一种,通过约定这些内容,使得项目结构统一,减少了开发人员学习成本。

试想一下,如果我们在不同的公司,他们的项目结构五花八门,甚至没有src/main/resources,没有src/main/java等目录,你学习成本是不是变高了??博主之前写过2、3年C/C++,那时候接触的项目就是这个鸟样子的,都是自定义资源存放目录,打包目录,源码目录,真的吐血... ... 我转战Java很大一个原因就是奔着Java的规范性去的。当然,我现在相当后悔,当时忍一忍就不会掉进Java的坑里了

  1. 使用application.properties/yml文件设置配置内容

这也是一种约定,拒绝程序员五花八门的配置文件

  1. 默认使用Tomcat容器

接触过SSM吗?SSM项目通常来说,需要额外配置启动一个Tomcat,然后将SSM项目打包部署到Tomcat上

而在SpringBoot中,我们启动项目就跟运行一个普通的main函数一样

  1. 提供了大量的自动配置接口,自动配置类,注解等,帮助程序员配置。严格来说,自动配置接口以及注解等,都是为了自动配置类服务的。SpringBoot提供的大量自动配置类,在里面默认设置了很多值用以配置第三方接口服务,这些默认配置甚至能让开发做到开箱即用,只有一些不得不需要用户设置的内容才需要开发人员自己设置。这就是上面概念所谓的开发者只需要关注那些不符合默认约定的配置的意义。

这是SpringBoot【约定大于配置】的重要实现。我们先稍微解释一下,这些所谓的接口、类、注解是指什么:

  • 自动配置接口:AutoConfigurationImportFilter、AutoConfigurationImportListener等

  • 自动配置类:DispatcherServletAutoConfiguration、HttpEncodingAutoConfiguration等

  • 注解:诸如@AutoConfiguration、@AutoConfigurationPackage等

    上述我只是简单列举了一些内容而已,其实具体的,可以看:org.springframework.boot:spring-boot-autoconfigure这个包。里面就是SpringBoot为大家做自动配置提供的默认配置,和相关接口

0.3 从SpringMVC看【约定大于配置】

说实在,SpringMVC我不是很熟悉,所以我不是很保证自己说的是对的,在网上也没看到非常令人信服的答案。但是关于【约定大于配置】的体现我觉得还是正确的。

有人说spring-boot-starter-web其实就是免配置的SSM。在SSM时代,我们想要开发一个web应用,免不了如下配置:

  1. 新增web.xml,在里面配置前端控制器DispatcherServlet、视图解析器ViewResolver、过滤器等
  2. 新增核心配置文件**-servlet.xml
  3. Tomcat容器的配置
  4. 等等...

其实说需要新增这种那种xml配置是有点狭隘的说法,我前面说过了xml只是其中一种配置形式而已。

准确来说,传统的SSM项目,需要我们开发人员显式、准确地向Spring容器注册DispatcherServletViewResolverSqlSessionFactoryBeanDriverManagerDataSource

如果我们没有【显式】地去声明、注册这些Bean,那我们项目直接启动失败!他会说找不到这个找不到那个。但是,在SpringBoot中,我们只需要在pom中引入spring-boot-starter-web就可以了,这个jar包,会自动向Spring中注册上述关键bean

怎么注册?我们简单看一下关于SpringMVC的自动配置类。

1)WebMvcAutoConfiguration.java:SpringMVC的自动配置入口

在这里我们无需关心上面的诸多注解的具体含义先,具体意义我会在后面的源码解析中介绍。

但是可以预见的是,在某种情况下,spring-boot-stater-web会进入DispatcherServletAutoConfiguration.class里面

2)DispatcherServletAutoConfiguration.java:在这里,有一个@Bean,暂不关心他什么时候会被触发,但至少可以预见的是:在某种情况下,DispatcherServletAutoConfiguration类会向Spring中注册一个DispatcherServlet类型的bean

啊,对的,本质上就是如此而已。SpringBoot的自动配置,实际上就是SpringBoot的源码中预先写好了一些配置类,预先定义好了一些Bean,我们在用SpringBoot时,这些配置类就已经在我们项目的依赖中了,而这些自动配置类或自动配置Bean到底生不生效,就看具体所指定的条件了。

不过还有一点需要说明,那就是SpringBoot可不是简简单单的给你注册了一个默认的bean,它还会给部分【符合约定】的基础参数设置默认的、至少有意义的、比较符合一般应用的初始值来初始化bean。这个就是SpringBoot提供的各种XxxProperties.java。例如:WebMvcProperties

O对了,说到了这个XxxProperties.java文件,其实它也是约定的一种体现。以前SSM我们相当于直接配置了服务接口的一些参数;而在SpringBoot里面,我们直接配置的其实是XxxProperties,然后再由SpringBoot使用XxxProperties配置属性去初始化服务接口参数。而通常这些XxxProperties对应的是application.properties里面的一些配置内容。

相比起直接配置服务接口参数,XxxProperties可读性更强,使用也更简单。

0.4 从Redis看【约定大于配置】

想到SpringMVC可能很多人都不熟悉,所以这边再介绍一下,Redis是怎么实现【约定大于配置】的。其实大体思路跟上面差不多:

  1. 找到有哪些不得不需要程序员配置的类
  2. 找到Redis自动配置类
  3. 看看Redis的相关自动配置XxxProperties文件

如下:(其实1和2基本上都可以在自动配置类里面体现出来)

当然不止这两个类,还有一些类在上面的@Import({ LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class })被声明,这俩是不同的客户端而已,给大家贴部分代码截图,如下:

从上面这两张代码截图图可以看出,SpringBoot默认使用Lettuce作为客户端。只有我们在项目中引入了JedisJedis客户端才会生效。

再然后就是这个RedisProperties文件,是SpringBoot提供给我们的直接配置类:

0.5 小结

OK,关于SpringBoot中的【约定大于配置】就介绍到这里了。那他们是怎么实现的呢?有经验的朋友,或者稍微细心的朋友估计也从上图看到了,【自动配置】主要是靠下图这些类、接口、注解实现的,后面我们就来稍微研究一下,里面的源码实现。(org.springframework.boot:spring-boot-autoconfigure包下)

笔记正文

一、@EnableAutoConfiguration源码解析

说实在,水平有限,解析不了很深层次。先来看看该注解声明:

java 复制代码
/**
 * 启用Spring应用程序上下文的自动配置,尝试猜测和配置您可能需要的bean。
 * 自动配置类通常基于您的类路径和您定义的bean来应用。例如,如果您的类路径中有
 * tomcat-embedded.jar,那么您可能需要一个TomcatServletWebServerFactory
 * (除非您已经定义了自己的ServletWebServerFactory bean)。
 * 当使用@SpringBootApplication时,上下文的自动配置是自动启用的,因此添加这个注释没有额外的效果。
 * 自动配置试图尽可能地智能,并且会随着您定义更多自己的配置而后退。
 * 您始终可以手动排除()任何您不想应用的配置(如果您没有访问权限,请使用excludeName())。
 * 你也可以通过spring.autoconfigure.exclude属性排除它们。
 * 自动配置总是在用户定义bean注册之后应用。
 * 用@EnableAutoConfiguration注释的类包,通常通过@SpringBootApplication,
 * 具有特定的意义,通常被用作"默认"。例如,它将在扫描@Entity类时使用。通常建议将
 * @EnableAutoConfiguration(如果不使用@SpringBootApplication)放在根包中,
 * 以便可以搜索所有子包和类。
 * 自动配置类是常规的Spring @Configuration bean。它们是使用importcandidate和
 * springfactoresloader机制定位的(与这个类相关)。通常自动配置bean是@条件bean(最常使用
 * @ConditionalOnClass和@ConditionalOnMissingBean注释)。
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {

	/**
	 * Environment property that can be used to override when auto-configuration is
	 * enabled.
	 */
	String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

	/**
	 * Exclude specific auto-configuration classes such that they will never be applied.
	 * @return the classes to exclude
	 */
	Class<?>[] exclude() default {};

	/**
	 * Exclude specific auto-configuration class names such that they will never be
	 * applied.
	 * @return the class names to exclude
	 * @since 1.3.0
	 */
	String[] excludeName() default {};

}

我把官方声明注释也一起搬过来了。我稍微解读一下:

  1. @EnableAutoConfiguration 用来启用Spring的自动配置功能
  2. 它核心原理主要在于:推测,推测程序要需要的Bean。比如:我们的项目jar包中依赖了Jetty,那么推测你可能需要Jetty容器;项目依赖了redis,那就推测你需要Redis
  3. 通常自动配置bean会与@Conditional注解一起工作
  4. 自动配置的实现使用了SPI机制

OK,回到正文。@EnableAutoConfiguration中,有两个很明显的引用:

java 复制代码
@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)

所以不难看出来,自动配置的核心实现逻辑就在这两个类里面了。

1)@AutoConfigurationPackage

点进去看,显然核心实现还是在于@Import(AutoConfigurationPackages.Registrar.class)

@Import注解,可以简单的理解为,直接导入一个bean

再点进去看,核心主要是registerBeanDefinitions方法(注册Bean图纸)。这个方法会在哪里调用呢?大概是在Spring容器初始化之后,实例化所有Bean之前。

其实也没啥好说的了,他的作用很简单,就是向Spring容器注册了一个BeanDefinition叫做autoConfigurationPackages,这个BeanDefinition里面有一个重要属性,属性存放的是当前扫描包路径,有啥用呢?大概是为了方便第三方扫描的时候,能够方便获取。比如Mybatis自动配置类扫描的时候就用到了这个BeanDefinition。

2)@Import(AutoConfigurationImportSelector.class)

老规矩,看里面这个AutoConfigurationImportSelector.class。他的定义如下:

java 复制代码
// DeferredImportSelector处理自动配置。
// 如果需要@EnableAutoConfiguration的自定义变体,这个类也可以被子类化。
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
		ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {

这个类的核心在于实现了DeferredImportSelector,他的类继承图如下:

整个方法的入口其实有点难找,需要看了源码才知道,具体在哪里会被调用呢?跟上面一样,【大概是在Spring容器初始化之后,实例化所有Bean之前】。但这并不是篇内容的核心,所以我直接贴图调用处:

可以看到,最终实现还是由红框内的getAutoConfigurationEntry,通过方法翻译函数名为:获取自动配置入口。(从这里开始就有点复杂了,SPI的实现就在里面)

步骤解析:

  1. 获取@SpringBootApplication注解上的排除路径配置
  2. 使用SPI技术,获取所有候选的配置类,这应该是最核心的实现了

这里会去本包,包括所有依赖的jar包下的META-INF/spring.factories下加载EnableAutoConfigurationAutoConfiguration注解的类

  1. 去重
  2. 加载排除路径
  3. 查找需要被去除自动配置类,然后去除
  4. 过滤一遍,判断所有自动配置类上面的条件注解是否生效。对于不生效的自动配置类也没必要再去走后面的逻辑了
  5. 发布自动配置类导入事件

3)源码入口

对上面提到的两个核心类被Spring容器调用时机感兴趣的朋友,可以看看ConfigurationClassPostProcessor类。对Spring源码不熟悉或者生命周期不熟悉的朋友估计有点压力。我也有点压力...

二、SpringBoot常用条件注解源码解析

SpringBoot中的常用条件注解有:

  • ConditionalOnBean:是否存在某个某类或某个名字的Bean
  • ConditionalOnMissingBean:是否缺失某个某类或某个名字的Bean
  • ConditionalOnSingleCandidate:是否符合指定类型的Bean只有一个
  • ConditionalOnClass:是否存在某个类
  • ConditionalOnMissingClass:是否缺失某个类
  • ConditionalOnExpression:指定的表达式返回的是true还是false
  • ConditionalOnJava:判断Java版本
  • ConditionalOnWebApplication:当前应用是不是一个Web应用
  • ConditionalOnNotWebApplication:当前应用不是一个Web应用
  • ConditionalOnProperty:Environment中是否存在某个属性
  • 当然我们也可以利用@Conditional来自定义条件注解

条件注解是可以写在类上和方法上的,如果某个条件注解写在了自动配置类上,那该自动配置类会不会生效就要看当前条件能不能符合;或者条件注解写在某个@Bean修饰的方法上,那这个Bean生不生效就看当前条件符不符合。

具体原理是:

  1. Spring在解析某个自动配置类时,会先检查该自动配置类上是否有条件注解,如果有,则进一步判断该条件注解所指定的条件当前能不能满足,如果满足了则继续解析该配置类,如果不满足则不进行解析了,也就是配置类所定义的Bean都得不到解析,也就是相当于没有这些Bean了。
  2. 同理,Spring在解析某个@Bean的方法时,也会先判断方法上是否有条件注解,然后进行解析,如果不满足条件,则该Bean不会生效

2.1 自定义条件注解

SpringBoot中众多的条件注解,都是基于Spring中的@Conditional来实现的,所以这里我们演示一下,如何使用@Conditional自定义一个我们的条件注解。

先来看下@Conditional注解的定义:

java 复制代码
/**
 * 
 * 指示组件只有在所有指定条件匹配时才有资格注册。
 * 条件是可以在注册bean定义之前以编程方式确定的任何状态(有关详细信息,请参阅条件)。
 * @Conditional注释可以以以下任何一种方式使用:
 * 在任何直接或间接用@Component注释的类(包括@Configuration类)上作为类型级注释
 * 作为元注释,用于组合自定义构造型注释
 * 作为任何@Bean方法上的方法级注释
 * 如果@Configuration类被标记为@Conditional,那么与该类关联的所有@Bean方法、@Import注释和@ComponentScan注释
 * 将受这些条件的约束。
 */
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Conditional {

	/**
	 * All {@link Condition} classes that must {@linkplain Condition#matches match}
	 * in order for the component to be registered.
	 */
	Class<? extends Condition>[] value();

}

根据定义我们在用@Conditional注解时,需要指定一个或多个Condition的实现类,所以我们先来提供一个实现类:

java 复制代码
public class ShenCondition implements Condition {
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        Map<String, Object> component = metadata.getAnnotationAttributes("org.springframework.stereotype.Component");
        System.out.println("下面开始打印condition里面的内容");
        System.out.println(component);
        Object value = component.get("value");
        return value.toString().equals("shen");
    }
}

上面这个实现类很简单,就是获取被@Conditional(ShenCondition.class)注解的类上,获取它@Component上的注解值,如果值是shen那就返回true,否则bean注册不成功。接下来就是自定义一个我们自己的注解:

java 复制代码
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(ShenCondition.class)
public @interface ShenConditional {
}

测试用例如下:

java 复制代码
@ShenConditional
@Component("shen")
public class ShenWorker {

    public String sayHello() {
        return "hello";
    }
}

@SpringBootApplication(scanBasePackages = "org.example.springboottest")
public class ShenMyApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(ShenMyApplication.class);
        ShenWorker shen = (ShenWorker) context.getBean(ShenWorker.class);

        System.out.println("------------------------");
        System.out.println(shen.sayHello());
    }
}

此时运行,控制台输出:

把上面的ShenWorker @Component值改成其他,如@Component("shenWorker"),运行则输出如下:

报错了,NoSuchBeanDefinitionException

我想到了这里大家应该多少知道@Conditional注解的作用了,接下来我们挑其中一两个SpringBoot声明的条件注解,看一下源码,剖析一下它是怎么实现的。

2.2 @ConditionalOnClass原理解析

@ConditionalOnClass注解的作用是:校验是否存在某个类。

Spring这种优秀源码,通常注释都是很完善的,我们先来看看它们是怎么定义的:

java 复制代码
/**
 * @Conditional,当且仅当指定的类在类路径上时,才匹配成功
 *
 * value()可以在@Configuration类上安全地指定,因为在加载类之前,
 * 会使用ASM解析注释元数据。当放置在@Bean方法上时,需要特别注意,
 * 考虑在单独的Configuration类中隔离条件,特别是如果方法的返回类型与条件的目标匹配
 */
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Conditional(OnClassCondition.class)
public @interface ConditionalOnClass {

	/**
	 * 必须设置的class信息。
	 * 因为这个注释是通过加载类字节码来解析的(classLoader.loadClass),
	 * 当且仅当这个注解直接作用在bean上,而不是当这个注解被用作组合的元注释时,
	 * 在这里指定最终可能不在classpath上的类是安全的(不会报错)。
	 * 想要将此注解用作元注释,那么就只使用name属性
	 */
	Class<?>[] value() default {};

	/**
	 * 必须设置的类全限定名
	 */
	String[] name() default {};
}

根据我们自定义注解经验,该注解的实现肯定是在@Conditional(OnClassCondition.class)OnClassCondition.class中。

顺便在这里介绍一下怎么用idea追踪代码吧。

1)寻找关键实现类

首先我们知道,OnClassCondition.class实现自Condition接口,主要是实现里面的matches()方法。虽然咱还不知道它是在哪里被Spring调用的,但起码他肯定是会被调用的对吧,所以,我们先跳到OnClassCondition.class文件,打开它的继承图,如下所示:

可以发现它继承或者实现了各种各样的接口,其中一个是Condition。那OK

我们在回到OnClassCondition.class文件,alft+7打开代码结构图,然后在右上角点击查看继承过来的属性、方法,如下所示:

如上图所示,绿色框内灰色的即为继承过来,但是没有重写的方法。可以看到Condition接口的matches方法,就没有被重写过,那说明父类中肯定有默认的实现类,点击一下,会跳到SpringBootCondition类中,它的默认实现方式如下:

可以看到,最重要的是红框内的2行代码。点击getMatchOutcome发现这是一个抽象方法,所以,又回到OnClassCondition.class文件中,查找一下看看是否实现或者重写了该方法。最后发现如下:

重写了,再结合之前的代码可以判断出来,这里就是OnClassCondition类的逻辑实现核心。上面的代码其实也不难,基本上通过方法名就知道啥意思了。主要原理就是:使用反射工具,根据全限定名去JVM加载类,如果能加载成功,那就证明class存在;反之则不存在。

特别注意:@ConditionalOnClass和@ConditionalOnMissingClass注解的核心实现都是这个类的这个方法

2.3 ConditionalOnBean原理解析

点击查看@ConditionalOnBean注解,会发现逻辑是在OnBeanCondition.class中,然后按照上面的思路也会发现,它跟前面的注解实现套路基本一致,逻辑也是在getMatchOutcome中,如下:

这个源码也很简单,通过阅读注解接口的各种字段信息就能推断出来具体过程。总的来说就是根据bean是否存在于Spring容器中来返回结果。

特别注意:@ConditionalOnBean和@ConditionalOnSingleCandidate,@ConditionalOnMissingBean注解的核心实现都是这个类的这个方法

三、SpringBoot之Mybatis自动配置源码解析

在我们的项目pom中,导入如下:

bash 复制代码
 <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
  </dependency>

我们就会看到我们项目依赖中出现以groupId+artifactId命名的jar包了,如下:

看到这里需要说明的是,SpringBoot不总是提供所有你知道的服务的自动配置类,有时候需要厂商自己去实现。就好比这个Mybatis,就是自己实现了自动配置类,让用户尽量做到开箱即用。

对的,通常第三方在写自动配置的时候,命名风格皆是如此。而在里面的MybatisAutoConfiguration即为Mybatis的自动配置类。通过源码我们可以发现,在里面注册了多个bean,当然是有条件的:

上面的意思是,在Spring中如果没有SqlSessionFactory这个类型的bean,则注册当前bean;

另一个注册的bean如下:

最核心的一个还是,在自动配置类下面有一个这玩意:

这个 Bean的作用是,如果没有其他显式的注册Mapper扫描映射器,那就注册一个默认的Mapper扫描映射器,扫描路径跟SpringBoot扫描路径一致。说到这里,是不是有朋友对Mybatis如何实现把接口转换成bean这个操作感兴趣呢?大家伙可以看看按照下面这个轨迹从1-9-AB,自上而下去看看,但是需要一些Spring功底。源码入口:MybatisAutoConfiguration.AutoConfiguredMapperScannerRegistrar#registerBeanDefinitions(我有一点功底,但不算很扎实,只能勉强看个大概)

总的来说,它的原理大致如下:(注意,这里是指默认自动配置类情况下,如果使用了@MapperScan注解,情况略有不同)

  1. Mybatis自动配置类会向Spring容器注册一个扫描器Bean定义,根据Bean定义,初始化扫描器属性。默认扫描路径是当前SpringBoot的扫描路径
  2. Mybatis会修改扫描到Mapper接口的BeanDefinition(生成Bean的图纸),关键代码在:org.mybatis.spring.mapper.ClassPathMapperScanner#processBeanDefinitions
  3. Mybatis会修改注册成为Bean的条件,原本接口类是不能够被注册成为Bean的,但是通过修改条件让其通过了判断
  4. 具体怎么生成的Bean,主要用到了Spring提供的FactoryBean,再然后是用到了Jdk的动态代理生成代理类。感兴趣的要自己去看看Spring的一些核心概念了

四、SpringBoot之AOP自动配置类

老套路了,AOP毕竟是Spring本家产品,所以他的自动配置类肯定在本包下org.springframework.boot:spring-boot-autoconfigure

源码如下:

java 复制代码
public class AopAutoConfiguration {

    @Configuration(proxyBeanMethods = false)
    @ConditionalOnClass(Advice.class)
    static class AspectJAutoProxyingConfiguration {

        @Configuration(proxyBeanMethods = false)
        // 开启AOP的注解,使用JDK动态代理
        @EnableAspectJAutoProxy(proxyTargetClass = false)
        // spring.aop.proxy-target-class=false时才生效
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "false")
        static class JdkDynamicAutoProxyConfiguration {

        }

        
        @Configuration(proxyBeanMethods = false)
        // 开启AOP的注解,使用CGLIB动态代理
        @EnableAspectJAutoProxy(proxyTargetClass = true)
        // spring.aop.proxy-target-class=true时生效,或者没有配置spring.aop.proxy-target-class时默认也生效
        @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
                matchIfMissing = true)
        static class CglibAutoProxyConfiguration {

        }

    }

    @Configuration(proxyBeanMethods = false)
    // 没有aspectj的依赖,但是又要使用cglib动态代理
    @ConditionalOnMissingClass("org.aspectj.weaver.Advice")
    @ConditionalOnProperty(prefix = "spring.aop", name = "proxy-target-class", havingValue = "true",
            matchIfMissing = true)
    static class ClassProxyingConfiguration {

        @Bean
        static BeanFactoryPostProcessor forceAutoProxyCreatorToUseClassProxying() {
            return (beanFactory) -> {
                if (beanFactory instanceof BeanDefinitionRegistry) {
                    BeanDefinitionRegistry registry = (BeanDefinitionRegistry) beanFactory;
                    // 注册InfrastructureAdvisorAutoProxyCreator从而开启Spring AOP
                    // @EnableAspectJAutoProxy会注册AnnotationAwareAspectJAutoProxyCreator,也会开启Spring AOP但是同时有用解析AspectJ注解的功能
                    AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
                    AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                }
            };
        }
    }
}

学习总结

  1. 终于理解了【约定大于配置】是什么意思
  2. 跟踪学习了Mybatis是如何实现自动配置,包括怎么让mapper接口变成一个可实例化的bean的

感谢

感谢阿里云社区的文章《探索Spring Boot中的原则:约定大于配置

感谢百度大佬【作者:编程师兄】的文章《SpringBoot约定大于配置到底是什么意思?

相关推荐
fallwind_of_july2 小时前
java项目分享-分布式电商项目附软件链接
java·redis·分布式·mongodb·elasticsearch·微服务·rabbitmq
武昌库里写JAVA2 小时前
Golang的消息中间件选型
java·开发语言·spring boot·学习·课程设计
小小鸭程序员3 小时前
Spring Boot项目连接MySQL数据库及CRUD操作示例
java·spring boot·python·mysql·spring
字节源流4 小时前
【spring Cloud Netflix】OpenFeign组件
java·spring boot·后端
AntBlack4 小时前
都说 SpringBoot 启动慢 ,你知道慢在哪里吗?
java·spring boot·面试
爱的叹息6 小时前
Spring boot 中QPS(Queries Per Second)与 TPS(Transactions Per Second)详细对比
java·spring boot·后端
小小鸭程序员6 小时前
Spring Boot整合MyBatis-Plus实现CRUD操作教程
java·spring boot·python·mysql·spring
菲兹园长7 小时前
配置文件、Spring日志
java·spring boot·spring
爱的叹息8 小时前
Spring Boot 集成 Redis中@Cacheable 和 @CachePut 的详细对比,涵盖功能、执行流程、适用场景、参数配置及代码示例
spring boot·redis·后端
Mr.wangh8 小时前
Spring Boot 打印日志
java·数据库·spring boot