Spring MVC 核心组件(下)

1. 前言

上一章节提到了 Spring MVC 的几大核心组件,并对前端控制器、用户控制器、映射器 3 大组件做了较全方面的讲解,相信大家一定对它们有了更理性的认识。

本节课继续讲解适配器、视图解析器组件。通过本节课,你将了解到:

  • 适配器的功能及配置;
  • 视图解析器的基本功能及配置;
  • 组件之间是如何协作完成用户的请求的。这个是本章节的重点,也是对各组件的归纳和总结。

2. 适配器

所谓适配器组件,其本质就是运用适配器设计模式,匹配不兼容的接口规范。

图片描述

如上图,调用者只能识别接口 2 类型,但是 A 提供的是接口 1 类型。适配器可以把接口 1 转换成接口 2。这样使用者就能使用 A 提供的功能了。

为什么要使用适配器组件?

欲解答这个问题,则先要了解如果不使用适配器组件,则如何编写用户控制器。既然称为用户控制器,则是开发者根据需要在框架外部定义的一个组件,Spring MVC 不可能未卜先知它的存在。

如果要让 Spring MVC 识别这个控制器,有一种方案 :预先定义好接口,强制性要求开发者在设计控制器时遵循接口规范。

比如说实现 Controller 接口编写控制器。

@Controller
public class HelloAction implements org.springframework.web.servlet.mvc.Controller {
public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) throws Exception {
	return null;
}
}

除此之外,Spriing MVC 提供有更灵活的用户控制器设计方案,可使用 “ 普通 JAVA 类” 充当控制器,控制器中的方法也可由开发者随性命名。

此时,就需要适配器组件把这些不符合规范的控制器以统一的接口方式告诉给 Spring MVC

Spring MVC 提供了 3 个默认适配器:

org.springframework.web.servlet.HandlerAdapter=org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter,\
	org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter,\
	org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter

这些适配对象都实现了 HandlerAdapter 接口,此接口就有一个统一的内部调用方法。

@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;

简要描述 3 个适配器的应用场景:

  • SimpleControllerHandlerAdapter: 简单的控制器处理器适配器,支持实现了 Controller 接口的控制器;
  • HttpRequestHandlerAdapter: http 请求处理器适配器,要求编写的控制器时实现 HttpRequestHandler 接口。此类控制器可以很方便的获取请求包中的相关信息。但,真正使用的并不多;
  • RequestMappingHandlerAdapter: 注解处理器适配器,适配使用了注解的用户控制器。本课程中的就是使用了此适配器,此适配器的实现比前两个都复杂。

因为有适配器的存在,可以让控制器的设计变得灵活。

Tips: 这 3 类适配器都是 Spring MVC 默认提供的,可以不用显示配置,除非有定制需求。

3. 视图解析器

3.1 视图解析器的功能

要讲解视图解析器,则需要回溯到用户控制器上。

@RequestMapping("/hello")
public String hello() {
	return "hello";
}

用户控制器中的方法的返回值可以是字符串,如果没有视图解析器的解析,这个字符串就是一个字符串。如果有了视图解析器,则会把这个字符串当成一个视图的逻辑名,并映射到真正的物理视图

Tips:视图解析器和映射器的有相似之处,映射器是入口时根据请求控制器逻辑名找到物理控制器,视图解析器是出口时根据视图逻辑名找到物理视图

Spring MVC 默认使用的 InternalResourceViewResolver 作为视图解析器, 提供对 JSP 视图的支持。

org.springframework.web.servlet.ViewResolver=org.springframework.web.servlet.view.InternalResourceViewResolver

无论是 Spring MVC 默认提供的、还是开发者自行定义的视图解析器,都必须实现 ViewResolver 接口:

public interface ViewResolver {
    @Nullable
    View resolveViewName(String viewName, Locale locale) throws Exception;
}

为什么不在用户控制器的方法中直接返回物理视图(完整资源路径描述),而是使用视图解析映射物理位置?

答案很简单:

  • 控制器中的响应代码不需要知道视图的具体物理位置,通过视图解析器解耦控制器对视图物理位置的依赖;
  • 通过独立的视图解组件可以扩展 Spring MVC 对各种不同视图技术的支持。Spring MVC 就支持多达 13 种视图技术;
  • 简化控制器中的响应代码;

3.2 配置视图解析器

视图解析器需要在 Spring MVC 项目中显示配置,Spring MVC 虽然提供了视图解析器,但它不可能知道开发者会把物理视图放在哪个位置,所以,需要通过配置指定物理视图的真正位置。

配置 InternalResourceViewResolver 很简单。打开 WebConfig 配置类,在配置类中添加如下代码;

@Bean
public InternalResourceViewResolver viewResolver() {	
	InternalResourceViewResolver inResolver=new InternalResourceViewResolver();	
	inResolver.setPrefix("/WEB-INF/jsp/");
	inResolver.setSuffix(".jsp");
	return inResolver;
}

解释一下上面的代码:

  • @Bean 注解表示此对象由 Spring 容器创建;
  • inResolver.setPrefix ("/WEB-INF/jsp/") 表示 JSP 页面视图所在物理位置;
  • inResolver.setSuffix (".jsp") 表示 JSP 视图的后缀。

Tips : 如果控制器中返回的是 “hello” 字符串,经视图解析器解析后,则认为对应的物理视图是 “/WEB-INF/jsp/hello.jsp”
需要保证存在这个文件,否则浏览器上就会出现 404 错误。

Ok 按要求在项目的 WEB-INF 目录下创建 jsp 目录,再在此目录下创建名为 hello.jsp 文件,并编辑内容。

再次在浏览器中输入:http://localhost:8888/sm-demo/hello

你会看到:

图片描述

Spring MVC 除了支持 JSP 视图,还支持其它如:freemarkerthymeleaf 等视图技术。会另设专题讲解。

3.3. 静态资源

如果浏览器中请求的是一个静态资源(浏览器能解释的资源,如 HtmlCssJs、图片……),有必要经过前端控制器吗?

当然不需要。

但是,你可以试着在 WEB 项目的 根目录下创建名为 static.html 的静态资源,然后在浏览器直接请求一下(http://localhost:8888/sm-demo/static.html)。会发现请求不到,那是因为你的请求还是经过了前端控制器。

所以,咱们要告诉 Spring MVC 静态资源还是交回给 Servlet 容器处理吧, 就不劳您大驾了。

  • 打开 WebConfig 配置类,让其实现 WebMvcConfigurer 接口;
public class WebConfig implements WebMvcConfigurer{ 

}
  • 重写 configureDefaultServletHandling() 方法,启动 Servletdefault Servlet 来处理静态资源;
public void configureDefaultServletHandling(DefaultServletHandlerConfigurer configurer) {
	configurer.enable();
}

图片描述

4. 组件之间的协作关系

通过对几大核心组件的介绍,相信大家对它们各自的功能有所了解。但是,你知道它们之间是如何协作一起完成用户的一次请求的吗?

当用户在浏览器发出请求的那一刻起,这些组件就紧密地团结在一起,为用户的请求保驾护航。

图片描述
如上图所述,简要描述一下它们是如何协调一致完成工作的:

  1. 浏览器的请求到达前端控制器DispatcherServlet);
  2. 前端控制器解析出请求路径后询问映射器,咱们是否提供的有用户需要的用户控制器映射器把查询结果返回给前端控制器
  3. 适配器的作用就是统一不同类型的用户控制器(也体现了 Spring MVC 中用户控制器的多样性和灵活性);
  4. 用户控制器开始工作(具体的响应逻辑);
  5. 用户控制器返回视图逻辑名和视图中所需要的数据ModelAndView);
  6. 前端控制器询问视图解析器,你能够根据逻辑名找到物理视图吗?视图解析器开始工作并找到物理视图;
  7. 前端控制器渲染物理视图和数据,生成浏览器能够识别的数据格式;
  8. 响应浏览器,并在浏览器中显示最终请求结果。

Spring MVC 中,用户的每一次请求都是众多组件通力合作完成的,它们是相亲相爱的一家人。

5. 小结

每一个组件都有自己的特点,但大家都有一个共同特点,都是为前端控制器服务的。

用户的每一次请求、响应,都是经由所有组件一起协作完成的。组件之间有很好的隔离性,但其内部又有其完善的功能,高内聚,低耦合是 Spring MVC 组件最大的特色。通过本章节内容的学习,务必记住这些组件及组件的功能。