SpringBoot项目建立及一些启动细节

# 初始化Spring Boot

Spring Initializer 是目前最方便的 Spring Boot 初始化工具,它可以帮助开发人员自动化构建 Spring Boot 项目目录,导入必要依赖,省去了前期的准备工作。

在IDEA中,选择New -> Project, 选择Spring Initializer, 输入必要的软件包信息,勾选需要的依赖信息,选择包管理框架, 系统将自动构建起项目目录结构,并导入勾选的依赖包。

# 依赖结构解析

Spring Boot 建立后不需要进行任何配置,可以直接启动@SpringBootApplication注解的类,不需要配置资源文件位置,不需要配置 IoC, 甚至不需要 Tomcat. 有时候我们看到一些技术文章会说 Spring Boot 在引入一些依赖时不需要填写版本号,究其原因都是 spring boot 启动器的一些配置和注解为开发者隐藏了这些繁杂的配置接口,这些隐藏方便了对 Spring 熟悉的开发人员,使他们可以快速进行软件开发,但是对于初学者来说可能会摸不着头脑,很奇怪这些配置都去哪了。

打开包管理文件 pom.xml 我们可以看到,项目首先导入了父项目 spring-boot-stater-parent:

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-parent</artifactId>
  <version>2.2.1.RELEASE</version>
  <relativePath/> <!-- lookup parent from repository -->
</parent>
1
2
3
4
5
6

# 1. Spring-boot-starter-parent 解析

进入 spring-boot-starter-parent, 可以看到该父结构又引入了一个新的父结构 spring-boot-dependencies

<parent>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-dependencies</artifactId>
  <version>2.2.1.RELEASE</version>
  <relativePath>../../spring-boot-dependencies</relativePath>
</parent>
1
2
3
4
5
6

spring-boot-dependencies解析见下方,这里继续往下看。parent 首先定义了一些 java 运行版本,编码信息等参数,往下浏览可以看到 resources:

<resources>
  <resource>
    <filtering>true</filtering>
    <directory>${basedir}/src/main/resources</directory>
    <includes>
      <include>**/application*.yml</include>
      <include>**/application*.yaml</include>
      <include>**/application*.properties</include>
    </includes>
  </resource>
  <resource>
    <directory>${basedir}/src/main/resources</directory>
    <excludes>
      <exclude>**/application*.yml</exclude>
      <exclude>**/application*.yaml</exclude>
      <exclude>**/application*.properties</exclude>
    </excludes>
  </resource>
</resources>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

这段代码标记了资源文件的位置,这也解释了为什么 Srping Boot 为什么会自动加载以 application-xxx 命名的配置文件。再往下文件描述了一些插件的默认配置。

# 2. Spring-boot-dependencies 解析

打开spring-boot-dependencies我们马上就会明白为什么我们在导入某些依赖的时候不需要指定版本名称:dependencies 内部为我们定义了大多数常见的依赖版本

<properties>
  <activemq.version>5.15.10</activemq.version>
  <antlr2.version>2.7.7</antlr2.version>
  <appengine-sdk.version>1.9.76</appengine-sdk.version>
  <artemis.version>2.10.1</artemis.version>
  <aspectj.version>1.9.4</aspectj.version>
  <assertj.version>3.13.2</assertj.version>
  <atomikos.version>4.0.6</atomikos.version>
  <awaitility.version>4.0.1</awaitility.version>
  <bitronix.version>2.1.4</bitronix.version>
  <build-helper-maven-plugin.version>3.0.0</build-helper-maven-plugin.version>
  <byte-buddy.version>1.10.2</byte-buddy.version>
  <caffeine.version>2.8.0</caffeine.version>
  ... 
1
2
3
4
5
6
7
8
9
10
11
12
13
14

当我们引入对应的依赖时,dependencies 会自动帮我们进行依赖匹配,如果与自己预先定义的列表中一致,会自动加载对应的版本和配置。

# 3. Spring-boot-starter-web 解析

在我们引入一种模块依赖(这里以Web为例),我们发现只需要引入一个spring-boot-starter-web即可完整启动我们的项目。这种spring-boot-starter-*为名称的依赖被称为 spring-boot 场景启动器, 打开spring-boot-starter-web, 可以看到:

<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
    <version>2.2.1.RELEASE</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-json</artifactId>
    <version>2.2.1.RELEASE</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-tomcat</artifactId>
    <version>2.2.1.RELEASE</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-validation</artifactId>
    <version>2.2.1.RELEASE</version>
    <scope>compile</scope>
    <exclusions>
      <exclusion>
        <artifactId>tomcat-embed-el</artifactId>
        <groupId>org.apache.tomcat.embed</groupId>
      </exclusion>
    </exclusions>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-web</artifactId>
    <version>5.2.1.RELEASE</version>
    <scope>compile</scope>
  </dependency>
  <dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.1.RELEASE</version>
    <scope>compile</scope>
  </dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45

看到这里就完全明白,Spring Boot启动时所需要的Spring, Spring MVC, Spring Web 依赖原来都被集成到一个 starter 里面了。场景启动器帮助我们将对应场景(比如web, mq, mail 等等)的所需依赖进行封装,这使得模块引入变得十分简单友好。在开发过程中,开发人员可以由简入繁,先通过通用配置形成Demo框架,再单独深入修改配置,实现具体项目。

# Spring Boot 入口详解

打开主程序入口,我们可以看到入口类由@SpringBootApplication进行注解:

@SpringBootApplication
public class DanmuApplication {
    public static void main(String[] args) {
        SpringApplication.run(DanmuApplication.class, args);
    }
}
1
2
3
4
5
6

Spring Boot 摆脱 IoC 配置文件而自动化运行的秘密就在这个注解里,点击进入注解我们可以看到该注解是一个复合注解:

@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 {
	...
}
1
2
3
4
5
6
7
8
9
10
11
12

主要的三个注解 @SpringBootConfiguration, @EnableAutoConfiguration@ComponentScan 是 Spring Boot 自动化配置的核心。

# 1. @SpringBootConfiguration

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration {
    @AliasFor(
        annotation = Configuration.class
    )
    boolean proxyBeanMethods() default true;
}
1
2
3
4
5
6
7
8
9
10

进入@SpringBootConfiguration 我们可以发现,该注解其实就是 @Configuration在 Spring Boot 下的封装(科技以换名为主)。@Configuration将一个类标记为配置类(本质上是一个@Component),将类内的@Bean将被纳入到 Spring 容器中。

# 2. @EnableAutoConfiguration

此注解根据名字翻译是“开启自动配置功能”,可想而知,项目内的所有自动配置都是该注解实现的。进入该注解,可以看到:

@AutoConfigurationPackage
@Import(AutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration {...}
1
2
3

首先看@AutoConfigurationPackage:

@Import(AutoConfigurationPackages.Registrar.class)
public @interface AutoConfigurationPackage {}
1
2

@Import是 Spring 的底层注解,给容器导入一些组件,进入AutoConfigurationPackages.Registrar 类,可以看到:

static class Registrar implements ImportBeanDefinitionRegistrar, DeterminableImports {
  @Override
  public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
    register(registry, new PackageImport(metadata).getPackageName());
  }
}
1
2
3
4
5
6

registerBeanDefinitions()传递了注解的元信息,根据元信息获取了注解的包名。回想我们的主配置注解@SpringBootApplicaiont 是注解在入口类的,因此该函数得到了我们当前包下的所有的所有组件。Spring Boot 可以不用配置 xml 而获取到包下的所有组件的原理就在于此。

继续看下方的@Import(AutoConfigurationImportSelector.class)注解,进入 AutoConfigurationImportSelector.class

public class AutoConfigurationImportSelector implements DeferredImportSelector, BeanClassLoaderAware,
	...
	@Override
	public String[] selectImports(AnnotationMetadata annotationMetadata) {
		if (!isEnabled(annotationMetadata)) {
			return NO_IMPORTS;
		}
		AutoConfigurationMetadata autoConfigurationMetadata = AutoConfigurationMetadataLoader
				.loadMetadata(this.beanClassLoader);
		AutoConfigurationEntry autoConfigurationEntry = getAutoConfigurationEntry(autoConfigurationMetadata,
				annotationMetadata);
		return StringUtils.toStringArray(autoConfigurationEntry.getConfigurations());
	}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14

selectImports()会给容器自动导入若干启动配置类,免去了手动编写配置文件的工作。观察代码发现,该方法通过getAutoConfigurationEntry()来家在我们需要的配置信息,并最终转换为String数组, 继续深入该方法:

protected AutoConfigurationEntry getAutoConfigurationEntry(AutoConfigurationMetadata autoConfigurationMetadata,
			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 = filter(configurations, autoConfigurationMetadata);
		fireAutoConfigurationImportEvents(configurations, exclusions);
		return new AutoConfigurationEntry(configurations, exclusions);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

看到配置信息由getCandidateConfigurations()获取:

/**
	 * Return the auto-configuration class names that should be considered. By default
	 * this method will load candidates using {@link SpringFactoriesLoader} with
	 * {@link #getSpringFactoriesLoaderFactoryClass()}.
	 * @param metadata the source metadata
	 * @param attributes the {@link #getAttributes(AnnotationMetadata) annotation
	 * attributes}
	 * @return a list of candidate configurations
	 */
protected List<String> getCandidateConfigurations(AnnotationMetadata metadata, AnnotationAttributes attributes) {
  	List<String> configurations = SpringFactoriesLoader.loadFactoryNames(getSpringFactoriesLoaderFactoryClass(),
				getBeanClassLoader());
		Assert.notEmpty(configurations, "No auto configuration classes found in META-INF/spring.factories. If you "
				+ "are using a custom packaging, make sure that file is correct.");
		return configurations;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

根据该函数的注释我们明白,该函数默认通过SpringFactoriesLoader实现自动配置的加载,进入SpringFactoriesLoader可以看到:

// SpringFactoriesLoader
public final class SpringFactoriesLoader {
   public static final String FACTORIES_RESOURCE_LOCATION = "META-INF/spring.factories";
 	 ...
}
1
2
3
4
5

默认的配置资源写在了一个文件中,我们在org.springframework.boot.autoconfigure包中找到这个文件:

# 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,\
...
1
2
3
4
5
6

由此可见,Spring Boot 在启动的时候,会从包内的META-INF/spring.factories获取指定值,并将这些值作为自动配置类导入容器中,帮助我们进行自动配置工作,以前在 Spring 中我们自己需要手动配置的文件都交由Spirng Boot AutoConfigure 帮助我们完成了。

# 配置文件

查看依赖结构文件我们知道了,Spring Boot 使用全局配置文件名: application.propertiesapplication.yml

这里展示一些常用配置,其他配置可以查看这里

server:
	# 部署端口
  port: 8888
  # 部署根路径
  context-path: /
1
2
3
4
5

有时候把所有的配置写在一个文件里可能不美观,如果希望使用加载多个配置文件,需要在主文件里添加:

spring:
  profiles:
  	active: file1,file2
1
2
3

不同文件用逗号隔开,同时文件命名为application-file1.ymlapplication-file2.yml