IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 【檀越剑指大厂—Springboot】Springboot高阶 -> 正文阅读

[Java知识库]【檀越剑指大厂—Springboot】Springboot高阶

一.整体介绍

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 接着继续执行。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2022-12-25 10:52:38  更:2022-12-25 10:54:14 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/27 8:26:14-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码