SrpingMVC

4/10/2022 SrpingMVC

SpringMVC大纲

# 简介

Spring MVC图标

Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 Servlet。

Spring MVC 角色划分清晰,分工明细。由于 Spring MVC 本身就是 Spring 框架的一部分,可以说和 Spring 框架是无缝集成。性能方面具有先天的优越性,是当今业界最主流的 Web 开发框架,最热门的开发技能。

一个好的框架要减轻开发者处理复杂问题的负担,内部有良好的扩展,并且有一个支持它的强大用户群体,恰恰 Spring MVC 都做到了。

# MVC设计模式

MVC设计模式是指MVC框架,其中M(Model)指数据模型层、V(View)指视图层、C(Controller)指控制层。MVC框架的目的是为了将M和V的代码实现分离,使同一个程序可以有不同的表现形式。其中,View 的定义比较清晰,就是用户界面。

  • 视图层(View):负责格式化数据并把它们呈现给用户,包括数据展示、用户交互、数据验证、界面设计等功能。
  • 控制层(Controller):负责接收并转发请求,对请求进行处理后,指定视图并将响应结果发送给客户端。
  • 数据模型层(Model):模型对象拥有最多的处理任务,是应用程序的主体部分,它负责数据逻辑(业务规则)的处理和实现数据操作(即在数据库中存取数据)。

典型的MVC开发模式,主要有两种,分别是JSP+JavaBean 和 Servlet+JSP+JavaBean。

# JSP+JavaBean

JSP+JavaBean 中 JSP 用于处理用户请求,JavaBean 用于封装和处理数据。该模式只有视图和模型,一般把控制器的功能交给视图来实现,适合业务流程比较简单的 Web 程序。

JSP+JavaBean

通过上图可以发现 JSP 从 HTTP Request(请求)中获得所需的数据,并进行业务逻辑的处理,然后将结果通过 HTTP Response(响应)返回给浏览器。从中可见,JSP+JavaBean 模式在一定程度上实现了 MVC,即 JSP 将控制层和视图合二为一,JavaBean 为模型层。

JSP+JavaBean 模式中 JSP 身兼数职,既要负责视图层的数据显示,又要负责业务流程的控制,结构较为混乱,并且也不是我们所希望的松耦合架构模式,所以当业务流程复杂的时候并不推荐使用。

# Servlet+JSP+JavaBean

Servlet+JSP+JavaBean 中 Servlet 用于处理用户请求,JSP 用于数据显示,JavaBean 用于数据封装,适合复杂的 Web 程序。

Servlet+JSP+JavaBean

相比 JSP+JavaBean 模式来说,Servlet+JSP+JavaBean 模式将控制层单独划分出来负责业务流程的控制,接收请求,创建所需的 JavaBean 实例,并将处理后的数据返回视图层(JSP)进行界面数据展示。

Servlet+JSP+JavaBean 模式的结构清晰,是一个松耦合架构模式,一般情况下,建议使用该模式。

# MVC优缺点

优点

  • 多视图共享一个模型,大大提高了代码的可重用性
  • MVC 三个模块相互独立,松耦合架构
  • 控制器提高了应用程序的灵活性和可配置性
  • 有利于软件工程化管理

缺点

  • 原理复杂
  • 增加了系统结构和实现的复杂性
  • 视图对模型数据的低效率访问

MVC 并不适合小型甚至中型规模的项目,花费大量时间将 MVC 应用到规模并不是很大的应用程序,通常得不偿失,所以对于 MVC 设计模式的使用要根据具体的应用场景来决定。

# SpringMVC

Spring MVC 是 Spring 提供的一个基于 MVC 设计模式的轻量级 Web 开发框架,本质上相当于 Servlet。Spring MVC 是结构最清晰的 Servlet+JSP+JavaBean 的实现,是一个典型的教科书式的 MVC 构架,不像 Struts 等其它框架都是变种或者不是完全基于 MVC 系统的框架。

Spring MVC 角色划分清晰,分工明细,并且和 Spring 框架无缝结合。Spring MVC 是当今业界最主流的 Web 开发框架,以及最热门的开发技能。

在 Spring MVC 框架中,Controller 替换 Servlet 来担负控制器的职责,用于接收请求,调用相应的 Model 进行处理,处理器完成业务处理后返回处理结果。Controller 调用相应的 View 并对处理结果进行视图渲染,最终客户端得到响应信息。

Spring MVC 框架采用松耦合可插拔的组件结构,具有高度可配置性,比起其它 MVC 框架更具有扩展性和灵活性。

优点

  • 清晰地角色划分,Spring MVC 在 Model、View 和 Controller 方面提供了一个非常清晰的角色划分,这 3 个方面真正是各司其职,各负其责。
  • 灵活的配置功能,可以把类当作 Bean 通过 XML 进行配置。
  • 提供了大量的控制器接口和实现类,开发者可以使用 Spring 提供的控制器实现类,也可以自己实现控制器接口。
  • 真正做到与 View 层的实现无关。它不会强制开发者使用 JSP,可以根据项目需求使用 Velocity、FreeMarker 等技术。
  • 国际化支持
  • 面向接口编程
  • 与 Spring 框架无缝集成

# Hello SpringMVC

演示第一个SpringMVC程序。具体步骤如下:

# 创建Web应用和引入依赖

创建Web应用可参考传送门

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-web -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-web</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-webmvc</artifactId>
        <version>5.2.9.RELEASE</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/javax.servlet.jsp/jsp-api -->
    <dependency>
        <groupId>javax.servlet.jsp</groupId>
        <artifactId>jsp-api</artifactId>
        <version>2.1</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/junit/junit -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37

# SpringMVC配置

Spring MVC 是基于 Servlet 的,DispatcherServlet 是整个 Spring MVC 框架的核心,主要负责截获请求并将其分派给相应的处理器处理

  1. 首先要定义 DispatcherServlet。跟所有 Servlet 一样,必须在 web.xml 中进行配置。

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="4.0" 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">
    
        <display-name>springMVC</display-name>
        
        <welcome-file-list>
            <welcome-file>index.jsp</welcome-file>
        </welcome-file-list>
    
        <!-- 部署 DispatcherServlet -->
        <servlet>
            <servlet-name>springmvc</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:springmvc-servlet.xml</param-value>
            </init-param>
            <!-- 表示容器再启动时立即加载servlet -->
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>springmvc</servlet-name>
            <!-- 处理所有URL -->
            <!--/ 匹配所有的请求;(不包括.jsp)-->
            <!--/* 匹配所有的请求;(包括.jsp)-->
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
    </web-app>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32

    上述代码配置了一个名为“springmvc”的 Servlet。该 Servlet 是 DispatcherServlet 类型,它就是 Spring MVC 的入口,并通过 <load-on-startup>1</load-on-startup> 配置标记容器在启动时就加载此 DispatcherServlet,即自动启动。然后通过 servlet-mapping 映射到“/”,即 DispatcherServlet 需要截获并处理该项目的所有 URL 请求。

  2. Spring MVC初始化时会在应用程序的目录内查询匹配Spring配置文件,该配置文件的命名规则是servletName-servlet.xml,例如 springmvc-servlet.xml。因此需要在resource目录下创建该配置文件。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd">
    
        <!--处理器映射器-->
        <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
        <!--处理器适配器-->
        <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    
        <!--视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
            <!--前缀-->
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <!--后缀-->
            <property name="suffix" value=".jsp"/>
        </bean>
    </beans>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

    Spring配置文件,注册Spring MVC的三个核心功能,分别是处理器映射器、处理器适配器和视图解析器。

    不配置,也不影响Spring MVC正常使用,应该是Spring MVC有默认的三个功能模块装配。

    只是解析JSP路径的时候需要注意一下,当没配置视图解析器时,需要加视图前缀和后缀。

# 创建Controller

创建 RegisterController 和 LoginController 两个传统风格的控制器类(实现 Controller 接口),分别处理首页中“注册”和“登录”超链接的请求。

Controller 是控制器接口,接口中只有一个方法 handleRequest,用于处理请求和返回 ModelAndView。

package top.snake8859.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class LoginController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        return new ModelAndView("login");
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
package top.snake8859.controller;

import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.mvc.Controller;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class RegisterController implements Controller {
    @Override
    public ModelAndView handleRequest(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws Exception {
        return new ModelAndView("register");
    }
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 配置Controller

在springmvc-servlet.xml内配置Controller。

<!-- LoginController控制器类,映射到"/login" -->
<bean name="/login" class="top.snake8859.controller.LoginController"/>
<!-- RegisterController,映射到"/register" -->
<bean name="/register" class="top.snake8859.controller.RegisterController"/>
1
2
3
4

# 创建JSP视图

index.jsp 代码如下。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    未注册的用户,请
    <a href="${pageContext.request.contextPath }/register"> 注册</a><br /> 已注册的用户,去
    <a href="${pageContext.request.contextPath }/login"> 登录</a></body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

在 WEB-INF 下创建 jsp 文件夹,将 login.jsp 和 register.jsp 放到 jsp 文件夹下。

login.jsp 代码如下。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Insert title here</title>
</head>
<body>
    登录页面!
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12

register.jsp 代码如下。

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8" %>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
<body>
    注册页面!
</body>
</html>
</head>
1
2
3
4
5
6
7
8
9
10
11
12

# 配置Tomcat和部署

将项目部署到Tomcat服务器,参考传送门 (opens new window)

注意Tomcat的Application context设置,不然容易404。传送门 (opens new window)

image-20220315194039047

# Spring MVC 执行流程

Spring MVC 执行流程如图 1 所示。

Spring MVC执行流程

SpringMVC的执行流程如下:

  1. 用户点击某个请求,发起一个HTTP request请求,该请求会被提交到DispatcherServlet(前端控制器);
  2. DispatcherServlet请求一个或者多个HanderMapping(处理器映射器),并返回一个执行链;
  3. DispatcherServlet将执行链返回的Handler信息发送给HandlerAdapter(处理器适配器);
  4. HandlerAdapter根据Handler信息找到并执行相应的Handler(常称为Controller);
  5. Handler执行完毕后会返回给HandlerAdapter一个ModelAndView对象(Spring MVC的底层对象,包括 Model 数据模型和 View 视图信息);
  6. HandlerAdapter接收到ModelAndView对象后,将其返回给DispatcherServlet
  7. DispatcherServlet接收到ModelAndView对象后,会请求ViewResolver(视图解析器)对视图进行解析;
  8. ViewResolver根据View信息匹配到相应的视图结果,并返回到DispatcherServlet
  9. DispatcherServlet接收到具体的View视图后,进行视图渲染,将Model中的模型数据填充到View视图中的request域,生成最终的View;
  10. 视图负责将结果显示到浏览器。

Spring MVC 涉及到的组件有 DispatcherServlet(前端控制器)、HandlerMapping(处理器映射器)、HandlerAdapter(处理器适配器)、Handler(处理器)、ViewResolver(视图解析器)和 View(视图)。下面对各个组件的功能说明如下。

# DispatcherServlet

DispatcherServlet 是前端控制器,从上图可以看出,Spring MVC 的所有请求都要经过 DispatcherServlet 来统一分发。DispatcherServlet 相当于一个转发器或中央处理器,控制整个流程的执行,对各个组件进行统一调度,以降低组件之间的耦合性,有利于组件之间的拓展。

# HandlerMapping

HandlerMapping 是处理器映射器,其作用是根据请求的 URL 路径,通过注解或者 XML 配置,寻找匹配的处理器(Handler)信息。

# HandlerAdapter

HandlerAdapter 是处理器适配器,其作用是根据映射器找到的处理器(Handler)信息,按照特定规则执行相关的处理器(Handler)。

# Handler

Handler 是处理器,和 Java Servlet 扮演的角色一致。其作用是执行相关的请求处理逻辑,并返回相应的数据和视图信息,将其封装至 ModelAndView 对象中。

# ViewResolver

View Resolver 是视图解析器,其作用是进行解析操作,通过 ModelAndView 对象中的 View 信息将逻辑视图名解析成真正的视图 View(如通过一个 JSP 路径返回一个真正的 JSP 页面)。

# View

View 是视图,其本身是一个接口,实现类支持不同的 View 类型(JSP、FreeMarker、Excel 等)。

以上组件中,需要开发人员进行开发的是处理器(Handler,常称Controller)和视图(View)。通俗的说,要开发处理该请求的具体代码逻辑,以及最终展示给用户的界面。

注意:由于 Spring MVC 结构比较复杂,所以学习的时候也要掌握学习方法。首先要明确 Spring MVC 是一个工具,既然是工具,那么我们就需要先掌握工具的使用方法,不要陷入细节中,深入浅出,慢慢通过实际运用来加深对其的理解。

# Spring MVC 注解开发

传统风格的控制器需要实现Controller接口,并在配置文件中部署映射,而且一个类智能编写一个处理方法,不够灵活。因此在Spring2.5版本后,新增了Spring MVC注解开发,用于替换传统的基于XML的Spring MVC配置。

使用基于注解的控制器具有以下 2 个优点:

  1. 在基于注解的控制器类中可以编写多个处理方法,进而可以处理多个请求(动作),这就允许将相关的操作编写在同一个控制器类中,从而减少控制器类的数量,方便以后维护。
  2. 基于注解的控制器不需要在配置文件中部署映射,仅需要使用 @RequestMapping 注解一个方法进行请求处理即可。

接下来介绍在Spring MVC注解开发中最重要的两个注解:@Controller 和 @RequestMapping。

# @Controller注解

@Controller 注解用于声明某类的实例是一个控制器。示例代码如下。

package top.snake8859.controller;
import org.springframework.stereotype.Controller;
@Controller
public class IndexController {
    // 处理请求的方法
}
1
2
3
4
5
6

Spring MVC 使用扫描机制找到应用中所有基于注解的控制器类,所以,为了让控制器类被 Spring MVC 框架扫描到,需要在配置文件中声明 spring-context,并使用 <context:component-scan/> 元素指定控制器类的基本包(请确保所有控制器类都在基本包及其子包下)。

在springmvc-servlet.xml 中添加以下代码:

<!-- 使用扫描机制扫描控制器类,控制器类都在top.snake8859.controller包及其子包下 -->
<context:component-scan base-package="top.snake8859.controller" />
1
2

# @RequestMapping注解

一个控制器内有多个处理请求的方法,如 UserController 里通常有增加用户、修改用户信息、删除指定用户、根据条件获取用户列表等。每个方法负责不同的请求操作,而 @RequestMapping 就负责将请求映射到对应的控制器方法上。

在基于注解的控制器类中可以为每个请求编写对应的处理方法。使用 @RequestMapping 注解将请求与处理方法一 一对应即可。

@RequestMapping 注解可用于类或方法上。用于类上,表示类中的所有响应请求的方法都以该地址作为父路径。

@RequestMapping 注解常用属性如下。

# value 属性

value 属性是 @RequestMapping 注解的默认属性,因此如果只有 value 属性时,可以省略该属性名,如果有其它属性,则必须写上 value 属性名称。如下。

@RequestMapping(value="toUser")
@RequestMapping("toUser")
1
2

value 属性支持通配符匹配,如 @RequestMapping(value="toUser/*") 表示 http://localhost:8080/toUser/1http://localhost:8080/toUser/hahaha 都能够正常访问。

# path属性

path 属性和 value 属性都用来作为映射使用。即 @RequestMapping(value="toUser") 和 @RequestMapping(path="toUser") 都能访问 toUser() 方法。

path 属性支持通配符匹配,如 @RequestMapping(path="toUser/*") 表示 http://localhost:8080/toUser/1http://localhost:8080/toUser/hahaha 都能够正常访问。

# name属性

name属性相当于方法的注释,使方法更易理解。如 @RequestMapping(value = "toUser",name = "获取用户信息")。

# method属性

method 属性用于表示该方法支持哪些 HTTP 请求。如果省略 method 属性,则说明该方法支持全部的 HTTP 请求。

@RequestMapping(value = "toUser",method = RequestMethod.GET) 表示该方法只支持 GET 请求。也可指定多个 HTTP 请求,如 @RequestMapping(value = "toUser",method = {RequestMethod.GET,RequestMethod.POST}),说明该方法同时支持 GET 和 POST 请求。

# params属性

params 属性用于指定请求中规定的参数,代码如下。

@RequestMapping(value = "toUser",params = "type")
public String toUser() {
    return "showUser";
}
1
2
3
4

以上代码表示请求中必须包含 type 参数时才能执行该请求。即 http://localhost:8080/toUser?type=xxx 能够正常访问 toUser() 方法,而 http://localhost:8080/toUser 则不能正常访问 toUser() 方法。

@RequestMapping(value = "toUser",params = "type=1")
public String toUser() {
    return "showUser";
}
1
2
3
4

以上代码表示请求中必须包含 type 参数,且 type 参数为 1 时才能够执行该请求。即 http://localhost:8080/toUser?type=1 能够正常访问 toUser() 方法,而 http://localhost:8080/toUser?type=2 则不能正常访问 toUser() 方法。

# header属性

header 属性表示请求中必须包含某些指定的 header 值。

@RequestMapping(value = "toUser",headers = "Referer=http://www.xxx.com") 表示请求的 header 中必须包含了指定的“Referer”请求头,以及值为http://www.xxx.com时,才能执行该请求。

# consumer属性

consumers 属性用于指定处理请求的提交内容类型(Content-Type),例如:application/json、text/html。如 @RequestMapping(value = "toUser",consumes = "application/json")。

# produces属性

produces 属性用于指定返回的内容类型,返回的内容类型必须是 request 请求头(Accept)中所包含的类型。如 @RequestMapping(value = "toUser",produces = "application/json")。

除此之外,produces 属性还可以指定返回值的编码。如 @RequestMapping(value = "toUser",produces = "application/json,charset=utf-8"),表示返回 utf-8 编码。

# 通过请求URL进行映射

  • 方法级别@RequestMapping注解

    方法级别注解的示例代码如下:

    package top.snake8859.controller;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    @Controller
    public class IndexController {
        @RequestMapping(value = "/index/login")
        public String login() {
            return "login";
        }
        @RequestMapping(value = "/index/register")
        public String register() {
            return "register";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    上述示例中有两个 RequestMapping 注解语句,它们都作用在处理方法上。在整个 Web 项目中,@RequestMapping 映射的请求信息必须保证全局唯一。

    用户可以使用如下 http://localhost:8080/springmvcDemo/index/login访问 login 方法(请求处理方法),在访问 login 方法之前需要事先在 /WEB-INF/jsp/ 目录下创建 login.jsp。

  • 类级别注解@RequestMapping注解

    类级别注解的示例代码如下:

    package top.snake8859.controller;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    @Controller
    @RequestMapping("/index")
    public class IndexController {
        @RequestMapping("/login")
        public String login() {
            return "login";
        }
        @RequestMapping("/register")
        public String register() {
            return "register";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    在类级别注解的情况下,控制器类中的所有方法都将映射为类级别的请求。用户可以使用如下http://localhost:8080/springmvcDemo/index/login访问 login 方法。

为了方便维护程序,建议开发者采用类级别注解,将相关处理放在同一个控制器类中。例如,对用户的增、删、改、查等处理方法都可以放在 UserController 控制类中。

# 通过请求参数、请求方法进行映射

@RequestMapping 除了可以使用请求 URL 映射请求之外,还可以使用请求参数、请求方法来映射请求,通过多个条件可以让请求映射更加精确

package top.snake8859.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
public class IndexController {
    @RequestMapping(value = "/index/success" method=RequestMethod.GET, Params="username")
    public String success(@RequestParam String username) {
        
        return "index";
}
1
2
3
4
5
6
7
8
9
10

# 请求方法参数和返回值

在控制类中每个请求处理方法可以有多个不同类型的参数,以及一个多种类型的返回结果。

  • 请求处理方法中常出现的参数类型

    如果需要在请求处理方法中使用 Servlet API 类型,那么可以将这些类型作为请求处理方法的参数类型。Servlet API 参数类型的示例代码如下:

    package top.snake8859.controller;
    import javax.servlet.http.HttpSession;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    @Controller
    @RequestMapping("/index")
    public class IndexController {
        @RequestMapping("/login")
        public String login(HttpSession session,HttpServletRequest request) {
            session.setAttribute("skey", "session范围的值");
            session.setAttribute("rkey", "request范围的值");
            return "login";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    除了 Servlet API 参数类型以外,还有输入输出流、表单实体类、注解类型、与 Spring 框架相关的类型等。

    其中特别重要的类型是 org.springframework.ui.Model 类型,该类型是一个包含 Map 的 Spring MVC类型。在每次调用请求处理方法时 Spring MVC 都将创建 org.springframework.ui.Model 对象。Model 参数类型的示例代码如下:

    package top.snake8859.controller;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    @Controller
    @RequestMapping("/index")
    public class IndexController {
        @RequestMapping("/register")
        public String register(Model model) {
            /*在视图中可以使用EL表达式${success}取出model中的值*/
            model.addAttribute("success", "注册成功");
            return "register";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  • 请求处理方法常见的返回类型

    请求处理方法可以返回如下类型的对象:

    • ModelAndView
    • Model
    • 包含模型属性的 Map
    • View
    • 代表逻辑视图名的 String
    • void
    • 其它任意Java类型

    最常见的返回类型就是代表逻辑视图名称的 String 类型

# 示例

  1. 创建Web项目,参考Hello SpringMVC

  2. 导入依赖,参考Hello SpringMVC

  3. 修改web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xmlns="http://java.sun.com/xml/ns/javaee"
             xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
             xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
             version="3.0">
    
      <display-name>Archetype Created Web Application</display-name>
      <!--配置DispatcherServlet-->
      <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!-- 表示容器再启动时立即加载servlet -->
        <load-on-startup>1</load-on-startup>
      </servlet>
      
      <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- 处理所有URL -->
        <url-pattern>/</url-pattern>
      </servlet-mapping>
      
    </web-app>
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    创建springmvc-servlet.xml配置文件,配置如下。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <!--mvc注解驱动-->
        <mvc:annotation-driven/>
        
        <!--注解扫描-->
        <context:component-scan base-package="top.snake8859.controller"/>
    
        <!--处理器映射器-->
        <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
        <!--处理器适配器-->
        <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
        <!--视图解析器-->
        <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <property name="suffix" value=".jsp"/>
        </bean>
    
    </beans>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
  4. 创建 User 实体类,代码如下。

    package top.snake8859.pojo;
    
    public class User {
        private String name;
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    '}';
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
  5. 创建 UserController,代码如下。

    package top.snake8859.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import top.snake8859.pojo.User;
    
    @Controller
    @RequestMapping("/user")
    public class UserController {
        @RequestMapping("/login")
        public String getLogin(Model model){
            User us = new User();
            us.setName("snake8859");
            model.addAttribute("user", us);
            return "login";
        }
    
        @RequestMapping("/register")
        public String getRegister() {
            return "register";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

    使用 Controller 注解的一个优点在于一个控制类可以包含多个请求处理方法。

  6. index.jsp、login.jsp和register.jsp代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
        未注册的用户,请
        <a href="${pageContext.request.contextPath }/user/register"> 注册</a><br /> 已注册的用户,去
        <a href="${pageContext.request.contextPath }/user/login"> 登录</a></body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
        登录页面! 欢迎 ${user.name} 登录
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    <%@ page language="java" contentType="text/html; charset=UTF-8"
             pageEncoding="UTF-8" %>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>Insert title here</title>
    </head>
    <body>
        注册页面!
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    在页面中,当用户单击“注册”超链接时,控制器会将该请求转发给 UserController 的 getLogin 方法处理,处理后跳转到 /WEB-INF/jsp 下的 register.jsp 视图。同理,当单击“登录”超链接时,控制器处理后转到 /WEB-INF/jsp下的 login.jsp 视图。

404异常汇总:https://blog.csdn.net/qq_36769100/article/details/71746449

# Spring MVC 参数接收

Spring MVC 接收请求参数主要有以下几种方式:

  • 通过实体 Bean 接收请求参数
  • 通过处理方法的形参接收请求参数
  • 通过 HttpServletRequest 接收请求参数
  • 通过 @PathVariable 接收 URL 中的请求参数
  • 通过 @RequestParam 接收请求参数
  • 通过 @ModelAttribute 接收请求参数

# 通过实体 Bean 接收请求参数

实体 Bean 接收请求参数适用于 get 和 post 提交请求方式。需要注意,Bean 的属性名称必须与请求参数名称相同。示例代码如下。

@RequestMapping("/login")
public String login(User user, Model model) {
    if ("snake8859".equals(user.getName())
            && "123456".equals(user.getPwd())) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 通过处理方法的形参接收请求参数

通过处理方法的形参接收请求参数就是直接把表单参数写在控制器类相应方法的形参中,即形参名称与请求参数名称完全相同。该接收参数方式适用于 get 和 post 提交请求方式。示例代码如下:

@RequestMapping("/login")
public String login(String name, String pwd, Model model) {
    if ("snake8859".equals(name)
            && "123456".equals(pwd)) {
      
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12

# 通过 HttpServletRequest 接收请求参数

通过 HttpServletRequest 接收请求参数适用于 get 和 post 提交请求方式,示例代码如下:

@RequestMapping("/login")
public String login(HttpServletRequest request, Model model) {
    String name = request.getParameter("name");
    String pwd = request.getParameter("pwd");
   
    if ("snake8859".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# 通过 @PathVariable 接收 URL 中的请求参数

通过 @PathVariable 获取 URL 中的参数,示例代码如下:

@RequestMapping("/login/{name}/{pwd}")
public String login(@PathVariable String name, @PathVariable String pwd, Model model) {
   
    if ("snake8859".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

在访问http://localhost:8080/springMVCDemo02/user/register/snake8859/123456路径时,上述代码会自动将 URL 中的模板变量 {name} 和 {pwd} 绑定到通过 @PathVariable 注解的同名参数上,即 name=snake8859、pwd=123456。

# 通过 @RequestParam 接收请求参数

在方法入参处使用 @RequestParam 注解指定其对应的请求参数。@RequestParam 有以下三个参数:

  • value:参数名
  • required:是否必须,默认为 true,表示请求中必须包含对应的参数名,若不存在将抛出异常
  • defaultValue:参数默认值

通过 @RequestParam 接收请求参数适用于 get 和 post 提交请求方式,示例代码如下。

@RequestMapping("/login")
public String login(@RequestParam String name, @RequestParam String pwd, Model model) {
   
    if ("snake8859".equals(name)
            && "123456".equals(pwd)) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

该方式与通过处理方法的形参接收请求参数部分的区别如下:当请求参数与接收参数名不一致时,通过处理方法的形参接收请求参数不会报 404 错误,而通过 @RequestParam 接收请求参数会报 404 错误。

# 通过 @ModelAttribute 接收请求参数

@ModelAttribute 注解用于将多个请求参数封装到一个实体对象中,从而简化数据绑定流程,而且自动暴露为模型数据,在视图页面展示时使用。

通过实体 Bean 接收请求参数中只是将多个请求参数封装到一个实体对象,并不能暴露为模型数据。

通过 @ModelAttribute 注解接收请求参数适用于 get 和 post 提交请求方式,示例代码如下。

@RequestMapping("/login")
public String login(@ModelAttribute("user") User user, Model model) {
   
    if ("bianchengbang".equals(user.getName())
            && "123456".equals(user.getPwd())) {
       
        model.addAttribute("message", "登录成功");
        return "main"; // 登录成功,跳转到 main.jsp
    } else {
        model.addAttribute("message", "用户名或密码错误");
        return "login";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

# Spring MVC 重定向和转发

Spring MVC 请求方式分为转发、重定向 2 种,分别使用 forward 和 redirect 关键字在 controller 层进行处理。

重定向是将用户从当前处理请求定向到另一个视图(例如 JSP)或处理请求,以前的请求(request)中存放的信息全部失效,并进入一个新的 request 作用域;转发是将用户对当前处理的请求转发给另一个视图或处理请求,以前的 request 中存放的信息不会失效。

转发是服务器行为,重定向是客户端行为。

  • 转发过程

    客户浏览器发送 http 请求,Web 服务器接受此请求,调用内部的一个方法在容器内部完成请求处理和转发动作,将目标资源发送给客户;在这里转发的路径必须是同一个 Web 容器下的 URL,其不能转向到其他的 Web 路径上,中间传递的是自己的容器内的 request。

    在客户浏览器的地址栏中显示的仍然是其第一次访问的路径,也就是说客户是感觉不到服务器做了转发的。转发行为是浏览器只做了一次访问请求。

  • 重定向过程

    客户浏览器发送 http 请求,Web 服务器接受后发送 302 状态码响应及对应新的 location 给客户浏览器,客户浏览器发现是 302 响应,则自动再发送一个新的 http 请求,请求 URL 是新的 location 地址,服务器根据此请求寻找资源并发送给客户。

    在这里 location 可以重定向到任意 URL,既然是浏览器重新发出了请求,那么就没有什么 request 传递的概念了。在客户浏览器的地址栏中显示的是其重定向的路径,客户可以观察到地址的变化。重定向行为是浏览器做了至少两次的访问请求。

在 Spring MVC 框架中,控制器类中处理方法的 return 语句默认就是转发实现,只不过实现的是转发到视图。示例代码如下:

@RequestMapping("/register")
public String register() {
    return "register";  //转发到register.jsp
}
1
2
3
4

在 Spring MVC 框架中,重定向与转发的示例代码如下:

package top.snake8859.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@Controller
@RequestMapping("/index")
public class IndexController {
    @RequestMapping("/login")
    public String login() {
        //转发到一个请求方法(同一个控制器类可以省略/index/)
        return "forward:/index/isLogin";
    }
    @RequestMapping("/isLogin")
    public String isLogin() {
        //重定向到一个请求方法
        return "redirect:/index/isRegister";
    }
    @RequestMapping("/isRegister")
    public String isRegister() {
        //转发到一个视图
        return "register";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

在 Spring MVC 框架中,不管是重定向或转发,都需要符合视图解析器的配置,如果直接转发到一个不需要 DispatcherServlet 的资源,例如:

return "forward:/html/my.html";
1

则需要使用 mvc:resources 配置:

<mvc:resources location="/html/" mapping="/html/**" />
1

# @ModelAttribute注解

@ModelAttribute是Spring MVC的重要注解之一,用于将请求参数绑定到Model对象。

在 Controller 中使用 @ModelAttribute 时,有以下几种应用情况。

  • 应用在方法上
  • 应用在方法的参数上
  • 应用在方法上,并且方法也使用了 @RequestMapping

# 应用在方法上

  • 应用在无返回值的方法

    package top.snake8859.controller;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    @Controller
    public class ModelAttributeController {
        // 方法无返回值
        @ModelAttribute
        public void myModel(@RequestParam(required = false) String name, Model model) {
            model.addAttribute("name", name);
        }
        @RequestMapping(value = "/model")
        public String model() {
            return "index";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    创建 index.jsp 页面,代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>snake8859</title>
    </head>
    <body>
        ${name }
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    以上示例,在请求 /model?name=snake8859后,Spring MVC 会先执行 myModel 方法,将 name 的值存入到 Model 中。然后执行 model 方法,这样 name 的值就被带到了 model 方法中

    将 myModel 和 model 方法合二为一后,代码如下。

    @RequestMapping(value = "/model")
    public String model(@RequestParam(required = false) String name, Model model) {
        model.addAttribute("name", name);
        return "index";
    }
    
    1
    2
    3
    4
    5
  • 应用在有返回值的方法

    package top.snkae8859.controller;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    @Controller
    public class ModelAttributeController {
        // 方法有返回值
        @ModelAttribute
        public String myModel(@RequestParam(required = false) String name) {
            return name;
        }
        @RequestMapping(value = "/model")
        public String model() {
            return "index";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    创建index.jsp,代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>编程帮</title>
    </head>
    <body>
        ${string }
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    对于以上情况,返回值对象 name 会被默认放到隐含的 Model 中,在 Model 中 key 为返回值首字母小写,value 为返回的值。等同于 model.addAttribute("string", name);

    但正常情况下,程序中尽量不要出现 key 为 string、int、float 等这样数据类型的返回值。使用 @ModelAttribute 注解 value 属性可以自定义 key,代码如下。

    // 方法有返回值
    @ModelAttribute("name")
    public String myModel(@RequestParam(required = false) String name) {
        return name;
    }
    
    1
    2
    3
    4
    5

    等同于

    model.addAttribute("name", name);
    
    1

# 应用在方法参数上

@ModelAttribute 注解在方法的参数上,调用方法时,模型的值会被注入。这在实际使用时非常简单,常用于将表单属性映射到模型对象。

@RequestMapping("/register")
public String register(@ModelAttribute("user") UserForm user) {
    if ("zhangsan".equals(user.getName()) && "123456".equals(user.getPwd())) {
        logger.info("成功");
        return "login";
    } else {
        logger.info("失败");
        return "register";
}
1
2
3
4
5
6
7
8
9

以“user”为键值存储在 Model 对象中,和“model.addAttribute("user",user)”语句的功能一样。如果没有指定键值,即“@ModelAttribute UserForm user”,那么在创建 UserForm 实例时以“userForm”为键值存储在 Model 对象中,和“model.addAtttribute("userForm", user)”语句的功能一样。

# 应用方法上 + @RequestMapping

package top.snake8859.controller;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
@Controller
public class ModelAttributeController {
    // @ModelAttribute和@RequestMapping同时放在方法上
    @RequestMapping(value = "/index")
    @ModelAttribute("name")
    public String model(@RequestParam(required = false) String name) {
        return name;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

创建index.jsp,代码如下。

<%@ page language="java" contentType="text/html; charset=UTF-8"
pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>snake8859</title>
</head>
<body>
    ${name }
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12

@ModelAttribute 和 @RequestMapping 注解同时应用在方法上时,有以下作用:

  1. 方法的返回值会存入到 Model 对象中,key 为 ModelAttribute 的 value 属性值。
  2. 方法的返回值不再是方法的访问路径,访问路径会变为 @RequestMapping 的 value 值,例如:@RequestMapping(value = "/index") 跳转的页面是 index.jsp 页面。

总而言之,@ModelAttribute 注解的使用方法有很多种,非常灵活,可以根据业务需求选择使用。

# Model和ModelView的区别

  • Model:每次请求中都存在的默认参数,利用其 addAttribute() 方法即可将服务器的值传递到客户端页面中。
  • ModelAndView:包含 model 和 view 两部分,使用时需要自己实例化,利用 ModelMap 来传值,也可以设置 view 的名称。

# 拓展

@ModelAttribute 注解的方法会在每次调用该控制器类的请求处理方法前被调用。这种特性可以用来控制登录权限。

控制登录权限的方法有很多,例如拦截器、过滤器等。

创建 BaseController,代码如下所示。

package top.snake8859.controller;
import javax.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.ModelAttribute;
public class BaseController {
    @ModelAttribute
    public void isLogin(HttpSession session) throws Exception {
        if (session.getAttribute("user") == null) {
            throw new Exception("没有权限");
        }
    }
}
1
2
3
4
5
6
7
8
9
10
11

创建 ModelAttributeController ,代码如下所示:

package top.snake8859.controller;
import org.springframework.web.bind.annotation.RequestMapping;
@RequestMapping("/admin")
public class ModelAttributeController extends BaseController {
    @RequestMapping("/add")
    public String add() {
        return "addSuccess";
    }
    @RequestMapping("/update")
    public String update() {
        return "updateSuccess";
    }
    @RequestMapping("/delete")
    public String delete() {
        return "deleteSuccess";
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

在上述 ModelAttributeController 类中的 add、update、delete 请求处理方法执行时,首先执行父类 BaseController 中的 isLogin 方法判断登录权限。

# Spring MVC 类型转换器(Converter)

Spring MVC 框架的 Converter<S,T> 是一个可以将一种数据类型转换成另一种数据类型的接口,这里 S 表示源类型,T 表示目标类型。开发者在实际应用中使用框架内置的类型转换器基本上就够了,但有时需要编写具有特定功能的类型转换器。

例如,用户输入的日期可能有许多种形式,如“December 25,2014”“12/25/2014”和“2014-12-25”,这些都表示同一个日期。默认情况下,Spring 会期待用户输入的日期样式与当前语言区域的日期样式相同。例如,对于美国的用户而言,就是月/日/年的格式。如果希望 Spring 在将输入的日期字符串绑定到 LocalDate 时,使用不同的日期样式,则需要编写一个 Converter,才能将字符串转换成日期。

java.time.LocalDate 类是 Java 8 的一个新类型,用来替代 java.util.Date。还需使用新的 Date/Time API 来替换旧有的 Date 和 Calendar 类。

# 内置的类型转换器

在 Spring MVC 框架中,对于常用的数据类型,开发者无须创建自己的类型转换器,因为 Spring MVC 框架有许多内置的类型转换器用于完成常用的类型转换。Spring MVC 框架提供的内置类型转换包括以下几种类型。

  1. 标量转换器

    名称 作用
    StringToBooleanConverter String 到 boolean 类型转换
    ObjectToStringConverter Object 到 String 转换,调用 toString 方法转换
    StringToNumberConverterFactory String 到数字转换(例如 Integer、Long 等)
    NumberToNumberConverterFactory 数字子类型(基本类型)到数字类型(包装类型)转换
    StringToCharacterConverter String 到 Character 转换,取字符串中的第一个字符
    NumberToCharacterConverter 数字子类型到 Character 转换
    CharacterToNumberFactory Character 到数字子类型转换
    StringToEnumConverterFactory String 到枚举类型转换,通过 Enum.valueOf 将字符串转换为需要的枚举类型
    EnumToStringConverter 枚举类型到 String 转换,返回枚举对象的 name 值
    StringToLocaleConverter String 到 java.util.Locale 转换
    PropertiesToStringConverter java.util.Properties 到 String 转换,默认通过 ISO-8859-1 解码
    StringToPropertiesConverter String 到 java.util.Properties 转换,默认使用 ISO-8859-1 编码
  2. 集合、数组相关转换器

    名称 作用
    ArrayToCollectionConverter 任意数组到任意集合(List、Set)转换
    CollectionToArrayConverter 任意集合到任意数组转换
    ArrayToArrayConverter 任意数组到任意数组转换
    CollectionToCollectionConverter 集合之间的类型转换
    MapToMapConverter Map之间的类型转换
    ArrayToStringConverter 任意数组到 String 转换
    StringToArrayConverter 字符串到数组的转换,默认通过“,”分割,且去除字符串两边的空格(trim)
    ArrayToObjectConverter 任意数组到 Object 的转换,如果目标类型和源类型兼容,直接返回源对象;否则返回数组的第一个元素并进行类型转换
    ObjectToArrayConverter Object 到单元素数组转换
    CollectionToStringConverter 任意集合(List、Set)到 String 转换
    StringToCollectionConverter String 到集合(List、Set)转换,默认通过“,”分割,且去除字符串两边的空格(trim)
    CollectionToObjectConverter 任意集合到任意 Object 的转换,如果目标类型和源类型兼容,直接返回源对象;否则返回集合的第一个元素并进行类型转换
    ObjectToCollectionConverter Object 到单元素集合的类型转换

类型转换是在视图与控制器相互传递数据时发生的。Spring MVC 框架对于基本类型(例如 int、long、float、double、boolean 以及 char 等)已经做好了基本类型转换。

注意:在使用内置类型转换器时,请求参数输入值与接收参数类型要兼容,否则会报 400 错误。请求参数类型与接收参数类型不兼容问题需要学习输入校验后才可解决。

# 自定义类型转换器

当 Spring MVC 框架内置的类型转换器不能满足需求时,开发者可以开发自己的类型转换器,只需要实现相应的接口并在配置文件中注册该实现类即可。

例如需要用户在页面表单中输入信息来创建商品信息。当输入“snake8859,18,1.85”时表示在程序中自动创建一个 new User,并将“snake8859”值自动赋给 name 属性,将“18”值自动赋给 age 属性,将“1.85”值自动赋给 height 属性。

  1. 创建实体类

    创建 User 实体类,代码如下。

    package top.snake8859.pojo;
    
    public class User {
        private String name;
        private Integer age;
        private Double height;
    
        public String getName() {
            return name;
        }
    
       /**省略setter和getter方法*/
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
  2. 创建控制器类

    创建 UserController 控制器,代码如下。

    package top.snake8859.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class UserController {
        @RequestMapping("/addUser")
        public String addUser() {
            return "addUser";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12

    创建 ConverterController 控制器,代码如下。

    package top.snake8859.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import org.springframework.web.bind.annotation.RequestParam;
    import top.snake8859.pojo.User;
    
    @Controller
    public class ConverterController {
        @RequestMapping(value = "/converter", method = RequestMethod.POST)
        public String myConverter(@RequestParam("user")User user, Model model){
            model.addAttribute("user", user);
            return "showUser";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
  3. 创建自定义类型转换器

    创建自定义类型转换器 UserConverter,代码如下。

    package top.snake8859.converter;
    
    import org.springframework.core.convert.converter.Converter;
    import top.snake8859.pojo.User;
    
    public class UserConverter implements Converter<String, User> {
    
        @Override
        public User convert(String source) {
            // 创建User实例
            User user = new User();
            // 以“,”分隔
            String stringvalues[] = source.split(",");
            if (stringvalues != null && stringvalues.length == 3) {
                // 为user实例赋值
                user.setName(stringvalues[0]);
                user.setAge(Integer.parseInt(stringvalues[1]));
                user.setHeight(Double.parseDouble(stringvalues[2]));
                return user;
            } else {
                throw new IllegalArgumentException(String.format("类型转换失败, 需要格式'snake8859, 18,1.85',但格式是[% s ] ", source));
            }
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
  4. 配置转换器

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <mvc:annotation-driven conversion-service="conversionService"/>
        <!--注册类型转换器UserConverter-->
        <bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
            <property name="converters">
                <list>
                    <bean class="top.snake8859.converter.UserConverter"/>
                </list>
            </property>
        </bean>
    
        <context:component-scan base-package="top.snake8859.controller"/>
    
        <!--处理器映射器-->
        <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
        <!--处理器适配器-->
        <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    
        <!--视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
            <!--前缀-->
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <!--后缀-->
            <property name="suffix" value=".jsp"/>
        </bean>
        
    </beans>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
  5. 修改web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="4.0" 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">
    
      <display-name>springMVC</display-name>
    
      <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>
    
      <!-- 部署 DispatcherServlet -->
      <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!-- 表示容器再启动时立即加载servlet -->
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- 处理所有URL -->
        <!--/ 匹配所有的请求;(不包括.jsp)-->
        <!--/* 匹配所有的请求;(包括.jsp)-->
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    
    </web-app>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
  6. 创建相关视图

    创建添加用户页面 addUser.jsp,代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
             pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <title>添加用户</title>
    </head>
    <body>
    <form action="${pageContext.request.contextPath}/converter"
          method="post">
        请输入用户信息(格式为snake8859, 18,1.85):
        <input type="text" name="user" />
        <br>
        <input type="submit" value="提交" />
    </form>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    创建显示用户页面 showUser.jsp,代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
             pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
      <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
      <title>显示用户</title>
    </head>
    <body>
    您创建的用户信息如下:
    <br/>
    <!-- 使用EL表达式取出model中的user信息 -->
    用户名:${user.name } <br/>
    年龄:${user.age } <br/>
    身高:${user.height }
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

# Spring MVC 数据格式化(Formatter)

Spring MVC 框架的 Formatter<T>Converter<S, T> 一样,也是一个可以将一种数据类型转换成另一种数据类型的接口。不同的是,Formatter 的源类型必须是 String 类型,而 Converter 的源类型可以是任意数据类型。Formatter 更适合 Web 层,而 Converter 可以在任意层中。所以对于需要转换表单中的用户输入的情况,应该选择 Formatter,而不是 Converter。

在 Web 应用中由 HTTP 发送的请求数据到控制器中都是以 String 类型获取,因此在 Web 应用中选择 Formatter<T> 比选择 Converter<S, T>更加合理。

# 内置的格式转换器

Spring MVC 提供了几个内置的格式化转换器,具体如下。

  • NumberFormatter:实现 Number 与 String 之间的解析与格式化。
  • CurrencyFormatter:实现 Number 与 String 之间的解析与格式化(带货币符号)。
  • PercentFormatter:实现 Number 与 String 之间的解析与格式化(带百分数符号)。
  • DateFormatter:实现 Date 与 String 之间的解析与格式化。

# 自定义格式化转换器

自定义格式化转换器就是编写一个实现 org.springframework.format.Formatter 接口的 Java 类。该接口声明如下。

public interface Formatter<T>
1

这里的 T 表示由字符串转换的目标数据类型。该接口有 parse 和 print 两个接口方法,自定义格式化转换器类必须覆盖它们。

public T parse(String s, java.util.Locale locale)
public String print(T object, java.util.Locale locale)
1
2

parse 方法的功能是利用指定的 Locale 将一个 String 类型转换成目标类型,print 方法与之相反,用于返回目标对象的字符串表示。

# 示例

  1. 创建实体类

    创建 User 实体类,代码如下。

    package top.snake8859.pojo;
    
    import java.util.Date;
    
    public class User {
        private String name;
        private Integer age;
        private Double height;
        private Date createDate;
    
        /**省略getter和setter**/
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", height=" + height +
                    ", createDate=" + createDate +
                    '}';
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
  2. 创建控制器

    创建UserController和FormatterController控制器类,代码如下。

    package top.snake8859.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    public class UserController {
        @RequestMapping("/addUser")
        public String addUser(){
            return "addUser";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    package top.snake8859.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import top.snake8859.pojo.User;
    
    @Controller
    public class FormatterController {
        @RequestMapping("/formatter")
        public String myFormatter(User us, Model model){
            model.addAttribute("user", us);
            return "showUser";
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  3. 创建自定义格式转换器类

    创建 MyFormatter 的自定义格式化转换器类,代码如下。

    package top.snake8859.formatter;
    
    import org.springframework.format.Formatter;
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.Locale;
    
    public class MyFormatter implements Formatter<Date> {
    
        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd");
    
        /**
         * 指定的 Locale 将一个 String 类型转换成目标类型
         * @param s
         * @param locale
         * @return
         * @throws ParseException
         */
        @Override
        public Date parse(String s, Locale locale) throws ParseException {
            return dateFormat.parse(s); // Formatter只能对字符串转换
        }
    
        /**
         * 用于返回目标对象的字符串表示
         * @param date
         * @param locale
         * @return
         */
        @Override
        public String print(Date date, Locale locale) {
            return dateFormat.format(date);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
  4. 注册格式转换器类

    在 springmvc-servlet.xml 配置文件中注册格式化转换器,具体代码如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <mvc:annotation-driven conversion-service="conversionService"/>
        <!--注册格式转换器MyConverter-->
        <bean id="conversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
            <property name="formatters">
                <set>
                    <bean class="top.snake8859.formatter.MyFormatter"/>
                </set>
            </property>
        </bean>
    
        <context:component-scan base-package="top.snake8859.controller"/>
    
        <!--处理器映射器-->
        <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
        <!--处理器适配器-->
        <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    
        <!--视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
            <!--前缀-->
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <!--后缀-->
            <property name="suffix" value=".jsp"/>
        </bean>
    
    </beans>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
  5. 修改web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="4.0" 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">
    
      <display-name>springMVC</display-name>
    
      <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>
    
      <!-- 部署 DispatcherServlet -->
      <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!-- 表示容器再启动时立即加载servlet -->
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- 处理所有URL -->
        <!--/ 匹配所有的请求;(不包括.jsp)-->
        <!--/* 匹配所有的请求;(包括.jsp)-->
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    
    </web-app>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
  6. 创建相关视图

    创建添加用户页面 addUser.jsp,代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>添加用户</title>
    </head>
    <body>
        <form action="${pageContext.request.contextPath}/formatter" method="post">
            用户名:<input type="text" name="name" />
            <br>
            年龄:<input type="text" name="age" />
            <br>
            身高:<input type="text" name="height" />
            <br>
            创建日期:<input type="text" name="createDate" />
            <br>
            <input type="submit" value="提交" />
        </form>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

    创建信息显示页面 showUser.jsp,代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>用户信息</title>
    </head>
    <body>
        您创建的用户信息如下:
        <br />
        <!-- 使用EL表达式取出model中的user信息 -->
        用户名:${user.name }
        <br />
        年龄:${user.age }
        <br />
        身高:${user.height }
        <br />
        创建日期:${user.createDate }
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

# Spring MVC 表单标签库

在进行 Spring MVC 项目开发时,一般会使用 EL 表达式和 JSTL 标签来完成页面视图的开发。其实 Spring 也有自己的一套表单标签库,通过 Spring 表单标签,可以很容易地将模型数据中的命令对象绑定到 HTML 表单元素中。下面我们就通过一个示例来演示该标签库的用法。

首先和 JSTL 标签的使用方法相同,在使用 Spring 表单标签之前,必须在 JSP 页面开头处声明 taglib 指令,指令代码如下。

<%@ taglib prefix="fm" uri="http://www.springframework.org/tags/form"%>
1

常用的 Spring 表单标签如下表所示。

名称 作用
form 渲染表单元素
input 输入框组件标签,渲染 <input type="text"/>元素
password 密码框组件标签,渲染 <input type="password"/>元素
hidden 隐藏框组件标签,渲染<input type="hidden"/>元素
textarea 多行输入框组件标签,渲染 textarea 元素
checkbox 复选框组件标签,渲染一个<input type="checkbox"/>元素
checkboxes 渲染多个 <input type="checkbox"/>元素
radiobutton 单选框组件标签,渲染一个 <input type="radio"/>元素
radiobuttons 渲染多个 <input type="radio"/> 元素
select 下拉列表组件标签,渲染一个选择元素
option 渲染一个选项元素
options 渲染多个选项元素
errors 显示表单数据校验所对应的错误信息

以上标签基本都拥有以下属性。

  • path:属性路径,表示表单对象属性,如 userName、userCode 等。
  • cssClass:表单组件对应的 CSS 样式类名。
  • cssErrorClass:当提交表单后报错(服务端错误),采用的 CSS 样式类。
  • cssStyle:表单组件对应的 CSS 样式。
  • htmlEscape:绑定的表单属性值是否要对 HTML 特殊字符进行转换,默认为 true。

<fm:form> 表单标签为例,模仿添加用户信息。

  1. 创建实体类

    创建实体类 user,代码如下。

    package top.snake8859.pojo;
    
    public class User {
        private String name;
        private Integer age;
        private Double height;
    
       /**省略getter和setter**/
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", height=" + height +
                    '}';
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
  2. 创建控制器

    package top.snake8859.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    import top.snake8859.pojo.User;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpSession;
    
    @Controller
    public class UserController {
    
        @RequestMapping(value = "/addUser", method = RequestMethod.GET)
        public String add(@ModelAttribute("user")User user){
            return "addUser";
        }
    
        @RequestMapping(value = "/showuser", method = RequestMethod.POST)
        public String showUser(User user, HttpSession session, HttpServletRequest request){
            return "showUser";
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
  3. 创建SpringMVC配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <mvc:annotation-driven/>
    
    
        <context:component-scan base-package="top.snake8859.controller"/>
    
        <!--处理器映射器-->
        <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
        <!--处理器适配器-->
        <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    
        <!--视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
            <!--前缀-->
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <!--后缀-->
            <property name="suffix" value=".jsp"/>
        </bean>
    
    </beans>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
  4. 修改web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="4.0" 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">
    
      <display-name>springMVC</display-name>
    
      <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>
    
      <!-- 部署 DispatcherServlet -->
      <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!-- 表示容器再启动时立即加载servlet -->
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- 处理所有URL -->
        <!--/ 匹配所有的请求;(不包括.jsp)-->
        <!--/* 匹配所有的请求;(包括.jsp)-->
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    
    </web-app>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
  5. 创建相关视图

    创建 addUser.jsp,代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ taglib prefix="fm" uri="http://www.springframework.org/tags/form"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>添加用户</title>
    </head>
    <body>
        <fm:form method="post" modelAttribute="user" action="${pageContext.request.contextPath }/showuser">
            用户名:<fm:input path="name"/>
            <br />
            年龄:<fm:input path="age"/>
            <br />
            身高:<fm:input path="height"/>
            <br />
            <input type="submit" value="保存" />
        </fm:form>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21

    <fm:form> 标签的 modelAttribute 属性用于指定绑定的模型属性。默认从模型中尝试取名为“command”的表单对象,若不存在此表单对象,将会报错。所以一般情况下会指定 modelAttribute 属性。

    此外,表单组件标签页拥有 HTML 标签的各种属性,如 id、onclick 等,都可以根据需要灵活使用。

    创建显示页面 showUser.jsp,代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>用户信息</title>
    </head>
    <body>
        您创建的用户信息如下:
        <br />
        <!-- 使用EL表达式取出model中的user信息 -->
        用户名:${user.name }
        <br /> 年龄:${user.age }
        <br /> 身高:${user.height }
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

# Sprin MVC JSON数据交互

Spring MVC 在数据绑定的过程中需要对传递数据的格式和类型进行转换,它既可以转换 String 等类型的数据,也可以转换 JSON 等其他类型的数据。

# JSON概述

JSON(JavaScript Object Notation, JS 对象标记)是一种轻量级的数据交换格式。与 XML 一样,JSON 也是基于纯文本的数据格式。它有对象结构和数组结构两种数据结构。

  1. 对象结构

    对象结构以{开始、以}结束,中间部分由 0 个或多个以英文,分隔的 key/value 对构成,key 和 value 之间以英文:分隔。对象结构的语法结构如下:

    {
        key1:value1,
        key2:value2,
        ...
    }
    
    1
    2
    3
    4
    5

    其中,key 必须为 String 类型,value 可以是 String、Number、Object、Array 等数据类型。例如,一个 person 对象包含姓名、密码、年龄等信息,使用 JSON 的表示形式如下:

    {
        "pname":"张三",
        "password":"123456",
        "page":40
    }
    
    1
    2
    3
    4
    5
  2. 数组结构

    数组结构以[开始、以]结束,中间部分由 0 个或多个以英文,分隔的值的列表组成。数组结构的语法结构如下:

    {
        value1,
        value2,
        ...
    }
    
    1
    2
    3
    4
    5

    上述两种(对象、数组)数据结构也可以分别组合构成更加复杂的数据结构。例如,一个 student 对象包含 sno、sname、hobby 和 college 对象,其 JSON 的表示形式如下:

    {
        "sno":"201802228888",
        "sname":"张三",
        "hobby":["篮球","足球"]"college":{
            "cname":"清华大学",
            "city":"北京"
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

# @RequestBody和@ResponseBody

为实现浏览器与控制器类之间的 JSON 数据交互,Spring MVC 提供了 MappingJackson2HttpMessageConverter实现类默认处理 JSON 格式请求响应。该实现类利用 Jackson 开源包读写 JSON 数据,将 Java 对象转换为 JSON 对象和 XML 文档,同时也可以将 JSON 对象和 XML 文档转换为 Java 对象。

在使用注解开发时需要用到两个重要的 JSON 格式转换注解,分别是 @RequestBody 和 @ResponseBody

  • @RequestBody:用于将请求体中的数据绑定到方法的形参中,该注解应用在方法的形参上
  • @ResponseBody:用于直接返回 return 对象,该注解应用在方法上

需要注意的是,在该处理方法上,除了通过 @RequestMapping 指定请求的 URL,还有一个 @ResponseBody 注解。该注解的作用是将标注该注解的处理方法的返回结果直接写入 HTTP Response Body(Response 对象的 body 数据区)中。一般情况下,@ResponseBody 都会在异步获取数据时使用,被其标注的处理方法返回的数据都将输出到响应流中,客户端获取并显示数据。

# JSON工具包

早期 JSON 的组装和解析都是通过手动编写代码来实现的,这种方式效率不高,所以后来有许多的关于组装和解析 JSON 格式信息的工具类出现,如 json-lib、Jackson、Gson 和 FastJson 等,可以解决 JSON 交互的开发效率。

  1. json-lib

    json-lib 最早也是应用广泛的 JSON 解析工具,缺点是依赖很多的第三方包,如 commons-beanutils.jar、commons-collections-3.2.jar、commons-lang-2.6.jar、commons-logging-1.1.1.jar、ezmorph-1.0.6.jar 等。

    对于复杂类型的转换,json-lib 在将 JSON 转换成 Bean 时还有缺陷,比如一个类里包含另一个类的 List 或者 Map 集合,json-lib 从 JSON 到 Bean 的转换就会出现问题。

    所以 json-lib 在功能和性能上面都不能满足现在互联网化的需求。

  2. 开源的Jackson

    开源的 Jackson 是 Spring MVC 内置的 JSON 转换工具。相比 json-lib 框架,Jackson 所依赖 jar 文件较少,简单易用并且性能也要相对高些。并且 Jackson 社区相对比较活跃,更新速度也比较快。

    但是 Jackson 对于复杂类型的 JSON 转换 Bean 会出现问题,一些集合 Map、List 的转换出现问题。而 Jackson 对于复杂类型的 Bean 转换 JSON,转换的 JSON 格式不是标准的 JSON 格式。

  3. Google的Gson

    Gson 是目前功能最全的 JSON 解析神器,Gson 当初是应 Google 公司内部需求由 Google 自行研发。自从在 2008 年 5 月公开发布第一版后,Gson 就已经被许多公司或用户应用。

    Gson 主要提供了 toJson 与 fromJson 两个转换函数,不需要依赖其它的 jar 文件,就能直接在 JDK 上运行。在使用这两个函数转换之前,需要先创建好对象的类型以及其成员才能成功的将 JSON 字符串转换为相对应的对象。

    类里面只要有 get 和 set 方法,Gson 完全可以将复杂类型的 JSON 到 Bean 或 Bean 到 JSON 的转换,是 JSON 解析的神器。Gson 在功能上面无可挑剔,但性能比 FastJson 有所差距。

  4. 阿里巴巴的FastJson

    FastJson 是用 Java 语言编写的高性能 JSON 处理器,由阿里巴巴公司开发。FastJson 不需要依赖其它的 jar 文件,就能直接在 JDK 上运行。

    FastJson 在复杂类型的 Bean 转换 JSON 上会出现一些问题,可能会出现引用的类型,导致 JSON 转换出错,需要制定引用。FastJson 采用独创的算法,将 parse 的速度提升到极致,超过所有 JSON 库。

综上 4 种 JSON 技术的比较,在项目选型的时候可以使用 Google 的 Gson 和阿里巴巴的 FastJson 两种并行使用,如果只是功能要求,没有性能要求,可以使用Google 的 Gson。如果有性能上面的要求可以使用 Gson 将 Bean 转换 JSON 确保数据的正确,使用 FastJson 将 JSON 转换 Bean。

# 示例

  1. 引入FastJson依赖

    <!-- https://mvnrepository.com/artifact/com.alibaba/fastjson -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.62</version>
    </dependency>
    
    1
    2
    3
    4
    5
    6
  2. 配置Spring MVC核心配置文件

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
       <!--注解扫描器-->
       <context:component-scan base-package="top.snake8859.controller"/>
    	
       <!-- 使用resources过滤掉不需要dispatcherservlet的资源(即静态资源,例如css、js、html、images)。
        在使用resources时必须使用annotation-driven,否则resources元素会阻止任意控制器被调用 -->
       <!-- 允许js目录下的所有文件可见 -->
       <mvc:resources location="/WEB-INF/js/" mapping="/js/**"/>
    
       <!-- annotation-driven用于简化开发的配置,注解DefaultAnnotationHandlerMapping和AnnotationMethodHandlerAdapter -->
    
       <mvc:annotation-driven>
          <!--配置@ResponseBody由fastjson解析 -->
          <mvc:message-converters>
             <bean class="org.springframework.http.converter.StringHttpMessageConverter">
                <property name="defaultCharset" value="UTF-8"/>
             </bean>
             <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"/>
          </mvc:message-converters>
       </mvc:annotation-driven>
    
       <mvc:default-servlet-handler/>
    
       <bean id="fastJsonViewResponseBodyAdvice" class="com.alibaba.fastjson.support.spring.FastJsonpResponseBodyAdvice">
          <constructor-arg>
             <list>
                <value>callback</value>
                <value>jsonp</value>
             </list>
          </constructor-arg>
       </bean>
    
       <!--处理器映射器-->
       <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
       <!--处理器适配器-->
       <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    
       <!--视图解析器-->
       <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
          <!--前缀-->
          <property name="prefix" value="/WEB-INF/jsp/"/>
          <!--后缀-->
          <property name="suffix" value=".jsp"/>
       </bean>
    
    </beans>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59

    注意静态资源过滤,参考传送门 (opens new window)

  3. 创建实体类

    创建 User 类,代码如下。

    package top.snake8859.pojo;
    
    public class User {
        private String name;
        private String password;
        private Integer age;
    
     	/**省略setter和getter方法*/
        
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", password='" + password + '\'' +
                    ", age=" + age +
                    '}';
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
  4. 创建控制器

    创建UserController类,代码如下。

    package top.snake8859.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestBody;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.ResponseBody;
    import top.snake8859.pojo.User;
    
    @Controller
    public class UserController {
        @RequestMapping("/testJson")
        @ResponseBody
        public User testJson(@RequestBody User user) {
            // 打印接收的 JSON数据
            System.out.println("name=" + user.getName() + ",password=" + user.getPassword() + ",age=" + user.getAge());
            // 返回JSON格式的响应
            return user;
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19

    在上述控制器类中编写了接收和响应 JSON 格式数据的 testJson 方法,方法中的 @RequestBody 注解用于将前端请求体中的 JSON 格式数据绑定到形参 user 上,@ResponseBody 注解用于直接返回 Person 对象(当返回 POJO 对象时默认转换为 JSON 格式数据进行响应)。

  5. 创建相关视图

    创建 index.jsp 页面测试 JSON 数据交互,代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>测试JSON交互</title>
    <script type="text/javaScript"
        src="${pageContext.request.contextPath }/js/jquery-3.2.1.min.js"></script>
    </head>
    <body>
        <form action="">
            用户名:<input type="text" name="name" id="name" />
            <br>
            密码:<input type="password" name="password" id="password" />
            <br>
            年龄:<input type="text" name="age" id="age">
            <br>
            <input type="button" value="测试" onclick="testJson()" />
        </form>
    </body>
    <script type="text/javaScript">
        function testJson() {
            var name = $("#name").val();
            var password = $("#password").val();
            var age = $("#age").val();
            $.ajax({
                //请求路径
                url : "${pageContext.request.contextPath}/testJson",
                //请求类型
                type : "post",
                //data表示发送的数据
                data : JSON.stringify({
                    name : name,
                    password : password,
                    age : age
                }), //定义发送请求的数据格式为JSON字符串
                contentType : "application/json;charset=utf-8",
                //定义回调响应的数据格式为JSON字符串,该属性可以省略
                dataType : "json",
                //成功响应的结果
                success : function(data) {
                    if (data != null) {
                        alert("输入的用户名:" + data.name + ",密码:" + data.password
                                + ", 年龄:" + data.age);
                    }
                }
            });
        }
    </script>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52

    JQuery:https://code.jquery.com/jquery-3.2.1.min.js

    由于在 index.jsp 中使用的是 JQuery 的 AJAX 进行的 JSON 的数据提交和响应,所以还需要引入 jquery.js 文件。这里我们引入的是 WEB-INF目录下的 js 文件夹中的 jquery-3.2.1.min.js。

    从运行结果可以看出,编写的代码可以将 JSON 格式的请求数据转换为方法中的 Java 对象,也可以将 Java 对象转换为 JSON 格式的响应数据。

# Spring MVC 拦截器

在系统中,经常需要在处理用户请求之前和之后执行一些行为,例如检测用户的权限,或者将请求的信息记录到日志中,即平时所说的“权限检测”及“日志记录”。当然不仅仅这些,所以需要一种机制,拦截用户的请求,在请求的前后添加处理逻辑。

Spring MVC 提供了 Interceptor 拦截器机制,用于请求的预处理和后处理。

在开发一个网站时可能有这样的需求:某些页面只希望几个特定的用户浏览。对于这样的访问权限控制,应该如何实现呢?拦截器就可以实现上述需求。在 Struts2 框架中,拦截器是其重要的组成部分,Spring MVC 框架也提供了拦截器功能。

Spring MVC 的拦截器(Interceptor)与 Java Servlet 的过滤器(Filter)类似,它主要用于拦截用户的请求并做相应的处理,通常应用在权限验证、记录请求信息的日志、判断用户是否登录等功能上。

# 拦截器的定义

在 Spring MVC 框架中定义一个拦截器需要对拦截器进行定义和配置,主要有以下 2 种方式。

  1. 通过实现 HandlerInterceptor 接口或继承 HandlerInterceptor 接口的实现类(例如 HandlerInterceptorAdapter)来定义;
  2. 通过实现 WebRequestInterceptor 接口或继承 WebRequestInterceptor 接口的实现类来定义。

以实现 HandlerInterceptor 接口的定义方式为例讲解自定义拦截器的使用方法。示例代码如下。

package top.snake8859.interceptor;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
public class TestInterceptor 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 false;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

上述拦截器的定义中实现了 HandlerInterceptor 接口,并实现了接口中的 3 个方法,说明如下。

  • preHandle( ):该方法在控制器的处理请求方法前执行,其返回值表示是否中断后续操作,返回 true 表示继续向下执行,返回 false 表示中断后续操作。
  • postHandle( ):该方法在控制器的处理请求方法调用之后、解析视图之前执行,可以通过此方法对请求域中的模型和视图做进一步的修改。
  • afterCompletion( ):该方法在控制器的处理请求方法执行完成后执行,即视图渲染结束后执行,可以通过此方法实现一些资源清理、记录日志信息等工作。

# 拦截器的配置

让自定义的拦截器生效需要在 Spring MVC 的配置文件中进行配置,配置示例代码如下:

<!-- 配置拦截器 -->
<mvc:interceptors>
    <!-- 配置一个全局拦截器,拦截所有请求 -->
    <bean class="net.biancheng.interceptor.TestInterceptor" /> 
    <mvc:interceptor>
        <!-- 配置拦截器作用的路径 -->
        <mvc:mapping path="/**" />
        <!-- 配置不需要拦截作用的路径 -->
        <mvc:exclude-mapping path="" />
        <!-- 定义<mvc:interceptor>元素中,表示匹配指定路径的请求才进行拦截 -->
        <bean class="net.biancheng.interceptor.Interceptor1" />
    </mvc:interceptor>
    <mvc:interceptor>
        <!-- 配置拦截器作用的路径 -->
        <mvc:mapping path="/gotoTest" />
        <!-- 定义在<mvc:interceptor>元素中,表示匹配指定路径的请求才进行拦截 -->
        <bean class="net.biancheng.interceptor.Interceptor2" />
    </mvc:interceptor>
</mvc:interceptors>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

在上述示例代码中,元素说明如下。

  • <mvc:interceptors>:该元素用于配置一组拦截器。
  • <bean>:该元素是 <mvc:interceptors>的子元素,用于定义全局拦截器,即拦截所有的请求。
  • <mvc:interceptor>:该元素用于定义指定路径的拦截器。
  • <mvc:mapping>:该元素是 <mvc:interceptor> 的子元素,用于配置拦截器作用的路径,该路径在其属性 path 中定义。path 的属性值为/**时,表示拦截所有路径,值为/gotoTest时,表示拦截所有以/gotoTest结尾的路径。如果在请求路径中包含不需要拦截的内容,可以通过 <mvc:exclude-mapping>子元素进行配置。

需要注意的是,<mvc:interceptor>元素的子元素必须按照<mvc:mapping.../>、<mvc:exclude-mapping.../>、<bean.../>的顺序配置。

# 示例

  1. 创建实体类

    创建 User 类,代码如下。

    package top.snake8859.pojo;
    
    public class User {
        private String name;
        private String pwd;
    	
        /**省略setter和getter方法*/
       
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", pwd='" + pwd + '\'' +
                    '}';
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
  2. 创建控制器类

    创建控制器类 UserController,代码如下。

    package top.snake8859.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import top.snake8859.pojo.User;
    
    import javax.servlet.http.HttpSession;
    
    @Controller
    public class UserController {
        /**
         * 登录页面初始化
         * @return
         */
        @RequestMapping("/toLogin")
        public String initLogin() {
            return "login";
        }
    
        /**
         * 处理登录请求
         * @param user
         * @param model
         * @param session
         * @return
         */
        @RequestMapping("/login")
        public String login(User user, Model model, HttpSession session){
            System.out.println(user.getName());
            if("snake8859".equals(user.getName()) && "123456".equals(user.getPwd())) {
                //登录成功,将用户信息保存到session对象中
                session.setAttribute("user", user);
                //重定向到主页
                return "redirect:main";
            }
            model.addAttribute("msg", "用户名或密码错误, 请重新登录!");
            return  "login";
        }
    
        /**
         * 跳转到主页面
         * @return
         */
        @RequestMapping("/main")
        public String toMain() {
            return "main";
        }
    
        /**
         * 处理退出登录
         * @param session
         * @return
         */
        @RequestMapping("/logout")
        public String logout(HttpSession session) {
            //清除 session
            session.invalidate();
            return "login";
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
  3. 创建拦截器类

    package top.snake8859.interceptor;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import javax.servlet.http.HttpSession;
    
    public class LoginInterceptor implements HandlerInterceptor {
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            // 获取请求的URL
            String url = request.getRequestURI();
            // 登录相关请求放行,不拦截
            if(url.indexOf("/toLogin") >= 0 || url.indexOf("/login") >= 0) {
                return true;
            }
            // 获取session
            HttpSession session = request.getSession();
            Object obj = session.getAttribute("user");
            if(obj != null){
                return true;
            }
            // 没有登录且不是登录页面,转发到登录页面,并给出提示错误信息
            request.setAttribute("msg", "还没登录,请先登录");
            request.getRequestDispatcher("/WEB-INF/jsp/login.jsp").forward(request, response);
            return false;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            HandlerInterceptor.super.postHandle(request, response, handler, modelAndView);
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            HandlerInterceptor.super.afterCompletion(request, response, handler, ex);
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
  4. 配置拦截器

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <mvc:annotation-driven/>
    
        <context:component-scan base-package="top.snake8859.controller"/>
    
        <!--拦截器配置-->
        <mvc:interceptors>
            <mvc:interceptor>
                <!--配置拦截器作用的路径-->
                <mvc:mapping path="/**"/>
                <bean class="top.snake8859.interceptor.LoginInterceptor"/>
            </mvc:interceptor>
        </mvc:interceptors>
    
        <!--处理器映射器-->
        <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
        <!--处理器适配器-->
        <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    
        <!--视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
            <!--前缀-->
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <!--后缀-->
            <property name="suffix" value=".jsp"/>
        </bean>
    
    </beans>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
  5. 创建相关视图

    创建 login.jsp 和 main.jsp。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>用户登录</title>
    </head>
    <body>
        ${msg }
        <form action="${pageContext.request.contextPath }/login" method="post">
            用户名:<input type="text" name="name" /><br>
            密码:<input type="password" name="pwd" /><br>
            <input type="submit" value="登录" />
        </form>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    <%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>首页</title>
    </head>
    <body>
        欢迎 ${user.name },登录编程帮!<br />
        <a href="${pageContext.request.contextPath }/logout">退出</a>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13

# Spring MVC 数据校验

一般情况下,用户的输入是随意的,为了保证数据的合法性,数据验证是所有 Web 应用必须处理的问题。

Spring MVC 有以下两种方法可以验证输入:

  • 利用 Spring 自带的验证框架
  • 利用 JSR 303 实现

数据验证分为客户端验证和服务器端验证,客户端验证主要是过滤正常用户的误操作,通过 JavaScript 代码完成。服务器端验证是整个应用阻止非法数据的最后防线,通过在应用中编程实现。

JSR 303 是 Java 为 Bean 数据合法性校验所提供的标准框架。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对 Bean 进行验证。可以通过 https://jcp.org/en/jsr/detail?id=303 查看详细内容并下载 JSR 303 Bean Validation。

JSR 303 不需要编写验证器,它定义了一套可标注在成员变量、属性方法上的校验注解,如下表所示。

名称 说明
@Null 被标注的元素必须为 null
@NotNull 被标注的元素必须不为 null
@AssertTrue 被标注的元素必须为 true
@AssertFalse 被标注的元素必须为 false
@Min(value) 被标注的元素必须是一个数字,其值必须大于等于指定的最小值
@Max(value) 被标注的元素必须是一个数字,其值必须小于等于指定的最大值
@DecimalMax(value) 被标注的元素必须是一个数字,其值必须大于等于指定的最大值
@DecimalMin(value) 被标注的元素必须是一个数字,其值必须小于等于指定的最小值
@size 被标注的元素的大小必须在指定的范围内
@Digits(integer,fraction) 被标注的元素必须是一个数字,其值必须在可接受的范围内;integer 指定整数精度,fraction 指定小数精度
@Past 被标注的元素必须是一个过去的日期
@Future 被标注的元素必须是一个将来的日期
@Pattern(value) 被标注的元素必须符合指定的正则表达式

Spring MVC 支持 JSR 303 标准的校验框架,Spring 的 DataBinder 在进行数据绑定时,可同时调用校验框架来完成数据校验工作,非常简单方便。在 Spring MVC 中,可以直接通过注解驱动的方式来进行数据校验。

Spring 本身没有提供 JSR 303 的实现,Hibernate Validator 实现了 JSR 303,所以必须在项目中加入来自 Hibernate Validator 库的 jar 文件,下载地址为 http://hibernate.org/validator/。

# 示例

  1. 导入依赖

    <dependencies>
        <!-- 数据校验 -->
        <dependency>
            <groupId>javax.validation</groupId>
            <artifactId>validation-api</artifactId>
            <version>2.0.1.Final</version>
        </dependency>
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-validator</artifactId>
            <version>6.0.5.Final</version>
        </dependency>
    </dependencies>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
  2. 创建实体类

    创建 User 实体类,代码如下。

    package top.snake8859.pojo;
    
    import org.hibernate.validator.constraints.Email;
    import org.hibernate.validator.constraints.Length;
    
    import javax.validation.constraints.NotNull;
    
    public class User {
        @NotNull(message = "用户id不能为空")
        private Integer id;
        @NotNull
        @Length(min = 2, max = 8, message = "用户名不能少于2位大于8位")
        private String name;
        @Email(regexp = "[a-zA-Z0-9]+@[a-zA-Z0-9]+\\.[a-zA-Z0-9]", message = "邮箱格式不正确")
        private String email;
    
        public Integer getId() {
            return id;
        }
    	
        /** 省略setter和getter方法*/
    
        @Override
        public String toString() {
            return "User{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", email='" + email + '\'' +
                    '}';
        }
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
  3. 创建控制器

    package top.snake8859.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.ObjectError;
    import org.springframework.web.bind.annotation.RequestMapping;
    import top.snake8859.pojo.User;
    import javax.validation.Valid;
    import java.util.List;
    
    @Controller
    public class UserController {
        @RequestMapping("/validate")
        public String validate(@Valid User user, BindingResult result) {
            //如果有异常信息
            if(result.hasErrors()){
                // 获取异常信息对象
                List<ObjectError> errors = result.getAllErrors();
                // 将异常信息输出
                for (ObjectError error : errors) {
                    System.out.println(error.getDefaultMessage());
                }
            }
            return "index";
        }
    
        @RequestMapping(value = "/addUser")
        public String add() {
            return "addUser";
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
  4. 创建SpringMVC核心配置文件和web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <mvc:annotation-driven/>
    
        <context:component-scan base-package="top.snake8859.controller"/>
    
        <!--处理器映射器-->
        <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
        <!--处理器适配器-->
        <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    
        <!--视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
            <!--前缀-->
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <!--后缀-->
            <property name="suffix" value=".jsp"/>
        </bean>
    
    </beans>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="4.0" 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">
    
      <display-name>springMVC</display-name>
    
      <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>
    
      <!-- 部署 DispatcherServlet -->
      <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!-- 表示容器再启动时立即加载servlet -->
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- 处理所有URL -->
        <!--/ 匹配所有的请求;(不包括.jsp)-->
        <!--/* 匹配所有的请求;(包括.jsp)-->
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    
    </web-app>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
  5. 创建相关视图

    创建 addUser.jsp,代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>添加用户</title>
    </head>
    <body>
        <form action="${pageContext.request.contextPath}/validate" method="post">
            用户id:<input type="text" name="id" />
            <br>
            用户名:<input type="text" name="name" />
            <br>
            邮箱:<input type="text" name="email" />
            <br>
            <input type="submit" value="提交" />
        </form>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20

# Spring MVC 异常处理

在 Spring MVC 应用的开发中,不管是操作底层数据库,还是业务层或控制层,都会不可避免地遇到各种可预知的、不可预知的异常。我们需要捕捉处理异常,才能保证程序不被终止。

Spring MVC 有以下 3 种处理异常的方式:

  1. 使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver。
  2. 实现 Spring 的异常处理接口 HandlerExceptionResolver,自定义自己的异常处理器。
  3. 使用 @ExceptionHandler 注解实现异常处理

# @ExceptionHandler

局部异常处理仅能处理指定 Controller 中的异常。

下面使用 @ExceptionHandler 注解实现。定义一个处理过程中可能会存在异常情况的 testExceptionHandle 方法。

@RequestMapping("/testException")
public String testExceptionHandler(@RequestParam("i") Integer i) {
    System.out.println(10 / i);
    return "success";
}
1
2
3
4
5

显然,当 i=0 时会产生算术运算异常。

下面在同一个类中定义处理异常的方法。

@ExceptionHandler({ArithmeticException.class})
public String testArithmeticException(Exception e) {
    System.out.println("打印错误信息 ===> ArithmeticException:" + e);
    // 跳转到指定页面
    return "error";
}
1
2
3
4
5
6

注意:该注解不是加在产生异常的方法上,而是加在处理异常的方法上。

异常页面 error.jsp 代码如下。

<%@ page language="java" contentType="text/html; charset=UTF-8"
         pageEncoding="UTF-8"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>错误页面</title>
</head>
<body>
发生算术运算异常,请重新输出数据!
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12

访问地址:http://localhost:8080/SpringMVCException/testException?i=0,页面跳转到 error.jsp 页面,运行结果如下图所示。

image-20220319161339180

控制器输出结果如下。

打印错误信息 ===> ArithmeticException:java.lang.ArithmeticException: / by zero
1

@ExceptionHandler 注解定义的方法优先级问题:例如发生的是 NullPointerException,但是声明的异常有 RuntimeException 和 Exception,这时候会根据异常的最近继承关系找到继承深度最浅的那个@ExceptionHandler 注解方法,即标记了 RuntimeException 的方法。

被 @ExceptionHandler 标记为异常处理方法,不能在方法中设置别的形参。但是可以使用 ModelAndView 向前台传递数据。

使用局部异常处理,仅能处理某个 Controller 中的异常,若需要对所有异常进行统一处理,可使用以下两种方法。

# HandlerExceptionResolver

Spring MVC 通过 HandlerExceptionResolver 处理程序异常,包括处理器异常、数据绑定异常以及控制器执行时发生的异常。HandlerExceptionResolver 仅有一个接口方法,源码如下。

public interface HandlerExceptionResolver {
    @Nullable
    ModelAndView resolveException(
            HttpServletRequest request, HttpServletResponse response, @Nullable Object handler, Exception ex);
}
1
2
3
4
5

发生异常时,Spring MVC 会调用 resolveException() 方法,并转到 ModelAndView 对应的视图中,返回一个异常报告页面反馈给用户。

创建一个 HandlerExceptionResolver 接口的实现类 MyExceptionHandler,代码如下。

package top.snake8859.exception;

import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.HashMap;

public class MyExceptionHandler implements HandlerExceptionResolver {
    @Override
    public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
        HashMap<String, Object> model = new HashMap<>();
        // 根据不同错误转向不同页面(统一处理),即异常与View的对应关系
        if(e instanceof ArithmeticException) {
            return new ModelAndView("error", model);
        }
        return new ModelAndView("error-1", model);
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

在 springmvc-servlet.xml 文件中添加以下代码。

 <!--注册异常实现类-->
<bean class="top.snake8859.exception.MyExceptionHandler"/>
1
2

再次访问地址:http://localhost:8080/SpringMVCException/testException?i=0,页面跳转到 error.jsp 页面。

# SimpleMappingExceptionResolver

全局异常处理可使用 SimpleMappingExceptionResolver 来实现。它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。

在 springmvc-servlet.xml 中配置全局异常,代码如下。

<!--注解全局异常处理器类-->
<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    <!-- 定义默认的异常处理页面,当该异常类型注册时使用 -->
    <property name="defaultErrorView" value="error"/>
    <!--定义异常处理页面来获取异常信息的变量名, 默认位expcetion-->
    <property name="exceptionAttribute" value="ex"/>
    <!--定义需要特殊处理的异常, 用类名或者完全路径名未作key, 异常名作为值-->
    <property name="exceptionMappings">
        <props>
            <prop key="ArithmeticException">error</prop>
        </props>
    </property>
</bean>
1
2
3
4
5
6
7
8
9
10
11
12
13

再次访问地址:http://localhost:8080/SpringMVCException/testException?i=0,页面跳转到 error.jsp 页面。

# Spring MVC REST风格

REST(Representational State Transfer)即表述性转移,是目前最流行的一种软件架构风格。它结构清晰、易于理解、有较好的扩展性。

Spring REST 风格可以简单理解为:使用 URL 表示资源时,每个资源都用一个独一无二的 URL 来表示,并使用 HTTP 方法表示操作,即准确描述服务器对资源的处理动作(GET、POST、PUT、DELETE),实现资源的增删改查。

  • GET:表示获取资源
  • POST:表示新建资源
  • PUT:表示更新资源
  • DELETE:表示删除资源

下面举例说明 REST 风格的 URL 与传统 URL 的区别。

  • /userview.html?id=12 VS /user/view/12

  • /userdelete.html?id=12 VS /user/delete/12

  • /usermodify.html?id=12 VS /user/modify/12

我们发现 REST 风格的 URL 中最明显的就是参数不再使用“?”传递。这种风格的 URL 可读性更好,使得项目架构清晰,最关键的是 Spring MVC 也提供对这种风格的支持。

但是也有弊端,对于国内项目,URL 参数有时会传递中文,而中文乱码是一个令人头疼的问题,所以我们应该根据实际情况进行灵活处理。很多网站都是传统 URL 风格与 REST 风格混搭使用。

由于 HTTP 不支持 PUT 和 DELETE 请求,所以需要将 DELETE 和 PUT 请求转换成 POST 请求,在 web.xml 中配置过滤器 HiddenHttpMethodFilter。

<!-- HiddenHttpMethodFilter过滤器可以将POST请求转化为put请求和delete请求! -->
<filter>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <filter-class>org.springframework.web.filter.HiddenHttpMethodFilter</filter-class>
</filter>
<filter-mapping>
    <filter-name>hiddenHttpMethodFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>
1
2
3
4
5
6
7
8
9
  1. 创建相关视图

    创建rest.jsp和success.jsp代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>REST风格</title>
    </head>
    <body>
        <h4>发送GET请求</h4>
        <a href=" user/1">GET</a>
        <h4>发送POST请求</h4>
        <form action="user/1" method="post">
            <input type="submit" value="POST" />
        </form>
        <!-- 发送PUT和DELETE请求时,需要添加一个隐藏域 -->
        <h4>发送PUT请求</h4>
        <form action=" user/1" method="post">
            <input type="hidden" name="_method" value="PUT" /> <input
                type="submit" value="PUT" />
        </form>
        <h4>发送DELETE请求</h4>
        <input type="hidden" name="_method" value="DELETE" />
        <form action=" user/1" method="post">
            <input type="hidden" name="_method" value="DELETE" /> <input
                type="submit" value="DELETE" />
        </form>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    <%@ page contentType="text/html;charset=UTF-8" language="java" isErrorPage="true" %>
    <html>
    <head>
        <title>success</title>
    </head>
    <body>
     success
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9

    在这里插入图片描述

    对于Tomcat8以上的版本可能会出现该问题,比较直接的解决方法为:传送门 (opens new window)

  2. 创建控制器

    创建UserController 代码如下。通过 @RequestMapping 映射请求中的 method 参数实现四种请求方式的调用。

    package top.snake8859.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.PathVariable;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestMethod;
    
    @Controller
    public class UserController {
    
        @RequestMapping("/torest")
        public String torest(){
            return "rest";
        }
    
        @RequestMapping(value = "/user/{id}", method = RequestMethod.GET)
        public String helloGet(@PathVariable Integer id) {
            System.out.println("test rest get:" + id);
            return "success";
        }
    
        @RequestMapping(value = "/user/{id}", method = RequestMethod.POST)
        public String helloPost(@PathVariable Integer id){
            System.out.println("test rest post:" + id);
            return "success";
        }
    
        @RequestMapping(value = "/user/{id}", method = RequestMethod.DELETE)
        public String helloDelete(@PathVariable Integer id) {
            System.out.println("test rest delete:" + id);
            return "success";
        }
    
        @RequestMapping(value = "/user/{id}", method = RequestMethod.PUT)
        public String helloPut(@PathVariable Integer id){
            System.out.println("test rest put:" + id);
            return "success";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39

依次点击请求按钮,控制器输出结果如下。

test rest get:1
test post get: 1
test rest put:1
test rest delete:1
1
2
3
4

# Spring MVC 文件上传

Spring MVC 框架的文件上传基于 commons-fileupload 组件,并在该组件上做了进一步的封装,简化了文件上传的代码实现,取消了不同上传组件上的编程差异。

# MultipartResolver接口

在 Spring MVC 中实现文件上传十分容易,它为文件上传提供了直接支持,即 MultpartiResolver 接口。MultipartResolver 用于处理上传请求,将上传请求包装成可以直接获取文件的数据,从而方便操作。

MultpartiResolver 接口有以下两个实现类:

  • StandardServletMultipartResolver:使用了 Servlet 3.0 标准的上传方式。
  • CommonsMultipartResolver:使用了 Apache 的 commons-fileupload 来完成具体的上传操作。

MultpartiResolver 接口具有以下方法。

名称 作用
byte[] getBytes() 以字节数组的形式返回文件的内容
String getContentType() 返回文件的内容类型
InputStream getInputStream() 返回一个InputStream,从中读取文件的内容
String getName() 返回请求参数的名称
String getOriginalFillename() 返回客户端提交的原始文件名称
long getSize() 返回文件的大小,单位为字节
boolean isEmpty() 判断被上传文件是否为空
void transferTo(File destination) 将上传文件保存到目标目录下

# 单文件上传

  1. 导入依赖

    文件上传使用 Apache Commons FileUpload 组件,需要导入 commons-io-2.4.jar 和 commons-fileupload-1.2.2.jar 两个 jar 文件。

    <dependency>
          <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.4</version>
    </dependency>
    <dependency>
        <groupId>commons-fileupload</groupId>
        <artifactId>commons-fileupload</artifactId>
        <version>1.2.2</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/javax.servlet/jstl -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
        <version>1.2</version>
    </dependency>
    
    <!-- https://mvnrepository.com/artifact/taglibs/standard -->
    <dependency>
        <groupId>taglibs</groupId>
        <artifactId>standard</artifactId>
        <version>1.1.2</version>
    </dependency>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
  2. 修改web.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <web-app version="4.0" 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">
    
      <display-name>springMVC</display-name>
    
      <welcome-file-list>
        <welcome-file>index.jsp</welcome-file>
      </welcome-file-list>
    
      <!-- 部署 DispatcherServlet -->
      <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springmvc-servlet.xml</param-value>
        </init-param>
        <!-- 表示容器再启动时立即加载servlet -->
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <!-- 处理所有URL -->
        <!--/ 匹配所有的请求;(不包括.jsp)-->
        <!--/* 匹配所有的请求;(包括.jsp)-->
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    
    </web-app>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
  3. 配置MultipartResolver

    使用 CommonsMultipartReslover 配置 MultipartResolver 解析器,在 springmvc-servlet.xml 中添加代码如下。

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xmlns:p="http://www.springframework.org/schema/p"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
        <mvc:annotation-driven/>
    
        <context:component-scan base-package="top.snake8859.controller"/>
    
        <!--配置MultipartResolver, 用于上传文件, 使用Spring的CommonsMultipartResolver-->
        <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            <property name="maxUploadSize" value="5000000"/>
            <property name="defaultEncoding" value="UTF-8"/>
        </bean>
    
        <!--处理器映射器-->
        <bean class="org.springframework.web.servlet.handler.BeanNameUrlHandlerMapping"/>
        <!--处理器适配器-->
        <bean class="org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter"/>
    
        <!--视图解析器-->
        <bean class="org.springframework.web.servlet.view.InternalResourceViewResolver" id="internalResourceViewResolver">
            <!--前缀-->
            <property name="prefix" value="/WEB-INF/jsp/"/>
            <!--后缀-->
            <property name="suffix" value=".jsp"/>
        </bean>
    
    </beans>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    • defaultEncoding:请求的编码格式,默认为 ISO-8859-1,此处设置为 UTF-8(注:defaultEncoding 必须和 JSP 中的 pageEncoding 一致,以便正确读取表单的内容)。
    • maxUploadSize:上传文件大小上限,单位为字节。
  4. 创建相关视图

    创建fileUpload.jsp 代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>文件上传</title>
    </head>
    <body>
        <form action="${pageContext.request.contextPath }/fileupload"
            method="post" enctype="multipart/form-data">
            选择文件:<input type="file" name="myfile"><br> 
            文件描述:<input type="text" name="description"><br> 
            <input type="submit" value="提交">
        </form>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    基于表单的文件上传需要使用 enctype 属性,并将它的值设置为 multipart/form-data,同时将表单的提交方式设置为 post。

    表单的 enctype 属性指定的是表单数据的编码方式,该属性有以下 3 个值。

    • application/x-www-form-urlencoded:这是默认的编码方式,它只处理表单域里的 value 属性值。
    • multipart/form-data:该编码方式以二进制流的方式来处理表单数据,并将文件域指定文件的内容封装到请求参数里。
    • text/plain:该编码方式只有当表单的 action 属性为“mailto:”URL 的形式时才使用,主要适用于直接通过表单发送邮件的方式。 由上面 3 个属性的解释可知,在基于表单上传文件时 enctype 的属性值应为 multipart/form-data。

    创建showFile.jsp 代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>文件上传</title>
    </head>
    <body>
        文件描述:${fileDomain.description }
        <br>
        <!-- fileDomain.getMyFile().getOriginalFilename()-->
        文件名称:${fileDomain.myfile.originalFilename }
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
  5. 创建实体类

    创建 FileDomain 类,在该 POJO 类中声明一个 MultipartFile 类型的属性封装被上传的文件信息,属性名与文件选择页面 filleUpload.jsp 中的 file 类型的表单参数名 myfile 相同,代码如下。

    package top.snake8859.pojo;
    
    import org.springframework.web.multipart.MultipartFile;
    
    public class FileDomain {
        private String description;
        private MultipartFile myfile;
    
        public String getDescription() {
            return description;
        }
    
        public void setDescription(String description) {
            this.description = description;
        }
    
        public MultipartFile getMyfile() {
            return myfile;
        }
    
        public void setMyfile(MultipartFile myfile) {
            this.myfile = myfile;
        }
    
        @Override
        public String toString() {
            return "FileDomain{" +
                    "description='" + description + '\'' +
                    ", myfile=" + myfile +
                    '}';
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
  6. 创建控制器

    创建 FileUploadController 控制类,具体代码如下。

    package top.snake8859.controller;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.RequestMapping;
    import top.snake8859.pojo.FileDomain;
    
    import javax.servlet.http.HttpServletRequest;
    import java.io.File;
    
    @Controller
    public class FileUploadController {
        // 日志对象
        private static final Log logger = LogFactory.getLog(FileUploadController.class);
    
        @RequestMapping("/toFileUpload")
        public String toFileUpload() {
            return "fileUpload";
        }
        
    	 /**
         * 单文件上传
         * @param fileDomain
         * @param request
         * @return
         */
        @RequestMapping("/fileupload")
        public String oneFileUpload(@ModelAttribute FileDomain fileDomain, HttpServletRequest request) {
            String realPath = request.getServletContext().getRealPath("uploadfiles");
            String fileName = fileDomain.getMyfile().getOriginalFilename();
            File targetFile = new File(realPath, fileName);
            if(! targetFile.exists()){
                targetFile.mkdirs();
            }
            //上传
            try {
                fileDomain.getMyfile().transferTo(targetFile);
                logger.info("上传成功");
            } catch (Exception e){
                e.printStackTrace();
            }
            return "showFile";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46

# 多文件上传

在单文件代码的基础上,实现多文件上传。

  1. 创建相关视图

    创建 multiFiles.jsp 页面,在该页面中使用表单上传多个文件。代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ taglib prefix="fm" uri="http://www.springframework.org/tags/form"%>
    <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
    <html>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>多文件上传</title>
    </head>
    <body>
        <form action="${pageContext.request.contextPath }/multifile"
            method="post" enctype="multipart/form-data">
            选择文件1:<input type="file" name="myfile"><br>
            文件描述1:<input type="text" name="description"><br>
            选择文件2:<input type="file" name="myfile"><br>
            文件描述2:<input type="text" name="description"><br>
            选择文件3:<input type="file" name="myfile"><br>
            文件描述3:<input type="text" name="description"><br>
                <input type="submit" value="提交">
        </form>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22

创建多文件上传成功显示页面 showMulti.jsp,具体代码如下。

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>多文件上传显示</title>
</head>
<body>
    <table border="1px">
        <tr>
            <td>详情</td>
            <td>文件名</td>
        </tr>
        <!-- 同时取两个数组的元素 -->
        <c:forEach items="${multiFileDomain.description}" var="description"
            varStatus="loop">
            <tr>
                <td>${description}</td>
                <td>${multiFileDomain.myfile[loop.count-1].originalFilename}</td>
            </tr>
        </c:forEach>
        <!-- fileDomain.getMyfile().getOriginalFilename() -->
    </table>
</body>
</html>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
  1. 创建实体类

    创建 MultiFileDomain 类,上传多文件时用于封装文件信息,代码如下。

    package top.snake8859.pojo;
    
    import org.springframework.web.multipart.MultipartFile;
    
    import java.util.List;
    
    public class MultiFileDomain {
        private List<String> description;
        private List<MultipartFile> myfile;
    
        public List<String> getDescription() {
            return description;
        }
    
        public void setDescription(List<String> description) {
            this.description = description;
        }
    
        public List<MultipartFile> getMyfile() {
            return myfile;
        }
    
        public void setMyfile(List<MultipartFile> myfile) {
            this.myfile = myfile;
        }
    
        @Override
        public String toString() {
            return "MultiFileDomain{" +
                    "description=" + description +
                    ", myfile=" + myfile +
                    '}';
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
  2. 创建控制器

    在 FileUploadController 控制器类中添加多文件上传处理方法 multifile,具体代码如下。

    package top.snake8859.controller;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.ModelAttribute;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.multipart.MultipartFile;
    import top.snake8859.pojo.FileDomain;
    import top.snake8859.pojo.MultiFileDomain;
    
    import javax.servlet.http.HttpServletRequest;
    import java.io.File;
    import java.util.List;
    
    @Controller
    public class FileUploadController {
        // 日志对象
        private static final Log logger = LogFactory.getLog(FileUploadController.class);
    
        @RequestMapping("toMultiFiles")
        public String toMultiFiles(){
            return "multiFiles";
        }
    
        /**
         * 多文件上传
         * @param multiFileDomain
         * @param request
         * @return
         */
        @RequestMapping("/multifile")
        public String multiFileUpload(@ModelAttribute MultiFileDomain multiFileDomain, HttpServletRequest request) {
            String realPath = request.getServletContext().getRealPath("uploadfiles");
            File targetDir = new File(realPath);
            if(! targetDir.exists()){
                targetDir.mkdirs();
            }
            List<MultipartFile> files = multiFileDomain.getMyfile();
            System.out.println("files " + files);
            for (MultipartFile file : files) {
                String filename = file.getOriginalFilename();
                File targetFile = new File(realPath, filename);
                //上传
                try{
                    file.transferTo(targetFile);
                    logger.info(filename + ": 上传成功");
                } catch (Exception e){
                    e.printStackTrace();
                }
            }
    
            return "showMultiFiles";
        }
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55

# Spring MVC 文件下载

文件下载有以下两种实现方法:

  • 通过超链接实现下载:实现简单,但暴露了下载文件的真实位置,并且只能下载 Web 应用程序所在目录下的文件,WEB-INF 目录除外。
  • 利用程序编码实现下载:增强安全访问控制,可以下载除 Web 应用程序所在目录以外的文件,也可以将文件保存到数据库中。

利用程序编码实现下载需要设置以下两个报头:

  1. Web 服务器需要告诉浏览器其所输出内容的类型不是普通文本文件或 HTML 文件,而是一个要保存到本地的下载文件,这需要设置 Content-Type 的值为 application/x-msdownload。
  2. Web 服务器希望浏览器不直接处理相应的实体内容,而是由用户选择将相应的实体内容保存到一个文件中,这需要设置 Content-Disposition 报头。

该报头指定了接收程序处理数据内容的方式,在 HTTP 应用中只有 attachment 是标准方式,attachment 表示要求用户干预。在 attachment 后面还可以指定 filename 参数,该参数是服务器建议浏览器将实体内容保存到文件中的文件名称。

设置报头的示例如下:

response.setHeader("Content-Type", "application/x-msdownload");
response.setHeader("Content-Disposition", "attachment;filename="+filename);
1
2

程序编码文件下载可分为两个步骤:

  1. 在客户端使用一个文件下载超链接,链接指向后台下载文件的方法以及文件名。
  2. 在控制器类中,提供文件下载方法进行下载。

# 示例

在文件上传的基础上,实现文件下载。

  1. 创建控制器类

    首先编写控制器类 FileDownController,在该类中有 3 个方法,即 show、down 和 toUTF8String。其中,show 方法获取被下载的文件名称;down 方法执行下载功能;toUTF8String 方法是下载保存时中文文件名的字符编码转换方法。

    package top.snake8859.controller;
    
    import org.apache.commons.logging.Log;
    import org.apache.commons.logging.LogFactory;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    
    import javax.servlet.ServletOutputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.UnsupportedEncodingException;
    import java.util.ArrayList;
    
    @Controller
    public class FileDownController {
        private static final Log logger = LogFactory.getLog(FileDownController.class);
    
        /**
         * 显示下载文件
         * @param request
         * @param model
         * @return
         */
        @RequestMapping("showDownFIles")
        public String show(HttpServletRequest request, Model model) {
            String realPath = request.getServletContext().getRealPath("uploadfiles");
            File dir = new File(realPath);
            File[] files = dir.listFiles();
            // 获取该目录下的所有文件名
            ArrayList<String> fileNames = new ArrayList<>();
            for (File file : files) {
                fileNames.add(file.getName());
            }
            model.addAttribute("files", fileNames);
            return "showDownFiles";
        }
    
    
        @RequestMapping("down")
        public String down(@RequestParam String filename, HttpServletRequest request, HttpServletResponse response){
            String aFilePath = null; // 下载文件路径
            FileInputStream in = null; // 输入流
            ServletOutputStream out = null; // 输出流
            try {
                aFilePath = request.getServletContext().getRealPath("uploadfiles");
                // 设置下载文件使用的报头
                response.setHeader("Content-Type", "application/x-msdownload");
                response.setHeader("Content-Disposition", "attachment; filename="
                        + toUTF8String(filename));
                // 读入文件
                in = new FileInputStream(aFilePath + "\\" + filename);
                // 得到响应对象的输出流,用于向客户端输出二进制数据
                out = response.getOutputStream();
                out.flush();
                int aRead = 0;
                byte buffer[] = new byte[1024];
                while ((aRead = in.read(buffer)) != -1 & in != null) {
                    out.write(buffer, 0, aRead);
                }
                out.flush();
                in.close();
                out.close();
            }catch (Throwable e){
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 中文文件名的字符编码转换方法
         * @param str
         * @return
         */
        public String toUTF8String(String str) {
            StringBuffer sb = new StringBuffer();
            int len = str.length();
            for (int i = 0; i < len; i++) {
                // 取出字符中的每个字符
                char c = str.charAt(i);
                // Unicode码值为0~255时,不做处理
                if (c >= 0 && c <= 255) {
                    sb.append(c);
                } else { // 转换 UTF-8 编码
                    byte b[];
                    try {
                        b = Character.toString(c).getBytes("UTF-8");
                    } catch (UnsupportedEncodingException e) {
                        e.printStackTrace();
                        b = null;
                    }
                    // 转换为%HH的字符串形式
                    for (int j = 0; j < b.length; j++) {
                        int k = b[j];
                        if (k < 0) {
                            k &= 255;
                        }
                        sb.append("%" + Integer.toHexString(k).toUpperCase());
                    }
                }
            }
            return sb.toString();
        }
    
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    89
    90
    91
    92
    93
    94
    95
    96
    97
    98
    99
    100
    101
    102
    103
    104
    105
    106
    107
    108
  2. 创建相关视图

    创建显示被下载文件的 JSP 页面 showDownFiles.jsp,代码如下。

    <%@ page language="java" contentType="text/html; charset=UTF-8"
        pageEncoding="UTF-8"%>
    <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
    <title>Insert title here</title>
    </head>
    <body>
        <table>
            <tr>
                <td>被下载的文件名</td>
            </tr>
            <!--遍历 model中的 files-->
            <c:forEach items="${files}" var="filename">
                <tr>
                    <td>
                        <a href="${pageContext.request.contextPath }/down?filename=${filename}">${filename}</a>
                    </td>
                </tr>
            </c:forEach>
        </table>
    </body>
    </html>
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23

# 参考资料

Last Updated: 11/21/2022, 10:03:43 PM