Java Web 开发:SpringBoot

4484 字
22 分钟
Java Web 开发:SpringBoot

[TOC]

前言#

学习目标#

  1. 掌握IDEA中创建SpringBoot应用程序
  2. 理解SpringBoot中key=value形式配置文件
  3. 理解SpringBoot应用中的starter依赖功能
  4. 理解SpringBoot约定大于配置原理(面试拔高)
  5. 熟悉搭建SpringBoot应用后SpringMVC和MyBatis的使用

前置知识准备#

  • Spring配置类注册组件 → @Bean
  • SpringMVC配置类和WebMvcConfigurer接口
  • maven父工程中的dependencyManagement标签
  • SpringMVC静态资源映射配置

SpringBoot介绍#

Spring阶段最困扰大家的事情是什么? 配置 → 配置魔鬼

  • 快速搭建一个独立的生产级别的Spring应用
  • 快速引入项目相关依赖
  • 开箱即用,约定大于配置,大多数应用只需要极少的Spring配置
  • 内置JavaEE容器,可以以Jar包的方式启动

核心点约定大于配置

提供一些约定项(其实就是默认值),在应用程序启动过程中,向容器中注册默认组件

早餐案例

wtr对wyf ,明早帮我带一碗热干面和一杯豆浆,wyf会给他带什么早餐

wtr对wyf说,以后都帮我带早餐,如果没告诉你买什么,就默认给我热干面和豆浆 → 约定

wtr对wyf说,明早带个早餐,wyf会给他带什么早餐? 热干面和豆浆

几天后

wtr对wyf说,明早带个豆皮和面窝,wyf会给他带什么早餐?豆皮和面窝

创建一个SpringBoot应用#

官网#

start.spring.io选择groupId、ArtifactId、版本号、扫描包、JDK版本、项目构建方式、开发语言、引入的其他依赖来创建SpringBoot应用,点击Generate会下载一个zip压缩包,解压开就是一个SpringBoot应用,同时也是一个Maven工程

解压后会包含这样的文件,包含src目录、pom.xml文件、帮助文档、Git忽略管理配置文件、Maven相关文件

IDEA#

其实需要的配置项和在官网上创建是完全一致的,只不过选择是在IDEA中选择,另外可以选择Project和Module的路径。

新建一个新的Project,其中starter service URL就是Spring官网创建SpringBoot应用的链接

选择基本的配置项

图片缺失:C:/Users/stone/AppData/Roaming/Typora/typora-user-images/1629704422525.png

选择依赖和SpringBoot应用的版本

配置Project和Module路径

选择Finish的话,就会在对应的目录创建文件夹,并且将下载下来的zip压缩包解压到指定目录下,通过在IDEA中打开对应的应用。

pom.xml文件#

parent标签#

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

父工程

所有的SpringBoot应用都有这样的一个父工程,parent标签里有一个version标签,SpringBoot应用的版本号;修改version标签里的值就是修改使用的使用SpringBoot版本

使用父工程的话,可以共享父工程里的配置 → 相同配置的解耦

父工程打包方式是pom,可以在本地仓库里找到这个文件

父工程中的标签

  • dependencies → 子工程里会引用父工程里依赖,SpringBoot应用中其实没有用到这个标签
  • dependencyManagement → 写的dependency标签的写法和我们前面的写法是一致的 → 提供的是依赖的版本信息,如果父工程中写了一个依赖,而子工程中也写了相同的依赖(groupid和artifactId一致)
    • 如果子工程中的依赖没有写版本号,复用父工程中的版本号
    • 如果子工程中的依赖写了版本号,使用自定义的这个版本号
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

和我们之前写的dependency标签不一样的点 → 没有写版本号

但实际上有版本信息 → 父工程(的父工程)中的dependencyManagement中的来

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>2.7.7</version>
</parent>

当前SpringBoot有一个爷爷工程,这个爷爷工程就是专门管理依赖的版本信息的

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>2.7.7</version>
</dependency>
</dependencies>
</dependencyManagement>

这是不是约定大于配置?是

在引入一个依赖 mysql-connector-java

  • 不写版本号的情况下,版本信息是什么?

    • <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      </dependency>
    • image-20221227112141555
      image-20221227112141555

    • <properties>
      <mysql.version>8.0.31</mysql.version>
      </properties>
      <dependencyManagement>
      <dependencies>
      <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>${mysql.version}</version>
      <exclusions>
      <exclusion>
      <groupId>com.google.protobuf</groupId>
      <artifactId>protobuf-java</artifactId>
      </exclusion>
      </exclusions>
      </dependency>
      </dependencies>
      </dependencyManagement>
  • 写了版本号的情况下,版本信息是什么?

    • <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.47</version>
      </dependency>

提供默认的版本号信息给我们带来什么好处?

开发方便一些,兼容性

starter依赖#

引入的依赖中,artifactid中有一个starter这样的一个词,这样的依赖就叫其starter依赖

  • spring-boot-starter SpringBoot本身的依赖,所有的SpringBoot应用都有这个依赖
  • spring-boot-starter-xxx SpringBoot官方提供的依赖(groupid → org.springframework.boot),提供的是SpringBoot对xxx技术的支持
    • 比如spring-boot-starter-web 就是SpringBoot对web技术的支持
    • 比如spring-boot-starter-tomcat就是SpringBoot对Tomcat的支持
    • 比如spring-boot-starter-json 就是SpringBoot对Json的支持
  • xxx-spring-boot-starter 第三方框架提供的依赖,提供的是SpringBoot对xxx技术的支持
    • 比如mybatis-spring-boot-starter,SpringBoot对MyBatis技术的支持
    • 比如pagehelper-spring-boot-starter

通常在SpringBoot中要使用某一项技术,只需要引入其starter依赖就可以了

为什么引入其starter依赖就可以了?

  • starter依赖中关联了其他依赖,当我们引入starter依赖的时候,会将该技术所需要的其他的依赖一同引入进来
    • 举个例子:使用mybatis的话,引入mybatis-spring-boot-starter,mybatis、mybatis-spring、spring-jdbc都会被引入进来
  • starter依赖中通常会包含另外一个依赖autoconfigure依赖
    • autoconfigure依赖能够帮我们做自动配置,自动配置里最主要的是自动注册默认的组件

启动类#

SpringBoot应用最终打包为Jar包,packaging的默认值也是jar

运行jar包的命令是

Terminal window
java -jar frontend.jar

执行的jar包的main方法

jar包里的文件里的内容

Start-Class: com.cskaoyan.frontend.FrontendApplication

启动类:main方法所处的类 → 启动类的包目录就是SpringBoot应用默认的扫描包目录

整合SpringMVC#

spring-boot-starter-web

整合配置类#

@ComponentScan("com.cskaoyan.controller")
@EnableWebMvc
public class MvcConfiguration implements WebMvcConfigurer{
}
// SpringMVC阶段我们写的配置类

配置类在SpringBoot阶段是可以使用的,但是有些内容产生了变化

  • 不需要写扫描包目录了 → springboot提供的默认的扫描包目录:启动类所在的包目录
  • 在配置类上增加@EnableWebMvc或@Configuration
    • 如果使用@EnableWebMvc意味着全面接管SpringMVC的相关配置,默认配置失效
    • 如果使用@Configuration意味着做的是配置项的补充 → 建议使用

SpringBoot阶段的

//@ComponentScan() // 默认的扫描包是启动类所在的包目录
//@EnableWebMvc // 全面接管SpringMVC的配置,默认配置失效 → 我们通常不全面接管
@Configuration // 配置项的增量补充,存量(默认配置)是自动配置
public class WebConfiguration implements WebMvcConfigurer {
@Override
public void addInterceptors(InterceptorRegistry registry) {
}
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
}
}

静态资源处理#

WebMvcConfigurer接口中的addResourceHandlers(registry)

registry.addResourceHandler(“映射范围”).addResourceLocations(“静态资源所处的位置”)

上面这种方式可以在配置类中使用

SpringBoot也给我们做了默认的配置

  • mapping映射范围:/**
  • location资源所处的位置:classpath:/public/、classpath:/static/、classpath:/META-INF/等

SpringBoot给我们提供了默认配置使用的是默认值;我们仍然可以使用其默认的配置,我们可以指定自定义的值

在SpringBoot的配置文件中可以提供指定的值

配置文件是properties → key=value

我们通过指定的key提供value,SpringBoot可以自动读取这些key对应的值

spring.web.resources.static-locations=file:d:/tmp/
spring.mvc.static-path-pattern=/pic/**

静态资源处理

  • 啥都不做采用默认值
  • 配置文件中按照指定的key来提供对应的值
  • 也可以写配置类

image-20230419113209237
image-20230419113209237

Filter#

前面做Javaconfig的时候是在AACDSI,在SpringBoot应用中只需要注册到容器中就生效

仍然可以使用OncePerRequestFilter,使用Filter直接将其注册为容器中的组件就会生效

有什么好处?

配置起来方便;也可以使用容器中的其他组件

Filter这么方便,HandlerInterceptor应该也挺方便的吧?

还是配置类的配置方式 → addInterceptors方法

Tomcat配置#

端口号:server.port

上下文路径:server.servlet.context-path

image-20221227145413465
image-20221227145413465

#Tomcat配置
server.servlet.context-path=/demo2
server.port=8090

其他配置#

  • 配置类
  • 配置文件
    • prefix为:spring.web
    • prefix为:spring.mvc
    • 2早期的一些SpringBoot版本:spring.resources → 它现在变了 spring.web.resources

jackson的配置也可以直接在配置文件中配置

spring.jackson.date-format=yyyy-MM-dd HH:mm:ss
spring.jackson.time-zone=GMT+8

小结#

spring-boot-starter-web

静态资源处理配置 → 尤其关注location要写file路径

端口号配置

整合MyBatis#

mybatis-spring-boot-starter

<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.2.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.47</version>
</dependency>

应用程序中引入mybatis-spring-boot-starter这个依赖,启动程序的时候报错了

先思考一个问题:starter依赖的功能是什么?

  • 引入mybatis这项技术所需要的依赖
  • spring-boot-autoconfigure依赖 → 自动配置

刚刚报错的原因就是因为自动配置;自动配置MyBatis过程中需要注册一些组件,这些组件会被自动注册,其中有一个组件DataSource → datasource.set值的时候,发现你没有给他提供值

解决这个问题,提供数据源的值就行

spring.datasource.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&characterEncoding=utf-8
spring.datasource.username=root
spring.datasource.password=123456

SpringBoot会根据这些默认向容器中注册DataSource组件:Hikari

如果要修改数据源的类型:spring.datasource.type

# 将数据源修改为了Druid
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource

还需要提供:Mapper接口的包目录

@MapperScan → 写在启动类上就行

@SpringBootApplication
@MapperScan("com.cskaoyan.mapper")
public class Demo3MybatisApplication {}

注意一个点:写Mapper包目录的时候,就写Mapper接口的包目录;写com.cskaoyan好不好,行不行 ?不行

如果要做额外的人配置:prefix为mybatis

mybatis:
type-handlers-package: com.cskaoyan.typehandler
configuration:
cache-enabled: true
lazy-loading-enabled: true

约定大于配置原理#

配置文件中的值的获取#

我们现在自己向容器中注册一个DataSource,想把driverClassName、url、username、password这样的一些值写在配置文件中

没有从配置文件中取值#

@Configuration
public class DataSourceConfiguration1 {
String driverClassName = "com.mysql.jdbc.Driver";
String url = "jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&characterEncoding=utf-8";
String username = "root";
String password = "123456";
@Bean
public DataSource dataSource1() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}

魔法值:Magic value

@Value#

@Configuration
public class DataSourceConfiguration2 {
@Value("${cskaoyan.datasource2.driver-class-name}") //SpEL
String driverClassName; // = "com.mysql.jdbc.Driver";
@Value("${cskaoyan.datasource2.url}")
String url; // = "jdbc:mysql://localhost:3306/cskaoyan_db?useUnicode=true&characterEncoding=utf-8";
@Value("${cskaoyan.datasource2.username}")
String username; // = "root";
@Value("${cskaoyan.datasource2.password}")
String password; // = "123456";
@Bean
public DataSource dataSource2() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}

${} 引用配置文件中的值 给@Value注解对应的成员变量赋值

注意事项:在容器中的组件中才可以使用@Value

@ConfigurationProperties#

使用set方法给成员变量赋值

注意事项:在容器中的组件中才可以使用

@ConfigurationProperties仍然是从配置文件中根据key来获得对应的值 → key=@ConfigurationProperties注解的prefix属性值 + 成员变量名

@Configuration
@Data
@ConfigurationProperties(prefix = "cskaoyan.datasource3")
public class DataSourceConfiguration3 {
String driverClassName; //cskaoyan.datasource3.driver-class-name
String url;
String username;
String password;
@Bean
public DataSource dataSource3() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
}

还可以进一步的解耦,可以提供一个(参数)组件,专门用来接收来自于配置文件中的值

@Configuration
public class DataSourceConfiguration4 {
/*@Autowired
DataSourceProperties4 dataSourceProperties4;*/
DataSourceProperties4 dataSourceProperties4;
public DataSourceConfiguration4(DataSourceProperties4 dataSourceProperties4) {
this.dataSourceProperties4 = dataSourceProperties4;
}
@Bean
public DataSource dataSource4() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(dataSourceProperties4.getDriverClassName());
dataSource.setUrl(dataSourceProperties4.getUrl());
dataSource.setUsername(dataSourceProperties4.getUsername());
dataSource.setPassword(dataSourceProperties4.getPassword());
return dataSource;
}
}
@Component
@ConfigurationProperties(prefix = "cskaoyan.datasource4")
@Data
public class DataSourceProperties4 {
String driverClassName; //cskaoyan.datasource4.driver-class-name
String url;
String username;
String password;
}

@EnableConfigurationProperties和@ConfigurationProperties#

和上面最后一个案例,只有Properties组件注册过程有差别,其他的均一致

@ConfigurationProperties(prefix = "cskaoyan.datasource5")
@Data
public class DataSourceProperties5 {
String driverClassName; //cskaoyan.datasource5.driver-class-name
String url;
String username;
String password;
}
@Configuration
@EnableConfigurationProperties(DataSourceProperties5.class)
public class DataSourceConfiguration5 {
DataSourceProperties5 dataSourceProperties5;
public DataSourceConfiguration5(DataSourceProperties5 dataSourceProperties5) {
this.dataSourceProperties5 = dataSourceProperties5;
}
@Bean
public DataSource dataSource5() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(dataSourceProperties5.getDriverClassName());
dataSource.setUrl(dataSourceProperties5.getUrl());
dataSource.setUsername(dataSourceProperties5.getUsername());
dataSource.setPassword(dataSourceProperties5.getPassword());
return dataSource;
}
}

@EnableConfigurationProperties注解写在配置上,其value属性写的Class数组,将Class数组对应的类注册为容器中的组件

yml配置文件#

和properties一样都是key=value形式的配置文件,只不过语法上有区别

可以将前面的properties配置文件修改为yml配置文件

  1. 如果key是多级的,比如spring.datasource.driver-clsss-name ,这个就是3级,yml 要写 冒号、换行、空格缩进(几个空格都可以,但是同一级要对齐)
  2. 等于 yml 要写 冒号 空格
  3. 如果有相同的前缀,省略掉重复的前缀

约定大于配置说明#

SpringBoot实现约定大于配置主要做的事情是帮我们注册一些默认的组件,而默认的组件是自动自动配置类来进行配置的

那么SpringBoot应用加载哪一些自动配置类呢,主要加载的使用autoconfigure依赖中的**/META-INF/spring.factories**文件里提供了自动配置类的列表

通过org.springframework.boot.autoconfigure.EnableAutoConfiguration这个key找到对应的value就是自动配置类的列表

# /META-INF/spring.factories中的文件
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
# 截取了其中一部分

最近的几个版本的SpringBoot也提供了另外的一个位置的配置类

/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports

org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration
org.springframework.boot.autoconfigure.aop.AopAutoConfiguration
org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration
org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration
org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration
org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration
org.springframework.boot.autoconfigure.context.ConfigurationPropertiesAutoConfiguration
org.springframework.boot.autoconfigure.context.LifecycleAutoConfiguration

@EnableAutoConfiguration注解上有一个注解

@Import({AutoConfigurationImportSelector.class})
  1. @Import注解可以直接写配置类Class → 把这个配置类注册为容器中的组件
  2. @Import注解写的Class实现了Selector接口 → selectImports方法 返回值String[] → 配置类的全限定类名
public interface ImportSelector {
String[] selectImports(AnnotationMetadata importingClassMetadata);
@Nullable
default Predicate<String> getExclusionFilter() {
return null;
}
}
public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware, ResourceLoaderAware, BeanFactoryAware, EnvironmentAware, Ordered {
public String[] selectImports(AnnotationMetadata annotationMetadata) {
// 读取/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
// 读取/META-INF/spring.factories 里的 org.springframework.boot.autoconfigure.EnableAutoConfiguration这个key对应的值
}
}

注解#

@ConditionalOnXXX → 满足XXX条件其他的注解生效

@ConditionalOnMissingXXX → 满足缺少XXX条件,其他的注解生效

// 应用程序中要引入对应的类 → 要有对应的依赖 → starter引入的
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
// 容器中DataSource类型的组件只有一个
@ConditionalOnSingleCandidate(DataSource.class)

自动配置类#

文件#

配置类信息:

autoconfigure依赖/META-INF文件夹

  • spring.factories → key “xxxAutoConfiguration” 对应的值是字符串的列表,这些字符串是 自动配置类的全限定类名
  • spring文件夹/文件(文件名很长) → 这个文件里面的值 字符串的列表,这些字符串是 自动配置类的全限定类名

image-20221227173845156
image-20221227173845156

image-20221227173932842
image-20221227173932842

# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.mybatis.spring.boot.autoconfigure.MybatisLanguageDriverAutoConfiguration,\
org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration

加载过程#

首先来看启动类,启动类上包含了@SpringBootApplication注解

@SpringBootApplication
@MapperScan("com.cskaoyan.mapper")
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

在@SpringBootApplication注解中包含了@EnableAutoConfiguration注解

@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 {

在@EnableAutoConfiguration中包含了@Import({AutoConfigurationImportSelector.class}),通过Selector选择器找到对应的自动配置类

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import({AutoConfigurationImportSelector.class})
public @interface EnableAutoConfiguration {

在AutoConfigurationImportSelector中包含了selectImports方法

public String[] selectImports(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return NO_IMPORTS;
} else {
AutoConfigurationImportSelector.AutoConfigurationEntry autoConfigurationEntry = this.getAutoConfigurationEntry(annotationMetadata);
return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
}
}

然后再看selectImports方法里的this.getAutoConfigurationEntry(annotationMetadata),在该方法中包含这样的一行代码,获得配置类的字符串的List

protected AutoConfigurationImportSelector.AutoConfigurationEntry getAutoConfigurationEntry(AnnotationMetadata annotationMetadata) {
if (!this.isEnabled(annotationMetadata)) {
return EMPTY_ENTRY;
} else {
AnnotationAttributes attributes = this.getAttributes(annotationMetadata);
//这一行就是获得配置类(全限定类名)的List信息
List<String> configurations = this.getCandidateConfigurations(annotationMetadata, attributes);
configurations = this.removeDuplicates(configurations);
Set<String> exclusions = this.getExclusions(annotationMetadata, attributes);
this.checkExcludedClasses(configurations, exclusions);
configurations.removeAll(exclusions);
configurations = this.getConfigurationClassFilter().filter(configurations);
this.fireAutoConfigurationImportEvents(configurations, exclusions);
return new AutoConfigurationImportSelector.AutoConfigurationEntry(configurations, exclusions);
}
}

进入到getCandidateConfigurations方法中

protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
// 加载/META-INF/spring.factories文件中的自动配置类
List<String> configurations = new ArrayList(SpringFactoriesLoader.loadFactoryNames(this.getSpringFactoriesLoaderFactoryClass(), this.getBeanClassLoader()));
// 加载/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports文件中的自动配置类
ImportCandidates.load(AutoConfiguration.class, this.getBeanClassLoader()).forEach(configurations::add);
Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories nor 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;
}

要看/META-INF/spring.factories接下来进入到loadFactoryNames这个方法中

public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {
ClassLoader classLoaderToUse = classLoader;
if (classLoader == null) {
classLoaderToUse = SpringFactoriesLoader.class.getClassLoader();
}
String factoryTypeName = factoryType.getName();
return (List)loadSpringFactories(classLoaderToUse).getOrDefault(factoryTypeName, Collections.emptyList());
}

最后来看loadSpringFactories这个方法,这个方法的返回值为Map,这个Map的key为字符串,value为字符串列表List,这个key其实就是需要EnableAutoConfiguration,value就是自动配置类的字符串List信息

private static Map<String, List<String>> loadSpringFactories(ClassLoader classLoader) {
Map<String, List<String>> result = (Map)cache.get(classLoader);
if (result != null) {
return result;
} else {
HashMap result = new HashMap();
try {
///META-INF/spring.factories
Enumeration urls = classLoader.getResources("META-INF/spring.factories");
//略掉很多代码
}
}
}

到这里大家其实可以看到最终加载的就是/META-INF/spring.factories文件

要看/META-INF/spring/xxx.imports

/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
public static ImportCandidates load(Class<?> annotation, ClassLoader classLoader) {
Assert.notNull(annotation, "'annotation' must not be null");
ClassLoader classLoaderToUse = decideClassloader(classLoader);
String location = String.format("META-INF/spring/%s.imports", annotation.getName());
Enumeration<URL> urls = findUrlsInClasspath(classLoaderToUse, location);
ArrayList importCandidates = new ArrayList();
while(urls.hasMoreElements()) {
URL url = (URL)urls.nextElement();
importCandidates.addAll(readCandidateConfigurations(url));
}
return new ImportCandidates(importCandidates);
}

自动配置类#

加载自动配置类,并且配置的生效是有条件的,包含@ConditionalOnXXX和@ConditionalOnMissingXXX这样的注解,满足一定的条件时生效和不满足一定的条件的时候生效

比如@ConditionalOnClass也就是包含对应的类的时候生效(也就是导包以后生效)

@ConditionalOnMissingBean当没有某个组件的时候生效,生效就会导致注册一个默认组件;如果自行注册组件,那么这个默认组件失效;其实这个就是约定大于配置

@Configuration(
proxyBeanMethods = false
)
@ConditionalOnClass({JdbcTemplate.class, TransactionManager.class})
@AutoConfigureOrder(2147483647)
@EnableConfigurationProperties({DataSourceProperties.class})
public class DataSourceTransactionManagerAutoConfiguration {
public DataSourceTransactionManagerAutoConfiguration() {
}
@Configuration(
proxyBeanMethods = false
)
@ConditionalOnSingleCandidate(DataSource.class)
static class JdbcTransactionManagerConfiguration {
JdbcTransactionManagerConfiguration() {
}
@Bean
@ConditionalOnMissingBean({TransactionManager.class})
DataSourceTransactionManager transactionManager(Environment environment, DataSource dataSource, ObjectProvider<TransactionManagerCustomizers> transactionManagerCustomizers) {
DataSourceTransactionManager transactionManager = this.createTransactionManager(environment, dataSource);
transactionManagerCustomizers.ifAvailable((customizers) -> {
customizers.customize(transactionManager);
});
return transactionManager;
}
private DataSourceTransactionManager createTransactionManager(Environment environment, DataSource dataSource) {
return (DataSourceTransactionManager)((Boolean)environment.getProperty("spring.dao.exceptiontranslation.enabled", Boolean.class, Boolean.TRUE) ? new JdbcTransactionManager(dataSource) : new DataSourceTransactionManager(dataSource));
}
}
}

@Bean和@ConditionalOnMissingBean通常同时出现

如果你没有注册组件,那么@Bean生效,注册默认组件

如果你注册了组件,那么@Bean不会生效,就不会注册默认组件,以你注册的组件为准

配置文件配置项#

都在autoconfigure依赖中的/META-INF路径下,包含了(additional-)spring-configuration-metadata.json文件,这个文件中包含了你可以做的配置项有哪些,并且这些配置的描述、默认值、值的格式等

值的格式

{
"name": "spring.web.resources.static-locations",
"type": "java.lang.String[]",
"description": "Locations of static resources. Defaults to classpath:[\/META-INF\/resources\/, \/resources\/, \/static\/, \/public\/].",
"sourceType": "org.springframework.boot.autoconfigure.web.WebProperties$Resources",
"defaultValue": [
"classpath:\/META-INF\/resources\/",
"classpath:\/resources\/",
"classpath:\/static\/",
"classpath:\/public\/"
]
}

但是呢,让你写json文件,是为难你,引入一个依赖,这个依赖会帮我们新增对应的json

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

rerun你的应用程序,重新启动一下

如果没有生成:在resources目录下新增一个文件夹 META-INF

完结撒花#

彩蛋banner

文章分享

如果这篇文章对你有帮助,欢迎分享给更多人!

Java Web 开发:SpringBoot
https://firefly-mu-weld.vercel.app/posts/16-springboot/
作者
Daisy
发布于
2026-06-10
许可协议
CC BY-NC-SA 4.0
Profile Image of the Author
Daisy
Hello, I'm Daisy.
公告
欢迎来到我的博客!这是一则示例公告。
分类
标签

文章目录