SpringMVC
Spring Web MVC
MVC 概念
- M:Model
- V:View
- C:Controller
SpringMVC

Controller:springmvc 中 Controller 由两部分组成
- 前端控制器(Front Controller): 指DispatcherServlet
- 应用控制器(App Controller):使用@Controller修饰或者实现Controller接口的类
Context:springmvc 中 Context 由两部分组成
Root WebApplicationContext:它由 ServletContextListener -> ContextLoadListener 来加载

Servlet WebApplicationContext:它由 DispatcherServlet 来加载
二者的关系是:Root WebApplicationContext处理底层,Servlet WebApplicationContext处理上层,二者之间有一个层次关系。
Services:使用 @Service 修饰的类
Repositories:使用 @Repository 修饰的类
处理映射
Servlet 处理方式
使用 web.xml 配置映射或者 @WebServlet注解的方式
@WebServlet("/IndexServlet")
//@WebServlet("/") 匹配当前你路径,不包含子路径
//@WebServlet("/*") 匹配所有路径,包含子路惊
//@WebServlet("*.jsp") 匹配以jsp结尾文件
public class IndexServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
/**
* @see HttpServlet#HttpServlet()
*/
public IndexServlet() {
super();
// TODO Auto-generated constructor stub
}
/**
* @see HttpServlet#doGet(HttpServletRequest request, HttpServletResponse response)
*/
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
response.getWriter().append("Served at: ").append(request.getContextPath());
}
/**
* @see HttpServlet#doPost(HttpServletRequest request, HttpServletResponse response)
*/
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
// TODO Auto-generated method stub
doGet(request, response);
}
}
ContextPath: /servlet-demo (在tomcat的server.xml文件中)

完整路径 = ContextPath + servlet Path = /servlet-demo/IndexServlet

SpringMvc 处理方式
@RestController // @Controller + @ResponseBody
public class RestDemoController {
@GetMapping
public String index() {
return "hello world";
}
}

Web URI组装
RequestURI = @RestController.value() + @RequestMapping.value()/@GetMapping.value()
当前 ServletContextPath = "" or "/"
这里没有配置任何路径,springboot也可以启动起来,要归功于 DispatcherServlet 的自动装配。

DispatcherSerlet 自动装配:DispatcherServletAutoConfiguration

Web Mvc 配置
Sprig Boot 允许通过 application.properties 去定义一些配置,配置外部化
Spring Web Mvc 的配置Bean: WebMvcProperties
WebMvcProperties 配置前缀: spring.mvc

在 application.properties 中进行配置:

Web Uri 映射处理
HandlerMapping 寻找 Request URI,匹配 Handler。
Handler 是处理的方法,是一种实例。
Handler 操作
- 整体流程:Request -> Handler -> 执行结果 -> 返回(REST)-> 普通文本
- 请求处理映射: RequestMappingHandlerMapping -> @RequestMapping Handler Mapping
Handler 是什么?
拦截器:HandlerInterceptor 可以理解 Handler 到底是什么。
public interface HandlerInterceptor {
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
return true;
}
default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception {
}
default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception {
}
}
HandlerInterceptor 处理顺序:
- preHandle (true) -> handle执行(Method#invoke)-> postHandle -> afterCompletition
- preHandle(false) -> end;preHandle 返回失败,直接结束,不执行后面流程。
自定义拦截器,DefaultHandlerInterceptor:
实现 HandlerInterceptor 接口,重写方法
public class DefaultHandlerInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { // 输出当前拦截器 System.out.println(handler.toString()); return true; } }将拦截器注册到 registry
实现 WebMvcConfigureAdapter,重写 addInterceptors 方法
@SpringBootApplication public class SpringWebmvcApplication extends WebMvcConfigurerAdapter { public static void main(String[] args) { SpringApplication.run(SpringWebmvcApplication.class, args); } @Override public void addInterceptors(InterceptorRegistry registry) { // 把拦截器注册到 registry registry.addInterceptor(new DefaultHandlerInterceptor()); } }查看结果

异常处理
Servlet 处理方式
配置 web.xml 错误页面
<?xml version="1.0" encoding="UTF-8"?> <web-app version="3.0" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xml="http://www.w3.org/XML/1998/namespace" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd "> <servlet> <servlet-name>pageNotFoundServlet</servlet-name> <servlet-class>com.gupao.servlet.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> </web-app>自定义错误处理servlet
public class PageNotFoundServlet extends HttpServlet { @Override protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { resp.setContentType("text/html;charset=UTF-8"); resp.setCharacterEncoding("UTF-8"); PrintWriter writer = resp.getWriter(); writer.write("页面找不到"); } @Override protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this.doGet(req, resp); } }浏览器访问

原因是,当前 context-path 是 “servlet-demo”,配置的错误处理页面只能处理 servlet-demo 路径下的 404错误,无法处理其他 context-path 下的 404错误,所以还是提示 404。
- 优点:统一处理,业界标准
- 不足:灵活度不够,只能定义 web.xml文件里面
SpringMvc 处理方式
使用 @ControllerAdvice + @ExceptionHandler 注解的方式
配置异常请求
@GetMapping("/npe") public String npe() { throw new NullPointerException("故意抛出异常!"); }设置拦截切面
@RestControllerAdvice("com.gupao.microservice.springwebmvc") public class RestControllerAdviser { // 拦截额空指针异常 @ExceptionHandler(NullPointerException.class) public Object handleNPE(Throwable throwable) { return throwable.getMessage(); } }浏览器访问

- @ExceptionHandler
- 优点:易于理解,尤其是全局异常处理
- 不足:很难完全掌握所有的异常类型
- @RestControllerAdvice = @ControllerAdvice + @ResponseBody
- @ControllerAdvice 专门拦截(AOP) @Controller
SpringBoot处理方式
实现 ErrorPageRegistrar 接口,重写registerErrorPages方法
注册ErrorPage对象
@SpringBootApplication public class SpringWebmvcApplication extends WebMvcConfigurerAdapter implements ErrorPageRegistrar { public static void main(String[] args) { SpringApplication.run(SpringWebmvcApplication.class, args); } @Override public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(new DefaultHandlerInterceptor()); } // 新增一个404错误页面处理处理 @Override public void registerErrorPages(ErrorPageRegistry registry) { registry.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/404.html")); } }实现 ErrorPage 对象中的Path 路径Web服务
@RestController public class PageNotFoundController { @GetMapping("/404.html") public Object pageNotFound(HttpServletRequest request, HttpStatus status, Throwable throwable) { Map<String, Object> errorMap = new HashMap<>(); // 获取错误码 errorMap.put("statusCode", request.getAttribute("javax.servlet.error.status_code")); // 获取请求的 uri errorMap.put("requestUri", request.getAttribute("javax.servlet.error.request_uri")); System.err.println(errorMap.toString()); return errorMap; } }浏览器访问

- 状态码:比较通用,不需要理解Spring WebMVC 异常体系
- 不足:页面处理的路径必须固定
视图技术
View
render 方法
public interface View {
// ...
void render(@Nullable Map<String, ?> model, HttpServletRequest request, HttpServletResponse response) throws Exception;
}
处理页面渲染的逻辑,例如:Velocity、JSP、Thymeleaf 官方推荐)
ViewResolver
View Resolver = 页面 + 解析器 -> resolveViewName 寻找合适/对应 View 对象
处理流程:
请求返回 View
RequestURI-> RequestMappingHandlerMapping -> HandleMethod -> return "viewName"
完整的 viewName = prefix + "viewName" + suffix
渲染 View
ViewResolver -> View -> render -> HTML
Spring Boot 解析完整的页面路径:
spring.view.prefix + HandlerMethod return + spring.view.suffix
ContentNegotiationViewResolver
用于协调多个ViewResolver:JSP、Velocity、Thymeleaf
当所有的ViewResover 配置完成时,他们的 order 默认值一样,所以先来先服务(List)
当他们定义自己的 order,通过 order 来倒序排列
Thymeleaf
自动装配类:ThymeleafAutoConfiguration
外部配置类:ThymeleafProperties

配置项前缀:spring.thymeleaf
模板寻找前缀:spring.thymeleaf.prefix
模板寻找后缀:spring.thymeleaf.suffix
代码示例:/thymeleaf/index.htm
- prefix: /thymeleaf/
- return value : index
- suffix: .htm
Thymeleaf 使用
引入 thymeleaf starter
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-thymeleaf</artifactId> <version>2.1.8.RELEASE</version> </dependency>定义 indexController
@Controller public class IndexController { @RequestMapping public String index() { return "index"; } }配置前缀、后缀
spring.thymeleaf.prefix=classpath:/thymeleaf/ spring.thymeleaf.suffix=.htm编写页面

<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>首页</title> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/> </head> <body> <p th:text="#{home.welcome}">Welcome to our grocery store!</p> </body> </html>浏览器访问

源码分析
org.springframework.web.servlet.view.AbstractCachingViewResolver#resolveViewName

国际化
•Locale/LocaleContext
•
•LocaleContextHolder
•
•LocaleResolver/LocaleContextResolver
SpringBoot 配置
application.properties 配置
spring.messages.basename=META-INF/locale/messagesmessage.properties & message_zh_CN.properties
# message.properties home.welcome=Hello, World # message_zh_CN.properties home.welcome=欢迎光临
浏览器访问

源码分析
MessageSourceProperties
org.springframework.boot.autoconfigure.context.MessageSourceAutoConfiguration#messageSource
使用 ResourceBundleMessageSource 来加载配置
