系列笔记:
- SpringBoot实战(1)-Spring 4.x
- SpringBoot实战(2)-Spring MVC 4.x
- SpringBoot实战(3)-Spring Boot
- SpringBoot实战(4)-Spring Boot Web&Data
- SpringBoot实战(5)-Spring Boot 企业级开发
- SpringBoot实战(6)-Spring Boot 开发部署与测试
- SpringBoot实战(7)-Spring Boot 应用监控
- SpringBoot实战(8)-Spring Boot 分布式系统开发
==约定优于配置,是 Spring Boot 的主导思想。==
Spring Boot 基础
随着动态语言的流行(Ruby、Groory、Scala、Node.js),Java的开发显得格外的笨重: 繁多的配置、低下的开发效率、复杂的部署流程以及第三方技术集成难度大。
在上述环境下,Spring Boot应运而生。它使用"习惯优于配置"(项目中存在大量的配置,此外还内置一个习惯性的配置,让你无须手动进行配置)的理念让你的项目快速运行起来。使用Sprine Boot很容易创建一个独立运行(运行jar,内嵌Servlet容器)、准生产级别的基于Spring框架的项目,使用SpringBoot你可以不用或者只需要很少的配置。
Spring Boot 核心功能
- 1.独立运行的Spring项目
Spring Boot 可以以jar包的形式独立运行,运行一个Spring Boot项目只需通过
java xx.jar
来运行。
- 2.内嵌Servlet容器
Spring Boot 可选择内嵌Tomcat、Jetty 或者Undertow,这样我们无须以war包形式部署项目。
- 3.提供 starter 简化配置
当对应的starter 被选中后,与这些技术相关的Spring的Bean将会被自动配置。
- 4.自动配置Spring
- 5.准生产的应用监控
提供基于http、ssh、telnet 对运行时的项目进行监控
- 6.无代码生成和xml配置
Spring 4.x 提倡使用Java配置和注解配置组合,而Sping Boot不需要任何xml配置即可实现Spring的所有配置。
Spring Boot CLI 是SpringBoot提供的控制台命令工具。
Spring Boot 核心
@SpringBootApplication
@SpringBootApplication 是 SpringBoot项目的核心注解,主要目的是开启自动配置。它是一个组合注解,主要组合了@SpringBootConfiguration,@EnableAutoConfiguration,@ComponentScan
。其中,@EnableAutoConfiguration
让SpringBoot根据类路径中的jar包依赖为当前项目进行自动配置。
SpringBoot会自动扫描@SpringBootApplication所在类的同级包以及下级包里的Bean。建议入口类放置的位置在groupId+arctifactId组合的包名下。
还可以使用exclude参数关闭特定的自动配置。例如:
@SpringBootApplication(exclude={DataSourceAutoConfiguration.class})
可实现CommandLineRunner
接口
如果需要在项目启动后执行一些操作,可以通过实现CommandLineRunner
接口的 run 方法来实现。
如果存在多个,还可以通过@Order
注解(或者实现Order
接口)来表明执行顺序。
@SpringBootApplication
public class DemoApplication implements CommandLineRunner {
public static void main(String[] args) {
SpringApplication.run(DemoApplication .class, args);
}
@Override
public void run(String... args) throws Exception {
logger.info(">>>>>>>>>>>>>>> 服务启动后执行... <<<<<<<<<<<<<");
// xxx
}
}
定制Banner
可参考Spring Boot学习2——自定义banner一文。
从Spring Boot 2.0开始 启动时的 ASCII 图像 Spring Boot banner 现已支持 ==GIF==。
配置文件
使用一个全局的配置文件 application.properties 或 application.yml,放置在src/main/resources
目录或者类路径的/config
下。
yaml 是以数据为中心的语言,在配置数据的时候具有面向对象的特征。
# properties
server.port=8080
server.context-path=/
## VS
# yml
server:
port: 8080
contextPath: /
Spring Boot 的参数配置会按照下列的优先级顺序进行加载:
- 命令行参数
- 来自 java:comp/env 的JNDI 属性
- Java 系统属性(System.getProperties())
- 操作系统环境变量 :
- RandomValuePropertySource 配置的 random.* 属性值
- jar 包外部的 application-.properties 或 application.yml ( 带 spring.profile )配置文件
- jar 包内部的 application-.properties 或 application.yml (带 spring.profile )配置文件
- jar 包外部的 application.properties 或 application.yml (不带 spring.profile )配置文件
- jar 包内部的 application.properties 或 application.ym (不带 spring.profile )配置文件:
- @Configuration 注解类上的 @PropertySource
- 通过 SpringApplication.setDefaultProperties 指定的默认属性
starter pom
Spring Boot 为我们提供了简化企业级开发绝大多数场景的 starter pom,只要使用了应用场景所需要的starter pom,相关的技术配置将会消除,就可以得到SpringBoot为我们提供的自动配置的Bean。
官方和第三方都有为SpringBoot提供 startrt pom。
使用XML配置
提倡零配置,即无xml配置,特殊要求必须使用xml配置时,可以通过Spring 提供的@ImpoerResource
来加载xml配置,例如:
@ImportResource({"classpath:xxx-context.xml", "classpath:yyy-context.xml"})
外部配置
Spring Boot允许使用 properties 文件,yaml 文件或者命令行参数作为外部配置。
例如:
# 指定服务端口
java -jar xx.jar --server.port=8080
# 指定Profile
java -jar xx.jar --spring.profiles.active=prod
# 指定外部配置文件
java –jar xx.jar --spring.config.location=xx/xx.properties
在SpringBoot里,我们可以通过
@Value
注入application.properties文件中定义的属性。而在常规的Spring环境下,需要通过 @PropertySource 指明 properties 文件的位置,然后再通过@Value注入。
类型安全的配置(基于properties)
使用@Value注入每个配置显得很麻烦,Spring Boot 还提供了基于类型安全的配置方式,通过@ConfigurationProperties
将properties属性和一个Bean及其属性关联,从而实现类型安全的配置。SpringBoot的自动配置就是基于此实现的。
具体使用示例可以参考Spring Boot学习1——配置文件。
日志配置
SpringBoot支持Java Util Logging、Log4j、log4j2、Logback作为日志框架,且为各日志框架的控制台输出和文件输出做好了配置。
默认情况下,SpringBoot使用Logback作为日志框架。
示例:
## 配置日志文件
logging.file=D:/xxx/log.log
## 配置日志级别,格式为logging.level.包名=级别
logging.level.org.springframework.web=DEBUG
## 开发中经常出现和参数类型相关的4XX错误,设置此项我们会看到更详细的错误信息。
具体可参考Spring Boot学习3——日志管理一文。
Profile 配置
Profile是Spring用来针对不同的环境对不同的配置提供支持的,全局Profile配置使用 application-.proeprties (如:application-prod.proeprties)。
通过在application.proeprties中设置spring.profiles.active=prod
来指定活动的Profile。
也可以通过命令行来设置:
java -jar xxx.jar --spring.profiles.active=prod
除了spring.profiles.active来激活一个或者多个profile之外,还可以用spring.profiles.include来叠加profile。
@Profile使用示例:
@Component
@Profile("test")
public class TestDBConnector {
@Override
public void configure() {
System.out.println("testdb");
}
}
可以如下总结多环境的配置思路:
application.properties 中配置通用内容,并设置 spring.profiles.active=dev,代表以开发环境为默认配置。
application-.properties 中配置各个环境不同的内容,然后通过命令行方式去激活不同环境的配置。
SpringBoot运行原理
SpringBoot中关于自动配置的源码在spring-boot-autoconfigure-x.x.x.jar
内。若想知道SpringBoot为我们做了哪些自动配置,可以查看这里的源码。
可以通过以下三种方式查看当前项目中已启用和未启用的自动配置的报告:
- 1.运行jar时增加 --debug 参数
java -jar xx.jar --debug
- 2.在 application.properties 中设置属性
debug=true
- 3.在 STS 中设置 VM arguments:
-Ddebug
。
启动会在控制台看到已启用的自动配置为:
============================
CONDITIONS EVALUATION REPORT
============================
Positive matches: 启用的自动配置
-----------------
....
Negative matches: 未启动的
-----------------
....
运作原理
前面提到过@SpringBootApplication是一个组合注解,它的核心功能是由@EnableAutoConfiguration注解提供的。其摘要源码如下(SpringBoot 2.1.1):
@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 {};
}
这里的关键功能是@Import 注解导入的配置功能,AutoConfigurationImportSelector使用SpringFactoriesLoader.loadFactoryNames()方法来扫描具有**META-INF/spring.factories
**文件的jar包,而前面提到的spring-boot-autoconfigure-x.x.x.jar
里就有一个spring.factories文件,此文件中声明了有哪些自动配置,摘要如下:
# Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener
# Application Listeners
org.springframework.context.ApplicationListener=\
org.springframework.boot.autoconfigure.BackgroundPreinitializer
# Auto Configuration Import Listeners
org.springframework.boot.autoconfigure.AutoConfigurationImportListener=\
org.springframework.boot.autoconfigure.condition.ConditionEvaluationReportAutoConfigurationImportListener
# Auto Configuration Import Filters
org.springframework.boot.autoconfigure.AutoConfigurationImportFilter=\
org.springframework.boot.autoconfigure.condition.OnBeanCondition,\
org.springframework.boot.autoconfigure.condition.OnWebApplicationCondition
# Auto Configure
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.boot.autoconfigure.admin.SpringApplicationAdminJmxAutoConfiguration,\
org.springframework.boot.autoconfigure.webservices.client.WebServiceTemplateAutoConfiguration
# Failure analyzers
org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.autoconfigure.diagnostics.analyzer.NoSuchBeanDefinitionFailureAnalyzer,\
org.springframework.boot.autoconfigure.session.NonUniqueSessionRepositoryFailureAnalyzer
# Template availability providers
org.springframework.boot.autoconfigure.template.TemplateAvailabilityProvider=\
org.springframework.boot.autoconfigure.freemarker.FreeMarkerTemplateAvailabilityProvider
核心注解
打开上面任意一个AutoConfiguation文件,一般都有下面的条件注解,在spring-boot-autoconfigure-x.x.x.jar
的org.springframwork.boot.autoconfigure.condition
包下,条件注解如下:
@ConditionalOnBean: 当容器里有指定的Bean的条件下。
@ConditionalOnClass: 当类路径下有指定的类的条件下。
@ConditionalOnExpression: 基于SpEL表达式作为判断条件。
@ConditionalOnJava: 基于JVM版本作为判断条件。
@ConditionalOnJndi: 在JNDI存在的条件下查找指定的位置。
@ConditionalOnMissingBean: 当容器里没有指定Bean的情况下。
@ConditionalOnMissingClass: 当类路径下没有指定的类的条件下。
@ConditionalOnNotWebApplication: 当前项目不是Web项目的条件下。
@ConditionalOnProperty: 指定的属性是否有指定的值。
@ConditionalOnResource: 类路径是否有指定的值。
@ConditionalOnSingleCandidate: 当指定 Bean 在容器中只有一个,或者虽然有多个但是
指定首选的Bean。
@ConditionalOnWebApplication: 当前项目是Web项目的条件下。
这些注解都是组合了
@Conditional
元注解,只是使用了不同的条件(Condition)。
分析 @ConditionOnWebApplication 注解
@ConditionOnWebApplication 主要源码分析:
package org.springframework.boot.autoconfigure.condition;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.Conditional;
@Target({ ElementType.TYPE, ElementType.METHOD })
@Retention(RetentionPolicy.RUNTIME)
@Documented
// 使用了 OnWebApplicationCondition 条件
@Conditional(OnWebApplicationCondition.class)
public @interface ConditionalOnWebApplication {
// ...
}
OnWebApplicationCondition 源码分析:
SpringBoot2/Spring5 引入了ReactiveWeb编程,所以要比 SpringBoot1/Spring4要复杂一些,Spring Boot1 只需要判断isWebApplication() 方法
-
其中 isServletWebApplication() 判断条件是:
-
org.springframework.web.context.support.GenericWebApplicationContext
是否在类路径中 -
容器中是否有名为session的scope
-
当前容器的 Enviroment 是否为
ConfigurableWebEnvironment
-
当前的 ResourceLoader 是否为
WebApplicationContext
(ResourceLoader 是 ApplicationCOntext 的顶级接口之一) -
isReactiveWebApplication() 判断条件是:
-
org.springframework.web.reactive.HandlerResult
是否在类路径中 -
当前容器的 Enviroment 是否为
ConfigurableReactiveWebEnvironment
-
当前的 ResourceLoader 是否为
ReactiveWebApplicationContext
最终通过 ConditionOutcome.isMatch() 方法返回布尔值来确定条件
简单分析 HttpEncodingAutoConfiguration 自动配置源码
源码摘要:
@Configuration
// 开启属性注入,通过@EnableConfigurationProperties声明
@EnableConfigurationProperties(HttpProperties.class)
// 使用了 OnWebApplicationCondition 条件,且要求为servlet web application
@ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET)
// 当 CharacterEncodingFilter 在类路径的条件下
@ConditionalOnClass(CharacterEncodingFilter.class)
// 当设置 spring.http.encoding=enabled 的情况下,如果没有设置则默认为 true,即符合条件
@ConditionalOnProperty(prefix = "spring.http.encoding", value = "enabled", matchIfMissing = true)
public class HttpEncodingAutoConfiguration {
@Bean // 像使用 Java 配置的方式配置 CharacterEncodingFilter 这个 Bean
@ConditionalOnMissingBean // 当容器中没有这个Bean的时候新建Bean。
public CharacterEncodingFilter characterEncodingFilter() {
CharacterEncodingFilter filter = new OrderedCharacterEncodingFilter();
filter.setEncoding(this.properties.getCharset().name());
filter.setForceRequestEncoding(this.properties.shouldForce(Type.REQUEST));
filter.setForceResponseEncoding(this.properties.shouldForce(Type.RESPONSE));
return filter;
}
}
再接着分析下 HttpProperties 配置类(SpringBoot1中为HttpEncodingProperties):
// 在 application.properties 配置的时候前缀是 spring.http
@ConfigurationProperties(prefix = "spring.http")
public class HttpProperties {
private final Encoding encoding = new Encoding();
public static class Encoding {
// 默认编码方式是 UTF-8,若修改可以使用spring.http.encoding.charset=编码
public static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8;
private Charset charset = DEFAULT_CHARSET;
// forceEncoding,若修改可以使用spring.http.encoding.force=true/false
private Boolean force;
}
}
启动引导:Spring Boot 应用启动的秘密
1.SpringApplication 初始化
SpringBoot 整个启动流程分为两个步骤:初始化一个org.springframework.boot.SpringApplication
对象,执行该对象的 run 方法。看下 SpringApplication 的初始化流程, SpringApplication 的构造方法代码如下:
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
// 判断是否是web项目
this.webApplicationType = WebApplicationType.deduceFromClasspath();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
// 找到入口类
this.mainApplicationClass = deduceMainApplicationClass();
}
2.Spring Boot 启动流程
Spring Boot 应用的整个启动流程都封装在 SpringApplication.run()
方法中,其整个流程真的是太长太长了,但本质上就是在 Spring 容器启动的基础上做了大量的扩展,按照这个思路来看看源码:
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch();
stopWatch.start();
ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
configureHeadlessProperty();
// 1
SpringApplicationRunListeners listeners = getRunListeners(args);
listeners.starting();
try {
// 2
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);
configureIgnoreBeanInfo(environment);
// 3
Banner printedBanner = printBanner(environment);
// 4
context = createApplicationContext();
// 5
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context);
// 6
prepareContext(context, environment, listeners, applicationArguments,
printedBanner);
// 7
refreshContext(context);
// 8
afterRefresh(context, applicationArguments);
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
// 9
listeners.started(context);
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
}
try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}
定制启动过程方法可参考Spring Boot 启动过程定制化
SpringBoot 获取运行的目录位置
ApplicationHome ah = new ApplicationHome(getClass());
/**
* 在 IDE 直接运行工程,返回(针对gradle构建的):
* XXX\out\production\classes
* 以 jar 运行,返回:
* XXX\build\libs\XXX.jar
*/
File jarF = ah.getSource();
评论区