前言

  Spring mvc框架相信很多人都很熟悉了,关于这方面的资料也是一搜一大把。但是感觉讲的都不是很细致,让很多初学者都云里雾里的。本人也是这样,之前研究过,但是后面一段时间不用发现又忘记了。所以决定写下来,以备后用。 
  本系列文基于spring-4.3.1,配置方式全部基于java-based方式

从配置讲起

先上一段配置的代码:

@EnableWebMvc
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter { @Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
} @Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/jsp/", ".jsp");
registry.enableContentNegotiation(new MappingJackson2JsonView());
} @Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true)
.ignoreAcceptHeader(true)
.parameterName("mediaType")
.defaultContentType(MediaType.TEXT_HTML)
.mediaType("html", MediaType.TEXT_HTML)
.mediaType("json", MediaType.APPLICATION_JSON);
} @Bean(name = "multipartResolver")
// 文件上传bean
public CommonsMultipartResolver commonsMultipartResolver() {
return new CommonsMultipartResolver();
}
}

  基于java-based方式的spring mvc配置,需要创建一个配置类并实现WebMvcConfigurer 接口,WebMvcConfigurerAdapter 抽象类是对WebMvcConfigurer 接口的简单抽象(增加了一些默认实现),所以上面配置代码选择直接继承WebMvcConfigurerAdapter 。然后根据项目的需要实现接口中特定的方法,最后要注意的是,要在配置类上标注@EnableWebMvc。 
  到这里可能有人会问,我怎么知道实现哪些方法?具体该怎么配?它们之间的处理流程是怎样的?好的,别急,我们一步步来。 
  首先第一步,我们需要知道WebMvcConfigurer 接口都提供了哪些回调方法?

WebMvcConfigurer

package org.springframework.web.servlet.config.annotation;
/**
* 篇幅原因,我们先只介绍Spring mvc常用的一些方法
*/
public interface WebMvcConfigurer { void addFormatters(FormatterRegistry registry); void configureMessageConverters(List<HttpMessageConverter<?>> converters); void extendMessageConverters(List<HttpMessageConverter<?>> converters); Validator getValidator(); /* 配置内容裁决的一些选项*/
void configureContentNegotiation(ContentNegotiationConfigurer configurer); void configureAsyncSupport(AsyncSupportConfigurer configurer); /* @since 4.0.3 */
void configurePathMatch(PathMatchConfigurer configurer); /*参数解析*/
void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers); /*返回值解析*/
void addReturnValueHandlers(List<HandlerMethodReturnValueHandler> returnValueHandlers); /*异常处理*/
void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers); void extendHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers); void addInterceptors(InterceptorRegistry registry); MessageCodesResolver getMessageCodesResolver(); void addViewControllers(ViewControllerRegistry registry); /**
* 这里配置视图解析器
*/
void configureViewResolvers(ViewResolverRegistry registry); /**
*静态资源处理
*/
void addResourceHandlers(ResourceHandlerRegistry registry); void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer); void addCorsMappings(CorsRegistry registry);
}

下面我们开始着重讲解以下几个常用的方法:

void configureViewResolvers(ViewResolverRegistry registry);
void configureContentNegotiation(ContentNegotiationConfigurer configurer);
void addViewControllers(ViewControllerRegistry registry);
void addResourceHandlers(ResourceHandlerRegistry registry);
void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer);

1. configureViewResolvers(ViewResolverRegistry registry)

  从方法名称我们就能看出这个方法是用来配置视图解析器的,该方法的参数ViewResolverRegistry 是一个注册器,用来注册你想自定义的视图解析器等。ViewResolverRegistry 常用的几个方法:

1).enableContentNegotiation
/** 启用内容裁决视图解析器*/
public void enableContentNegotiation(View... defaultViews) {
initContentNegotiatingViewResolver(defaultViews);
}

  该方法会创建一个内容裁决解析器ContentNegotiatingViewResolver ,该解析器不进行具体视图的解析,而是管理你注册的所有视图解析器,所有的视图会先经过它进行解析,然后由它来决定具体使用哪个解析器进行解析。具体的映射规则是根据请求的media types来决定的。

2).  UrlBasedViewResolverRegistration
    public UrlBasedViewResolverRegistration jsp(String prefix, String suffix) {
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix(prefix);
resolver.setSuffix(suffix);
this.viewResolvers.add(resolver);
return new UrlBasedViewResolverRegistration(resolver);
}

  该方法会注册一个内部资源视图解析器InternalResourceViewResolver 显然访问的所有jsp都是它进行解析的。该方法参数用来指定路径的前缀和文件后缀,如:  

    registry.jsp("/WEB-INF/jsp/", ".jsp");

  对于以上配置,假如返回的视图名称是example,它会返回/WEB-INF/jsp/example.jsp给前端,找不到则报404。 
  

3).  beanName
    public void beanName() {
BeanNameViewResolver resolver = new BeanNameViewResolver();
this.viewResolvers.add(resolver);
}

  该方法会注册一个BeanNameViewResolver 视图解析器,这个解析器是干嘛的呢?它主要是将视图名称解析成对应的bean。什么意思呢?假如返回的视图名称是example,它会到spring容器中找有没有一个叫example的bean,并且这个bean是View.class类型的?如果有,返回这个bean。 
  

4).  viewResolver
    public void viewResolver(ViewResolver viewResolver) {
if (viewResolver instanceof ContentNegotiatingViewResolver) {
throw new BeanInitializationException(
"addViewResolver cannot be used to configure a ContentNegotiatingViewResolver. Please use the method enableContentNegotiation instead.");
}
this.viewResolvers.add(viewResolver);
}

  这个方法想必看名字就知道了,它就是用来注册各种各样的视图解析器的,包括自己定义的。 
  

2. configureContentNegotiation(ContentNegotiationConfigurer configurer)

  上面一节我们讲了configureViewResolvers 方法,假如在该方法中我们启用了内容裁决解析器,那么configureContentNegotiation(ContentNegotiationConfigurer configurer) 这个方法是专门用来配置内容裁决的一些参数的。这个比较简单,我们直接通过一个例子看: 
  

   public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
/* 是否通过请求Url的扩展名来决定media type */
configurer.favorPathExtension(true)
/* 不检查Accept请求头 */
.ignoreAcceptHeader(true)
.parameterName("mediaType")
/* 设置默认的media yype */
.defaultContentType(MediaType.TEXT_HTML)
/* 请求以.html结尾的会被当成MediaType.TEXT_HTML*/
.mediaType("html", MediaType.TEXT_HTML)
/* 请求以.json结尾的会被当成MediaType.APPLICATION_JSON*/
.mediaType("json", MediaType.APPLICATION_JSON);
}

 到这里我们就可以举个例子来进一步熟悉下我们上面讲的知识了,假如我们MVC的配置如下:

    @EnableWebMvc
@Configuration
public class MvcConfig extends WebMvcConfigurerAdapter { @Override
public void configureViewResolvers(ViewResolverRegistry registry) {
registry.jsp("/WEB-INF/jsp/", ".jsp");
registry.enableContentNegotiation(new MappingJackson2JsonView());
} @Override
public void configureContentNegotiation(ContentNegotiationConfigurer configurer) {
configurer.favorPathExtension(true)
.ignoreAcceptHeader(true)
.parameterName("mediaType")
.defaultContentType(MediaType.TEXT_HTML)
.mediaType("html", MediaType.TEXT_HTML)
.mediaType("json", MediaType.APPLICATION_JSON);
}
}

  controller的代码如下:

    @Controller
public class ExampleController {
@RequestMapping("/example1")
public ModelAndView example1() {
Map<String, String> map = new HashMap();
map.put("1", "a");
map.put("2", "b");
return new ModelAndView("example1", map);
}
}

  在WEB-INF/jsp目录下创建一个example1.jsp文件,内容随意。现在启动tomcat,在浏览器输入以下链接:http://localhost:8080/example1.json,浏览器返回如下: 
   
在浏览器输入http://localhost:8080/example1 或者http://localhost:8080/example1.html,返回如下: 
 
  显然,两次使用了不同的视图解析器,那么底层到底发生了什么?在配置里我们注册了两个视图解析器:ContentNegotiatingViewResolver 和 InternalResourceViewResolver,还有一个默认视图:MappingJackson2JsonView。controller执行完毕之后返回一个ModelAndView,其中视图的名称为example1。返回首先会交给ContentNegotiatingViewResolver 进行视图解析处理,而ContentNegotiatingViewResolver 会先把视图名example1交给它持有的所有ViewResolver尝试进行解析(本实例中只有InternalResourceViewResolver),然后根据请求的mediaType,再将example1.mediaType(这里是example1.json 和example1.html)作为视图名让所有视图解析器解析一遍,两步解析完毕之后会获得一堆候选的List<View> 再加上默认的MappingJackson2JsonView ,最后根据请求的media type从候选的List<View> 中选择一个最佳的返回,至此视图解析完毕。现在就可以理解上例中为何请求链接加上.json 和不.json 结果会不一样。当加上.json 时,表示请求的media type 为MediaType.APPLICATION_JSON,而InternalResourceViewResolver 解析出来的视图的ContentType与其不符,而与MappingJackson2JsonView 的ContentType相符,所以选择了MappingJackson2JsonView 作为视图返回。当不加.json 请求时,默认的media type 为MediaType.TEXT_HTML,所以就使用了InternalResourceViewResolver解析出来的视图作为返回值了。我想看到这里你已经大致可以自定义视图了。

3. addViewControllers(ViewControllerRegistry registry)

  此方法可以很方便的实现一个请求到视图的映射,而无需书写controller,例如: 
  

    @Override
public void addViewControllers(ViewControllerRegistry registry){
registry.addViewController("/login").setViewName("login");
}

  这是访问${domain}/login时,会直接返回login页面。

4. addResourceHandlers(ResourceHandlerRegistry registry)

  此方法用来专门注册一个Handler,来处理静态资源的,例如:图片,js,css等。举例: 
  

    @Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resource/**").addResourceLocations("/WEB-INF/static/");
}

  当你请求http://localhost:8083/resource/1.png时,会把/WEB-INF/static/1.png返回。注意:这里的静态资源是放置在WEB-INF目录下的。

5. configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer)

  用法:

    @Override
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
configurer.enable();
}

  此时会注册一个默认的Handler:DefaultServletHttpRequestHandler,这个Handler也是用来处理静态文件的,它会尝试映射/*。当DispatcherServelt映射/时(/ 和/* 是有区别的),并且没有找到合适的Handler来处理请求时,就会交给DefaultServletHttpRequestHandler 来处理。注意:这里的静态资源是放置在web根目录下,而非WEB-INF 下。 
  可能这里的描述有点不好懂(我自己也这么觉得),所以简单举个例子,例如:在webroot目录下有一个图片:1.png 我们知道Servelt规范中web根目录(webroot)下的文件可以直接访问的,但是由于DispatcherServlet配置了映射路径是:/ ,它几乎把所有的请求都拦截了,从而导致1.png 访问不到,这时注册一个DefaultServletHttpRequestHandler 就可以解决这个问题。其实可以理解为DispatcherServlet破坏了Servlet的一个特性(根目录下的文件可以直接访问),DefaultServletHttpRequestHandler是帮助回归这个特性的。

题外话

  问: //* 有什么区别? 
  答: /会拦截除了jsp以外的所有url,/* 会拦截所有url,包括jsp。例如:在webroot下面有一个test.jsp,当DispatcherServlet 配置映射/ 时,浏览器输入:http://localhost:8083/test.jsp 这个jsp是可以直接访问的,并且不经过DispatcherServlet ,而当DispatcherServlet 配置映射/* 时,这个请求就会被DispatcherServlet 拦截。

 

Spring mvc请求处理流程详解(一)之视图解析的更多相关文章

  1. Spring MVC 工作流程详解

    1.首先先来一张图 开始流程----------------> 1.用户发送请求到前端控制器,前端控制器会过滤用户的请求,例如我们在web.xml里面配置的内容: <!-- 配置Sprin ...

  2. Spring MVC测试框架详解——服务端测试

    随着RESTful Web Service的流行,测试对外的Service是否满足期望也变的必要的.从Spring 3.2开始Spring了Spring Web测试框架,如果版本低于3.2,请使用sp ...

  3. spring mvc+myBatis配置详解

    一.spring mvc Spring框架(框架即:编程注解+xml配置的方式)MVC是Spring框架的一大特征,Spring框架有三大特征(IOC(依赖注入),AOP(面向切面),MVC(建模M- ...

  4. Spring MVC 请求处理流程概览

    SpringMVC工作流程 图一:请求流程概述 图二:请求在每个组件的处理 解释Spring工作流程 1.用户向服务器发送请求,请求被spring前端控制Servelt DispatcherServe ...

  5. Spring MVC请求处理流程

    从web.xml中 servlet的配置开始, 根据servlet拦截的url-parttern,来进行请求转发   Spring MVC工作流程图   图一   图二    Spring工作流程描述 ...

  6. ASP.NET Core MVC 源码学习:MVC 启动流程详解

    前言 在 上一篇 文章中,我们学习了 ASP.NET Core MVC 的路由模块,那么在本篇文章中,主要是对 ASP.NET Core MVC 启动流程的一个学习. ASP.NET Core 是新一 ...

  7. Spring mvc整合freemarker详解

    1.什么是FreeMarker FreeMarker是一个模板引擎,一个基于模板生成文本输出的通用工具,使用纯Java编写 FreeMarker被设计用来生成HTML Web页面,特别是基于MVC模式 ...

  8. Spring MVC @RequestMapping注解详解

    @RequestMapping 参数说明 value:定义处理方法的请求的 URL 地址.(重点) method:定义处理方法的 http method 类型,如 GET.POST 等.(重点) pa ...

  9. spring集成spring mvc 和hibernate详解

    1.配置IOC容器 <!-- 配置IOC容器 --> <context-param> <param-name>contextConfigLocation</p ...

随机推荐

  1. sysmain服务怎么启动 & Win7 SuperFetch无法启动

    在控制面板/管理工具/服务中,只需找到Superfetch这个服务,双击,然后将其启动类型改为自动,并点击启动按钮并确定即可. Superfetch无法启动,系统找不到指定档案 ms-windows ...

  2. [技术博客]React-Native中的组件加载、卸载与setState问题

    React-Native中的组件加载.卸载与setState问题. Warning: Can only update a mounted or mounting component. This usu ...

  3. vue+elementui项目打包后样式变化问题

    博主刚刚解决了index.html空白问题,刚打开项目页面又发现了样式出现了大问题,样式与开发版本有很大不同,有些样式没有生效.利用搜索引擎,找到了问题所在以及解决办法:main.js中的引入顺序决定 ...

  4. OpenGL的核心模式与立即渲染模式

    早期的OpenGL使用立即渲染模式(Immediate mode,也就是固定渲染管线),这个模式下绘制图形很方便.OpenGL的大多数功能都被库隐藏起来,开发者很少能控制OpenGL如何进行计算的自由 ...

  5. linux驱动由浅入深系列:高通sensor架构实例分析之三(adsp上报数据详解、校准流程详解)【转】

    本文转载自:https://blog.csdn.net/radianceblau/article/details/76180915 本系列导航: linux驱动由浅入深系列:高通sensor架构实例分 ...

  6. pymysql装饰器封装

    pymysql装饰器封装 def openClose(fun): def run(sql=None): coon =pymysql.connect(host='localhost' ,port=330 ...

  7. Promise和Observable的映射

    前言 promise解决了嵌套地狱的问题,Observable解决了promise只有一个结果,和不可以取消的问题. 使用的是rxjs6版本. 这篇文章是方便使用Observable的API替换Pro ...

  8. Python logging模块日志存储位置踩坑

    问题描述 项目过程中写了一个小模块,设计到了日志存储的问题,结果发现了个小问题. 代码结构如下: db.py run.py 其中db.py是操作数据库抽象出来的一个类,run.py是业务逻辑代码.两个 ...

  9. LeetCode 108. Convert Sorted Array to Binary Search Tree (将有序数组转换成BST)

    108. Convert Sorted Array to Binary Search Tree Given an array where elements are sorted in ascendin ...

  10. Elasticsearch学习笔记——索引模板

    在索引模板里面,date类型的字段的format支持多种类型,在es中全部会转换成long类型进行存储,参考 https://zhuanlan.zhihu.com/p/34240906 一个索引模板范 ...