SpringBoot自动配置和注入的使用和原理

学习资源:尚硅谷-雷神-SpringBoot2 老师讲得贼棒!

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后所有的依赖都自动导入

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条件注入组件

官方文档 condition-annotations

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);给容器中批量导入一些组件

  2. 方法源码如下:

    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
            );
        }
    }
    
  3. getCandidateConfigurations方法源码如下:

    protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
        // 利用工厂加载方法得到所有的组件
        List<String> configurations = 
            SpringFactoriesLoader.loadFactoryNames(
                this.getSpringFactoriesLoaderFactoryClass(),
                this.getBeanClassLoader()
        );
        return configurations;
    }
    
  4. 深入 SpringFactoriesLoader.loadFactoryNames 方法,发现使用了loadSpringFactories方法,这个方法里有一行代码如下:

    Enumeration<URL> urls = 
        classLoader != null ? 
        	classLoader.getResources("META-INF/spring.factories") : 
    		ClassLoader.getSystemResources("META-INF/spring.factories");
    
  5. 我们看得出来,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组件

SpringBoot-自动注入SpringMVC的DispatcherServlet

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值