首页 > 编程笔记

Spring MVC教程(简明版)

虽然 Spring Boot 近几年发展迅猛,但是 Spring MVC 在 Web 开发领域仍然占有重要的地位。本文主要讲解 Spring MVC 的核心:DispatcherServlet 类、拦截器及控制器的相关知识。

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的工作流程图 

从图 1 中可以看到,Spring MVC 框架的主要工作流程可以分为以下几步:

  1. 在浏览器中输入 URL 地址后,所有的请求都被 DispatcherServlet 拦截。
  2. DispatcherServlet 通过 HandlerMapping 解析 URL 地址,找到匹配的能处理该请求的 Controller,然后请求被 Controller 处理。
  3. Controller 通过调用具体的业务逻辑,返回 ModelAndView。
  4. DispatcherServlet通过ViewResolver(视图解析器),组装 Model 数据与对应的视图,如某个 JSP 页面等。
  5. 将视图结果展现在浏览器页面上。

Spring MVC 框架提供了很多重要的接口,用于完成一个 HTTP 请求的处理,主要的接口如表 1 所示。

表1 Spring MVC框架的主要接口
接   口 说  明
HandlerMapping 通过一系列拦截器将请求映射到一个控制器(Controller)上
HandlerAdapter DispatcherServlet的辅助类,辅助映射请求处理
HandlerExceptionResolver 解析异常,可以映射到一个处理器、视图或其他目标对象上
ViewResolver 视图解析器,返回对应的真正视图
LocalResolver 区域解析器
MultipartResolver 处理文件上传等请求

 DispatcherServlet 类

DispatcherServlet 类是 Servlet 的一种实现,置于 Controller 前,对所有的请求提供统一的处理逻辑。

DispatcherServlet 类需要提前声明,可以采用 Java 配置或 web.xml 方式进行声明。它通过请求映射、视图解析及统一异常处理等 Spring 配置发现真正的请求处理组件,从而完成对请求的处理。

DispatcherServlet 类处理请求的步骤如下:

  1. DispatcherServlet 类将 WebApplicationContext 绑定到请求中,WebApplication-Context 会持有 Controller、ViewResolver、HandlerMapping、Service 及 Repository 等。
  2. HandlerMapping 通过匹配规则授权给一个 Handler 来处理具体的逻辑,此处的 Handler 是一个处理链,整个处理过程会通过拦截器(Interceptor)和控制器(Controller)等来处理。
  3. 返回 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 个方法:


下面展示一个简单的 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 还提供了一些更简单的注解,具体如下:

2) 参数注解

参数注解可以对方法的请求参数进行注解,用于获取 HTTP 请求中的属性值。常用的参数注解如下:

下面给出一个请求注解与参数注解的示例:
//定义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";
    }
}

优秀文章