spring-boot-starter-web(Web启动器)
简介
Spring MVC是Spring提供的一个基于MVC设计模式的轻量级开发框架,其本身是Spring框架的一部分,与Spring无缝集成。
Spring Boot是在Spring基础上创建的一款开源框架,它提供spring-boot-starter-web(Web场景启动器)为Web开发提供支持。其中spring-boot-starter-web为我们提供了大量的自动配置类,并嵌入了Servlet容器以及SpringMVC的依赖,使用于大多数Web开发场景
Spring Boot Web开发
Spring Boot为SpringMVC提供了大量的自动配置,并在对其进行了拓展:
-
引入了 ContentNegotiatingViewResolver 和 BeanNameViewResolver(视图解析器)
-
对包括 WebJars 在内的静态资源的支持
-
自动注册 Converter、GenericConverter 和 Formatter (转换器和格式化器)
-
对 HttpMessageConverters 的支持(Spring MVC 中用于转换 HTTP 请求和响应的消息转换器)
5.自动注册 MessageCodesResolver(用于定义错误代码生成规则)
-
支持对静态首页(index.html)的访问
-
自动使用 ConfigurableWebBindingInitializer(初始化数据绑定器)
只要pom.xml引入了spring-boot-starter-web,就可以直接运行web项目了。
示例
- 创建一个名为spring-boot-springmvc-demo1的Spring Boot项目,发现在pom.xml已经帮我们导入了spring-boot-starter-web依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
- 在项目的包名下添加一个controller的子包,创建一个名为UseController的类,代码如下:
package com.liang.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
@Controller
@RequestMapping("hello")
public class UseController {
@RequestMapping("/s")
@ResponseBody
public String test()
{
return "hello,springboot";
}
}
- 启动Spring Boot ,浏览器访问“http://localhost:8080/hello/s”
注意:如果我们要开发的是web项目,则不需要去导入spring-boot-starter包了,因为spring-boot-starter-web已经帮我们导入了
MVC自动配置原理
官方阅读
基于源码分析外:,我们可以通过官方文档来查看Spring Boot 对Spring MVC做了哪些配置,包括如何拓展,如何配置
Spring MVC Auto-configuration
// Spring Boot为Spring MVC提供了自动配置,它可以很好地与大多数应用程序一起工作。
Spring Boot provides auto-configuration for Spring MVC that works well with most applications.
// 自动配置在Spring默认设置的基础上添加了以下功能:
The auto-configuration adds the following features on top of Spring’s defaults:
// 包含视图解析器
Inclusion of ContentNegotiatingViewResolver and BeanNameViewResolver beans.
// 支持静态资源文件夹的路径,以及webjars
Support for serving static resources, including support for WebJars
// 自动注册了Converter:
// 转换器,这就是我们网页提交数据到后台自动封装成为对象的东西,比如把"1"字符串自动转换为int类型
// Formatter:【格式化器,比如页面给我们了一个2019-8-10,它会给我们自动格式化为Date对象】
Automatic registration of Converter, GenericConverter, and Formatter beans.
// HttpMessageConverters
// SpringMVC用来转换Http请求和响应的的,比如我们要把一个User对象转换为JSON字符串,可以去看官网文档解释;
Support for HttpMessageConverters (covered later in this document).
// 定义错误代码生成规则的
Automatic registration of MessageCodesResolver (covered later in this document).
// 首页定制
Static index.html support.
// 图标定制
Custom Favicon support (covered later in this document).
// 初始化数据绑定器:帮我们把请求数据绑定到JavaBean中!
Automatic use of a ConfigurableWebBindingInitializer bean (covered later in this document).
/*
如果您希望保留Spring Boot MVC功能,并且希望添加其他MVC配置(拦截器、格式化程序、视图控制器和其他功能),则可以添加自己
的@configuration类,类型为webmvcconfiguer,但不添加@EnableWebMvc。如果希望提供
RequestMappingHandlerMapping、RequestMappingHandlerAdapter或ExceptionHandlerExceptionResolver的自定义
实例,则可以声明WebMVCregistrationAdapter实例来提供此类组件。
*/
If you want to keep Spring Boot MVC features and you want to add additional MVC configuration
(interceptors, formatters, view controllers, and other features), you can add your own
@Configuration class of type WebMvcConfigurer but without @EnableWebMvc. If you wish to provide
custom instances of RequestMappingHandlerMapping, RequestMappingHandlerAdapter, or
ExceptionHandlerExceptionResolver, you can declare a WebMvcRegistrationsAdapter instance to provide such components.
// 如果您想完全控制Spring MVC,可以添加自己的@Configuration,并用@EnableWebMvc进行注释。
If you want to take complete control of Spring MVC, you can
add your own @Configuration annotated with @EnableWebMvc.
首先配置了视图解析器,我们先看看它是怎么配置的
ContentNegotiatingViewResolver内容协商视图器
自动配置了ViewResolver,我们之前SpringMVC学习的视图解析器。
即根据方法的返回值取得视图对象(View),然后由视图对象决定如何渲染(转发,重定向)。
@Bean
@ConditionalOnBean(ViewResolver.class)
//在ViewResolver在视图解析器的条件下
@ConditionalOnMissingBean(name = "viewResolver", value = ContentNegotiatingViewResolver.class)
public ContentNegotiatingViewResolver viewResolver(BeanFactory beanFactory) {
ContentNegotiatingViewResolver resolver = new ContentNegotiatingViewResolver();
resolver.setContentNegotiationManager(beanFactory.getBean(ContentNegotiationManager.class));
// ContentNegotiatingViewResolver uses all the other view resolvers to locate
// a view so it should have a high precedence
resolver.setOrder(Ordered.HIGHEST_PRECEDENCE);
// ContentNegotiatingViewResolver使用所有其他视图解析器来定位视图,因此它应该具有较高的优先级
return resolver;
}
点进ContentNegotiationViewResolver类看看
@Nullable // 注解说明:@Nullable 即参数可为null
public View resolveViewName(String viewName, Locale locale) throws Exception {
RequestAttributes attrs = RequestContextHolder.getRequestAttributes();
Assert.state(attrs instanceof ServletRequestAttributes, "No current ServletRequestAttributes");
List<MediaType> requestedMediaTypes = this.getMediaTypes(((ServletRequestAttributes)attrs).getRequest());
if (requestedMediaTypes != null) {
// 获取候选的视图对象
List<View> candidateViews = this.getCandidateViews(viewName, locale, requestedMediaTypes);
View bestView = this.getBestView(candidateViews, requestedMediaTypes, attrs);
// 选择一个最适合的视图对象,然后把这个对象返回
if (bestView != null) {
return bestView;
}
}
继续点击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");
Iterator var5 = this.viewResolvers.iterator();
//获取所有的解析视图器,进行while循环
while(var5.hasNext()) {
ViewResolver viewResolver = (ViewResolver)var5.next();
View view = viewResolver.resolveViewName(viewName, locale);
if (view != null) {
candidateViews.add(view);
}
Iterator var8 = requestedMediaTypes.iterator();
while(var8.hasNext()) {
MediaType requestedMediaType = (MediaType)var8.next();
List<String> extensions = this.contentNegotiationManager.resolveFileExtensions(requestedMediaType);
Iterator var11 = extensions.iterator();
while(var11.hasNext()) {
String extension = (String)var11.next();
String viewNameWithExtension = viewName + '.' + extension;
view = viewResolver.resolveViewName(viewNameWithExtension, locale);
if (view != null) {
candidateViews.add(view);
}
}
}
}
}
通过getCandidateViews()
方法中看到它是把所有的视图解析器拿来,进行while循环,挨个解析!
结论: ContentNegotiatingViewResolver 这个视图解析器就是用来组合所有的视图解析器的
查看ContentNegotiatingViewResolver的组合逻辑,有个viewResolvers
,看看它是如何赋值的
protected void initServletContext(ServletContext servletContext) {
Collection<ViewResolver> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(this.obtainApplicationContext(), ViewResolver.class).values();
//可以看到viewResolver 是从BeanFactoryUtils工具类获取容器中所有的视图解析器
//viewResolver.class把所有的视图解析器组合
ViewResolver viewResolver;
if (this.viewResolvers == null) {
this.viewResolvers = new ArrayList(matchingBeans.size());
Iterator var3 = matchingBeans.iterator();
while(var3.hasNext()) {
viewResolver = (ViewResolver)var3.next();
if (this != viewResolver) {
this.viewResolvers.add(viewResolver);
}
}
} else {
for(int i = 0; i < this.viewResolvers.size(); ++i) {
viewResolver = (ViewResolver)this.viewResolvers.get(i);
if (!matchingBeans.contains(viewResolver)) {
String name = viewResolver.getClass().getName() + i;
this.obtainApplicationContext().getAutowireCapableBeanFactory().initializeBean(viewResolver, name);
}
}
}
AnnotationAwareOrderComparator.sort(this.viewResolvers);
this.cnmFactoryBean.setServletContext(servletContext);
}
给容器添加自定义视图解析器
- 我们在我们的主程序中写一个视图解析器
@Bean
public ViewResolver myViewResolver()
{
return new MyViewResolver();
}
//写一个静态内部类,将视图解析器需要实现ViewResolver接口
private static class MyViewResolver implements ViewResolver{
@Override
public View resolveViewName(String viewName, Locale locale) throws Exception {
return null;
}
}
- 怎么看我们自己写的视图解析器有没有起作用呢?
我们给 DispatcherServlet 中的 doDispatch方法 加个断点进行调试一下(DispatcherServlet可以声明这个类),因为所有的请求都会走到这个方法中
- 我们启动我们的项目,然后随便访问一个页面,看一下Debug信息;
如果想要使用自己定制化的东西,我们只需要给容器中添加这个组件就好了!剩下的事情SpringBoot就会帮我们做了!
转换器和格式化器
找到格式化转化器:
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
// Must be @Primary for MvcUriComponentsBuilder to work
return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,
resourceUrlProvider);
}
发现FormattingConversionService一个格式化转换服务
发现了有很多的格式化服务,我们也可以自己去配置自己的格式化方式。如果配置了自己的格式化方式,就会注册到Bean中生效,我们可以在配置文件中配置日期格式化的规则:
。