Spring Boot究竟是如何进行自动配置的!

我们都知道 SpringBoot 是要比原始的 SpringMVC 这些好用的,毕竟如果经历过最早的 SSM 模式的开发的话,一定对那些大批量的繁琐的配置文件印象颇深,因为之前使用 SSM 框架来进行开发的时候,那配置文件多的让人心态都容易崩溃,所以就有了这个 SpringBoot 来简化这些配置项,于是面试官就开始了对 SpringBoot 的各种面试题的问法,而比较经典的,就是 SpringBoot 是如何实现自动配置的。

配置属性

在通常需要我们在property中配置信息时,通常使用@ConfigurationProperties(pefix="前缀")注解的方式从配置文件中获取配置,如下:

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.boot.context.properties.ConfigurationProperties;

@RestController
@ConfigurationProperties(prefix = "test")
//@Component //如果这里添加了注解那么在自动配置类的时候就不用添加@enableConfigurationProperties(HelloProperties.class)注解.
public class Demo {

    private String msg="default";//现在我们在配置文件写hello.msg=world,因为简单就不再展示;如果那么默认为default.

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
 }
 
 
 @RequestMapping("/msg")
 public Object index(){
  return this.msg;
 }

}

application.yml中配置信息

test:
  msg: bamboo

访问url获取配置信息返回的值

http://localhost:8080/msg

如果把application.yml中的配置信息注释掉则默认使用default值,否则使用配置信息中的值,以上便是普通配置方式

SpringBootApplication

SpringBoot 一直是约定优于配置。具体的体现就是在下面的几点

  • maven 项目的配置文件存放在 resources 资源目录下。

  • maven 项目默认编译后的文件放于 target 目录。

  • maven 项目默认打包成 jar 格式。

  • 配置文件默认为 application.yml 或者 application.yaml 或者 application.properties。

  • 默认通过配置文件 spring.profiles.active 来激活配置。

SpringBootApplication 实际上是一个复合注解,其实了不起认为的复合注解就是把一些其他的注解组合起来组装成一个壳子,而外面套的这个壳子,就是复合注解。

其中,@SpringBootConfiguration ,@EnableAutoConfiguration,@ComponentScan三个注解尤为重要。今天我们就来看看这三个注解是干嘛用的。

SpringBootConfiguration

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {

 @AliasFor(annotation = Configuration.class)
 boolean proxyBeanMethods() default true;

}

它实际上就是一个 @Configuration 注解,这个注解大家应该很熟悉了,加上这个注解就是为了让当前类作为一个配置类交由 Spring 的 IOC 容器进行管理

ComponentScan

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {


 @AliasFor("basePackages")
 String[] value() default {};


 @AliasFor("value")
 String[] basePackages() default {};


 Class<?>[] basePackageClasses() default {};


 Class<? extends BeanNameGenerator> nameGenerator() default BeanNameGenerator.class;


 Class<? extends ScopeMetadataResolver> scopeResolver() default AnnotationScopeMetadataResolver.class;


 ScopedProxyMode scopedProxy() default ScopedProxyMode.DEFAULT;


 String resourcePattern() default ClassPathScanningCandidateComponentProvider.DEFAULT_RESOURCE_PATTERN;


 boolean useDefaultFilters() default true;


 Filter[] includeFilters() default {};


 Filter[] excludeFilters() default {};


 boolean lazyInit() default false;



 @Retention(RetentionPolicy.RUNTIME)
 @Target({})
 @interface Filter {

 
  FilterType type() default FilterType.ANNOTATION;


  @AliasFor("classes")
  Class<?>[] value() default {};

  @AliasFor("value")
  Class<?>[] classes() default {};

 
  String[] pattern() default {};

 }

}

用于定义 Spring 的扫描路径,等价于在 xml 文件中配置 context:component-scan,假如不配置扫描路径,那么 Spring 就会默认扫描当前类所在的包及其子包中的所有标注了 @Component,@Service,@Controller 等注解的类

EnableAutoConfiguration

这个注解才是实现自动装配的关键,而这个注解,也是一个复合注解,

点进去之后发现,它是一个由 @AutoConfigurationPackage 和 @Import 注解组成的复合注解。

主要的就是这个 import,我们来看看他的源码

@Import 注解其实就是为了去导入一个类AutoConfigurationImportSelector,

@Override
 public String[] selectImports(AnnotationMetadata annotationMetadata) {
  //检查自动配置功能是否开启,默认开启
  if (!isEnabled(annotationMetadata)) {
   return NO_IMPORTS;
  }
  //加载自动配置的元信息
  AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
    .loadMetadata(this.beanClassLoader);
  AnnotationAttributes attributes = getAttributes(annotationMetadata);
  //获取候选配置类
  List<String> configurations = getCandidateConfigurations(annotationMetadata,
    attributes);
  //去掉重复的配置类
  configurations = removeDuplicates(configurations);
  //获得注解中被exclude和excludeName排除的类的集合
  Set<String> exclusions = getExclusions(annotationMetadata, attributes);
  //检查被排除类是否可实例化、是否被自动注册配置所使用,不符合条件则抛出异常
  checkExcludedClasses(configurations, exclusions);
  //从候选配置类中去除掉被排除的类
  configurations.removeAll(exclusions);
  //过滤
  configurations = filter(configurations, autoConfigurationMetadata);
  //将配置类和排除类通过事件传入到监听器中
  fireAutoConfigurationImportEvents(configurations, exclusions);
  //最终返回符合条件的自动配置类的全限定名数组
  return StringUtils.toStringArray(configurations);
 }

了不起已经在注释中给大家解释了他们每一步的意思

既然我们已经看完了这个 import了,接下来就得看看这个 AutoConfigurationPackage

AutoConfigurationPackage

AutoConfigurationPackage注解的作用是将添加该注解的类所在的package作为自动配置package进行管理。

可以通过 AutoConfigurationPackages 工具类获取自动配置package列表。

当通过注解@SpringBootApplication标注启动类时,已经为启动类添加了@AutoConfigurationPackage注解。

路径为 @SpringBootApplication -> @EnableAutoConfiguration -> @AutoConfigurationPackage。

也就是说当SpringBoot应用启动时默认会将启动类所在的package作为自动配置的package。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {

 String[] basePackages() default {};

 Class<?>[] basePackageClasses() default {};

}

我们看到源码中,他 import 了一个 Registrar ,

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {

  @Override
  public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
   register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
  }

  @Override
  public Set<Object> determineImports(AnnotationMetadata metadata) {
   return Collections.singleton(new PackageImports(metadata));
  }

 }

而这个类其实作用就是读取到我们在最外层的 @SpringBootApplication 注解中配置的扫描路径(没有配置则默认当前包下),然后把扫描路径下面的类都加到数组中返回。

自己实现一个自己的自动配置

xm-common:普通jar项目- src/main    java        BambooServer.java  需要被实例化的服务类        BambooServerProperties.java 配置信息属性类        BmbooServiceAutoConfiguration.java 自动配置类    resources        META-INF/spring.factories 配置自动配置的属性文件demo:普通springboot-web项目

需要实例化的服务类

public class BambooServer {
    private String name;

    public String sayServerName(){
        return "I'm " + name + "! ";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

配置信息对应的属性映射类,需要pom中加入spring-boot-starter依赖

import org.springframework.boot.context.properties.ConfigurationProperties;

@ConfigurationProperties(prefix = "bamboo")
public class BambooServerProperties {

    private static final String NAME = "bamboo_server0";

    private String name = NAME;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

自动配置文件

/**
 * Author: bamboo
 * Describe: 自动配置类
 * 根据条件判断是否要自动配置,创建Bean
 */
@Configuration
@EnableConfigurationProperties(BambooServerProperties.class)
@ConditionalOnClass(BambooServer.class)//判断BambooServer这个类在类路径中是否存在
@ConditionalOnProperty(prefix = "bamboo",value = "enabled",matchIfMissing = true)
public class BmbooServiceAutoConfiguration {

    @Autowired
    private BambooServerProperties mistraServiceProperties;

    @Bean(name = "bambooServer")
    @ConditionalOnMissingBean(BambooServer.class)//当容器中没有这个Bean时(BambooServer)就自动配置这个Bean,Bean的参数来自于BambooServerProperties
    public BambooServer mistraService(){
        BambooServer mistraService = new BambooServer();
        mistraService.setName(mistraServiceProperties.getName());
        return mistraService;
    }
}

在创建如下路径文件src/main/resources/META-INF/spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.bamboo.common.autoconfigure.bamboo.BmbooServiceAutoConfiguration

必须是自动配置类的全路径

mvn install 该项目

创建一个springboot-mvc项目pom依赖上面的jar

@SpringBootApplication
@RestController
//@Import(value = {CorsConfig.class, LogFilter.class}) //跨域,接口访问请求日志
public class DemoApplication {
 @Autowired
 private BambooServer bmbooService;

 public static void main(String[] args) {
  SpringApplication.run(DemoApplication.class, args);
 }

 @RequestMapping("/")
 public Object index(){
  return "helll demo"+bmbooService.getName()+DateUtils.getDate();
 }
}

http://localhost:8080/则返回当前服务的默认值

在applicaton.yml中加,重启刷新则会更新为如下信息

bamboo:
  name: 测试服务
相关推荐
激流丶几秒前
【Kafka 实战】如何解决Kafka Topic数量过多带来的性能问题?
java·大数据·kafka·topic
Themberfue4 分钟前
Java多线程详解⑤(全程干货!!!)线程安全问题 || 锁 || synchronized
java·开发语言·线程·多线程·synchronized·
让学习成为一种生活方式21 分钟前
R包下载太慢安装中止的解决策略-R语言003
java·数据库·r语言
晨曦_子画27 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
南宫生1 小时前
贪心算法习题其三【力扣】【算法学习day.20】
java·数据结构·学习·算法·leetcode·贪心算法
Heavydrink1 小时前
HTTP动词与状态码
java
ktkiko111 小时前
Java中的远程方法调用——RPC详解
java·开发语言·rpc
计算机-秋大田1 小时前
基于Spring Boot的船舶监造系统的设计与实现,LW+源码+讲解
java·论文阅读·spring boot·后端·vue
神里大人1 小时前
idea、pycharm等软件的文件名红色怎么变绿色
java·pycharm·intellij-idea
小冉在学习2 小时前
day53 图论章节刷题Part05(并查集理论基础、寻找存在的路径)
java·算法·图论