SpringBoot配置
SpringBoot使用一个全局的配置文件 , 配置文件名称是固定的。
文件名必须为application,才会被SpringBoot所识别。
配置文件后缀有两种方式:
properties
key=value
yaml,yml(详见/Java/Additional/YAML)
key: value
两个配置文件可以共存。
1. 将配置文件中的对象注入实体类中
1.1. yaml
准备工作
添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> <optional>true</optional> </dependency>
修改依赖
<project> <build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <excludes> <exclude> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-configuration-processor</artifactId> </exclude> </excludes> </configuration> </plugin> </plugins> </build> </project>
编写实体类
package org.gs.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import java.util.Date; import java.util.List; import java.util.Map; /** * @author admin * @date 9/24/21 5:42 PM */ @AllArgsConstructor @NoArgsConstructor @Data // 需要注册为组件 @Component // 指定配置文件中的字段 @ConfigurationProperties(prefix = "person") public class Person { private String name; private Integer age; private Boolean adult; private Date birth; private Map<String, Object> maps; private List<String> lists; @Autowired private Dog dog; }
package org.gs.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.stereotype.Component; /** * @author admin * @date 9/24/21 5:44 PM */ @Component @AllArgsConstructor @NoArgsConstructor @Data public class Dog { private String name; private Integer age; }
配置文件中对对象赋值
Person: name: zhangsan age: 18 adult: true birth: 1999/06/18 maps: k1: v1 k2: v2 k3: v3 lists: - l1 - l2 - l3 dog: name: wangcai age: 1
测试
package org.gs; import org.gs.pojo.Person; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class MyspringbootApplicationTests { @Autowired private Person person; @Test void contextLoads() { System.out.println(person); } }
1.2. Properties
配置文件
# 只能单个赋值 name=zhangsan age=19 adult=true birth=1999/06/18
实体类
package org.gs.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.PropertySource; import org.springframework.stereotype.Component; import java.util.Date; import java.util.List; import java.util.Map; /** * @author admin * @date 9/24/21 5:42 PM */ @AllArgsConstructor @NoArgsConstructor @Data @Component // 加载指定的配置文件 @PropertySource(value = "classpath:person.properties") // 如果在Springboot的配置文件中,可以用 @ConfigurationProperties 从全局配置文件中获取值 public class Person { // 使用SpEL表达式对属性进行单个赋值 @Value("${name}") private String name; @Value("${age}") private Integer age; @Value("${adult}") private Boolean adult; @Value("${birth}") private Date birth; }
测试
package org.gs; import org.gs.pojo.Person; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class MyspringbootApplicationTests { @Autowired private Person person; @Test void contextLoads() { System.out.println(person); } }
YAML比较简单,开发中较常使用,也是SpringBoot官方推荐的。
2. SpringBoot中YAML和Properties对比
@ConfigurationProperties(YAML) | @Value(Properties) | |
---|---|---|
功能 | 批量注入配置文件中的属性 | 一个个指定 |
松散绑定 | 支持 | 不支持 |
SpEL | 不支持 | 支持 |
JSR303数据校验 | 支持 | 不支持 |
复杂类型封装 | 支持 | 不支持 |
松散绑定:yml中写的last-name,这个和lastName是一样的。
JSR303数据校验:可以在字段是增加一层过滤器验证 , 可以保证数据的合法性。
复杂类型封装,yml中可以封装对象 , 使用value就不支持。
Properties的短处
Properties配置文件在写中文的时候,会有乱码, 我们需要去IDEA中设置编码格式为UTF-8;settings-->FileEncodings 中配置。
@ConfigurationProperties只需要写一次即可 , @Value则需要每个字段都添加。
3. 在SpringBoot中使用JSR303校验
JSR303详见Java--->Additional--->JSR303
导入依赖
在SpirngBoot中导入这个依赖,会自动导入文章开头的依赖,不需要手动导入。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency>
添加校验
package org.gs.pojo; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.stereotype.Component; import org.springframework.validation.annotation.Validated; import javax.validation.constraints.Email; /** * @author admin * @date 9/24/21 5:42 PM */ @AllArgsConstructor @NoArgsConstructor @Data @Component // 开启校验 @Validated public class Person { // 指定元素必须为Email格式,否则出错 @Email private String name; }
4. 多环境切换
profile是Spring对不同环境提供不同配置功能的支持,可以通过激活不同的环境版本,实现快速切换环境。
4.1. 多配置文件properties
我们在主配置文件编写的时候,文件名可以是 application-{profile}.properties
, 用来指定多个环境版本。
例如:
application-test.properties 代表测试环境配置
application-dev.properties 代表开发环境配置
但是Springboot并不会直接启动这些配置文件,它默认使用application.properties主配置文件。
我们需要通过一个配置来选择需要激活的环境:
#比如在配置文件中指定使用dev环境
spring.profiles.active=dev
4.2. 多文档块yaml
和properties配置文件中一样,但是使用yml去实现不需要创建多个配置文件,更加方便了 !
server:
port: 8081
#选择要激活那个环境块
spring:
profiles:
active: test
---
server:
port: 8082
spring:
profiles: dev #配置环境的名称
---
server:
port: 8083
spring:
profiles: test #配置环境的名称
注意:如果yml和properties同时都配置了端口,并且没有激活其他环境 , 默认会使用properties配置文件。
4.3. 配置文件读取的优先级问题
springboot 启动会扫描以下位置的application.properties或者application.yml文件作为Spring boot的默认配置文件:
优先级1:项目路径下的config文件夹配置文件 file:./config
优先级2:项目路径下配置文件 file:./
优先级3:资源路径下的config文件夹配置文件 classpath:/config/
优先级4:资源路径下配置文件 classpath:/
优先级由高到底,高优先级的配置会覆盖低优先级的配置;
SpringBoot会从这四个位置全部加载主配置文件,互补配置。
5. 指定位置加载配置文件
我们还可以通过spring.config.location
来改变默认的配置文件位置
项目打包好以后,我们可以使用命令行参数的形式,启动项目的时候来指定配置文件的新位置;
这种情况,一般是后期运维做的多,相同配置,外部指定的配置文件优先级最高
java -jar spring-boot-config.jar --spring.config.location=/Users/admin/application.properties
6. 配置内容
#配置项目的访问路径
server.servlet.context-path=/hello
7. 静态资源导入探究
WebMvcAutoConfiguration.java中
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 如果自行配置静态资源的路径,则默认的路径失效
if (!this.resourceProperties.isAddMappings()) {
logger.debug("Default resource handling disabled");
} else {
Duration cachePeriod = this.resourceProperties.getCache().getPeriod();
CacheControl cacheControl = this.resourceProperties.getCache().getCachecontrol().toHttpCacheControl();
// 配置webjars
if (!registry.hasMappingForPattern("/webjars/**")) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{"/webjars/**"}).addResourceLocations(new String[]{"classpath:/META-INF/resources/webjars/"}).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl).setUseLastModified(this.resourceProperties.getCache().isUseLastModified()));
}
// 默认静态资源的路径
String staticPathPattern = this.mvcProperties.getStaticPathPattern();
if (!registry.hasMappingForPattern(staticPathPattern)) {
this.customizeResourceHandlerRegistration(registry.addResourceHandler(new String[]{staticPathPattern}).addResourceLocations(WebMvcAutoConfiguration.getResourceLocations(this.resourceProperties.getStaticLocations())).setCachePeriod(this.getSeconds(cachePeriod)).setCacheControl(cacheControl).setUseLastModified(this.resourceProperties.getCache().isUseLastModified()));
}
}
}
从源码中可以读出的信息:
静态资源路径若被自定义,则默认的路径会失效
webjars的路径为/webjars/**
像是jquery、bootstrap、npm等依赖这这里可以通过Maven坐标导入,导入后会有jar包,包内的classpath:/META-INF/resources/webjars/就是资源目录,会被映射到/webjars/**,方便使用。
默认路径(常用)
默认的路径在staticPathPattern中
进入staticPathPattern,发现private String staticPathPattern = "/**";
即映射的url路径为/**
进入@EnableConfigurationProperties下的WebProperties.class,发现
private static final String[] CLASSPATH_RESOURCE_LOCATIONS = { "classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/" };
这四个就是默认的静态资源路径。
即:默认静态资源的url映射路径为/**
在"classpath:/META-INF/resources/", "classpath:/resources/", "classpath:/static/", "classpath:/public/"四种路径下的资源均能被映射到。
经测试,当静态资源文件发生重名时,会有优先级的问题。
优先级:resources > static > public.
8. 首页和图标
8.1. 首页
WebMvcAutoConfiguration.java
private Optional<Resource> getWelcomePage() {
String[] locations = getResourceLocations(this.resourceProperties.getStaticLocations());
return Arrays.stream(locations).map(this::getIndexHtml).filter(this::isReadable).findFirst();
}
private Resource getIndexHtml(String location) {
return this.resourceLoader.getResource(location + "index.html");
}
看到主页默认为index.html。位置就是上面提到的静态资源的四个位置。
8.2. 图标
Springboot 对 favicon 配置在 2.2.0以上 和 2.2.0 之前的版本是不一样的。
在2.2.0之前
只需要在 application.properties (或者 application.yaml) 中配置
spring.mvc.favicon.enabled=false
在2.2.0之后
不需要在 applicaiton.properties 文件中配置了而是在 html 中配置
<link rel="icon" th:href="@{/public/favicon.ico}" type="image/x-icon"/> <link rel="bookmark" th:href="@{/public/favicon.ico}" type="image/x-icon"/>
9. MVC自动配置原理
自动配置在Spring的默认值之外添加了以下功能:
- 包含
ContentNegotiatingViewResolver
和BeanNameViewResolver
豆。 - 支持提供静态资源,包括对WebJars的支持。
- 自动注册
Converter
、GenericConverter
和Formatter
豆。 - 支持
HttpMessageConverters
。SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串。 - 自动注册
MessageCodesResolver
。定义错误代码生成规则的。 - Static
index.html
支持。 - 自动使用
ConfigurableWebBindingInitializer
豆子。
如果您想保留这些Spring Boot MVC自定义,并进行更多的MVC自定义(拦截器、格式化程序、视图控制器和其他功能),您可以添加自己的@Configuration
类类型为WebMvcConfigurer
,但没有@EnableWebMvc
。
如果您想提供RequestMappingHandlerMapping
、RequestMappingHandlerAdapter
或ExceptionHandlerExceptionResolver
的自定义实例,但仍然保留Spring Boot MVC自定义,您可以声明类型WebMvcRegistrations
的bean,并使用它来提供这些组件的自定义实例。
如果您想完全控制Spring MVC,您可以添加自己的@Configuration
注释为@EnableWebMvc
,或者按照Javadoc of@EnableWebMvc
所述添加您自己的@Configuration
-annotated DelegatingWebMvcConfiguration
。
它告诉我们SpringBoot已经帮我们自动配置好了SpringMVC,然后自动配置了哪些东西呢?
9.1. ContentNegotiatingViewResolver
内容协商视图解析器
public class ContentNegotiatingViewResolver extends WebApplicationObjectSupport implements ViewResolver, Ordered, InitializingBean
这个类继承了ViewResolver,也就是SpringMVC的视图解析器。
即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。
找到 WebMvcAutoConfiguration
, 然后搜索ContentNegotiatingViewResolver
。找到如下方法:
@Bean
@ConditionalOnBean(ViewResolver.class)
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
return resolver;
}
进入ContentNegotiatingViewResolver,可以找到对应的解析视图的源码
@Override
@Nullable
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = getMediaTypes(((ServletRequestAttributes) attrs).getRequest());
if (requestedMediaTypes != null) {
// 获取候选的视图对象
List<View> candidateViews = getCandidateViews(viewName, locale, requestedMediaTypes);
// 选择一个最适合的视图对象,然后把这个对象返回
View bestView = getBestView(candidateViews, requestedMediaTypes, attrs);
if (bestView != null) {
return bestView;
}
}
String mediaTypeInfo = logger.isDebugEnabled() && requestedMediaTypes != null ?
" given " + requestedMediaTypes.toString() : "";
if (this.useNotAcceptableStatusCode) {
if (logger.isDebugEnabled()) {
logger.debug("Using 406 NOT_ACCEPTABLE" + mediaTypeInfo);
}
return NOT_ACCEPTABLE_VIEW;
}
else {
logger.debug("View remains unresolved" + mediaTypeInfo);
return null;
}
}
getCandidateViews获取候选视图,进入看方法
private List<View> getCandidateViews(String viewName, Locale locale, List<MediaType> requestedMediaTypes)
throws Exception {
List<View> candidateViews = new ArrayList<>();
if (this.viewResolvers != null) {
Assert.state(this.contentNegotiationManager != null, "No ContentNegotiationManager set");
for (ViewResolver viewResolver : this.viewResolvers) {
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view);
}
for (MediaType requestedMediaType : requestedMediaTypes) {
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
for (String extension : extensions) {
String viewNameWithExtension = viewName + '.' + extension;
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
candidateViews.add(view);
}
}
}
}
}
if (!CollectionUtils.isEmpty(this.defaultViews)) {
candidateViews.addAll(this.defaultViews);
}
return candidateViews;
}
其中,for (ViewResolver viewResolver : this.viewResolvers)
中可以得知,获取候选视图的方法就是进行挨个判断。
所以得出结论:ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的
溯源,看ViewResolver
是哪里赋值的
@Override
protected void initServletContext(ServletContext servletContext) {
Collection<ViewResolver> matchingBeans =
// 这里它是从beanFactory工具中获取容器中的所有视图解析器
// ViewRescolver.class 把所有的视图解析器来组合的
BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList<>(matchingBeans.size());
for (ViewResolver viewResolver : matchingBeans) {
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
}
else {
for (int i = 0; i < this.viewResolvers.size(); i++) {
ViewResolver vr = this.viewResolvers.get(i);
if (matchingBeans.contains(vr)) {
continue;
}
String name = vr.getClass().getName() + i;
obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(vr, name);
}
}
AnnotationAwareOrderComparator.sort(this.viewResolvers);
this.cnmFactoryBean.setServletContext(servletContext);
}
得出,它是在容器中去找视图解析器。所以,我们可以在容器中自定义视图解析器。
9.2. 自定义视图解析器
这里按照官方文档说明来定义,详见上面的MVC自动配置原理。
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
// WebMvcConfigurer是一个实现了ViewResolver视图解析器接口的类,所以实际上也是一个视图解析器
@Bean
public ViewResolver myViewResolver() {
return new MyViewResolver();
}
// 自定义了一个自己的视图解析器
public static class MyViewResolver implements ViewResolver {
@Override
public View resolveViewName(String s, Locale locale) throws Exception {
return null;
}
}
}
在DispatcherServlet 中的 doDispatch方法,加断点进行调试,就可以看到this-->viewReslovers中出现自定义的视图解析器。
所以说,我们如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了。
10. 转换器和格式化器
在
WebMvcAutoConfiguration
中找到格式化转换器:@Bean @Override public FormattingConversionService mvcConversionService() { // 从配置文件中取出格式化的规则 Format format = this.mvcProperties.getFormat(); WebConversionService conversionService = new WebConversionService(new DateTimeFormatters().dateFormat(format.getDate()).timeFormat(format.getTime()).dateTimeFormat(format.getDateTime())); addFormatters(conversionService); return conversionService; }
进入mvcProperties,找到配置的源码
public static class Format { /** * Date format to use, for example `dd/MM/yyyy`. */ private String date; /** * Time format to use, for example `HH:mm:ss`. */ private String time; /** * Date-time format to use, for example `yyyy-MM-dd HH:mm:ss`. */ private String dateTime; // ... }
@Deprecated @DeprecatedConfigurationProperty(replacement = "spring.mvc.format.date") public String getDateFormat() { return this.format.getDate(); }
如果配置了自己的格式化方式,就会注册到Bean中生效,可以在配置文件中配置日期格式化的规则:
2.2.x版本之前的
配置文件
# 配置文件 spring.mvc.date-format=
2.2.x版本之后的
配置文件
spring.mvc.date=
11. 修改SpringBoot的默认配置
SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;
如果有些组件可以存在多个,比如视图解析器,就将用户配置的和自己默认的组合起来。
这里按照官方文档说明来定义,详见上面的MVC自动配置原理。
扩展SpringMVC的方法:编写一个
@Configuration
注解类,并且类型要为WebMvcConfigurer
,不能标注@EnableWebMvc
注解MyMvcConfig
@Configuration public class MyMvcConfig implements WebMvcConfigurer { @Override public void addViewControllers(ViewControllerRegistry registry) { // 浏览器发送/test , 就会跳转到test页面; registry.addViewController("/test2").setViewName("test"); } }
访问成功。表示扩展SpringMVC这么使用可以即保留自动配置的内容,也保留扩展配置的内容。
11.1. 能够扩展配置的原理
WebMvcAutoConfiguration
是 SpringMVC的自动配置类,里面有一个静态内部类WebMvcAutoConfigurationAdapter
@Configuration(proxyBeanMethods = false)
@Import(EnableWebMvcConfiguration.class)
@EnableConfigurationProperties({ WebMvcProperties.class,
org.springframework.boot.autoconfigure.web.ResourceProperties.class, WebProperties.class })
@Order(0)
public static class WebMvcAutoConfigurationAdapter implements WebMvcConfigurer
其中,这个类上有一个注解,在做其他自动配置时会导入
@Import(EnableWebMvcConfiguration.class)
而进入EnableWebMvcConfiguration
@Configuration(proxyBeanMethods = false)
@EnableConfigurationProperties(WebProperties.class)
public static class EnableWebMvcConfiguration extends DelegatingWebMvcConfiguration implements ResourceLoaderAware
可见EnableWebMvcConfiguration继承了DelegatingWebMvcConfiguration,在DelegatingWebMvcConfiguration中
@Configuration(proxyBeanMethods = false)
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
private final WebMvcConfigurerComposite configurers = new WebMvcConfigurerComposite();
// 从容器中获取所有的webmvcConfigurer
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
// ...
}
找到自定义的MyMvcConfig类重写的addViewControllers方法在DelegatingWebMvcConfiguration的源码,可以看到
@Override
protected void addViewControllers(ViewControllerRegistry registry) {
this.configurers.addViewControllers(registry);
}
再溯源,可以发现
@Override
public void addViewControllers(ViewControllerRegistry registry) {
// 将所有的WebMvcConfigurer相关配置一起调用。包括我们自己配置的和Spring给我们配置的
for (WebMvcConfigurer delegate : this.delegates) {
delegate.addViewControllers(registry);
}
}
结论:所有的WebMvcConfiguration
都会被作用,不止Spring自己的配置类,我们自己的配置类也会被调用。
11.2. 为什么不能加上@EnableWebMvc注解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
这个注解实际上导入了DelegatingWebMvcConfiguration类,进入发现:
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport
DelegatingWebMvcConfiguration是WebMvcConfigurationSupport的子类。
而,根据WebMvcAutoConfiguration自动配置类
@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication(type = Type.SERVLET)
@ConditionalOnClass({ Servlet.class, DispatcherServlet.class, WebMvcConfigurer.class })
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE + 10)
@AutoConfigureAfter({ DispatcherServletAutoConfiguration.class, TaskExecutionAutoConfiguration.class,
ValidationAutoConfiguration.class })
public class WebMvcAutoConfiguration {
其中,判断情况为
@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
即,当WebMvcConfigurationSupport类存在的时候,自动配置将失效。
如果加上@EnableWebMvc,意味着我们将全面接管Spring MVC,所有的东西需要自己配置。
12. 自定义拦截器
配置拦截器参见这里。
将拦截器加入到mvc自定配置中
MyMvcConfig类
@Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new MyHandlerInterceptor()).addPathPatterns("/**").excludePathPatterns("/asserts/**", "/user/login", "/index.html", "/"); }
13. 自定义出错页
在templates目录下创建error目录,将HTTP常见状态码作为名字,创建网页文件就会自动识别。如404.html。