Spring Web MVC

Spring Web MVC - 图1

在《J2EE 核心模式》这本书里面有个“前端控制器”的概念,提供一个集中的访问点来处理所有相关请求。

DispatcherServlet 也是围绕着“前端控制器”这个概念来设计的。在 Spring 的官方文档中有对应的描述。

https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-servlet

image.png

映射处理

Servlet 映射处理

在 Servlet 规范中有描述映射匹配问题,存在4种匹配方式。

image.png

我们在 eclipse 中创建一个 Dynamic Web Project 项目,创建一个 IndexServlet 类,Servlet 可以通过 web.xml or 注解的方式配置,如下图所示。

在创建 web.xml 文件的时候选择 http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd schemo

image.png

or

image.png

在 eclipse 的 Servers 中添加这个创建的项目,在 Servers 目录下的 server.xml 文件中可以看到 ServletContext Path,这里是 “/servlet-demo”

image.png

ServletContext Path = /servlet-demo
IndexServlet = /index
URI = /servlet-demo/index

浏览器通过访问 http://localhost:8080/test2/index 就可以请求到该 Servlet。

Spring Web MVC

Spring Web MVC 的配置 Bean 是 WebMvcProperties。

处理映射(HandlerMapping)

HandlerMapping(实现类 RequestMappingHandlerMapping):寻找 Request URI 匹配的 Handler,Handler(实现类 HandlerMethod) 包装了 Controller 类中的方法,查看 HandlerMethod 的源码就知道,它其实是利用反射来执行 Controller 中对应的方法。

image.png

请求处理流程:Request -> Handler -> 执行方法 -> 返回

处理拦截(HandlerInterceptor)

自定义拦截器:可以通过实现接口 HandlerInterceptor(定义拦截器的接口)来实现自定义拦截器功能。

注册拦截器:通过继承 WebMvcConfigurerAdapter 类,重写 addInterceptors() 方法来实现。不过在 Spring 5 里面 WebMvcConfigurerAdapter 类已经过期,可以通过实现 WebMvcConfigurer 接口,重写 addInterceptors() 方法来实现。

具体实现代码可以参考 Spring 官方文档:
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-config-interceptors

可以通过拦截器来来理解 Handler 是什么,我们通过 debug 如下代码来看 Handler 中的内容。

image.png

控制台输出:

Handler Object:public java.lang.String com.demo.springwebmvc.controller.IndexController.index()

注意:这里的 Handler 不止是 HandlerMethod,还有可能是其他的对象,所以这里的参数类型使用的是 Object。这里是 HandlerMethod 是因为这里使用的是 RequestMapping。

异常处理

Servlet 标准

在 Servlet 规范的“10.9 Error Handling”章节中描述了 Servlet 异常处理的规范。

接下来我们通过实现一个异常处理的例子来分析 Servlet 的异常处理流程,在 web.xml 中配置异常处理内容,提供一个异常处理的 Servlet 类 PageNotFoundServlet。

  1. <servlet>
  2. <servlet-name>PageNotFoundServlet</servlet-name>
  3. <servlet-class>com.servlet.demo.PageNotFoundServlet</servlet-class>
  4. </servlet>
  5. <servlet-mapping>
  6. <servlet-name>PageNotFoundServlet</servlet-name>
  7. <url-pattern>/404.html</url-pattern>
  8. </servlet-mapping>
  9. <error-page>
  10. <error-code>404</error-code>
  11. <location>/404.html</location>
  12. </error-page>
  1. public class PageNotFoundServlet extends HttpServlet {
  2. private static final long serialVersionUID = 1L;
  3. protected void doGet(HttpServletRequest request, HttpServletResponse response)
  4. throws ServletException, IOException {
  5. response.setContentType("text/html;charset=UTF-8");
  6. response.setCharacterEncoding("UTF-8");
  7. PrintWriter writer = response.getWriter();
  8. writer.println("status_code:" + request.getAttribute("javax.servlet.error.status_code"));
  9. writer.println("exception_type:" + request.getAttribute("javax.servlet.error.exception_type"));
  10. writer.println("message:" + request.getAttribute("javax.servlet.error.message"));
  11. writer.println("exception:" + request.getAttribute("javax.servlet.error.exception"));
  12. writer.println("request_uri:" + request.getAttribute("javax.servlet.error.request_uri"));
  13. writer.println("servlet_name:" + request.getAttribute("javax.servlet.error.servlet_name"));
  14. }
  15. }

在浏览器中请求一个不存在的地址,请求的 response 返回的内容如下图所示。

image.png

从上面的 response 中的内容我们可以反推出容器异常处理的流程。在请求一个不存在的地址,容器根据 error-page 配置的 location forward 到 PageNotFoundServlet 类,返回页面。

image.png

Spring Web MVC

Spring MVC 提供了一个注解来处理 Controller 的异常,示例代码如下所示。

  1. @RestControllerAdvice
  2. public class RestControllerAdviser {
  3. @ExceptionHandler({NullPointerException.class})
  4. public Object pageNotFound(HttpServletRequest request, HttpServletResponse response) {
  5. return "空指针异常";
  6. }
  7. }
  1. @RestController
  2. public class IndexController {
  3. @GetMapping("npe")
  4. public void npe() {
  5. throw new NullPointerException("空指针异常");
  6. }
  7. }

Spring 官网参考文档:
https://docs.spring.io/spring/docs/5.0.0.RELEASE/spring-framework-reference/web.html#mvc-exceptionhandlers

Spring Boot

Spring Boot 也提供了和 Servlet 类似的处理方式,需要在配置类中继承 ErrorPageRegistrar 接口,实现 registerErrorPages() 方法,配置错误请求的转发地址,同时提供错误处理方法。

  1. @SpringBootApplication
  2. public class SpringWebMvcApplication implements WebMvcConfigurer, ErrorPageRegistrar {
  3. public static void main(String[] args) {
  4. SpringApplication.run(SpringWebMvcApplication.class, args);
  5. }
  6. @Override
  7. public void registerErrorPages(ErrorPageRegistry registry) {
  8. registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html"));
  9. }
  10. }
  1. @RestController
  2. public class IndexController {
  3. @GetMapping("404.html")
  4. public String PageNotFound(){
  5. return "页面找不到";
  6. }
  7. }

视图解析

View

render:处理页面渲染的逻辑,比如把 Thymeleaf 模板文件渲染成最终的 HTML 代码在浏览其中显示,例如:JSP、Thymeleaf 等

ViewResolver

ViewResolver:页面解析器,提供一个 resolveViewName() 方法,通过这个方法寻找对应的 View 对象。

请求处理流程:Request -> RequestMappingHandlerMapping -> HandlerMethod -> return “viewName” -> 完整的页面名称 = prefix + “viewName” + suffix -> ViewResolver -> View -> render -> HTML

ContentNegotiatingViewResolver(内容协调):用于处理多个 ViewReolver(JSP、Thymeleaf ),有一个最佳匹配原则。所以上面的“请求处理流程”在 ViewResolver 前面还有一个 ContentNegotiatingViewResolver。

Spring 官方文档中也有描述,这里配置了多个 ViewResolver,它会根据最佳匹配原则来选择对应的 ViewResolver。

image.png

源代码中的最佳匹配原则如下图所示。

image.png

Thymeleaf

Thymeleaf 在 Spring Boot 中有一个自动装配类 ThymeleafAutoConfiguration,属性类 ThymeleafProperties。

image.png

在 ThymeleafProperties 类中设置了一些默认属性,比如 prefix、suffix,prefix 默认是放在 classpath:/templates/ 目录里。

在 templates 目录里面创建一个 view.html 文件,同时创建一个 ViewController。

image.png

  1. @Controller
  2. public class ViewController {
  3. @GetMapping("/view")
  4. public String view (){
  5. return "view";
  6. }
  7. }

国际化

Spring Boot 中提供了一个国际化的自动装配类 MessageSourceAutoConfiguration,使用的属性配置类是 MessageSourceProperties,在该类中存在一些默认配,比如 basename 属性。

image.png

在 resources 目录创建国际化配置文件 messages.properties 和 messages_zh_CN.properties,这里需要配置一个默认的国际化属性文件 messages.properties,官方文档中有描述。

image.png

项目文件地址如下图所示。

image.png

在 messages_zh_CN.properties 配置文件中的内容如下。

  1. home.welcome = \u6b22\u8fce

页面 Thymeleaf 模板文件使用该国际化配置。

  1. <!DOCTYPE html>
  2. <html lang="en">
  3. <head>
  4. <meta charset="UTF-8">
  5. <title>Title</title>
  6. </head>
  7. <body>
  8. Hello World
  9. <p th:text="#{home.welcome}">Welcome to our grocery store!</p>
  10. </body>
  11. </html>