Spring MVC教程(简明版)
虽然 Spring Boot 近几年发展迅猛,但是 Spring MVC 在 Web 开发领域仍然占有重要的地位。本文主要讲解 Spring MVC 的核心:DispatcherServlet 类、拦截器及控制器的相关知识。
Spring MVC 是一种将业务、视图、数据分离的设计模式。它不仅出现在 Java 后端开发中,在前端开发中也经常使用这种设计模式。Spring MVC 提供了高度可配置的 Web 框架和多种视图解决方案,并且提供了基于 Servlet 的接口,可以灵活地处理请求。
	
图1 Spring MVC的工作流程图
从图 1 中可以看到,Spring MVC 框架的主要工作流程可以分为以下几步:
Spring MVC 框架提供了很多重要的接口,用于完成一个 HTTP 请求的处理,主要的接口如表 1 所示。
	
DispatcherServlet 类需要提前声明,可以采用 Java 配置或 web.xml 方式进行声明。它通过请求映射、视图解析及统一异常处理等 Spring 配置发现真正的请求处理组件,从而完成对请求的处理。
DispatcherServlet 类处理请求的步骤如下:
DispatcherServlet 类通过 web.xml 方式可以声明,代码如下:
DispatcherServlet 类也可以通过实现 WebApplicationInitializer 接口来声明。以下是摘自 Spring 官网的一段示例代码:
Handler-Interceptor 接口提供了 3 个方法:
下面展示一个简单的 Interceptor 登录拦截器的例子:
如果想让 Interceptor 生效,还需要声明一下,代码如下:
根据不同的请求方法,Spring MVC 还提供了一些更简单的注解,具体如下:
下面给出一个请求注解与参数注解的示例:
声明:《Java系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。
	Spring MVC 是一种将业务、视图、数据分离的设计模式。它不仅出现在 Java 后端开发中,在前端开发中也经常使用这种设计模式。Spring MVC 提供了高度可配置的 Web 框架和多种视图解决方案,并且提供了基于 Servlet 的接口,可以灵活地处理请求。
Spring MVC 的工作流程
Spring MVC 框架主要由核心 Servlet(DispatcherServlet)、处理器映射(Handler-Mapping)、控制器(Controller)、视图解析器(ViewResolver)、模型(Model)及视图(View)等几部分组成,其主要的工作流程如图 1 所示。
图1 Spring MVC的工作流程图
从图 1 中可以看到,Spring MVC 框架的主要工作流程可以分为以下几步:
- 在浏览器中输入 URL 地址后,所有的请求都被 DispatcherServlet 拦截。
- DispatcherServlet 通过 HandlerMapping 解析 URL 地址,找到匹配的能处理该请求的 Controller,然后请求被 Controller 处理。
- Controller 通过调用具体的业务逻辑,返回 ModelAndView。
- DispatcherServlet通过ViewResolver(视图解析器),组装 Model 数据与对应的视图,如某个 JSP 页面等。
- 将视图结果展现在浏览器页面上。
Spring MVC 框架提供了很多重要的接口,用于完成一个 HTTP 请求的处理,主要的接口如表 1 所示。
| 接 口 | 说 明 | 
|---|---|
| HandlerMapping | 通过一系列拦截器将请求映射到一个控制器(Controller)上 | 
| HandlerAdapter | DispatcherServlet的辅助类,辅助映射请求处理 | 
| HandlerExceptionResolver | 解析异常,可以映射到一个处理器、视图或其他目标对象上 | 
| ViewResolver | 视图解析器,返回对应的真正视图 | 
| LocalResolver | 区域解析器 | 
| MultipartResolver | 处理文件上传等请求 | 
DispatcherServlet 类
DispatcherServlet 类是 Servlet 的一种实现,置于 Controller 前,对所有的请求提供统一的处理逻辑。DispatcherServlet 类需要提前声明,可以采用 Java 配置或 web.xml 方式进行声明。它通过请求映射、视图解析及统一异常处理等 Spring 配置发现真正的请求处理组件,从而完成对请求的处理。
DispatcherServlet 类处理请求的步骤如下:
- DispatcherServlet 类将 WebApplicationContext 绑定到请求中,WebApplication-Context 会持有 Controller、ViewResolver、HandlerMapping、Service 及 Repository 等。
- HandlerMapping 通过匹配规则授权给一个 Handler 来处理具体的逻辑,此处的 Handler 是一个处理链,整个处理过程会通过拦截器(Interceptor)和控制器(Controller)等来处理。
- 返回 Model 数据并渲染视图。
DispatcherServlet 类通过 web.xml 方式可以声明,代码如下:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://
xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
    </context-param>
    <listener>
        <listener-class>org.springframework.web.context.Context
            LoaderListener
        </listener-class>
    </listener>
    <servlet>
        <servlet-name>dispatcher</servlet-name>
        <servlet-class>org.springframework.web.servlet.Dispatcher
            Servlet
        </servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
</web-app>
DispatcherServlet 类也可以通过实现 WebApplicationInitializer 接口来声明。以下是摘自 Spring 官网的一段示例代码:
public class MyWebApplicationInitializer implements WebApplication
Initializer {
    @Override
    public void onStartup(ServletContext servletCxt) {
        //加载Spring Web应用上下文
        AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
        ac.register(AppConfig.class);
        ac.refresh();
        //创建DispatcherServlet
        DispatcherServlet servlet = new DispatcherServlet(ac);
        //注册DispatcherServlet
        ServletRegistration.Dynamic registration = servletCxt.
        addServlet("app", servlet);
        registration.setLoadOnStartup(1);
        registration.addMapping("/app/*");
    }
}
HandlerInterceptor 拦截器
为了实现一些特殊功能,如在请求处理之前进行用户校验或日志记录等,Spring MVC 提供了灵活的处理方式,即定义拦截器。自定义拦截器实现 HandlerInterceptor 接口,然后实现对应的抽象方法,这样可以做一些预处理或请求之后的处理。Handler-Interceptor 接口提供了 3 个方法:
- preHandle(HttpServletRequest request, HttpServletResponse response, Object handler):默认返回 true。该方法是在处理器执行之前调用,通过返回 true 或 false,判断是否继续执行后面的处理链。返回 true 表示继续向后执行,返回 false 表示中断后续处理。
- postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView):该方法是在处理器处理请求之后及解析视图之前调用,可以通过该方法对请求后的模型和视图进行修改。
- afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex):该方法是在处理器处理完请求之后,即视图渲染结束后执行。
下面展示一个简单的 Interceptor 登录拦截器的例子:
//自定义拦截器
public class LogInterceptor implements HandlerInterceptor {
    //处理器处理请求之后且视图渲染结束执行
    @Override
    public void afterCompletion(HttpServletRequest request,
                                HttpServletResponse response, Object handler,Exception ex) throws Exception {
        System.out.println("afterCompletion方法是在处理器处理完请求之后,即视图渲染结束后执行");
    }
    //处理器处理请求之后,即解析视图之前调用
    @Override
    public void postHandle(HttpServletRequest request,
                           HttpServletResponse response, Object handler,
                           ModelAndView modelAndView) throws Exception {
        System.out.println("postHandle方法是在处理器处理请求之后解析视图之前调用");
    }
    //处理器处理请求之前执行
    @Override
    public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler)throws Exception {
        System.out.println("preHandle方法是在处理器执行之前调用");
        return true;
    }
}
如果想让 Interceptor 生效,还需要声明一下,代码如下:
<mvc:interceptors>
    <!--配置一个全局拦截器,拦截所有请求 -->
    <bean class="com.cn.springmvc.LogInterceptor" />
</mvc:interceptors>
注意:postHandle() 方法很少与 @ResponseBody 和 ResponseEntity 配合使用,因为 @ResponseBody 和 ResponseEntity 已经提交了响应,无法再修改。
Spring MVC 注解
Spring MVC 框架提供了大量的注解,如请求注解、参数注解、响应注解及跨域注解等。这些注解提供了解决 HTTP 请求的方案。1) 请求注解
请求注解声明在类或者方法中用于声明接口类或者请求方法的类型。1. @Controller 注解
@Controller 注解声明在类中,表示该类是一个接口类。@RestController 也是声明接口类,它是一个组合注解,由 @Controller 与 @ResponseBody 注解组合而成。2. @RequestMapping 注解
@RequestMapping 注解声明在类或者方法中,可以指定路径、方法(GET、HEAD、POST、PUT、PATCH、DELETE、OPTIONS或TRACE等)或参数等。根据不同的请求方法,Spring MVC 还提供了一些更简单的注解,具体如下:
- @GetMapping:相当于@RequestMapping(method = {RequestMethod.GET})。
- @PostMapping:相当于@RequestMapping(method = {RequestMethod.POST})。
- @PutMapping:相当于@RequestMapping(method = {RequestMethod.PUT})。
- @DeleteMapping:相当于@RequestMapping(method = {RequestMethod.DELETE})。
- @PatchMapping:相当于@RequestMapping(method = {RequestMethod.PATCH})。
2) 参数注解
参数注解可以对方法的请求参数进行注解,用于获取 HTTP 请求中的属性值。常用的参数注解如下:- @PathVariable:URL 路径参数,用于将 URL 路径中的参数映射到对应方法的参数中。
- @RequestBody:可以映射请求的 Body 对象。
- @RequestHeader:请求 Header 的映射。
- @CookieValue:用于获取 Cookie 的属性值。
- @SessionAttribute:用于获取 Session 的属性值。
下面给出一个请求注解与参数注解的示例:
//定义HiController
@RestController
@RequestMapping("/hi")
public class HiController {
    //请求路径/hi/mvc/{id}
    @RequestMapping("/mvc/{id}")
    public ModelAndView sayHi(@PathVariable Integer id){
        System.out.println(id);
        //定义视图模型
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("say");
        modelAndView.addObject("name","mvc");
        return modelAndView;
    }
}
对应的 say.jsp 代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
hi ,${name}
</body>
</html>
3) 异常注解
有时接口请求的业务处理逻辑会产生异常,为了全局统一异常处理,返回同一个异常页面,可以使用 @ExceptionHandler 注解。@ExceptionHandler 注解声明在方法中,用于提供统一的异常处理。具体的示例代码如下:
public class BaseController {
    //统一异常处理
    @ExceptionHandler
    public String exceptionHandler(HttpServletRequest request,
Exception ex){
        request.setAttribute("ex", ex);
        return "error";
    }
}
在以上代码中,用 @ExceptionHandler 声明 exceptionHandler() 方法,如果接口处理有异常,则跳转到 error.jsp 页面。其他接口类继承 BaseController 类的示例代码如下:
@RestController
@RequestMapping("/hi")
public class TestExceptionController extends BaseController {
    @RequestMapping("/error")
    public ModelAndView sayHi(){
        throw new RuntimeException("testExceptionHandler");
    }
}
为了方便测试,处理方法直接抛出异常,则浏览器跳转到 error.jsp 页面。error.jsp 页面的示例代码如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>Title</title>
</head>
<body>
hi,error
</body>
</html>
4) 跨域注解
后台服务器如果要满足多个客户端的访问,则需要设置跨域访问。@CrossOrigin 注解提供了跨域访问的可能性,它可以声明在类中,也可以声明在方法中。代码如下:
//声明在类中的跨域注解
@CrossOrigin(maxAge = 3600)
@RestController
@RequestMapping("/account")
public class AccountController {
    //声明在方法中的跨域注解
    @CrossOrigin("https://domain2.com")
    @GetMapping("/{id}")
    public Account retrieve(@PathVariable Long id) {
        ...
    }
    @DeleteMapping("/{id}")
    public void remove(@PathVariable Long id) {
        ...
    }
}
如果要全局配置跨域,则需要实现 WebMvcConfigurer 接口的 addCorsMappings() 方法,代码如下:
@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        //全局配置跨域属性
        registry.addMapping("/api/**")
            .allowedOrigins("https://domain2.com")
            .allowedMethods("PUT", "DELETE")
            .allowedHeaders("header1", "header2", "header3")
            .exposedHeaders("header1", "header2")
            .allowCredentials(true)
            .maxAge(3600);
    }
}
5) 请求跳转
请求跳转可以分为“主动跳转”“被动跳转”,其中,“被动跳转”又称为“重定向”。Spring MVC 提供了 forword 和 redirect 关键字用于实现主动跳转和重定向,示例代码如下:
@RestController
@RequestMapping("/hi")
public class HiController {
    @RequestMapping("/forward")
    public String testForward(){
        //请求forward跳转
        return "forward:/hi/error";
    }
    @RequestMapping("/redirect")
    public String testRedirect(){
        //请求redirect跳转
        return "redirect:/hi/error";
    }
}声明:《Java系列教程》为本站“54笨鸟”官方原创,由国家机构和地方版权局所签发的权威证书所保护。
 
	