[SpringBoot系列】前文:
SpringBoot-web开发(一): 静态资源的导入(源码分析)
SpringBoot-web开发(二): 页面和图标定制(源码分析)
SpringBoot-web开发(三): 模板引擎Thymeleaf
目录
- 一. 解读官方文档
- 二. 拓展SpringMVC
-
- 1. 拓展原理
- 2. 环境搭建:编写拓展配置类
- 3. 示例:拓展视图解析器
-
- 1. 默认视图解析器源码分析
- 2. 自定义视图解析器
- 4. 修改SpringBoot默认配置
-
- 示例:修改默认日期格式
- 5. 总结
- 三. 全面接管SpringMVC
-
- 1. 什么是全面接管?
- 2. 测试
- 3. @EnableWebMvc原理
一. 解读官方文档
SpringBoot在底层对我们的SpringMVC新增了很多配置,我们接下来需要了解如何扩展,如何定制自己的配置
官方文档点击这里:官方文档
Spring Boot为Spring MVC提供了自动配置,可与大多数应用程序完美配合
自动配置在Spring的默认设置之上添加了以下功能:
- 包含
ContentNegotiatingViewResolver
和BeanNameViewResolver
beans(视图解析器) - 支持服务静态资源,包括对WebJars的支持
- 自动注册
Converter
,GenericConverter
(类型转换器)和Formatter
(格式化器)beans - 对
HttpMessageConverters
(消息转换,转换Http请求和响应)的支持 - 自动注册
MessageCodesResolver
(生成绑定错误消息) - 静态
index.html
支持(首页映射) - 自定义
Favicon
支持(图标自定义) - 自动使用
ConfigurableWebBindingInitializer
bean(数据web的初始化绑定)
使用方法:
如果要保留这些SpringBoot MVC特点并添加更多的MVC功能(拦截器,格式化程序,视图控制器和其他功能),则将@Configuration
注解添加到类型为WebMvcConfigurer
的类上,但不添加@EnableWebMvc
注解
如果要提供RequestMappingHandlerMapping
,RequestMappingHandlerAdapter
或ExceptionHandlerExceptionResolver
的自定义实例,并且仍然保留Spring Boot MVC自定义,则可以声明WebMvcRegistrations
类型的bean,并使用它提供这些组件的自定义实例
如果要完全控制Spring MVC,则可以添加用@EnableWebMvc
注解的自己的@Configuration
,或者按照@EnableWebMvc
的Javadoc中的说明添加自己的@Configuration
注解的DelegatingWebMvcConfiguration
二. 拓展SpringMVC
根据官方文档:如果要保留这些SpringBoot MVC特点并添加更多的MVC功能(拦截器,格式化程序,视图控制器和其他功能),则将
@Configuration
注解添加到类型为WebMvcConfigurer
的类上,但不添加@EnableWebMvc
注解
1. 拓展原理
我们查看SpringBoot底层webmvc自动配置类WebMvcAutoConfiguration
中的自动适配类WebMvcAutoConfigurationAdapter
可以看到这样一个注解@Import(EnableWebMvcConfiguration.class)
也就是导入了EnableWebMvcConfiguration
这个类
我们继续查看该类源码,发现它继承了一个父类DelegatingWebMvcConfiguration
我们继续查看DelegatingWebMvcConfiguration
的源码,可以找到这样一个方法
@Autowired(required = false)
public void setConfigurers(List<WebMvcConfigurer> configurers) {
if (!CollectionUtils.isEmpty(configurers)) {
this.configurers.addWebMvcConfigurers(configurers);
}
}
该方法就是从Spring容器中获取所有的webmvcConfigurer
,及所有的配置类
也就是SpringBoot在底层自动获取了所有的配置类,包括默认的配置类以及我们自定义的配置类,这也就是我们拓展的原理,我们可以新增自己配置类,注入到Spring容器中,然后SpringBoot即可自动配置
2. 环境搭建:编写拓展配置类
接下来,我们搭建一个拓展配置类环境进行实验:
首先在主程序同级目录下新建一个
congfig
包,用来放置的配置类,其中新建配置类MyMvcConfiguration
用来拓展装配MVC的配置
通过官方文档的介绍,我们需要将@Configuration
注解添加到类型为WebMvcConfigurer
的类上,但不添加@EnableWebMvc
注解
因此我们现在IDEA
中搜索(连按shift)一下WebMvcConfigurer
,可以发现它是一个接口
因此我们需要自定义的配置类MyMvcConfiguration
需要实现这个接口
package com.zsr.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
}
3. 示例:拓展视图解析器
搭建好配置类环境后,我们接下来以官方文档中的第一条视图解析器为例,配置拓展一个自定义的视图解析器
在SpringMVC中,我们在其配置文件中手动配置
视图解析器
;<!--视图解析器:DispatcherServlet给他的ModelAndView 1.获取了ModelAndView的数据 2.解析ModelAndView的视图名字 3.拼接视图名字,找到对应的视图 hello 4.将数据渲染到这个视图上 --> <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="InternalResourceViewResolver"> <!--前缀--> <property name="prefix" value="/WEB-INF/jsp/"/> <!--后缀--> <property name="suffix" value=".jsp"/> </bean>
而在SpringBoot,自动配置了视图解析器;我们接下来查看源码,分析一下其配置好的的视图解析器;
1. 默认视图解析器源码分析
官网文档中提到SpringBoot默认的一个视图解析器
ContentNegotiatingViewResolver
,我们来分析分析
我们在IDEA
中搜索ContentNegotiatingViewResolver
类
发现它实现了ViewResolver
接口,我们继续查看ViewResolver
的源码
其中有一个解析视图名称方法resolveViewName
我们查看ContentNegotiatingViewResolver
继承ViewResolver
接口实现的该方法
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;
}
那么所有的视图是从那里来的呢?我们可以找到initServletContext
方法,该方法就是得到所有视图解析器的方法
@Override
protected void initServletContext(ServletContext servletContext) {
//从BeanFactoryUtils工具类中获取容器中的所有视图解析器
Collection<ViewResolver> matchingBeans =
BeanFactoryUtils.beansOfTypeIncludingAncestors(obtainApplicationContext(), ViewResolver.class).values();//ViewRescolver.class 把所有的视图解析器来组合的
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList<>(matchingBeans.size());
for (ViewResolver viewResolver : matchingBeans) {
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
}
//...
}
其中从BeanFactoryUtils
工具类中获取容器中的所有视图解析器,然后再对其进行赋值,拿来组合
因此:SpringBoot默认的ContentNegotiatingViewResolver
视图解析器就是用来组合所有的视图解析器的
2. 自定义视图解析器
上述默认的
ContentNegotiatingViewResolver
类通过在Spring容器中去找视图解析器并进行组合那如果我们自己向Spring容器中去添加一个视图解析器,这个类也会帮我们自动的将它组合进来
这样是不是就实现了拓展一个自定义的视图解析器呢?我们可以试试!
在上述编写好的配置类MyMvcConfig
类中编写一个自己的视图解析器静态内部类,实现视图解析器ViewResolver
接口,重写其抽象方法
@Configuration
public class MyMvcConfig implements WebMvcConfigurer {
//将自定义视图解析器实现类对象注入到bean中
@Bean
public ViewResolver myViewResolver() {
return new MyViewResolver();
}
//自定义视图解析器实现类
static class MyViewResolver implements ViewResolver {
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
}
接下来我们通过打断点查看我们自定义的视图解析器是否生效
我们给DispatcherServlet
类中的doDispatch
方法加个断点进行调试一下,因为所有的请求都会走到这个方法中
然后我们Debug
主程序
程序启动后,访问localhost:8080
,程序进入doDispatcher
方法
我们点击this
可以查看所有的视图解析器对象
`ContentNegotiatingViewResolver`:SpringBoot默认视图解析器
`BeanNameViewResolver`:SpringBoot默认视图解析器
`TymeleafViewResolver`:导入了Tymeleaf模板引擎后Tymeleaf的视图解析器
`MyViewResolver`:我们自定义的视图解析器
我们发现了自定义的视图解析器,证明ContentNegotiatingViewResolver
成功将我们自定义的视图解析器组合进来;
4. 修改SpringBoot默认配置
上述我们通过拓展视图解析器的例子简单了解了如何在SpringBoot新增自定义功能
我们还可以直接通过修改默认的配置达到自己想要的效果,接下来我们以修改默认的日期格式为例,找寻修改默认配置的方法
示例:修改默认日期格式
SpringBoot底层的自动装配,都在
WebMvcAutoConfiguration
自动配置类中,可以在其中找到关于格式化的方法mvcConversionService()
找到格式化转换器:
@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;
}
可以发现是从配置文件中获取格式化的规则,然后我们按住ctrl点击mvcProperties
private final WebMvcProperties mvcProperties;
然后点击进入WebMvcProperties
webMVC的配置文件类,可以找到关于日期格式化的方法
可以看到我们可以通过spring.mvc.format.date
在配置文件中设置自定义日期格式,但是已经不推荐使用了
@Deprecated
@DeprecatedConfigurationProperty(replacement = "spring.mvc.format.date")
public String getDateFormat() {
return this.format.getDate();
}
我们再点击getDate方法
public String getDate() {
return this.date;
}
再点击date
public static class Format {
private String date;
...
}
可以看到默认的日期格式为dd/MM/yyyy
我们可以在配置文件中修改默认的格式,自定义日期格式,比如这里为dd-MM-yyyy
spring.mvc.format.date=dd-MM-yyyy
如果配置了自己的格式化方式,就会注册到Bean中生效,以后就必须按照自定义的日期格式书写
其余的默认配置亦是如此,我们都可以在源码中找到答案
5. 总结
通过上述拓展原理以及示例,我们可以得出以下结论:
- SpringBoot的底层,大量用到了上述设计细节思想,很多的自动配置,原理都相同;
- 如果我们想自定义一些功能组件,只需要给Spring容器中添加这个组件,然后SpringBoot就会帮我们自动配置了
- SpringBoot在自动配置很多组件的时候,先看容器中有没有用户自己配置的(如果用户自己配置@bean),如果有就用用户配置的,如果没有就用自动配置的;
- 如果有些组件可以存在多个,比如我们的视图解析器,就将用户配置的和自己默认的组合起来!
三. 全面接管SpringMVC
1. 什么是全面接管?
全面接管:SpringBoot对SpringMVC的自动配置不再需要,所有东西都是我们自己去配置!
- 实际开发中,并不推荐使用全面接管SpringMVC
- 而是推荐拓展配置,使用SpringBoot的自动配置和我们自己写的扩展配置相结合的方式进行开发
在官方文档中可以看到:如果要完全控制Spring MVC
-
可以添加用
@EnableWebMvc
注解的自己的@Configuration
-
或者按照
@EnableWebMvc
的Javadoc中的说明添加自己的@Configuration
注解的DelegatingWebMvcConfiguration
2. 测试
根据官方文档,我们在配置类上添加@EnableWebMvc
注解即实现全面接管SpringMVC
package com.zsr.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.View;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import java.util.Locale;
@Configuration
@EnableWebMvc
public class MyMvcConfig implements WebMvcConfigurer {
//将自定义视图解析器实现类对象注入到bean中
@Bean
public ViewResolver myViewResolver() {
return new MyViewResolver();
}
//自定义视图解析器实现类
static class MyViewResolver implements ViewResolver {
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
}
我们重启主程序进行测试,访问localhost:8080
可以看到先前配置的主页已经失效,所有都回归到了最初的样子
3. @EnableWebMvc原理
为什么加了这个注解,自动配置就失效了,我们来一探究竟~
我们查看@EnableWebMvc
注解源码,发现导入了类DelegatingWebMvcConfiguration
类
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}
进入该类看看,发现它继承了一个父类WebMvcConfigurationSupport
public class DelegatingWebMvcConfiguration extends WebMvcConfigurationSupport {
//...
}
也就是说,我们使用了@EnableWebMvc
注解,就相当于导入了WebMvcConfigurationSupport
类
我们再查看Webmvc自动配置类WebMvcAutoConfiguration
可以这样一个注解@ConditionalOnMissingBean(WebMvcConfigurationSupport.class)
意思是:在WebMvcConfigurationSupport
类不存在的情况下生效
也就是如果这个类存在,则整个WebMvcAutoConfiguration
自动配置类会失效,即SpringBoot的自动配置全部失效
而我们导入@EnableWebMvc
注解,就导入了WebMvcConfigurationSupport
类,因此SpringBoot所有的自动配置失效