【檀越剑指大厂—Springboot】Springboot高阶

news2025/7/15 2:51:31

一.整体介绍

1.什么是 Springboot?

Springboot 是一个全新的框架,简化 Spring 的初始搭建和开发过程,使用了特定的方式来进行配置,让开发人员不再需要定义样板化的配置。此框架不需要配置 xml,依赖于 maven 这样的构建系统。

简单、快速、方便的搭建项目;对主流开发框架的无配置集成;极大提高了开发、部署效率。

2.Springboot 的优点

  • 提供一个快速的 Spring 项目搭建渠道。
  • 开箱即用 ,很少的 Spring 配置就能运行一个 Java EE 项目 。
  • 提供了生产级的服务监控方案。
  • 内嵌服务器,可以快速部署。
  • 提供了一系列非功能性的通用配置。
  • 纯 Java 配置,没有代码生成,也不需要 XML 配置。

3.Springboot 的缺点

  • 将现有或传统的 Spring Framework 项目转换为 Spring boot 应用程序是一个非常困难和耗时的过程。
  • 使用简单, 学习成本高, 精通难。
  • 版本迭代速度很快,一些模块改动很大。
  • 由于不用自己做配置,报错时很难定位。
  • 网上现成的解决方案比较少。

4.Springboot 和 Spring

  • springboot 可以建立独立的 spring 应用程序。

  • 内嵌了如 tomcat,Jetty 和 Undertow 这样的容器,也就是说可以直接跑起来,用不着再做部署工作。

  • 无需再像 spring 那样写一堆繁琐的 XML 配置文件

  • 可以自动配置 spring

  • 提供的 POM 可以简化 maven 的配置

5.springboot 和 springMVC

  • SpringMVC 是基于 spring 的一个 MVC 框架。
  • springboot 的基于 spring 的条件注册的一套快速开发整合包。

6.为何可以快速集成?

答:因为启动器 start 包含一些依赖项,配置一下就可以用,所以快

再问:底层原理是啥?

基于观察者模式,启动后会把相关配置加入到 environment 对象,监听到对象后 springboot 去加载实现整体自动化,加载的内容也就是我们常说基于的那三个注解

7.SpringBoot 种启动方式?

1.main 启动

直接main方法启动DemoApplication

2.maven 启动

mvn spring-boot:run

3.jar 包

java -jar tx_demo2-0.0.1-SNAPSHOT.jar

4.docker 启动

FROM  openjdk:8-jdk-alpine
ARG  JAR_FILE
COPY  ${JAR_FILE}  app.jar
EXPOSE  10001
ENTRYPOINT  ["java","-jar","/app.jar"]
docker run -p 8080:8080 tx_demo2:1.0

8.Spring Boot devtools

Spring Boot 提供了一组开发工具 spring-boot-devtools 可以提高开发者的工作效率,开发者可以将该模块包含在任何项目中,spring-boot-devtools 最方便的地方莫过于热部署了。

要想在项目中加入 devtools 模块,只需添加相关依赖即可,代码如下:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-devtools</artifactId>
    <optional>true</optional>
</dependency>

注意:这里多了 一个 optional 选项,是为了防止将 devtools 依赖传递到其他模块中。当开发者将应用打包运行后,devtools 会被自动禁用
当开发者将 spring-boot-devtools 引入项目后,只要 classpath 路径下发生变化,项目就会自动重启,这极大地提高了项目的开发速度。如果开发者使用 Eclipse ,那么在修改完代码并保存之后,项目将自动编译井触发重启,而开发者如果使用 IntelliJ IDEA 默认情况下,需要开发者手动编译才会触发重启。手动编译时,单击 Build -> Build Project 菜单或者按 Ctrl+F9 快捷键进行编译,编译成功后就会触发项目重启。当然,使用 IntelliJ IDEA 开发者也可以配置项目自动编译,配置步骤:

单击 File -> settings 菜单,打开 settings 页面,在左边的菜单栏依次找到 Build,Execution,Deployment -> Compile,勾选 Build project automatically 如图所示。

在这里插入图片描述

按住 Ctrl+Shift+Alt+/ 快捷捷键调出 Maintenance 页面

在这里插入图片描述

选择 Registry,在新打开的 Registry 页面中,勾选 compiler.automake.allow.when.app.running 复选框。配置完成后,若开发者修改了代码则将会自动重启。

在这里插入图片描述

注意:classpath 路径下的静态资源或者视图模板等发生变化时,并不会导致项目重启。

9.网络环境

SpringApplication试图代表您创建正确类型的ApplicationContext。用于确定WebApplicationType的算法非常简单:

  • 如果存在 Spring MVC,则使用AnnotationConfigServletWebServerApplicationContext
  • 如果 Spring MVC 不存在且存在 Spring WebFlux,则使用AnnotationConfigReactiveWebServerApplicationContext
  • 否则,使用AnnotationConfigApplicationContext

这意味着如果您在同一个应用程序中使用 Spring MVC 和来自 Spring WebFlux 的新WebClient,默认情况下将使用 Spring MVC。您可以通过调用setWebApplicationType(WebApplicationType)轻松覆盖它。

也可以通过调用setApplicationContextClass(…)来完全控制使用的ApplicationContext类型。

10.如何解决循环依赖

从 2.6 版本开始,如果你的项目里还存在循环依赖,SpringBoot 将拒绝启动!

image-20221221164250165

临时开启循环依赖

spring.main.allow-circular-references=true

二.配置

1.父依赖

其中它主要是依赖一个父项目,主要是管理项目的资源过滤及插件!

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath/> <!-- lookup parent from repository -->
</parent>

点进去,发现还有一个父依赖

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-dependencies</artifactId>
    <version>2.2.5.RELEASE</version>
    <relativePath>../../spring-boot-dependencies</relativePath>
</parent>

这里才是真正管理 SpringBoot 应用里面所有依赖版本的地方,SpringBoot 的版本控制中心;

以后我们导入依赖默认是不需要写版本;但是如果导入的包没有在依赖中管理着就需要手动配置版本了;

在没有父 POM 的情况下使用 Spring Boot

不是每个人都喜欢继承spring-boot-starter-parent POM。您可能拥有自己需要使用的公司标准父级,或者您可能希望明确声明所有 Maven 配置。

如果您不想使用spring-boot-starter-parent,您仍然可以通过使用scope=import依赖项来保持依赖项管理(但不是插件管理)的好处,如下所示:

<dependencyManagement>
		<dependencies>
		<dependency>
			<!-- Import dependency management from Spring Boot -->
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-dependencies</artifactId>
			<version>2.1.1.RELEASE</version>
			<type>pom</type>
			<scope>import</scope>
		</dependency>
	</dependencies>
</dependencyManagement>

此时,就可以不用继承 spring-boot-starter-parent 了,但是 Java 的版本、编码的格式等都需要开发者手动配置。 Java 版本的配置很简单,添加一个 plugin 即可:

<plugin>
   <groupId>org.apache.maven.plugins</groupId>
   <artifactId>maven-compiler-plugin</artifactId>
   <version>3.1</version>
   <configuration>
       <source>1.8</source>
       <target>1.8</target>
   </configuration>
</plugin>

编码格式

 <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
     <project.reporting.outputEncodin>UTF-8</project.reporting.outputEncodin>
 </properties>

2.配置文件优先级

加载顺序

bootstrap.yml > bootstrap.properties > application.yml > application.properties

启动命令加 -D 参数名 = 参数值 覆盖配置文件中的配置

  • SpringBoot 提供了 2 种配置文件类型:properteis yml/yaml
  • 默认配置文件名称:application
  • 在同一级目录下优先级为:properties > yml > yaml

3.导入其他配置类

你不需要将所有@Configuration放入一个班级。@Import注释可用于导入其他配置类。或者,您可以使用@ComponentScan自动选取所有 Spring 组件,包括@Configuration类。

4.导入 XML 配置

如果您绝对必须使用基于 XML 的配置,我们建议您仍然使用@Configuration类。然后,您可以使用@ImportResource注释来加载 XML 配置文件。

5.禁用特定的自动配置类

如果发现正在应用您不需要的特定自动配置类,则可以使用@EnableAutoConfiguration的 exclude 属性禁用它们,如以下示例所示:

import org.springframework.boot.autoconfigure.*;
import org.springframework.boot.autoconfigure.jdbc.*;
import org.springframework.context.annotation.*;

@Configuration
@EnableAutoConfiguration(exclude={DataSourceAutoConfiguration.class})
public class MyConfiguration {
}

如果类不在类路径上,则可以使用注释的excludeName属性并指定完全限定名称。最后,您还可以使用spring.autoconfigure.exclude属性控制要排除的自动配置类列表。

6.Spring Beans 和依赖注入

您可以自由使用任何标准 Spring 框架技术来定义 beans 及其注入的依赖项。为简单起见,我们经常发现使用@ComponentScan(找到你的 beans)和使用@Autowired(做构造函数注入)效果很好。

如果按照上面的建议构建代码(在根包中定位应用程序类),则可以添加@ComponentScan而不带任何参数。您的所有应用程序组件(@Component@Service@Repository@Controller等)都会自动注册为 Spring Beans。

@ComponentScan可以使用替代@Import({ MyConfig.class, MyAnotherConfig.class })

7.自定义横幅

通过将banner.txt文件添加到类路径或将spring.banner.location属性设置为此类文件的位置,可以更改启动时打印的横幅。如果文件的编码不是 UTF-8,则可以设置spring.banner.charset。除了文本文件,您还可以将banner.gifbanner.jpgbanner.png图像文件添加到类路径或设置spring.banner.image.location属性。图像将转换为 ASCII 艺术表示,并打印在任何文本横幅上方。

Spring Boot 项目在启动时会打印一个 banner,这个 banner 是可以定制的,在 sources 目录下创建 banner.txt 文件,在这个文件中写入的文本将在项目启动时打印出来 如果想将 TXT 文本设成艺术字体,有以下几个在线网站可供参考

8.配置随机值

RandomValuePropertySource对于注入随机值(例如,进入秘密或测试用例)非常有用。它可以生成整数,长整数,uuids 或字符串,如以下示例所示:

my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
my.uuid=${random.uuid}
my.number.less.than.ten=${random.int(10)}
my.number.in.range=${random.int[1024,65536]}

random.int*语法为OPEN value (,max) CLOSE,其中OPEN,CLOSE为任意字符,value,max为整数。如果提供max,那么value是最小值,max是最大值(不包括)。

9.特定于配置文件的属性

除了application.properties文件之外,还可以使用以下命名约定来定义特定于配置文件的属性:application-{profile}.propertiesEnvironment有一组默认配置文件(默认情况下为[default]),如果未设置活动配置文件,则使用这些配置文件。换句话说,如果没有显式激活配置文件,则会加载application-default.properties中的属性。

特定于配置文件的属性从标准application.properties的相同位置加载,特定于配置文件的文件始终覆盖非特定文件,无论特定于配置文件的文件是在打包的 jar 内部还是外部。

如果指定了多个配置文件,则应用 last-wins 策略。例如,spring.profiles.active属性指定的配置文件将在通过SpringApplication API 配置的配置文件之后添加,因此优先。

10.属性中的占位符

application.properties中的值在使用时通过现有的Environment进行过滤,因此您可以返回先前定义的值(例如,从系统属性中)。

app.name=MyApp
app.description=${app.name} is a Spring Boot application

11.YAML

  • 大小写敏感
  • 数据值前边必须有空格,作为分隔符
  • 使用缩进表示层级关系
  • 缩进时不允许使用 Tab 键,只允许使用空格(各个系统 Tab 对应的空格数目可能不同,导致层次混乱)。
  • 缩进的空格数目不重要,只要相同层级的元素左侧收对齐即可
  • #表示注释,从这个字符一直到行尾,都会被解析器忽略。

12.配置加载顺序

内部配置加载顺序

Springboot 程序启动时,会从以下位置加载配置文件:

  1. file:./config/: 当前项目下的/config 目录下

  2. file:./: 当前项目的根目录

  3. classpath:/config/: classpath 的/config 目录

  4. classpath:/: classpath 的根目录 (上面文档中的方式)

image.png

注意: 1、2 是不会打入项目的 jar 包的, 而 3、4 是会最终编入 jar 中。

加载顺序为上文的排列顺序,高优先级配置的属性会生效

指定位置加载配置文件

我们还可以通过 spring.config.location 来改变默认的配置文件位置

项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高

java -jar spring-boot-config.jar --spring.config.location=F:/application.properties

外部配置的加载顺序

外部配置优先级高于内部的配置,内部配置是静态的可以看作是默认值,而外部配置是可以动态修改,这样极大提高灵活性。

三.注解

1.选择器注解

  • @Conditional,当指定的条件都满足时,组件才被注册
  • @ConditionalOnBean,指定 bean 在上下文中时,才注册当前 bean。用在方法上,则默认依赖类为方法的返回类型
  • @ConditionalOnClass,指定类在 classpath 上时,才初始化当前 bean。用在方法上,则默认依赖类为方法的返回类型
  • @ConditionalOnCloudPlatform,在指定云平台才注册配置
  • @ConditionalOnExpression,指定 spel 为 true 时注册配置
  • @ConditionalOnJava,在指定 java 版本时注册配置
  • @ConditionalOnJndi
  • @ConditionalOnMissingBean,指定 bean 不在上下文中时,才初始化当前 bean。用在方法上,则默认依赖类为方法的返回类型
  • @ConditionalOnMissingClass,指定类不在 classpath 上时,才初始化当前 bean。用在方法上,则默认依赖类为方法的返回类型
  • @ConditionalOnNotWebApplication,不是在 web 环境才注册配置
  • @ConditionalOnProperty,配置文件中的值与指定值是否相等,相等才注册配置
  • @ConditionalOnResource,指定 resources 都在 classpath 上才注册配置
  • @ConditionalOnSingleCandidate,上下文中只有一个候选者 bean 时才注册配置
  • @ConditionalOnWebApplication,是在 web 环境才注册配置

2.缓存注解

  • @EnableCaching,开启缓存配置,支持子类代理或者 AspectJ 增强
  • @CacheConfig,在一个类下,提供公共缓存配置
  • @Cacheable,放着方法和类上,缓存方法或类下所有方法的返回值
  • @CachePut,每次先执行方法,再将结果放入缓存
  • @CacheEvict,删除缓存
  • @Caching,可以配置@Cacheable、@CachePut、@CacheEvict

3.定时器注解

  • @EnableScheduling,开启定时任务功能
  • @Scheduled,按指定执行周期执行方法
  • @Schedules,包含多个@Scheduled,可同时运行多个周期配置
  • @EnableAsync,开启方法异步执行的能力,通过@Async 或者自定义注解找到需要异步执行的方法。通过实现 AsyncConfigurer 接口的 getAsyncExecutor()和 getAsyncUncaughtExceptionHandler()方法自定义 Executor 和异常处理。
  • @Async,标记方法为异步线程中执行

4.注入配置文件

  • @EnableConfigurationProperties,启动@ConfigurationProperties 功能
  • @ConfigurationProperties,将 properties 文件里的内容,自动注入 bean 对应的属性中
  • @DeprecatedConfigurationProperty,用在配置文件的 getter()方法上,标记字段已经过期,并提示替换的字段。一般给 spring-boot-configuration-processor 使用。
  • @NestedConfigurationProperty,标记在配置文件的字段上,提示 spring-boot-configuration-processor,配置包含嵌套的配置。
  • spring-configuration-metadata.json 提供配置的元信息,在写 properties 配置时,会有语法提示。在项目中引入 spring-boot-configuration-processor 项目,会扫描@ConfigurationProperties 注解,自动生成 spring-configuration-metadata.json

四.常见问题

1.@SpringBootApplication

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
		@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {}

首先是四个元注解

  • @Target(ElementType.TYPE): 标志该注解可以作用于 interface、class、enum。
  • @Retention(RetentionPolicy.RUNTIME): 注解的保留策略;
    • 当前注解会出现在.class 字节码文件中
    • 并且在运行的时候可以通过反射被获得。
  • @Documented: 标志该注解可以被 javadoc 工具所引用。
  • @Inherited: 标志该注解可以被继承。

核心注解

@SpringBootConfiguration、@EnableAutoConfiguration、@ComponentScan

@SpringBootConfiguration 它继承了@Configuration;Spring3.0 开始增加了@Configuration、@Bean 注解,

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

@ComponentScan 与@Component 这一类的注解配合使用,相当于以前 xml 配置中开启自动扫描的 context:component-scan 标签,可以配置 Spring 的扫描范围、Bean 的 ID 命名策略等属性;

  • 默认扫描范围为当前目录下的所有 class,
  • 这也是 SpringBoot 入口类要放在代码根目录的原因。
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan {}

@ComponentScan 会扫描并装配代码根目录下的,所有的带@Component 注解的 class;但是 SpringBoot 如何加载代码根目录之外的 Bean,比方说 classpath 下 maven 依赖中的;
这个时候就需要了解一下 META-INF/spring.factories 文件的作用了

@import :Spring 底层注解@import , 给容器中导入一个组件

@Import({AutoConfigurationImportSelector.class}) :给容器导入组件 ;

AutoConfigurationImportSelector :自动配置导入选择器

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";

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

    String[] excludeName() default {};
}

单个@SpringBootApplication注释可用于启用这三个功能,即:

  • @EnableAutoConfiguration:启用Spring Boot 的自动配置机制
  • @ComponentScan:对应用程序所在的软件包启用@Component扫描(请参阅最佳实践)
  • @Configuration:允许在上下文中注册额外的 beans 或导入其他配置类

@SpringBootApplication注释等效于使用@Configuration@EnableAutoConfiguration@ComponentScan及其默认属性

META-INF/spring.factories ,核心配置文件,主要加载 maven 引入的包的类

结论: springboot 所有自动配置都是在启动的时候扫描并加载, spring.factories 所有的自动配置类都在这里面

但是不一定生效, 要@ConditionalonXXXaa条件成立

image.png

2.加载整体流程?

img

img

3.starter 是如何被加载的?

  1. springboot 在启动的时候, 从类路径下 META-INF/spring.factories 获取指定的值

  2. 将这些自动配置的类导入容器, 自动配置就会生效, 帮我们进行自动配置

  3. 以前是需要自己配置的东西, 现在 springboot 帮忙做

  4. 整合 javaEE, 解决方案和自动配置的东西都在 spring-boot-autoconfigure-2.0.0.RELEASE.jar 包下

  5. 它会把所有需要导入的组件, 已类名的方式返回,这些组件就会被添加到容器.

run 方法

public ConfigurableApplicationContext run(String... args) {
  StopWatch stopWatch = new StopWatch();
  stopWatch.start();
  ConfigurableApplicationContext context = null;
  Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
  configureHeadlessProperty();
  //获取所有的org.springframework.boot.SpringApplicationRunListeners实现类,这里我们也可以使用spring.factories自定义实现类。
  SpringApplicationRunListeners listeners = getRunListeners(args);
  listeners.starting();
  try {
    ApplicationArguments applicationArguments = new DefaultApplicationArguments(
      args);
    //自定义监听器加载配置信息和系统环境变量
    ConfigurableEnvironment environment = prepareEnvironment(listeners,
                                                             applicationArguments);
    configureIgnoreBeanInfo(environment);
    Banner printedBanner = printBanner(environment);
    context = createApplicationContext();
    //获取所有spring.factories自定义实现,根据type值找到对应的 class  的全限定名称列表
    exceptionReporters = getSpringFactoriesInstances(
      SpringBootExceptionReporter.class,
      new Class[] { ConfigurableApplicationContext.class }, context);
    prepareContext(context, environment, listeners, applicationArguments,
                   printedBanner);
    //执行自定义spring.fatories中的类(Bean初始化类,环境变量初始化类)的指定方法
    refreshContext(context);
    afterRefresh(context, applicationArguments);
    stopWatch.stop();
    if (this.logStartupInfo) {
      new StartupInfoLogger(this.mainApplicationClass)
        .logStarted(getApplicationLog(), stopWatch);
    }
    listeners.started(context);
    //一些程序启动就要执行的的任务,包括spring.fatories中定义的key为EnableAutoConfiguration, j接口CommandLineRunner的实现类。
    callRunners(context, applicationArguments);
  }
  catch (Throwable ex) {
    handleRunFailure(context, listeners, exceptionReporters, ex);
    throw new IllegalStateException(ex);
  }
  listeners.running(context);
  return context;
}

获取 Spring 工厂

SpringApplication

这个类主要做了以下四件事情:

  1. 推断应用的类型是普通的项目还是 Web 项目
  2. 查找并加载所有可用初始化器 , 设置到 initializers 属性中
  3. 找出所有的应用程序监听器,设置到 listeners 属性中
  4. 推断并设置 main 方法的定义类,找到运行的主类
public class SpringApplication {
  private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
    return getSpringFactoriesInstances(type, new Class<?>[] {});
  }
  // 获取Spring工厂
  private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
                                                        Class<?>[] parameterTypes, Object... args) {
    // 获取ClassLoader
    ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
    // Use names and ensure unique to protect against duplicates
    // 定义class数组,即返回值 names 是所有 classpath 下面的 META-INF/spring.factories 中定义的父节点
    Set<String> names = new LinkedHashSet<>(
      SpringFactoriesLoader.loadFactoryNames(type, classLoader));
    // 内部循环初始化 names的构造函数,获取实例实例对象
    List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
                                                       classLoader, args, names);
    AnnotationAwareOrderComparator.sort(instances);
    return instances;
  }
  // 创建Spring工厂实例
  private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,
                                                     ClassLoader classLoader, Object[] args, Set<String> names) {
    List<T> instances = new ArrayList<>(names.size());
    // 遍历实例对象的全限定名
    for (String name : names) {
      try {
        // 加载该类,通过指定的classloader加载对应的类获取对应的Class对象
        Class<?> instanceClass = ClassUtils.forName(name, classLoader);
        // 断言是否为该接口的实现类
        Assert.isAssignable(type, instanceClass);
        // 获取构造方法
        Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);
        // 实例化该类
        T instance = (T) BeanUtils.instantiateClass(constructor, args);
        // 添加到结果集当中
        instances.add(instance);
      }
      catch (Throwable ex) {
        throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);
      }
    }
    return instances;
  }
}

SpringFactoriesLoader

public abstract class SpringFactoriesLoader {
   public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
   private static final Map<ClassLoader, MultiValueMap<String, String>> cache = new ConcurrentReferenceHashMap<>();
    // 加载工厂
   public static List<String> loadFactoryNames(Class<?> factoryClass, @Nullable ClassLoader classLoader) {
      // 获取类名称(图4)
      String factoryClassName = factoryClass.getName();
      // 根据类名称获取需要加载的工厂类名称
      return loadSpringFactories(classLoader).getOrDefault(factoryClassName, Collections.emptyList());
   }
    // 通过加载所有 classpath 下面的 META-INF/spring.factories文件,扫描加载类
   private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {
        // 从cache获取实例的结果集 当是Null表示当前的cache是空的;cache 实现 new ConcurrentReferenceHashMap<>()
      MultiValueMap<String, String> result = cache.get(classLoader);
      if (result != null) {
         return result;
      }
      try {
          // 获取所有 classpath 下面的 META-INF/spring.factories 中的资源 urls
          // 当classLoader为非空的时候调用getResouurces方法获取
          // 当classLoader为空的时候调用ClassLoader.getSystemResouurces方法获取
         Enumeration<URL> urls = (classLoader != null ?
               classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
               ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
         result = new LinkedMultiValueMap<>();
          // 循环处理 urls中的元素,获取元素
         while (urls.hasMoreElements()) {
             // 获取元素 url地址
            URL url = urls.nextElement();
            UrlResource resource = new UrlResource(url);
             // 解析文件 把文件变成配置属性
            Properties properties = PropertiesLoaderUtils.loadProperties(resource);
             // 循环解析并把结果放入result
            for (Map.Entry<?, ?> entry : properties.entrySet()) {
                // 类名列表
               List<String> factoryClassNames = Arrays.asList(
                     StringUtils.commaDelimitedListToStringArray((String) entry.getValue()));

               //记录所有的key和对应的value集合
               result.addAll((String) entry.getKey(), factoryClassNames);
            }
         }
          // 缓存类加载器和文件解析器的结果集
         cache.put(classLoader, result);
         return result;
      }
      catch (IOException ex) {
         throw new IllegalArgumentException("Unable to load factories from location [" +
               FACTORIES_RESOURCE_LOCATION + "]", ex);
      }
   }
}

在获取到所有的接口之后,就会调用 getSpringFactoriesInstances()创建接口的实现类。

spring.factories 就像是工厂一样配置了大量的接口对应的实现类,我们通过这些配置 + 反射处理就可以拿到相应的实现类。这种类似于插件式的设计方式,只要引入对应的 jar 包,那么对应的 spring.factories 就会被扫描到,对应的实现类也就会被实例化,如果不需要的时候,直接把 jar 包移除即可。

4.Starter 的实现原理?

Starters 是一组方便的依赖描述符,您可以在应用程序中包含这些描述符。您可以获得所需的所有 Spring 和相关技术的一站式服务,而无需搜索示例代码和复制粘贴依赖描述符的负载。例如,如果要开始使用 Spring 和 JPA 进行数据库访问,请在项目中包含spring-boot-starter-data-jpa依赖项。

启动器包含许多依赖项,这些依赖项是使项目快速启动和运行所需的依赖项,以及一组受支持的托管传递依赖项。

如何命名

所有官方首发都遵循类似的命名模式; spring-boot-starter-*,其中*是一种特殊类型的应用程序。此命名结构旨在帮助您找到启动器。许多 IDE 中的 Maven 集成允许您按名称搜索依赖项。例如,安装了适当的 Eclipse 或 STS 插件后,可以在 POM 编辑器中按ctrl-space并输入“spring-boot-starter”以获取完整列表。

正如“ 创建自己的初学者 ”部分所述,第三方启动者不应以spring-boot开头,因为它是为官方 Spring Boot 工件保留的。相反,第三方启动器通常以项目名称开头。例如,名为thirdpartyproject的第三方启动项目通常被命名为thirdpartyproject-spring-boot-starter

命名归约:

官方命名:

  • 前缀:spring-boot-starter-xxx
  • 比如:spring-boot-starter-web…

自定义命名:

  • xxx-spring-boot-starter
  • 比如:mybatis-spring-boot-starter

启动器:

  • 启动器: 说白了就是 springboot 的启动场景(可以理解特定场景的整套解决方案)
  • 比如 spring-boot-starter-web, 它就会帮我们自动导入 web 环境所有的依赖
  • springboot 会讲所有的功能场景, 都变成一个个的启动器
  • 我们要用什么功能, 就只需要找到对应的启动器就可以了.

比如:

<dependency>
  <groupId>com.baomidou</groupId>
  <artifactId>dynamic-datasource-spring-boot-starter</artifactId>
  <version>3.5.1</version>
</dependency>

5.自定义 starter

我们的多项目权限是如何做的?只需要一个登录校验即可解决,也就是自定义的 starter

1.需要封装的代码

@Configuration
@ConditionalOnWebApplication //web应用生效
@EnableConfigurationProperties(HelloProperties.class)
public class HelloServiceAutoConfiguration {
    @Autowired
    HelloProperties helloProperties;

    @Bean
    public HelloService helloService(){
        HelloService service = new HelloService();
        service.setHelloProperties(helloProperties);
        return service;
    }

}

2.spring.factories

META-INF\spring.factories

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.kuang.HelloServiceAutoConfiguration

作用:EnableAutoConfiguration 它用来记录 jar 包中需要注册到容器中的 Bean 的 qualified name,这样可以通过反射获取到对应的 class

AutoConfigurationImportSelector
它里边有一个方法:getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes),作用是加载这些定义在 spring.factories 中的 Bean。对应@ConditionalOnWebApplication

3.安装到 maven 仓库

image-20221221154030508

6.@Scheduled 不生效

if neither of two is resolvable, a local single-thread default scheduler will be created within in the registrar.

这句话意味着,如果我们不主动配置我们需要的 TaskScheduler,SpringBoot 会默认使用一个单线程的 scheduler 来处理我们用@Scheduled 注解实现的定时任务,到此我们刚才的问题就可以理解了。

因为是单个线程执行所有的定时任务,所有 task2 如果先执行,因为执行中等待了 5s,所以 task2 执行完后,task1 接着继续执行。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/108862.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

嵌入式分享合集125

一、多层板PCB设计中电源平面相对地平面要进行内缩&#xff1f; 有一些人绘制的PCB&#xff0c;在GND层和电源层会进行一定程度的内缩设计&#xff0c;那么大家有没有想过为什么要内缩呢。 需要搞清楚这个问题&#xff0c;我们需要来先了解一个知识点&#xff0c;那就是“20H”…

matlab 功率谱分析

谱分析介绍 谱分析是一种用于研究函数的数学方法。在数学中&#xff0c;谱分析的基本概念是将函数分解成不同的频率成分&#xff0c;以便更好地理解其行为。这些频率成分可以表示为正弦或余弦函数的级数和&#xff0c;称为谱线。 谱分析常用于信号处理、音频信息处理和图像处…

Windows系统增强优化工具

计算机系统优化的作用很多&#xff0c;它可以清理WINDOWS临时文件夹中的临时文件&#xff0c;释放硬盘空间&#xff1b;可以清理注册表里的垃圾文件&#xff0c;减少系统错误的产生&#xff1b;它还能加快开机速度&#xff0c;阻止一些程序开机自动执行&#xff1b;还可以加快上…

数据也能开口说话?这次汇报,老板疯狂给我点赞

年底了&#xff0c;大家的工作汇报进行得怎么样了&#xff1f; 是不是少不了各种数据&#xff1f;饼图、柱形图、条形图、折线图、散点图有没有充斥在你的 PPT 中&#xff1f; 我们出版社的数据统计一般截止到 12 月中下旬&#xff0c;所以前两天&#xff0c;我已经做完了年终…

白话说Java虚拟机原理系列【第三章】:类加载器详解

文章目录jvm.dllBootstrapLoader&#xff1a;装载系统类ExtClassLoader&#xff1a;装载扩展类AppClassLoader&#xff1a;装载自定义类双亲委派模型类加载器加载类的方式类加载器特性类加载器加载字节码到JVM的过程自定义/第三方类加载器类加载器加载字节码到哪&#xff1f;Cl…

浅谈冯诺依曼体系,操作系统和进程概念

文章目录浅谈冯诺依曼体系结构和操作系统冯诺依曼体系结构冯诺依曼体系结构图操作系统进程task_struct内容分类进程内核数据结构&#xff08;task_struct)进程对应的磁盘代码查看进程ps 列出系统中运行的进程ps ajx 查看系统中所有运行的进程ps ajx | grep 程序名 &#xff1a;…

【Linux操作系统】——在Ubuntu20.04上安装MySQL数据库

在Ubuntu上安装MySQL MySQL是一个开源数据库管理系统&#xff0c;通常作为流行的LAMP&#xff08;Linux&#xff0c;Apache&#xff0c;MySQL&#xff0c;PHP / Python / Perl&#xff09;堆栈的一部分安装。它使用关系数据库和SQL&#xff08;结构化查询语言&#xff09;来管…

类美团外卖、骑手、类快递取餐柜、整合菜品供应商、前厅、后厨、配送、智能厨电设备的智慧餐饮业务

一种商业模型之类美团外卖、骑手、类快递取餐柜、整合前厅、后厨、智能厨电设备智慧餐饮业务架构 涉及到&#xff1a; 0、基础数据管理 1、菜谱创错 2、菜谱编译 3、菜谱商业化 4、厨电管理 5、后厨管理 6、前厅管理 …

【Call for papers】SIGKDD-2023(CCF-A/数据挖掘/2023年2月2日截稿)

29TH ACM SIGKDD CONFERENCE ON KNOWLEDGE DISCOVERY AND DATA MINING. 文章目录1.会议信息2.时间节点3.论文主题1.会议信息 会议介绍&#xff1a; 29TH ACM SIGKDD CONFERENCE ON KNOWLEDGE DISCOVERY AND DATA MINING. 会议全称&#xff1a; ACM Knowledge Discovery and D…

为什么 APISIX Ingress 是比 Traefik 更好的选择?

本文可以为正在选型 Kubernetes Ingress Controller 产品的用户提供一些帮助。 作者张晋涛&#xff0c;API7.ai 云原生专家&#xff0c;Apache APISIX Committer、Kubernetes Ingress Nginx Reviewer Apache APISIX Ingress Apache APISIX Ingress 是一个使用 Apache APISIX 作…

FrameLayout布局案例

框架布局-FrameLayout 1.FrameLayout简介 1.简介&#xff1a;白话&#xff0c;墙角堆砌东西 就是开辟一个巨大的空间控件的位置不能够指定&#xff0c;默认就是左上角后面对挡住前面的2.属性 属性名称 对应方法 说明 android:foreground setForeground(Drawable) 设置绘制…

【408篇】C语言笔记-第十四章( 二叉树的建树和遍历考研真题实战)

文章目录第一节&#xff1a;冒泡排序1. 排序2. 冒泡排序第二节&#xff1a;冒泡排序实战1. 步骤2. 代码3. 时间复杂度与空间复杂度第三节&#xff1a;快速排序原理与实战1. 基本思想2. 快速排序实战3. 时间复杂度与空间复杂度第四节&#xff1a;插入排序原理及实战1. 插入排序原…

HSF 实现原理

HSF 实现原理 提供服务的流程 - server启动时候向ConfigServer注册 - client启动时候向ConfigServer请求list - client缓存list&#xff0c;发现不可用的server&#xff0c;从缓存中remove - ConfigServer通过心跳包维护可用server的list - list有更新的时候&#xff0c;…

单片机——LED

0. 单片机编程的一般步骤 目标分析&#xff1a;点亮开发板上的LED灯 电路原理图分析&#xff1a;相关器件的工作原理 数据手册分析&#xff1a;IO端口控制 代码编写、编译 下载与调试 1. LED简介 Led&#xff1a;即发光二极管&#xff0c;具有单向导通性&#xff0c;一般…

验证码、通知短信API常见使用问题

如今短信应用于我们生活工作的方方面面&#xff0c;注册或者登录一个应用可以用短信验证码快速登录&#xff0c;支付可以使用短信验证码&#xff1b;商家搞促销活动可以发送通知短信给客户&#xff0c;会员到期了商家可以发送告警短信给会员用户…可见验证码短信API和通知短信A…

JavaFX爱好者看过来,这款工具值得拥有

前言 各位CSDN的博友们&#xff0c;随着各地政策的放开&#xff0c;大伙现在是在水深火热当中呢&#xff1f;还是天选打工人-安然无羊。在这里&#xff0c;希望阳了的朋友&#xff0c;赶紧恢复健康&#xff0c;早日康复。希望没有阳的朋友们&#xff0c;继续坚持&#xff0c;万…

聊聊设计模式-解释器模式?

简介 解释器模式属于行为型模式。它是指给定一门语言&#xff0c;定义它的文法的一种表示&#xff0c;并定义一个解释器&#xff0c;该解释器使用该表示来解释语言中的句子。是一种按照规定的语法进行解析的模式 编译器可以将源码编译解释为机器码&#xff0c;让CPU能进行识别并…

C++调用matlab引擎画三维图

VS2012设置 项目–项目属性–配置属性–VC目录–包含目录 D:\MATLAB\R2016a\extern\include 项目–项目属性–配置属性–VC目录–库目录 D:\MATLAB\R2016a\extern\lib\win64\microsoft 添加依赖项有两种方法&#xff1a; 方法一&#xff1a;项目中设置 项目–项目属性–配置属…

一、线程相关概念

文章目录相关概念程序(program)进程线程单线程与多线程并发与并行相关概念 程序(program) 是为完成特定任务、用某种语言编写的一组指令的集合。简单的说:就是我们写的代码。 进程 进程是指运行中的程序&#xff0c;比如我们使用QQ&#xff0c;就启动了一个进程&#xff0c…

基于注解方式Spring Security忽略拦截

文章目录1.Spring Security忽略拦截配置2.基于配置文件注入2.1.添加配置2.2.修改Spring Security配置类2.3. 测试3.基于注解的方式过滤接口3.1.添加注解3.2.获取所有使用了IgnoreWebSecurity注解的接口访问路径3.3.测试1.Spring Security忽略拦截配置 关于Spring Securite的使…