SpringBoot自动配置和注入

  • SpringBoot自动配置和注入
  • 1. 自动配置原理
  • 1.1 依赖管理
  • 1.1.1 版本仲裁
  • 1.1.2 starter场景启动器
  • 1.1.3 变更版本
  • 1.2 自动配置
  • 2. Bean组件装配
  • 2.1 @Configuration配置类
  • 2.2 @Bean注解
  • 2.3 单例、组建依赖
  • 2.4 @Import导入组件
  • 2.5 @Conditional条件注入组件
  • 2.6 Spring原生配置文件导入
  • 3. 自动配置绑定
  • 3.1 配置绑定方式
  • 3.1.1 原始方式:
  • 3.1.2 @ConfigurationProperties导入配置
  • 3.1.3 @EnableConfigurationProperties导入配置
  • 3.2 自动注入原理
  • 3.2.1 @EnableAutoConfiguration自动注入原理
  • 3.3 按需配置和注入原理
  • 3.4 应用


SpringBoot自动配置和注入

1. 自动配置原理

1.1 依赖管理

1.1.1 版本仲裁

依赖管理通过父项目实现

项目依赖了spring-boot-starter-parent这个父项目,而它还有一个更高级的父项目spring-boot-dependencies,在这个项目的pom文件里,声明了很多依赖的版本号。几乎声明了所有开发中常用的jar包,无需关注版本



1.1.2 starter场景启动器

Starters are a set of convenient dependency descriptors that you can include.

starter是SpringBoot中一种开发环境依赖的集合,导入starter后所有的依赖都自动导入



Spring官方的starter:spring-boot-starter-*(*将用于描述某种开发场景)

第三方starter:*-spring-boot-starter-*

所有场景启动器,最底层的依赖就是spring-boot-starter,这是SpringBoot自动配置的核心依赖



1.1.3 变更版本

如果需要变更自动导入的依赖版本,可以指定同名的properties配置

<properties>
	<mysql.version>5.1.43</mysql.version>
</properties>

这是基于maven的就近优先原则



1.2 自动配置

会自动配置:

  • tomcat
  • SpringMVC组件以及Web常见功能
  • DispatcherServlet
  • characterEncodingFilter
  • InternalResourceViewResolver文件上传解析器
  • 默认包结构
  • 提供了默认的包扫描规则,主程序MainApplication所在的包及其下面的所有子包里面的组件都会被扫描进来
  • MainApplication不能直接放在java包下面,因为写在java包下的Application类,是不从属于任何一个包的,因而启动类没有包
  • 如果你想要修改默认包的扫描位置:
  • 可以给MainApplication中的@SpringBootApllication添加scanBasePackages属性
package com.atguigu.boot;

// 扫描com包及其下的所有包,和MainApplication所在的包不同
// 默认是scanBasePackages = "com"
@SpringBootApllication(scanBasePackages = "com.atguigu.boot")
public class MainApplication { ... }
  • 或者通过@CompoonentScan指定扫描路径(和SpringMVC差不多)
@SpringBootApplication
// 等同于
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan("com.atguigu.boot")
  • 各种配置拥有默认值
  • 默认配置最终都是映射到某个类上,如:MultipartProperties
  • 配置文件的值最终会绑定每个类上,这个类会在容器中创建对象
  • 需要修改默认值:通过application.properties配置文件
  • 按需加载所有自动配置项
  • 非常多的starter
  • 需要先引入了指定场景,这个场景的自动配置才会开启
  • SpringBoot所有的自动配置功能都在 spring-boot-autoconfigure 包里面


2. Bean组件装配

2.1 @Configuration配置类

可以将一个类声明为配置文件类,相当于Spring的配置文件

@Configuration() // 诉SpringBoot这是一个配置类 ==> 配置文件
public class MyConfig {
	// 容器中添加组件。以方法名作为组件的id。返回类型就是组件类型。
    // 返回的值,就是组件在容器中的实例
    @Bean 
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        return zhangsan;
    }

    @Bean("tom22")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}

可以在MainApplication中使用

@SpringBootApplication
public class MainApplication {
    public static void main(String[] args) {
		//1、返回我们IOC容器
		ConfigurableApplicationContext run = 
            SpringApplication.run(MainApplication.class, args);

		//2、查看容器里面的组件
		String[] names = run.getBeanDefinitionNames();
		for (String name : names) {
		    System.out.println(name);
		}
		// 获取的是单例对象
		Pet tom1 = run.getBean("tom22", Pet.class);
    }
}



2.2 @Bean注解

添加了@Bean注解的方法,返回值的类型会被视为组件,返回的值就是组件在容器中的实例,这等价于在xml中配置

可以给@Bean添加value属性值,会以这个value作为组件id,如上方的@Bean("tom22")

对类使用@Component、@Controller、@Service、@Repository注解也是可以的配置组件的



被@Bean修饰的方法,即使有方法参数,Spring也会优先从容器中找对应类型的组件并自动传入方法,不一定需要传参

源码示例:SpringBoot默认配置的文件上传解析器MultipartResolver组件的名字就是multipartResolver,但如果一些已有项目的命名不是这个multipartResolver,那么当调用multipartResolver获取组件时,它会执行下方的方法获取组件实例。这个方法需要传入一个对象,但实际上你调用时不用传参,Spring会自动地从容器中寻找对应类型的组件传入

@Bean
// 容器中有这个类型组件
@ConditionalOnBean(MultipartResolver.class)  
// 容器中没有这个名字 multipartResolver 的组件
@ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME) 
public MultipartResolver multipartResolver(MultipartResolver resolver) {
	// 给@Bean标注的方法传入了对象参数,这个参数的值就会从容器中找。
	// SpringMVC multipartResolver。防止有些用户配置的文件上传解析器不符合规范
	// Detect if the user has created a MultipartResolver but named it incorrectly
	return resolver;
}



2.3 单例、组建依赖

直接是用@Bean配置的对象默认是单例

当你是用ConfigurableApplicationContext的getBean()方法获取Bean组件时,获取的对象都是单例的

但是,配置类本身也是一个Bean组件,也就是说你可以获得MyConfig的实例,并调用其创建Bean的方法:

MyConfig bean = run.getBean(MyConfig.class);
// 通过MyConfig获取Bean
User user = bean.user01();

这样子获取到的对象就不是单例的了!因为你没有通过ConfigurableApplicationContext获取



不过,你可以使用修改proxyBeanMethods属性,使用@Configuration(proxyBeanMethods = true),使用代理对象调用方法。由于配置类本身也是一个Bean组件,所以你在获取这个配置类时,返回的其实是一个被代理过的对象。而这个代理对象增强的功能其实就是单例。所以,使用这个注解后,即使你使用MyConfig的方法获取Bean,也是单例的对象

  • Full模式(proxyBeanMethods = true)
  • 保证每个@Bean方法被调用多少次返回的组件都是单例
  • 可以处理组件依赖关系
  • 每一次获取Bean,都需要检查是否已有单例对象,启动加载效率会很低
  • Lite模式(proxyBeanMethods = false)
每个@Bean方法被调用多少次返回的组件都是新创建的
启动加载速度更快


应用场景:直接在MyConfig中调用方法(组件依赖):

public class MyConfig {
    @Bean 
    public User user01(){
        User zhangsan = new User("zhangsan", 18);
        // user组件依赖了Pet组件
        zhangsan.setPet(tomcatPet());
        return zhangsan;
    }

    @Bean("tom")
    public Pet tomcatPet(){
        return new Pet("tomcat");
    }
}



2.4 @Import导入组件

使用**@Import**导入组件:

@Import({User.class, DBHelper.class})
@Configuraton(proxyBeanMethods = false)
public class MyConfig {}

Spring会自动根据传入的class参数,通过无参构造创建组件



2.5 @Conditional条件注入组件

org.springframework.boot.autoconfigure.condition

注解

创建bean的时机

@ConditionalOnBean

存在某个bean时

@ConditionalOnMissingBean

不存在某个bean时,常见于源码。

当用于配置了某个Bean后,由于这个注解,Spring源码中的某些配置就失效了

@ConditionalOnClass

存在某个类时

@ConditionalOnMissingClass

不存在某个类时

@ConditionalOnProperty

当配置文件配置了某些属性时

@ConditionalOnProperty(prefix = “syj”, name = “algorithm”, havingValue = “token”)

当存在配置文件中以syj为前缀的属性,属性名称为algorithm,然后它的值为token时创建

@ConditionalOnProperty(prefix = “syj”, name = “algorithm”, havingValue = “counter”, matchIfMissing = true)

如果所有的都不满足的话就默认实现,不管这个属性syj.algorithm是不是等于counter

@ConditionalOnJava

如果是Java应用

@ConditionalOnWebApplication

如果是Web应用

@ConditionalOnNotWebApplication

如果不是Web应用

@ConditionalOnSingleCandidate

如果只有一个实例,或者仅有一个主实例时


2.6 Spring原生配置文件导入

使用@ImportResource(“classpath:spring/bean.xml”)导入Spring原生的xml配置文件



3. 自动配置绑定

SpringBoot可以自动将配置文件中给定的值绑定到类中

3.1 配置绑定方式

3.1.1 原始方式:

使用Java原生代码读取到properties文件中的内容,并且把它封装到JavaBean中:

public class getProperties {
    public static void main(String[] args) throws FileNotFoundException, IOException {
        Properties pps = new Properties();
        pps.load(new FileInputStream("a.properties"));
        Enumeration enum1 = pps.propertyNames();//得到配置文件的名字
        while(enum1.hasMoreElements()) {
            String strKey = (String) enum1.nextElement();
            String strValue = pps.getProperty(strKey);
            System.out.println(strKey + "=" + strValue);
            //封装到JavaBean。
        }
    }
}



3.1.2 @ConfigurationProperties导入配置

只有在容器中的组件才能使用SpringBoot提供的注解功能

使用@ConfigurationProperties(prefix = "xxx")注解可以配置properties配置文件里前缀为xxx.的参数



3.1.3 @EnableConfigurationProperties导入配置

如果你需要的类不在容器中(比如第三方的类),但是你也想能够自动导入配置,可以在配置类中使用注解:

@Configuration
@EnableConfigurationProperties(Car.class)
public class MyConfig {}

作用:

  1. 为Car这个类开启配置绑定功能
  2. 把Car这个类加入到容器中,成为组件


3.2 自动注入原理

看一看@SpringBootApplication注解的源码:

@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { 
    @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
    @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) 
})
public @interface SpringBootApplication{}

其中涉及了三个注解:

  1. @SpringBootConfiguration:底层是@Configuration,代表当前是一个配置类,而且是朱配置类
  2. @ComponentScan:指定扫描哪些包
  3. @EnableAutoConfiguration:自动注入一系列的组件,包括:
  1. SpringBoot项目需要的组件,按需注入
  2. 你的项目的MainApplication所在包下的所有组件。


3.2.1 @EnableAutoConfiguration自动注入原理

包含了另外两个注解,该注解源码如下:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {}



第一个注解@AutoConfigurationPackage:可以自动配置包,同时它指定了默认的包规则。原理如下:

@Import(AutoConfigurationPackages.Registrar.class)  //给容器中导入一个组件
public @interface AutoConfigurationPackage {}

利用Registrar给容器中导入一系列组件。将 MainApplication 所在包下的所有组件导入进来。

AutoConfigurationPackages.java源码:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
	
    /*
    AnnotationMetadata:提供注解的源信息,被注解的类的信息。
    此处我们把注解借助@SpringBootApplication将注解标注在了MainApplication上
    所以这里可以获取到MainApplication所在的包的信息
    */
    public void registerBeanDefinitions(AnnotationMetadata metadata,
                                        BeanDefinitionRegistry registry) {
        AutoConfigurationPackages.register(
            registry, 
            // 通过.PackageImports(metadata)获取源信息构建对象
            // 通过.getPackageNames()获取被注解的类的包名
            // 借此,它可以通过包名,将这个包下的所有组件注册进来
            (String[])(new AutoConfigurationPackages.PackageImports(metadata)).
            getPackageNames().toArray(new String[0])
        );
    }
}



第二个注解@Import(AutoConfigurationImportSelector.class)

  1. 在AutoConfigurationImportSelector类中,有一个方法:
public String[] selectImports(AnnotationMetadata annotationMetadata) {
    if (!this.isEnabled(annotationMetadata)) {
        return NO_IMPORTS;
    } else {
        // 注意这里:
        AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry
            = this.getAutoConfigurationEntry(annotationMetadata);
        return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
    }
}

利用getAutoConfigurationEntry(annotationMetadata);给容器中批量导入一些组件

  1. 方法源码如下:
protected AutoConfigurationImportSelector.AutoConfigurationEntry 
    getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    
    if (!this.isEnabled(annotationMetadata)) {
        return EMPTY_ENTRY;
    } else {
        AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
        // 获取到所有需要导入到容器中的配置类
        List<String> configurations = 
            this.getCandidateConfigurations(annotationMetadata, attributes);
        // 过滤筛选没必要的配置类,然后返回
        return new AutoConfigurationImportSelector.AutoConfigurationEntry(
            configurations, exclusions
        );
    }
}
  1. getCandidateConfigurations方法源码如下:
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
    // 利用工厂加载方法得到所有的组件
    List<String> configurations = 
        SpringFactoriesLoader.loadFactoryNames(
            this.getSpringFactoriesLoaderFactoryClass(),
            this.getBeanClassLoader()
    );
    return configurations;
}
  1. 深入 SpringFactoriesLoader.loadFactoryNames 方法,发现使用了loadSpringFactories方法,这个方法里有一行代码如下:
Enumeration<URL> urls = 
    classLoader != null ? 
    	classLoader.getResources("META-INF/spring.factories") : 
		ClassLoader.getSystemResources("META-INF/spring.factories");
  1. 我们看得出来,SpringBoot默认扫描我们当前系统里面所有META-INF/spring.factories位置的文件
    spring-boot-autoconfigure-2.3.4.RELEASE.jar包里面也有META-INF/spring.factories ,这里面写死了spring-boot一启动就要给容器中加载的所有的127个配置类

综上,第二个注解@Import(AutoConfigurationImportSelector.class)的作用就是在系统启动时注入需要的Bean,而且这些Bean是默认在配置文件里配置好的。

虽然我们127个场景的所有自动配置启动的时候默认全部加载。但xxxxAutoConfiguration 借助@Conditional注解按照条件装配规则,最终会按需配置



3.3 按需配置和注入原理

按需注入是通过@Conditional注解实现的,其中包含了@ConditionalOnMissingBean,指的是当用户没有配置这个Bean时,就采用默认配置;当用户已经手动配置时,由于这个注解,默认配置将不生效

同时,许多Bean的默认注入,其参数都是来自于properties配置文件的,通过@EnableConfigurationProperties(XxxxPropreties.class)获取,因此你自己指定properties配置文件即可修改默认配置的效果。

综上,有两种方法自定义组件配置:

  1. 使用@Bean指定一个新的组件配置类
  2. 了解到组件配置对应的配置文件的属性,并在application.properties中修改配置属性的值


3.4 应用

在spring-boot=autoconfigure-2.x.x.RELEASE.jar的org.springframeword.boot.autoconfigure.web.servlet.DispatcherServletAutoConfigutation中,自动配置了SpringMVC的DispatcherServlet组件

spring动态注册bean xml_springmvc