Spring Web MVC
在《J2EE 核心模式》这本书里面有个“前端控制器”的概念,提供一个集中的访问点来处理所有相关请求。
DispatcherServlet 也是围绕着“前端控制器”这个概念来设计的。在 Spring 的官方文档中有对应的描述。
https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-servlet
映射处理
Servlet 映射处理
在 Servlet 规范中有描述映射匹配问题,存在4种匹配方式。
我们在 eclipse 中创建一个 Dynamic Web Project 项目,创建一个 IndexServlet 类,Servlet 可以通过 web.xml or 注解的方式配置,如下图所示。
在创建 web.xml 文件的时候选择 http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd schemo
or
在 eclipse 的 Servers 中添加这个创建的项目,在 Servers 目录下的 server.xml 文件中可以看到 ServletContext Path,这里是 “/servlet-demo”
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 中对应的方法。
请求处理流程: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 中的内容。
控制台输出:
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。
<servlet>
<servlet-name>PageNotFoundServlet</servlet-name>
<servlet-class>com.servlet.demo.PageNotFoundServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>PageNotFoundServlet</servlet-name>
<url-pattern>/404.html</url-pattern>
</servlet-mapping>
<error-page>
<error-code>404</error-code>
<location>/404.html</location>
</error-page>
public class PageNotFoundServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setContentType("text/html;charset=UTF-8");
response.setCharacterEncoding("UTF-8");
PrintWriter writer = response.getWriter();
writer.println("status_code:" + request.getAttribute("javax.servlet.error.status_code"));
writer.println("exception_type:" + request.getAttribute("javax.servlet.error.exception_type"));
writer.println("message:" + request.getAttribute("javax.servlet.error.message"));
writer.println("exception:" + request.getAttribute("javax.servlet.error.exception"));
writer.println("request_uri:" + request.getAttribute("javax.servlet.error.request_uri"));
writer.println("servlet_name:" + request.getAttribute("javax.servlet.error.servlet_name"));
}
}
在浏览器中请求一个不存在的地址,请求的 response 返回的内容如下图所示。
从上面的 response 中的内容我们可以反推出容器异常处理的流程。在请求一个不存在的地址,容器根据 error-page 配置的 location forward 到 PageNotFoundServlet 类,返回页面。
Spring Web MVC
Spring MVC 提供了一个注解来处理 Controller 的异常,示例代码如下所示。
@RestControllerAdvice
public class RestControllerAdviser {
@ExceptionHandler({NullPointerException.class})
public Object pageNotFound(HttpServletRequest request, HttpServletResponse response) {
return "空指针异常";
}
}
@RestController
public class IndexController {
@GetMapping("npe")
public void npe() {
throw new NullPointerException("空指针异常");
}
}
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() 方法,配置错误请求的转发地址,同时提供错误处理方法。
@SpringBootApplication
public class SpringWebMvcApplication implements WebMvcConfigurer, ErrorPageRegistrar {
public static void main(String[] args) {
SpringApplication.run(SpringWebMvcApplication.class, args);
}
@Override
public void registerErrorPages(ErrorPageRegistry registry) {
registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html"));
}
}
@RestController
public class IndexController {
@GetMapping("404.html")
public String PageNotFound(){
return "页面找不到";
}
}
视图解析
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。
源代码中的最佳匹配原则如下图所示。
Thymeleaf
Thymeleaf 在 Spring Boot 中有一个自动装配类 ThymeleafAutoConfiguration,属性类 ThymeleafProperties。
在 ThymeleafProperties 类中设置了一些默认属性,比如 prefix、suffix,prefix 默认是放在 classpath:/templates/ 目录里。
在 templates 目录里面创建一个 view.html 文件,同时创建一个 ViewController。
@Controller
public class ViewController {
@GetMapping("/view")
public String view (){
return "view";
}
}
国际化
Spring Boot 中提供了一个国际化的自动装配类 MessageSourceAutoConfiguration,使用的属性配置类是 MessageSourceProperties,在该类中存在一些默认配,比如 basename 属性。
在 resources 目录创建国际化配置文件 messages.properties 和 messages_zh_CN.properties,这里需要配置一个默认的国际化属性文件 messages.properties,官方文档中有描述。
项目文件地址如下图所示。
在 messages_zh_CN.properties 配置文件中的内容如下。
home.welcome = \u6b22\u8fce
页面 Thymeleaf 模板文件使用该国际化配置。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
Hello World
<p th:text="#{home.welcome}">Welcome to our grocery store!</p>
</body>
</html>