1、自定义SpringApplication
自定义bannnr
public static void main(String[] args){
SpringApplication app = new SpringApplication(MySpringConfiguration.class);
app.setShowBanner(false);
app.run(args);
}
流畅式API构建
new SpringApplicationBuilder()
.showBanner(false)
.sources(Parent.class)
.child(Application.class)
.run(args);
时间和监听
可以使用多种方式注册事件监听器,最普通的是使用SpringApplication.addListeners(…)方法。在你的应用运行时,应用事
件会以下面的次序发送:
1. 在运行开始,但除了监听器注册和初始化以外的任何处理之前,会发送一个ApplicationStartedEvent。
2. 在Environment将被用于已知的上下文,但在上下文被创建前,会发送一个ApplicationEnvironmentPreparedEvent。
3. 在refresh开始前,但在bean定义已被加载后,会发送一个ApplicationPreparedEvent。
4. 启动过程中如果出现异常,会发送一个ApplicationFailedEvent。
web环境
在默认情况下,使用
AnnotationConfigApplicationContext或AnnotationConfigEmbeddedWebApplicationContext取决于你正在开发的是否是web
应用。
用于确定一个web环境的算法相当简单(基于是否存在某些类)。如果需要覆盖默认行为,你可以使用
setWebEnvironment(boolean webEnvironment)。通过调用setApplicationContextClass(…),你可以完全控制
ApplicationContext的类型。
注:当JUnit测试里使用SpringApplication时,调用setWebEnvironment(false)是可取的。
命令行启动
如果你想获取原始的命令行参数,或一旦SpringApplication启动,你需要运行一些特定的代码,你可以实现
CommandLineRunner接口。在所有实现该接口的Spring beans上将调用run(String… args)方法。
import org.springframework.boot.*
import org.springframework.stereotype.*
@Component
public class MyBean implements CommandLineRunner {
public void run(String... args) {
// Do something...
}
}
如果一些CommandLineRunner beans被定义必须以特定的次序调用,你可以额外实现org.springframework.core.Ordered接
口或使用org.springframework.core.annotation.Order注解。
退出
每个SpringApplication在退出时为了确保ApplicationContext被优雅的关闭,将会注册一个JVM的shutdown钩子。所有标准的
Spring生命周期回调(比如,DisposableBean接口或@PreDestroy注解)都能使用。
此外,如果beans想在应用结束时返回一个特定的退出码(exit code),可以实现
org.springframework.boot.ExitCodeGenerator接口
2、外化配置使用
Spring Boot使用一个非常特别的PropertySource次序来允许对值进行合理的覆盖,需要以下面的次序考虑属性:
1. 命令行参数
2. 来自于java:comp/env的JNDI属性
3. Java系统属性(System.getProperties())
4. 操作系统环境变量
5. 只有在random.*里包含的属性会产生一个RandomValuePropertySource
6. 在打包的jar外的应用程序配置文件(application.properties,包含YAML和profile变量)
7. 在打包的jar内的应用程序配置文件(application.properties,包含YAML和profile变量)
8. 在@Configuration类上的@PropertySource注解
9. 默认属性(使用SpringApplication.setDefaultProperties指定
配置随机值
RandomValuePropertySource在注入随机值(比如,密钥或测试用例)时很有用。它能产生整数,longs或字符串,比如:
my.secret=${random.value}
my.number=${random.int}
my.bignumber=${random.long}
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是最大的值(不包含在内)
命令行属性
默认情况下,SpringApplication将任何可选的命令行参数(以'--'开头,比如,--server.port=9000)转化为property,并将其
添加到Spring Environment中。如上所述,命令行属性总是优先于其他属性源。
如果你不想将命令行属性添加到Environment里,你可以使用SpringApplication.setAddCommandLineProperties(false)来禁
止它们。
Application属性文件
SpringApplication将从以下位置加载application.properties文件,并把它们添加到Spring Environment中:
1. 当前目录下的一个/config子目录
2. 当前目录
3. 一个classpath下的/config包
4. classpath根路径(root)
这个列表是按优先级排序的(列表中位置高的将覆盖位置低的)。
注:你可以使用YAML('.yml')文件替代'.properties'。
如果不喜欢将application.properties作为配置文件名,你可以通过指定spring.config.name环境属性来切换其他的名称。你也
可以使用spring.config.location环境属性来引用一个明确的路径(目录位置或文件路径列表以逗号分割)。
$ java -jar myproject.jar --spring.config.name=myproject
//or
$ java -jar myproject.jar --spring.config.location=classpath:/default.properties,classpath:/override.properties
如果spring.config.location包含目录(相对于文件),那它们应该以/结尾(在加载前,spring.config.name产生的名称将被追
加到后面)。不管spring.config.location是什么值,默认的搜索路径classpath:,classpath:/config,file:,file:config/总会被使用。
以这种方式,你可以在application.properties中为应用设置默认值,然后在运行的时候使用不同的文件覆盖它,同时保留默认
配置。
注:如果你使用环境变量而不是系统配置,大多数操作系统不允许以句号分割(period-separated)的key名称,但你可以使
用下划线(underscores)代替(比如,使用SPRING_CONFIG_NAME代替spring.config.name)。如果你的应用运行在一
个容器中,那么JNDI属性(java:comp/env)或servlet上下文初始化参数可以用来取代环境变量或系统属性,当然也可以使
用环境变量或系统属性
属性占位符
app.name=MyApp
app.description=${app.name} is a Spring Boot application
属性配置文件加载
Spring框架提供两个便利的类用于加载YAML文档,YamlPropertiesFactoryBean会将YAML作为Properties来加载,
YamlMapFactoryBean会将YAML作为Map来加载,
注意:yaml文件无法像properties文件那样使用@PropertySource 注解加载
特殊用法:
YAML列表被表示成使用[index]间接引用作为属性keys的形式,例如下面的YAML:
my:
servers:
- dev.bar.com
- foo.bar.com
将会转化到下面的属性中:
my.servers[0]=dev.bar.com
my.servers[1]=foo.bar.com
类型安全的配置属性
即使用@ConfigurationProperties注解,适合于和外部配置文件结合使用;
@Component
@ConfigurationProperties(prefix="connection")
public class ConnectionSettings {
private String username;
private InetAddress remoteAddress;
// ... getters and setters
}
# application.yml
connection:
username: admin
remoteAddress: 192.168.1.1
# additional configuration as required
使用方式:
@Service
public class MyService {
@Autowired
private ConnectionSettings connection;
//...
@PostConstruct
public void openConnection() {
Server server = new Server();
this.connection.configure(server);
}
}
你可以通过在@EnableConfigurationProperties注解中直接简单的列出属性类来快捷的注册@ConfigurationProperties bean
的定义。
@Configuration
@EnableConfigurationProperties(ConnectionSettings.class)
public class MyConfiguration {
}
第三方配置
当你需要绑定属性到不受你控制的第三
方组件时,这种方式非常有用。
为了从Environment属性配置一个bean,将@ConfigurationProperties添加到它的bean注册过程:
@ConfigurationProperties(prefix = "foo")
@Bean
public FooComponent fooComponent() {
...
}
和上面ConnectionSettings的示例方式相同,任何以foo为前缀的属性定义都会被映射到FooComponent上。
松散绑定
Spring Boot使用一些宽松的规则用于绑定Environment属性到@ConfigurationProperties beans,所以Environment属性名和
bean属性名不需要精确匹配。常见的示例中有用的包括虚线分割(比如,context--path绑定到contextPath)和将环境属性转
为大写字母(比如,PORT绑定port)。
示例:
@Component
@ConfigurationProperties(prefix="person")
public class ConnectionSettings {
private String firstName;
}
下面的属性名都能用于上面的@ConfigurationProperties类:
属性
说明
person.firstName
标准驼峰规则
person.first-name
虚线表示,推荐用于.properties和.yml文件中
PERSON_FIRST_NAME
大写形式,使用系统环境变量时推荐
Spring会尝试强制外部的应用属性在绑定到@ConfigurationProperties beans时类型是正确的。如果需要自定义类型转换,你
可以提供一个ConversionService bean(bean id为conversionService)或自定义属性编辑器(通过一个
CustomEditorConfigurer bean)。
@ConfigurationProperties校验
@Component
@ConfigurationProperties(prefix="connection")
public class ConnectionSettings {
@NotNull
private InetAddress remoteAddress;
// ... getters and setters
}
你也可以通过创建一个叫做configurationPropertiesValidator的bean来添加自定义的Spring Validator。
注:spring-boot-actuator模块包含一个暴露所有@ConfigurationProperties beans的端点。简单地将你的web浏览器指
向/configprops或使用等效的JMX端点。
Profiles
Spring Profiles提供了一种隔离应用程序配置的方式,并让这些配置只能在特定的环境下生效。任何@Component或
@Configuration都能被@Profile标记,从而限制加载它的时机。
@Configuration
@Profile("production")
public class ProductionConfiguration {
// ...
}
或使用命令行:--spring.profiles.active=dev,hsqldb
Profiles激活
spring.profiles.active属性和其他属性一样都遵循相同的排列规则,最高的PropertySource获胜。也就是说,你可以在
application.properties中指定生效的配置,然后使用命令行开关替换它们。
有时,将特定的配置属性添加到生效的配置中而不是替换它们是有用的。spring.profiles.include属性可以用来无条件的添加
生效的配置。SpringApplication的入口点也提供了一个用于设置额外配置的Java API(比如,在那些通过
spring.profiles.active属性生效的配置之上):参考setAdditionalProfiles()方法。
示例:当一个应用使用下面的属性,并用 --spring.profiles.active=prod 开关运行,那proddb和prodmq配置也会生效:
---
my.property: fromyamlfile
---
spring.profiles: prod
spring.profiles.include: proddb,prodmq
注:spring.profiles属性可以定义到一个YAML文档中,用于决定什么时候该文档被包含进配置中。
你也可以通过调用SpringApplication.setAdditionalProfiles(…)方法,以编程的方式设置生效的配置。使用
Spring的ConfigurableEnvironment接口激动配置也是可行的。
日志
Spring Boot内部日志系统使用的是Commons Logging,但开放底层的日志实现。默认情况下,Spring Boot只会将日志记录到控制台而不会写进日志文件。
通过 --debug 标识开启控制台的DEBUG级别日志记录。
如果你的终端支持ANSI,为了增加可读性将会使用彩色的日志输出。你可以设置 spring.output.ansi.enabled 为一个支持的值来覆盖自动检测
通过将适当的库添加到classpath,可以激活各种日志系统。然后在classpath的根目录(root)或通过Spring Environment
的 logging.config 属性指定的位置提供一个合适的配置文件来达到进一步的定制(注意由于日志是在ApplicationContext被创
建之前初始化的,所以不可能在Spring的@Configuration文件中,通过@PropertySources控制日志。系统属性和平常的
Spring Boot外部配置文件能正常工作)。
3、自动配置
自动配置添加了以下特性:
1. 引入ContentNegotiatingViewResolver和BeanNameViewResolver beans。
2. 对静态资源的支持,包括对WebJars的支持。
3. 自动注册Converter,GenericConverter,Formatter beans。
4. 对HttpMessageConverters的支持。
5. 自动注册MessageCodeResolver。
6. 对静态index.html的支持。
7. 对自定义Favicon的支持。
如果想全面控制Spring MVC,你可以添加自己的@Configuration,并使用@EnableWebMvc对其注解。如果想保留Spring
Boot MVC的特性,并只是添加其他的MVC配置(拦截器,formatters,视图控制器等),你可以添加自己的WebMvcConfigurerAdapter类型的@Bean(不使用@EnableWebMvc注解)。
4、HttpMessageConverter
Spring MVC使用HttpMessageConverter接口转换HTTP请求和响应。合理的缺省值被包含的恰到好处(out of the box),例
如对象可以自动转换为JSON(使用Jackson库)或XML(如果Jackson XML扩展可用则使用它,否则使用JAXB)。字符串
默认使用UTF-8编码。
如果需要添加或自定义转换器,你可以使用Spring Boot的HttpMessageConverters类:
import org.springframework.boot.autoconfigure.web.HttpMessageConverters;
import org.springframework.context.annotation.*;
import org.springframework.http.converter.*;
@Configuration
public class MyConfiguration {
@Bean
public HttpMessageConverters customConverters() {
HttpMessageConverter<?> additional = ...
HttpMessageConverter<?> another = ...
return new HttpMessageConverters(additional, another);
}
}
任何在上下文中出现的HttpMessageConverter bean将会添加到converters列表,你可以通过这种方式覆盖默认的转换器(converters)。
该接口主要方法如下:
boolean canRead(Class<?> clazz, MediaType mediaType);
boolean canWrite(Class<?> clazz, MediaType mediaType);
List<MediaType> getSupportedMediaTypes();
T read(Class<? extends T> clazz, HttpInputMessage inputMessage) throws IOException, HttpMessageNotReadableException;
void write(T t, MediaType contentType, HttpOutputMessage outputMessage) throws IOException, HttpMessageNotWritableException;
源码中数据转化默认如下:
protected final void addDefaultHttpMessageConverters(List<HttpMessageConverter<?>> messageConverters) {
StringHttpMessageConverter stringConverter = new StringHttpMessageConverter();
stringConverter.setWriteAcceptCharset(false);
messageConverters.add(new ByteArrayHttpMessageConverter());
messageConverters.add(stringConverter);
messageConverters.add(new ResourceHttpMessageConverter());
messageConverters.add(new SourceHttpMessageConverter<Source>());
messageConverters.add(new AllEncompassingFormHttpMessageConverter());
if (romePresent) {
messageConverters.add(new AtomFeedHttpMessageConverter());
messageConverters.add(new RssChannelHttpMessageConverter());
}
if (jackson2XmlPresent) {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.xml().applicationContext(this.applicationContext).build();
messageConverters.add(new MappingJackson2XmlHttpMessageConverter(objectMapper));
}
else if (jaxb2Present) {
messageConverters.add(new Jaxb2RootElementHttpMessageConverter());
}
if (jackson2Present) {
ObjectMapper objectMapper = Jackson2ObjectMapperBuilder.json().applicationContext(this.applicationContext).build();
messageConverters.add(new MappingJackson2HttpMessageConverter(objectMapper));
}
else if (gsonPresent) {
messageConverters.add(new GsonHttpMessageConverter());
}
}
只有在没有定制时才调用默认的方法:
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
configureMessageConverters(this.messageConverters);
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters);
}
extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}
定制化
空值处理
请求和返回的数据有很多空值,这些值有时候并没有实际意义,我们可以过滤掉和不返回,或设置成默认值。比如通过重写 getObjectMapper 方法,将返回结果的空值不进行序列化:
converters.add(0, new MappingJackson2HttpMessageConverter(){
@Override
public ObjectMapper getObjectMapper() {
super.getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
return super.getObjectMapper();
}
}
XSS 脚本攻击
为了保证输入的数据更安全,防止 XSS 脚本攻击,我们可以添加自定义反序列化器:
//对应无法直接返回String类型
converters.add(0, new MappingJackson2HttpMessageConverter(){
@Override
public ObjectMapper getObjectMapper() {
super.getObjectMapper().setSerializationInclusion(JsonInclude.Include.NON_NULL);
// XSS 脚本过滤
SimpleModule simpleModule = new SimpleModule();
simpleModule.addDeserializer(String.class, new StringXssDeserializer());
super.getObjectMapper().registerModule(simpleModule);
return super.getObjectMapper();
}
将各种常用 HttpMessageConverter 支持的MediaType 和 JavaType 以及对应关系总结在此处:
类名 支持的JavaType 支持的MediaType
ByteArrayHttpMessageConverter byte[] application/octet-stream, */*
StringHttpMessageConverter String text/plain, */*
MappingJackson2HttpMessageConverter Object application/json, application/*+json
AllEncompassingFormHttpMessageConverter Map<K, List<?>> application/x-www-form-urlencoded, multipart/form-data
SourceHttpMessageConverter Source application/xml, text/xml, application/*+xml