锋盈数科-知识库 Logo
首页
软件开发
计算机基础
Hello Halo
新手必读
关于本知识库
登录 →
锋盈数科-知识库 Logo
首页 软件开发 计算机基础 Hello Halo 新手必读 关于本知识库
登录
  1. 首页
  2. 软件开发
  3. JAVA
  4. @SpringBootApplication详解

@SpringBootApplication详解

0
  • JAVA
  • 发布于 2024-08-09
  • 0 次阅读
黄健
黄健

本文由 简悦 SimpRead 转码, 原文地址 blog.csdn.net

@SpringBootApplication 详解

1 . 简介

在 Spring Boot 的学习中难免需要接触源码,而入手及时从 Spring Boot 项目启动类开始入手。项目启动类非常简单,仅仅存在一个注解 @SpringBootApplication 以及一个运行参数为被该注解标注类 run 函数。

@SpringBootApplication
 public class BiuApplication {
    public static void main(String[] args) {
        SpringApplication.run(BiuApplication.class, args);
    }
 }
@Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Inherited
 @SpringBootConfiguration
 @EnableAutoConfiguration//引入EnableAutoConfiguration 
 @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
      @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
 public @interface SpringBootApplication {
    ...//具体参数暂时忽略
 }

对于该启动类的分析,就从这个 Spring Boot 的核心注解开始入手。

2 . 核心注解 @SpringBootApplication

字面分析,这个注解是标注一个 Spring Boot 应用。

@Target({ElementType.TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Configuration
 @Indexed
 public @interface SpringBootConfiguration {
     @AliasFor(
         annotation = Configuration.class
     )
     boolean proxyBeanMethods() default true;
 }
@Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Component
 public @interface Configuration {...}

进入到这个注解后,可以发现该注解由四个元注解,以及其他三个注解组成分别是:@SpringBootConfiguration、

@EnableAutoConfiguration(自动配置有关的核心注解,主要分析)、

@ComponentScan(包扫描)(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })

2.1 Spring 的配置类 @SpringBootConfiguration

字面分析,这是一个 Spring Boot 的配置类。

 @Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Inherited
 @AutoConfigurationPackage
 @Import(AutoConfigurationImportSelector.class)//自动配置的引入选择器实现
 public @interface EnableAutoConfiguration {...}
@Target(ElementType.TYPE)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Inherited
 @Import(AutoConfigurationPackages.Registrar.class)
 public @interface AutoConfigurationPackage {...}

从他的源码来看,除了元注解之外,它仅仅被@Configuration注解所标注,那么可以理解@SpringBootConfiguration为他仅仅就是一个配置类。

@Configuration 源码

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));
     }
 ​
 }
 public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
         register(registry, new PackageImports(metadata).getPackageNames().toArray(new String[0]));
 }

再分析 @Configuration 的源码,该注解为 Spring 中的配置类注解,其中被 @Component 标注为 Spring 组件,意味着他被注册到 IOC 容器。

因此,套用官方文档的答案:@SpringBootConfiguration 表示一个类提供 Spring Boot 应用程序 @Configuration。可以用作 Spring 标准 @Configuration 注释的替代方法,以便可以自动找到配置(例如在测试中)。

2.2 开启自动配置 @EnableAutoConfiguration !!!

这个注解是 Spring Boot 的自动装配的核心。

PackageImports(AnnotationMetadata metadata) {
     AnnotationAttributes attributes = AnnotationAttributes
                     .fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
     List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
     for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
                 packageNames.add(basePackageClass.getPackage().getName());
     }
     if (packageNames.isEmpty()) {
                 packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
     }
     this.packageNames = Collections.unmodifiableList(packageNames);
 }
private static final String BEAN = AutoConfigurationPackages.class.getName();
 ​
 public static void register(BeanDefinitionRegistry registry, String... packageNames) {
     //BeanDefinitionRegistry registry  其中放方法boolean containsBeanDefinition(String beanName);是为了判断是否已经注册了`AutoConfigurationPackages`的类路径所对应的`bean(AutoConfigurationPackages)`
    if (registry.containsBeanDefinition(BEAN)) {
       BasePackagesBeanDefinition beanDefinition =(BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN);
       beanDefinition.addBasePackages(packageNames);
    }
    else {
       registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
    }
 }

除了四个元注解,这个注解被两个注解所标注:

@AutoConfigurationPackage、

@Import(AutoConfigurationImportSelector.class)

那么我们先直接往下分析

2.2.1 自动配置包 @AutoConfigurationPackage
public String[] selectImports(AnnotationMetadata annotationMetadata) {//selectImports方法会被springboot自动调用,从而得到他返回的全类名的字符串数组,然后把对应类的bean对象注入到ioc容器中
         if (!this.isEnabled(annotationMetadata)) {
             return NO_IMPORTS;
         } else {
             AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
             return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
         }
     }
protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
       return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
 }

从注解来看,@AutoConfigurationPackage中使用注解@Import(@Import: 的作用) 导入了 AutoConfigurationPackages.Registrar.class 到容器中,那么来分析这个类,进入到这个内部类 Regisrar:

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
         List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())//load 加载的意思
             .getCandidates();
         Assert.notEmpty(configurations,
                 "No auto configuration classes found in "
                         + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
                         + "are using a custom packaging, make sure that file is correct.");
         return configurations;
 }
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
 @AutoConfiguration(after = ServletWebServerFactoryAutoConfiguration.class)
 @ConditionalOnWebApplication(type = Type.SERVLET)
 @ConditionalOnClass(DispatcherServlet.class)//如果当前环境存在DispatchServlet类,则注入,否则不注入
     //注意 DispatchServlet类 是引用web启动依赖后会有的
 public class DispatcherServletAutoConfiguration {
     ...
     @Configuration(proxyBeanMethods = false)
     @Conditional(DefaultDispatcherServletCondition.class)
     @ConditionalOnClass(ServletRegistration.class)
     @EnableConfigurationProperties(WebMvcProperties.class)
     protected static class DispatcherServletConfiguration {
 ​
         @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
         public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
             DispatcherServlet dispatcherServlet = new DispatcherServlet();
             dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
             dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
             dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
             dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
             dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
             return dispatcherServlet;
         }
 ​
         @Bean
         @ConditionalOnBean(MultipartResolver.class)
         @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
         public MultipartResolver multipartResolver(MultipartResolver resolver) {
             // Detect if the user has created a MultipartResolver but named it incorrectly
             return resolver;
         }
 ​
     }
     ...
 }

该类引入的重点在于方法**registerBeanDefinitions():**

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

首先先分析方法体中所调用的方法register()的第二个参数

PackageImports(metadata).getPackageNames().toArray(new String[0])

进入到类 PackageImports 的构造方法:

PackageImports(AnnotationMetadata metadata) {
     AnnotationAttributes attributes = AnnotationAttributes
                     .fromMap(metadata.getAnnotationAttributes(AutoConfigurationPackage.class.getName(), false));
     List<String> packageNames = new ArrayList<>(Arrays.asList(attributes.getStringArray("basePackages")));
     for (Class<?> basePackageClass : attributes.getClassArray("basePackageClasses")) {
                 packageNames.add(basePackageClass.getPackage().getName());
     }
     if (packageNames.isEmpty()) {
                 packageNames.add(ClassUtils.getPackageName(metadata.getClassName()));
     }
     this.packageNames = Collections.unmodifiableList(packageNames);
 }

在这个构造方法中将元数据即启动类AnnotationMetadata metadata经过处理

  1. 获取标签注解信息,注解信息里面的 basePackages 和 basePackageClasses是否有数据。

basePackages、 basePackageClasses为注解@AutoConfigurationPackage中的属性。

  1. 如果没有数据则获取注解所在的类的名字目录,放到 List 中

获得packageNames属性也就是启动类所在的包。

回到Registrar中的registerBeanDefinitions()方法中register()方法的第二个参数即为启动类所在的包的名称,并且使用数组来进行表示。

在分析**register()**方法,register() 源码如下:

private static final String BEAN = AutoConfigurationPackages.class.getName();
 ​
 public static void register(BeanDefinitionRegistry registry, String... packageNames) {
     //BeanDefinitionRegistry registry  其中放方法boolean containsBeanDefinition(String beanName);是为了判断是否已经注册了`AutoConfigurationPackages`的类路径所对应的`bean(AutoConfigurationPackages)`
    if (registry.containsBeanDefinition(BEAN)) {
       BasePackagesBeanDefinition beanDefinition =(BasePackagesBeanDefinition)registry.getBeanDefinition(BEAN);
       beanDefinition.addBasePackages(packageNames);
    }
    else {
       registry.registerBeanDefinition(BEAN, new BasePackagesBeanDefinition(packageNames));
    }
 }

这个方法的if语句为判断registry这个参数中是否已经注册了AutoConfigurationPackages的类路径所对应的bean(AutoConfigurationPackages)。如若已经被注册,则把上面分析的第二个参数所获取的包(启动类所在的包的名称)添加到这个bean的定义中。如若没有,则注册这个bean并且把包名设置到该bean的定义中。

小结:@AutoConfigurationPackage就是添加该注解的类所在的包作为自动配置包进行管理。他的实现就是依赖于工具类 AutoConfigurationPackages 中的内部类 Registrar 对所标注的包进行注册。

2.2.2 导入 自动配置导入选择器 @Import(AutoConfigurationImportSelector.class)

这个@import使用了 2.2.1 中 @import 的用法中的通过 ImportSelector 方式导入的类,所以我们进入到该类,直接找到selectImports方法。在这个用法中,所返回的字符串数组为所有的将要被导入的类的全类名。那么知道这个方法是做什么的,就开始分析这个方法。

再分析**selectImports**方法,selectImports()源码如下:

public String[] selectImports(AnnotationMetadata annotationMetadata) {//selectImports方法会被springboot自动调用,从而得到他返回的全类名的字符串数组,然后把对应类的bean对象注入到ioc容器中
         if (!this.isEnabled(annotationMetadata)) {
             return NO_IMPORTS;
         } else {
             AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
             return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
         }
     }

从 return 开始分析,autoConfigurationEntry 自动配置实体中 List 的属性 configurations 将被返回。autoConfigurationEntry 是通过方法**getAutoConfigurationEntry()**获得的,那么就进入到这个方法

protected AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
    if (!isEnabled(annotationMetadata)) {
       return EMPTY_ENTRY;
    }
    AnnotationAttributes attributes = getAttributes(annotationMetadata);
    List<String> configurations = getCandidateConfigurations(annotationMetadata, attributes);
    configurations = removeDuplicates(configurations);
    Set<String> exclusions = getExclusions(annotationMetadata, attributes);
    checkExcludedClasses(configurations, exclusions);
    configurations.removeAll(exclusions);
    configurations = getConfigurationClassFilter().filter(configurations);
    fireAutoConfigurationImportEvents(configurations, exclusions);
    return new AutoConfigurationEntry(configurations, exclusions);
 }

根据 return 所返回的内容,返回的是一个使用属性 configurations 所生成的自动配置实体,configurations 是使用**getCandidateConfigurations()**获取候选配置所得到的。

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
         List<String> configurations = ImportCandidates.load(AutoConfiguration.class, getBeanClassLoader())//load 加载的意思
             .getCandidates();
         Assert.notEmpty(configurations,
                 "No auto configuration classes found in "
                         + "META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports. If you "
                         + "are using a custom packaging, make sure that file is correct.");
         return configurations;
 }
 ​

从以上源码可以发现,在springboot3之后,已完全放弃对META-INF/spring.factories的读取。而是从传的AutoConfiguration.class取的类名称。即org.springframework.boot.autoconfigure.AutoConfiguration。拼接之后 ,META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(.imports 配置文件)。(2.7 升级到 3.0 后的变化)

读取META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports中的org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration

DispatcherServletAutoConfiguration配置类中还有配置类DispatcherServletConfiguration,其中还有方法dispatcherServlet(WebMvcProperties webMvcProperties)添加了@bean注解springboot会继续解析,直到把@Bean注解的方法都解析到,然后执行这些方法,把返回值注入到ioc容器中,因此自动配置的核心在配置文件里

@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
 @AutoConfiguration(after = ServletWebServerFactoryAutoConfiguration.class)
 @ConditionalOnWebApplication(type = Type.SERVLET)
 @ConditionalOnClass(DispatcherServlet.class)//如果当前环境存在DispatchServlet类,则注入,否则不注入
     //注意 DispatchServlet类 是引用web启动依赖后会有的
 public class DispatcherServletAutoConfiguration {
     ...
     @Configuration(proxyBeanMethods = false)
     @Conditional(DefaultDispatcherServletCondition.class)
     @ConditionalOnClass(ServletRegistration.class)
     @EnableConfigurationProperties(WebMvcProperties.class)
     protected static class DispatcherServletConfiguration {
 ​
         @Bean(name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
         public DispatcherServlet dispatcherServlet(WebMvcProperties webMvcProperties) {
             DispatcherServlet dispatcherServlet = new DispatcherServlet();
             dispatcherServlet.setDispatchOptionsRequest(webMvcProperties.isDispatchOptionsRequest());
             dispatcherServlet.setDispatchTraceRequest(webMvcProperties.isDispatchTraceRequest());
             dispatcherServlet.setThrowExceptionIfNoHandlerFound(webMvcProperties.isThrowExceptionIfNoHandlerFound());
             dispatcherServlet.setPublishEvents(webMvcProperties.isPublishRequestHandledEvents());
             dispatcherServlet.setEnableLoggingRequestDetails(webMvcProperties.isLogRequestDetails());
             return dispatcherServlet;
         }
 ​
         @Bean
         @ConditionalOnBean(MultipartResolver.class)
         @ConditionalOnMissingBean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
         public MultipartResolver multipartResolver(MultipartResolver resolver) {
             // Detect if the user has created a MultipartResolver but named it incorrectly
             return resolver;
         }
 ​
     }
     ...
 }
2.2.3 总结!!!

@SpringBootApplication是一个组合注解,其中有@EnableAutoConfiguration也是一个组合注解,@EnableAutoConfiguration有 `@Import(AutoConfigurationImportSelector.class),导入了AutoConfigurationImportSelector.class类,AutoConfigurationImportSelector.class实现了selectImports方法,经过层层调用最终读取一个配置文件META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports(注意,springboot2.7前读取的是spring.factories文件,2.7-3.0 兼容两个,3.0 之后只有.imports) 这个配置文件中写了一堆的全类名,其中有一个是org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration,它是完成DispatcherServlet这个对象的自动注入的,DispatcherServletAutoConfiguration类中有@AutoConfiguration标明是一个自动配置类,@ConditionalOnClass(DispatcherServlet.class)用来去设置bean注册的条件(如果环境里有DispatcherServlet.class这个配置类,DispatcherServletAutoConfiguration这个自动配置类就生效,否则就不生效,注意引入web启动依赖就有DispatcherServlet.class这个配置类),因此说引入了web启动依赖springboot就自动注入了一个DispatcherServlet。

标签: #JAVA 991
相关文章

Spring 实现 3 种异步接口 2024-10-18 09:07

大家好,我是苏三~ 如何处理比较耗时的接口? 这题我熟,直接上异步接口,使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地

重学SpringBoot3-集成Redis(五)之布隆过滤器 2024-10-08 11:24

更多SpringBoot3内容请关注我的专栏:《SpringBoot3》 期待您的点赞👍收藏⭐评论✍ 重学SpringBoot3-集成Redis(五)之布隆过滤器 1. 什么是布隆过滤器? * 基本概念 适用场景 2. 使用 Redis 实现布隆过滤器 * 项目依赖 Redis 配置

SpringBoot整合异步任务执行 2024-10-08 11:24

同步任务: 同步任务是在单线程中按顺序执行,每次只有一个任务在执行,不会引发线程安全和数据一致性等 并发问题 同步任务需要等待任务执行完成后才能执行下一个任务,无法同时处理多个任务,响应慢,影响用 户体验 异步任务: 异步任务是在多线程中同时执行,多个任务可以并发执行,同时处理多个请求,响应快,资源

springboot kafka多数据源,通过配置动态加载发送者和消费者 2024-10-08 11:24

前言 最近做项目,需要支持kafka多数据源,实际上我们也可以通过代码固定写死多套kafka集群逻辑,但是如果需要不修改代码扩展呢,因为kafka本身不处理额外逻辑,只是起到削峰,和数据的传递,那么就需要对架构做一定的设计了。 准备test kafka本身非常容易上手,如果我们需要单元测试,引入ja

SpringBoot 集成 Redis 2024-10-08 11:24

一:SpringBoot 集成 Redis ①Redis是一个 NoSQL(not only)数据库, 常作用缓存 Cache 使用。 ②Redis是一个中间件、是一个独立的服务器;常用的数据类型: string , hash ,set ,zset , list ③通过Redis客户端可以使用多种语

SpringBoot整合QQ邮箱 2024-10-08 11:24

SpringBoot可以通过导入依赖的方式集成多种技术,这当然少不了我们常用的邮箱,现在本章演示SpringBoot整合QQ邮箱发送邮件…. 下面按步骤进行: 1.获取QQ邮箱授权码 1.1 登录QQ邮箱 1.2 开启SMTP服务 找到下图中的SMTP服务区域,如果当前账号未开启的话自己手动开启。

目录

IT 外包服务商

  • 意见投递
  • zyf6619

软件开发应用

主菜单

  • 首页
  • 软件开发
  • 计算机基础
  • Hello Halo
  • 新手必读
  • 关于本知识库
Copyright © 2024 your company All Rights Reserved. Powered by Halo.