学习Spring5 WebMVC这一篇就够了

目录


配套资料,免费下载
链接:https://pan.baidu.com/s/1gsHGUjRI8nPe_Gv1bZ7BMg
提取码:5la2
复制这段内容后打开百度网盘手机App,操作更方便哦

第八章 WebMVC

8.1、SpringMVC的概述

三层架构

三层架构的介绍:

我们的开发架构一般都是基于两种形式,一种是C/S架构,也就是客户端/服务器,另一种是B/S架构,也就是浏览器/服务器。在JavaEE开发中,几乎全都是基于B/S架构的开发。那么在B/S架构中,系统标准的三层架构包括:用户界面层(User Interface layer)、业务逻辑层(Business Logic Layer)、数据访问层(Data access layer)。区分层次的目的就是为了解除各个模块之间的耦合关系,提高代码复用性。

学习Spring5 WebMVC这一篇就够了

三层架构的优势:

  • 结构清晰,耦合度低
  • 可维护性高,可扩展性高
  • 利于开发任务同步进行,容易适应需求变化

三层架构的劣势:

  • 降低了系统的性能
  • 有时会导致级联的修改
  • 增加了代码量,增加了工作量

各层之间的方案:

  • 用户界面层: Spring MVC、Struts 2
  • 业务逻辑层: Spring
  • 数据访问层: Hibernate、MyBatis、JdbcTemplate

MVC

MVC最开始是存在于桌面程序中的,全名是Model View Controller,是数据模型(model)-用户界面(view)-控制器(controller)的缩写,它是一种软件设计模式。使用MVC的目的在于将M(数据模型)和V(用户界面)的实现代码分离,从而使同一个程序可以使用不同的表现形式。C(控制器)存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新。

学习Spring5 WebMVC这一篇就够了

我用Windows的计算器小程序为例,解释一下MVC模式,虽然它不一定使用这个模式编写。

在这个计算器程序中,外部的那些按钮和最上面的显示条,就是"视图层",那些需要运算的数字就是"数据层",执行加减乘除的那些内部运算步骤就是"控制层",每一层执行不同的功能,整个程序的结构非常清楚。

Spring MVC

“ Spring Web MVC”通常简称为“ Spring MVC”,他是基于Servlet API构建的原始Web框架,从一开始就已包含在Spring框架中,专门是做Web 开发的,Spring MVC内部也维护了一个容器,被称为Spring MVC容器,他是IOC容器的一个子类,既然是子类,Spring MVC容器是可以访问IOC中管理的对象的。

8.2、SpringMVC的原理

请求流程:

学习Spring5 WebMVC这一篇就够了

父子容器:

学习Spring5 WebMVC这一篇就够了

8.3、SpringMVC的入门

8.3.1、项目创建

学习Spring5 WebMVC这一篇就够了

学习Spring5 WebMVC这一篇就够了

学习Spring5 WebMVC这一篇就够了

学习Spring5 WebMVC这一篇就够了

修改 web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">
    
</web-app>

修改 pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.caochenlei</groupId>
    <artifactId>springmvc-demo-01</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>war</packaging>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                    <include>**/*.conf</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                    <include>**/*.conf</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>
</project>

8.3.2、环境准备

导入依赖:

<!--导入SpringMVC依赖-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.2</version>
</dependency>
<!--导入JSON处理工具包-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.12.0</version>
</dependency>
<!--Servlet、JSP相关依赖-->
<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>3.1.0</version>
    <scope>provided</scope>
</dependency>
<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.3</version>
    <scope>provided</scope>
</dependency>
<!--JSP相关标签依赖-->
<dependency>
    <groupId>taglibs</groupId>
    <artifactId>standard</artifactId>
    <version>1.1.2</version>
</dependency>
<dependency>
    <groupId>jstl</groupId>
    <artifactId>jstl</artifactId>
    <version>1.2</version>
</dependency>

创建容器:

<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:spring-ioc.xml</param-value>
</context-param>

<servlet>
    <servlet-name>spring-mvc</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <init-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-mvc.xml</param-value>
    </init-param>
    <load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
    <servlet-name>spring-mvc</servlet-name>
    <url-pattern>/*.do</url-pattern>
</servlet-mapping>

添加类包:

  • com.caocheneli.spring5.pojo
  • com.caocheneli.spring5.dao
  • com.caocheneli.spring5.service
  • com.caocheneli.spring5.controller

添加配置: spring-ioc.xml

xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
<!--开启组件扫描-->
<context:component-scan base-package="com.caocheneli.spring5">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>

添加配置: spring-mvc.xml

xmlns:context="http://www.springframework.org/schema/context"
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
<!--开启组件扫描-->
<context:component-scan base-package="com.caocheneli.spring5.controller"></context:component-scan>

创建控制器: HelloController

@Controller
public class HelloController {
    @RequestMapping("/hello.do")
    public void hello() {
        System.out.println("Hello SpringMVC");
    }
}

打开浏览器: http://localhost:8080/hello.do

学习Spring5 WebMVC这一篇就够了

打开控制台:

学习Spring5 WebMVC这一篇就够了

8.4、SpringMVC的控制器对象

Spring MVC提供了一个基于注解的编程模型,使用 @Controller 组件来标识当前类为控制器类。

@Controller
public class HelloController {
    @RequestMapping("/hello.do")
    public void hello() {
        System.out.println("Hello SpringMVC");
    }
}

8.5、SpringMVC的请求映射

@RequestMapping 是一个用来处理请求地址映射的注解,可用于类或方法上。

8.5.1、位置

用在控制器类上面: 表示类中的所有响应请求的方法都是以该地址作为父路径

@Controller
@RequestMapping("/hello")
public class HelloController {

}

用在控制器方法上: 提供进一步的细分映射信息,他跟控制器类上的请求路径组成一个完整的请求路径,如果类上没有使用 @RequestMapping 注解,则父路径就是为空("")。

@Controller
@RequestMapping("/hello")
public class HelloController {
    //完整请求地址:localhost:8080/hello/world.do
    @RequestMapping("/world.do")
    public void world() {
        System.out.println("Hello World");
    }

    //完整请求地址:localhost:8080/hello/spring.do
    @RequestMapping("/spring.do")
    public void spring() {
        System.out.println("Hello Spring");
    }
}

8.5.2、属性

8.5.2.1、value

说明: 指定请求的实际地址。

示例:

第一种形式: 单个请求地址

@RequestMapping(value = "/world.do")
public void todo1() {
    System.out.println("Hello World");
}

第二种形式: 多个请求地址

@RequestMapping(value = {
    "/world.do",
    "/spring.do",
    "/mybatis.do"
})
public void todo2() {
    System.out.println("Hello World");
    System.out.println("Hello Spring");
    System.out.println("Hello MyBatis");
}

第三种形式: 地址路径含有变量

@RequestMapping(value = "/spring/{content}.do")
public void todo2(@PathVariable String content) {
    System.out.println("Hello Spring " + content);
}

第四种形式: 地址路径含有正则表达式

@RequestMapping(value = "/spring/{content:[a-z]+}.do")
public void todo3(@PathVariable String content) {
    System.out.println("Hello Spring " + content);
}

8.5.2.2、method

说明: 指定请求的method类型, GET、POST、PUT、DELETE等。

示例:

只接收GET请求:

@RequestMapping(value = "/get.do", method = RequestMethod.GET)
public void get() {
    System.out.println("get ...");
}
<form action="/hello/get.do" method="get">
    <input type="submit" value="get请求">
</form>

只接收POST请求:

@RequestMapping(value = "/post.do", method = RequestMethod.POST)
public void post() {
    System.out.println("post ...");
}
<form action="/hello/post.do" method="post">
    <input type="submit" value="post请求">
</form>

只接收PUT请求:

<!--用于处理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>
@RequestMapping(value = "/put.do", method = RequestMethod.PUT)
public void put() {
    System.out.println("put ...");
}
<form action="/hello/put.do" method="post">
    <input type="hidden" name="_method" value="put">
    <input type="submit" value="put请求">
</form>

只接收DELETE请求:

<!--用于处理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>
@RequestMapping(value = "/delete.do", method = RequestMethod.DELETE)
public void delete() {
    System.out.println("delete ...");
} 
<form action="/hello/delete.do" method="post">
    <input type="hidden" name="_method" value="delete">
    <input type="submit" value="delete请求">
</form>

GET请求简写形式:

@GetMapping("/get.do")
public void get() {
    System.out.println("get ...");
}

POST请求简写形式:

@PostMapping("/post.do")
public void post() {
    System.out.println("post ...");
}

PUT请求简写形式:

@PutMapping("/put.do")
public void put() {
    System.out.println("put ...");
}

DELETE请求简写形式:

@DeleteMapping("/delete.do")
public void delete() {
    System.out.println("delete ...");
}

8.5.2.3、params

说明: 指定请求中必须包含某些参数值时,才让该方法处理。

示例:

//设定必须包含username和age两个参数,且age参数不为10(可以有多个参数)
@RequestMapping(value = "/params.do", method = RequestMethod.GET, params = {"username", "age!=10"})
public void params() {
    System.out.println("params ...");
}

8.5.2.4、headers

说明: 指定请求中必须包含某些指定的header值,才能让该方法处理请求。

示例:

@RequestMapping(value = "/headers.do", method = RequestMethod.GET, headers = "Host=localhost:8080")
public void headers() {
    System.out.println("headers ...");
}

8.5.2.5、consumes

说明: 指定处理请求的提交内容类型 (Content-Type),例如 application/json, text/html。

示例:

@RequestMapping(value = "/consumes.do", method = RequestMethod.POST, consumes = "application/json")
public void consumes() {
    System.out.println("consumes ...");
}
$.ajax({
    url: "/hello/consumes.do",
    type: "post",
    headers: {
        "Content-Type": "application/json"
    }
});

8.5.2.6、produces

说明: 指定请求响应返回的内容类型(Accept),例如 application/json, text/html。

示例:

$.ajax({
    url: "/hello/produces.do",
    type: "post",
    headers: {
        "Accept": "application/json"
    }
});

8.6、SpringMVC的控制器方法

处理器方法可以包含以下四类参数,这些参数会在系统调用时由系统自动赋值,即程序员可在方法内直接使用。

  • HttpServletRequest
  • HttpServletResponse
  • HttpSession
  • 请求中所携带的请求参数

8.6.1、控制器方法的参数

8.6.1.1、接收单个参数

第一种形式: 通过参数名称,只要保证请求参数名与该请求处理方法的参数名相同即可。

请求地址:http://localhost:8080/hello/getParam1.do?username=zhangsan

@RequestMapping("/getParam1.do")
public void getParam1(String username) {
    System.out.println("getParam1 " + username);
}

第二种形式: 通过注解获取,只要保证请求参数名与 @RequestParam 注解的参数名相同即可。

请求地址:http://localhost:8080/hello/getParam1.do?username=zhangsan

@RequestMapping("/getParam1.do")
public void getParam1(@RequestParam("username") String name) {
    System.out.println("getParam1 " + name);
}

第三种形式: 通过路径变量,只要保证在路径中的变量名和 @PathVariable 注解的参数名相同即可。

请求地址:http://localhost:8080/hello/zhangsan.do

@RequestMapping("/{username}.do")
public void getParam1(@PathVariable("username") String name) {
    System.out.println("getParam1 " + name);
}

8.6.1.2、接收多个参数

第一种形式: 通过参数名称,只要保证请求参数名与该请求处理方法的参数名相同即可。

请求地址:http://localhost:8080/hello/getParam2.do?username=zhangsan&password=123546

@RequestMapping("/getParam2.do")
public void getParam2(String username, String password) {
    System.out.println("getParam2 " + username + " " + password);
}

第二种形式: 通过注解获取,只要保证请求参数名与 @RequestParam 注解的参数名相同即可。

请求地址:http://localhost:8080/hello/getParam2.do?username=zhangsan&password=123546

@RequestMapping("/getParam2.do")
public void getParam2(@RequestParam("username") String name, @RequestParam("password") String pwd) {
    System.out.println("getParam2 " + name + " " + pwd);
}

第三种形式: 通过路径变量,只要保证在路径中的变量名和 @PathVariable 注解的参数名相同即可。

请求地址:http://localhost:8080/hello/zhangsan/123456.do

@RequestMapping("/{username}/{password}.do")
public void getParam2(@PathVariable("username") String name, @PathVariable("password") String pwd) {
    System.out.println("getParam2 " + name + " " + pwd);
}

8.6.1.3、接收对象数据类型

public class User {
    private String name;
    private String age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                ", age='" + age + '\'' +
                '}';
    }
}

第一种形式: 通过属性名称

访问地址:http://localhost:8080/hello/getParam3.do?name=zhangsan&age=18

@RequestMapping(value = "/getParam3.do", method = RequestMethod.GET)
public void getParam3(@RequestBody User user) {
    System.out.println("getParam3 " + user);
}

第二种形式: 通过JSON串

xmlns:mvc="http://www.springframework.org/schema/mvc"
http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd
<!--开启MVC注解-->
<mvc:annotation-driven/>
@RequestMapping(value = "/getParam3.do", method = RequestMethod.POST)
public void getParam3(@RequestBody User user) {
    System.out.println("getParam3 " + user);
}
let user = {
    name: "zhangsan",
    age: 18
};
$.ajax({
    url: "/hello/getParam3.do",
    type: "post",
    contentType: "application/json",
    data: JSON.stringify(user)
});

8.6.1.4、接收数组数据类型

第一种形式: 通过注解获取

@RequestMapping(value = "/getParam4.do", method = RequestMethod.POST)
public void getParam4(@RequestParam("array[]") String[] array) {
    System.out.println("getParam4 " + Arrays.toString(array));
}
let arr = ["zhangsan", "lisi"];
$.ajax({
    url: "/hello/getParam4.do",
    type: "post",
    data: {
        array: arr
    }
});

第二种形式: 通过JSON串

@RequestMapping(value = "/getParam4.do", method = RequestMethod.POST)
public void getParam4(@RequestBody String[] array) {
    System.out.println("getParam4 " + Arrays.toString(array));
}
let arr = ["zhangsan", "lisi"];
$.ajax({
    url: "/hello/getParam4.do",
    type: "post",
    contentType: "application/json",
    data: JSON.stringify(arr)
});

8.6.1.5、接收集合数据类型

list集合:

@RequestMapping(value = "/getParam5.do", method = RequestMethod.POST)
public void getParam5(@RequestBody List<String> list) {
    System.out.println("getParam5 " + list);
}
let list = ["zhangsan", "lisi"];
$.ajax({
    url: "/hello/getParam5.do",
    type: "post",
    contentType: "application/json",
    data: JSON.stringify(list)
});

set集合:

@RequestMapping(value = "/getParam5.do", method = RequestMethod.POST)
public void getParam5(@RequestBody Set<String> set) {
    System.out.println("getParam5 " + set);
}
let set = ["zhangsan", "lisi"];
$.ajax({
    url: "/hello/getParam5.do",
    type: "post",
    contentType: "application/json",
    data: JSON.stringify(set)
});

map集合:

@RequestMapping(value = "/getParam5.do", method = RequestMethod.POST)
public void getParam5(@RequestBody Map<String, String> map) {
    System.out.println("getParam5 " + map);
}
let map = {
    "key1": "value1",
    "key2": "value2",
    "key3": "value3"
};
$.ajax({
    url: "/hello/getParam5.do",
    type: "post",
    contentType: "application/json",
    data: JSON.stringify(map)
});

8.6.1.6、解决中文乱码问题

<!--注册字符集过滤器-->
<filter>
    <filter-name>characterEncodingFilter</filter-name>
    <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
    <!--指定字符集编码-->
    <init-param>
        <param-name>encoding</param-name>
        <param-value>utf-8</param-value>
    </init-param>
    <!--强制Request使用字符集encoding-->
    <init-param>
        <param-name>forceRequestEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
    <!--强制Response使用字符集encoding-->
    <init-param>
        <param-name>forceResponseEncoding</param-name>
        <param-value>true</param-value>
    </init-param>
</filter>
<filter-mapping>
    <filter-name>characterEncodingFilter</filter-name>
    <url-pattern>/*</url-pattern>
</filter-mapping>

8.6.2、控制器方法的返回值

8.6.2.1、返回值为 ModeAndView

若处理器方法处理完后,需要跳转到其它资源,且又要在跳转的资源间传递数据,此时处理器方法返回 ModelAndView 比较好。当然,若要返回 ModelAndView,则处理器方法中需要定义 ModelAndView 对象。

在使用时,若该处理器方法只是进行跳转而不传递数据,或只是传递数据而并不向任何资源跳转(如对页面的 AJAX 异步响应),此时若返回 ModelAndView,则将总是有一部分多余:要么 Model 多余,要么 View 多余,即此时返回 ModelAndView 将不合适。

请求地址:http://localhost:8080/hello/page.do

@RequestMapping("/page.do")
public ModelAndView page() {
    ModelAndView mv = new ModelAndView();
    mv.addObject("msg", "Hello World");
    mv.setViewName("/page.jsp");
    return mv;
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title></title>
</head>
<body>
<h1>${msg}</h1>
</body>
</html>

但是,细心的你如果注意到,在浏览器地址输入:http://localhost:8080/page.jsp,他也能访问 page.jsp 这是很不安全的,因此,我们建议把所有的 jsp 放到 WEB-INF 文件夹中,这样一来,外部直接输入 http://localhost:8080/page.jsp 就访问不到了,那这时候,我们的视图名称该如何写呢,请参考以下示例:

@RequestMapping("/page.do")
public ModelAndView page() {
    ModelAndView mv = new ModelAndView();
    mv.addObject("msg", "Hello World");
    mv.setViewName("/WEB-INF/page.jsp");
    return mv;
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title></title>
</head>
<body>
<h1>${msg}</h1>
</body>
</html>

我们会觉得,每次都要是写上要跳转视图的物理视图地址,会感觉很麻烦,而且,万一我的所有 jsp 界面都移动到了 WEB-INF/aaa 文件夹中,那我还得需要手动来修改源码中的视图名称,这是不可取的,我们希望,在代码中,只写返回视图的名称,而不是一个物理视图地址,这时候,我们可以使用Spring给我们提供的视图解析器,此时视图名称这个字符串与视图解析器中的 prefix、suffix 相结合,即可形成要访问的 URI。

<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver">
    <property name="prefix" value="/WEB-INF/"/>
    <property name="suffix" value=".jsp"/>
</bean>
@RequestMapping("/page.do")
public ModelAndView page() {
    ModelAndView mv = new ModelAndView();
    mv.addObject("msg", "Hello World");
    mv.setViewName("page");
    return mv;
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title></title>
</head>
<body>
<h1>${msg}</h1>
</body>
</html>

8.6.2.2、返回值为 String

处理器方法返回的字符串可以指定逻辑视图名,通过视图解析器解析可以将其转换为物理视图地址。

如果我们想要携带数据,可以直接在参数中指明数据模型 Model model

@RequestMapping("/page.do")
public String page(Model model) {
    model.addAttribute("msg", "Hello World");
    return "page";
}

当然,也可以直接返回资源的物理视图名。不过,此时就不需要再在视图解析器中再配置前辍与后辍了。

@RequestMapping("/page.do")
public String page(Model model) {
    model.addAttribute("msg", "Hello World");
    return "/WEB-INF/page.jsp";
}

8.6.2.3、返回值为 Void

对于处理器方法返回 void 的应用场景,主要用于服务端直接向浏览器发回数据,所以也就无需视图页面了。使用此种方式可以进行 AJAX 响应。处理器对于 AJAX 请求中所提交的参数,可以使用逐个接收的方式,也可以以对象的方式整体接收。只要保证 AJAX 请求参数与接收的对象类型属性同名。为了方便模拟,我们直接使用GET请求来进行演示,浏览器地址:http://localhost:8080/hello/myVoid.do?name=zhangsan&age=18

学习Spring5 WebMVC这一篇就够了

@RequestMapping("/myVoid.do")
public void myVoid(String name, String age, HttpServletResponse response) throws IOException {
    //创建用户
    User user = new User();
    user.setName(name);
    user.setAge(age);

    //转为JSON
    ObjectMapper om = new ObjectMapper();
    String json = om.writeValueAsString(user);

    //写到页面
    PrintWriter pw = response.getWriter();
    pw.write(json);
    pw.flush();
    pw.close();
}

8.6.2.4、返回值为 Object

处理器方法也可以返回 Object 对象。这个 Object 可以是 Integer,String,自定义对象, Map,List 等。但返回的对象不是作为逻辑视图出现的,而是作为直接在页面显示的数据出现的。 返回对象,需要使用@ResponseBody 注解,将转换后的 JSON 数据放入到响应体中。

第一步:需要导入相关依赖,之前我们已经导入过了。

<!--导入JSON处理工具包-->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-core</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.0</version>
</dependency>
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-annotations</artifactId>
    <version>2.12.0</version>
</dependency>

第二步:将 Object 数据转化为 JSON 数据,需要由消息转换器 HttpMessageConverter 完成。而转换器的开启,需要由<mvc:annotation-driven/>来完成。 SpringMVC 使用消息转换器实现请求数据和对象,处理器方法返回对象和响应输出之间的自动转换。

<!--开启MVC注解-->
<mvc:annotation-driven/>

第三步:返回自定义类型对象时,不能以对象的形式直接返回给客户端浏览器,而是使用 @ResponseBody 注解将对象转换为 JSON 格式的数据发送给浏览器的。 由于转换器底层使用了 Jackson 转换方式将对象转换为 JSON 数据,所以需要导入 Jackson 的相关 Jar 包。

请求地址:http://localhost:8080/hello/myObject.do?name=zhangsan&age=18

@RequestMapping("/myObject.do")
@ResponseBody
public User myObject(String name, String age) {
    //创建对象
    User user = new User();
    user.setName(name);
    user.setAge(age);
    //返回对象
    return user;
}

学习Spring5 WebMVC这一篇就够了

如果说,我们当前控制器中的所有方法均是返回 JSON 字符串,我们不必在每一个方法上都写一遍 @ResponseBody ,而是直接在类上写,这样就代表了,当前类下所有的方法都是返回的 JSON 字符串。

@Controller
@RequestMapping("/hello")
@ResponseBody
public class HelloController {
    
}

当然了,SpringMVC给我们提供了一个复合注解叫做 @RestController ,它是由 @Controller@ResponseBody 复合组成的,目的就是为了简化书写,提高程序阅读。

@RestController
@RequestMapping("/hello")
public class HelloController {
    
}

8.7、SpringMVC的页面跳转

当处理器对请求处理完毕后,向其它资源进行跳转时,有两种跳转方式:请求转发与重定向。而根据所要跳转的资源类型,又可分为两类:跳转到页面与跳转到其它处理器。 注意,对于请求转发的页面,可以是 WEB-INF 中页面;而重定向的页面,是不能为 WEB-INF 中的页面。因为重定向相当于用户再次发出一次请求,而用户是不能直接访问 WEB-INF 中资源的。

学习Spring5 WebMVC这一篇就够了

SpringMVC 框架把原来 Servlet 中的请求转发和重定向操作进行了封装。现在可以使用简单的方式实现转发和重定向。

  • forward:表示转发,实现 request.getRequestDispatcher(“xx.jsp”).forward()
  • redirect:表示重定向,实现 response.sendRedirect(“xxx.jsp”)

8.7.1、请求转发

处理器方法返回 ModelAndView 时,需在 setViewName() 指定的视图前添加 forward:,且此时的视图不再与视图解析器一同工作,这样可以在配置了解析器时指定不同位置的视图。视图页面必须写出相对于项目根的路径。forward 操作不需要视图解析器。处理器方法如果返回 String,在视图路径前面加入 forward: 加上视图完整路径。

请求地址:http://localhost:8080/hello/forward.do

返回值为 ModelAndView :

@RequestMapping("/forward.do")
public ModelAndView forward() {
    ModelAndView mv = new ModelAndView();
    mv.addObject("msg", "forward ...");
    mv.setViewName("forward:/WEB-INF/page.jsp");
    return mv;
}

返回值为 String :

@RequestMapping("/forward.do")
public String forward(Model model) {
    model.addAttribute("msg", "forward ...");
    return "forward:/WEB-INF/page.jsp";
}

8.7.2、重定向

处理器方法返回 ModelAndView 时,需在 setViewName() 指定的视图前添加 redirect:,且此时的视图不再与视图解析器一同工作,这样可以在配置了解析器时指定不同位置的视图。视图页面必须写出相对于项目根的路径。redirect 操作不需要视图解析器。处理器方法如果返回 String,在视图路径前面加入 redirect: 加上视图完整路径。

请求地址:http://localhost:8080/hello/redirect.do

返回值为 ModelAndView :

@RequestMapping("/redirect.do")
public ModelAndView redirect() {
    ModelAndView mv = new ModelAndView();
    System.out.println("redirect ...");
    mv.setViewName("redirect:/page.jsp");
    return mv;
}

返回值为 String :

@RequestMapping("/redirect.do")
public String redirect() {
    System.out.println("redirect ...");
    return "redirect:/page.jsp";
}

8.8、SpringMVC的拦截器

8.8.1、拦截器概述

SpringMVC 中的 Interceptor 拦截器是非常重要和相当有用的,它的主要作用是拦截指定的用户请求,并进行相应的预处理与后处理。其拦截的时间点在“处理器映射器根据用户提交的请求映射出了所要执行的处理器类,并且也找到了要执行该处理器类的处理器适配器, 在处理器适配器执行处理器之前”。当然,在处理器映射器映射出所要执行的处理器类时, 已经将拦截器与处理器组合为了一个处理器执行链,并返回给了*调度器。

8.8.2、拦截器定义

public class MyInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
            throws Exception {
        System.out.println("MyInterceptor preHandle ...");
        return true;
    }

    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView)
            throws Exception {
        System.out.println("MyInterceptor postHandle ...");
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
            throws Exception {
        System.out.println("MyInterceptor afterCompletion ...");
    }
}

自定义拦截器,需要实现 HandlerInterceptor 接口。而该接口中含有三个方法:

  • preHandle(request,response, Object handler):该方法在处理器方法执行之前执行。其返回值为 boolean,若为 true,则紧接着会执行处理器方法,且会将 afterCompletion()方法放入到一个专门的方法栈中等待执行。
  • postHandle(request,response, Object handler,modelAndView):该方法在处理器方法执行之后执行。处理器方法若最终未被执行,则该方法不会执行。 由于该方法是在处理器方法执行完后执行,且该方法参数中包含 ModelAndView,所以该方法可以修改处理器方法的处理结果数据,且可以修改跳转方向。
  • afterCompletion(request,response, Object handler, Exception ex):当 preHandle()方法返回 true 时,会将该方法放到专门的方法栈中,等到对请求进行响应的所有工作完成之后才执行该方法。即该方法是在*调度器渲染(数据填充)了响应页面之后执行的,此时对 ModelAndView 再操作也对响应无济于事。afterCompletion 是最后执行的方法,主要用于清除资源。

8.8.3、拦截器注册

<!--注册拦截器-->
<mvc:interceptors>
    <mvc:interceptor>
        <mvc:mapping path="/**"/>
        <bean class="com.caocheneli.spring5.interceptor.MyInterceptor"/>
    </mvc:interceptor>
</mvc:interceptors>

8.8.4、单个拦截器执行顺序

单个拦截器中方法与处理器方法的执行顺序如下图:

学习Spring5 WebMVC这一篇就够了

8.8.5、多个拦截器执行顺序

多个拦截器中方法与处理器方法的执行顺序如下图:

学习Spring5 WebMVC这一篇就够了

8.9、SpringMVC的国际化

第一步:编写国际化资源文件集合,在 resources 目录下,分别建立以下文件

i18n.properties

i18n.username=username:
i18n.password=password:

i18n_zh_CN.properties

i18n.username=账户:
i18n.password=密码:

i18n_en_US.properties

i18n.username=username:
i18n.password=password:

第二步:配置国际化资源文件

<bean id="messageSource" class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
    <property name="defaultEncoding" value="UTF-8"/>
    <property name="useCodeAsDefaultMessage" value="true"/>
    <property name="basenames">
        <list>
            <value>classpath:i18n</value>
        </list>
    </property>
</bean>

第三步:配置默认区域信息解析器

<bean id="localeResolver" class="org.springframework.web.servlet.i18n.SessionLocaleResolver">
    <property name="defaultLocale" value="zh_CN"/>
</bean>

第四步:配置区域设置更改拦截器

<mvc:interceptors>
    ...
    <!--配置区域设置更改拦截器-->
    <bean id="localeChangeInterceptor" class="org.springframework.web.servlet.i18n.LocaleChangeInterceptor">
        <property name="paramName" value="lang"/>
    </bean>
</mvc:interceptors>

第五步:编写页面跳转控制方法

@RequestMapping("/loginPage.do")
public String loginPage() {
    return "login";
}

第六步:编写需要国际化的页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<html>
<head>
    <title></title>
</head>
<body>
<form action="/login.do" method="post">
    <p>
        <spring:message code="i18n.username"/>
        <input type="text" name="username">
    </p>
    <p>
        <spring:message code="i18n.password"/>
        <input type="text" name="password">
    </p>
</form>
<p>
    <a href="/hello/loginPage.do?lang=zh_CN">切换为中文</a>
    <a href="/hello/loginPage.do?lang=en_US">切换为英文</a>
</p>
</body>
</html>

第七步:在浏览器地址栏输入http://localhost:8080/hello/loginPage.do进行访问

学习Spring5 WebMVC这一篇就够了

8.10、SpringMVC的异常处理

8.10.1、异常处理概述

SpringMVC 框架常使用 @ExceptionHandler 注解处理异常。使用注解 @ExceptionHandler 可以将一个方法指定为异常处理方法。该注解只有一个可选属性 value,为一个 Class 数组,用于指定该注解的方法所要处理的异常类,即所要匹配的异常。而被注解的方法,其返回值可以是 ModelAndView、String、void、自定义Object,方法名随意,方法参数可以是 Exception 及其子类对象、HttpServletRequest、HttpServletResponse 等。系统会自动为这些方法参数赋值。对于异常处理注解的用法,也可以直接将异常处理方法注解于 Controller 之中。

8.10.2、自定义异常类

UserException

public class UserException extends Exception {
    public UserException() {
        super();
    }

    public UserException(String message) {
        super(message);
    }
}

UserNameException

public class UserNameException extends UserException {
    public UserNameException() {
        super();
    }

    public UserNameException(String message) {
        super(message);
    }
}

UserAgeException

public class UserAgeException extends UserException {
    public UserAgeException() {
        super();
    }

    public UserAgeException(String message) {
        super(message);
    }
}

8.10.3、定义全局异常处理

GlobalExceptionHandler

//@ControllerAdvice:控制器增强(也就是给控制器类增加异常处理功能)
@ControllerAdvice
public class GlobalExceptionHandler {
    //@ExceptionHandler(异常的class):表示异常的类型,当发生此类型异常时,由当前方法处理
    @ExceptionHandler(value = UserNameException.class)
    public ModelAndView doNameException(Exception exception) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "姓名不能为空!");
        mv.addObject("ex", exception);
        mv.setViewName("error");
        return mv;
    }

    //@ExceptionHandler(异常的class):表示异常的类型,当发生此类型异常时,由当前方法处理
    @ExceptionHandler(value = UserAgeException.class)
    public ModelAndView doAgeException(Exception exception) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "年龄必须在18-60岁之间!");
        mv.addObject("ex", exception);
        mv.setViewName("error");
        return mv;
    }

    //处理其它异常,UserNameException、UserAgeException之外的异常
    @ExceptionHandler
    public ModelAndView doOtherException(Exception exception) {
        ModelAndView mv = new ModelAndView();
        mv.addObject("msg", "系统发生错误,请通知管理员!");
        mv.addObject("ex", exception);
        mv.setViewName("error");
        return mv;
    }
}

8.10.4、定义全局错误页面

error.jsp

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>error page</title>
</head>
<body>
<p>异常信息:${ex.message}</p>
<p>异常提示:${msg}</p>
</body>
</html>

8.10.5、配置组件扫描异常

<!--开启组件扫描-->
<context:component-scan base-package="com.caocheneli.spring5.controller"></context:component-scan>
<context:component-scan base-package="com.caocheneli.spring5.interceptor"></context:component-scan>

<!--开启MVC注解-->
<mvc:annotation-driven/>

8.10.6、定义错误方法测试

@RequestMapping("/error.do")
public String error(String name, Integer age) throws UserException {
    if (name == null) {
        throw new UserNameException("姓名不正确");
    }

    if (age == null || (age < 18 && age > 60)) {
        throw new UserAgeException("年龄不正确");
    }
    return "login";
}

请求地址:http://localhost:8080/hello/error.do?name=%E5%BC%A0%E4%B8%89&age=20

学习Spring5 WebMVC这一篇就够了

请求地址:http://localhost:8080/hello/error.do?age=20

学习Spring5 WebMVC这一篇就够了

请求地址:http://localhost:8080/hello/error.do?name=%E5%BC%A0%E4%B8%89age=90

学习Spring5 WebMVC这一篇就够了

8.11、SpringMVC的文件上传

第一步:创建表单上传页面

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>upload page</title>
</head>
<body>
<form action="/hello/upload.do" method="post" enctype="multipart/form-data">
    文件上传:<input type="file" name="file"><input type="submit" value="上传">
</form>
</body>
</html>

第二步:配置多媒体解析器

<!--配置多媒体解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
    <!--设定文件默认编码-->
    <property name="defaultEncoding" value="UTF-8"></property>
    <!--设定文件的最大值(5*1024*1024=5M)-->
    <property name="maxUploadSize" value="5242880"></property>
    <!--设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为10240-->
    <property name="maxInMemorySize" value="40960"></property>
    <!--上传文件的临时路径,目录需要自己手动创建-->
    <property name="uploadTempDir" value="fileUpload/temp"></property>
    <!--是否延迟文件解析-->
    <property name="resolveLazily" value="true"/>
</bean>

第三步:导入文件上传依赖

<!--文件上传相关依赖-->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.4</version>
</dependency>

第四步:创建文件上传方法

@RequestMapping(value = "/upload.do", method = RequestMethod.POST)
@ResponseBody
public String upload(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
    // 文件不为空
    if (!file.isEmpty()) {
        // 文件存放目录
        String catalog = "/fileUpload/";
        // 文件存放路径
        String path = request.getServletContext().getRealPath(catalog);
        // 文件存放名称
        String name = String.valueOf(new Date().getTime() + "_" + file.getOriginalFilename());
        File destFile = new File(path, name);
        // 文件保存操作
        try {
            file.transferTo(destFile);
        } catch (IllegalStateException | IOException e) {
            e.printStackTrace();
        }
        // 文件访问地址
        String url = request.getScheme() + "://" + request.getServerName() + ":" + request.getServerPort() + request.getContextPath() + catalog + name;
        // 返回文件地址
        return "success, the file url is : " + url;
    } else {
        return "error!";
    }
}

第五步:测试文件上传是否可用

请求地址:http://localhost:8080/upload.jsp

学习Spring5 WebMVC这一篇就够了

8.12、SpringMVC的跨域解决

Spring MVC使您可以处理CORS(跨源资源共享)。出于安全原因,浏览器禁止AJAX调用当前来源以外的资源。例如,您可以在一个标签页中拥有您的银行帐户,而在另一个标签页中拥有evil.com。来自evil.com的脚本不应使用您的凭据向您的银行API发出AJAX请求,例如从您的帐户中提取资金!跨域资源共享(CORS)是由大多数浏览器实施的W3C规范,可让您指定授权哪种类型的跨域请求,而不是使用基于IFRAME或JSONP的安全性较低且功能较弱的变通办法。

该 @CrossOrigin 注释能够对带注释的控制器方法跨域请求,如下面的示例所示:

@CrossOrigin
@GetMapping("/getUserInfo.do")
@ResponseBody
public User getUserInfo(){
    User user = new User();
    user.setName("张三");
    user.setAge("18");
    return user;
}

除了细粒度的控制器方法级别配置外,您可能还想定义一些全局CORS配置。

<mvc:cors>
    <mvc:mapping path="/api/**"
                 allowed-origins="https://domain1.com, https://domain2.com"
                 allowed-methods="GET, PUT"
                 allowed-headers="header1, header2, header3"
                 exposed-headers="header1, header2"
                 allow-credentials="true"
                 max-age="3600"/>

    <!--Add more mappings...-->
</mvc:cors>

8.13、SpringMVC的其他技术

8.13.1、提交表单中有日期

当form表单中的数据是基本类型的时,直接请求action中的url,一点问题都没有。但是当form表单中有时间类型的数据时,且对应的controller是用一个java对象来绑定对应form提交的数据时,就会出现问题,无法提交成功。解决办法就是在对应的controller中新增下面的方法:

@InitBinder
public void initBinder(WebDataBinder binder) {
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    dateFormat.setLenient(false);
    binder.registerCustomEditor(Date.class, new CustomDateEditor(dateFormat, true));
}

8.13.2、返回数据中有日期

我们经常会返回JSON类型的字符串,有时候,难免会包含一些日期类型,但是我们想要在转换为JSON字符串的时候,就将日期的格式给固定了,那我们可以参考以下做法:

<mvc:annotation-driven>
    <!--处理@ResponseBody里面日期类型-->
    <mvc:message-converters>
        <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter">
            <property name="objectMapper">
                <bean class="com.fasterxml.jackson.databind.ObjectMapper">
                    <property name="dateFormat">
                        <bean class="java.text.SimpleDateFormat">
                            <constructor-arg type="java.lang.String" value="yyyy-MM-dd HH:mm:ss"/>
                        </bean>
                    </property>
                </bean>
            </property>
        </bean>
    </mvc:message-converters>
</mvc:annotation-driven>

8.13.3、视图快捷导航标签

当我们为了确保系统安全而把所有的 jsp 页面放到 WEB-INF 目录下时,外界是不能直接进行访问的,我们必需先要定义一个视图跳转的方法,跳转到相对应的界面,但是这样做你不觉得太麻烦了吗,那么,SpringMVC又提供了一种便捷的配置方式来进行访问:

<mvc:view-controller path="/loginPage.do" view-name="login"/>

8.13.4、url-pattern的写法

  • *.do

    在没有特殊要求的情况下,SpringMVC 的*调度器 DispatcherServlet 常使用后辍匹配方式,如写为*.do 或者 *.action, *.mvc 等。

  • /

    可以写为 /,如果访问静态资源,这时 DispatcherServlet 会将例如 .css、.js、.jpg 等资源的获取请求,当作是一个普通的控制器请求。*调度器会调用处理器映射器为其查找相应的处理器。当然也是找不到的,所以在这种情况下,所有的静态资源获取请求也均会报 404 错误。<url-pattern/> 的值并不是说写为/后,静态资源就无法访问了。经过一些配置后,该问题也是可以解决的。

    第一种解决方法:声明 <mvc:default-servlet-handler/> 后 , SpringMVC 框架会在容器中创建 DefaultServletHttpRequestHandler 处理器对象。它会像一个检查员,对进入 DispatcherServlet 的 URL 进行筛查,如果发现是静态资源的请求,就将该请求转由 Web 应用服务器默认的Servlet处理。一般的服务器都有默认的Servlet, 在Tomcat中,有一个专门用于处理静态资源访问的 Servlet 名叫 DefaultServlet。

    第二种解决方法:在 Spring3.0 版本后,Spring 定义了专门用于处理静态资源访问请求的处理器 ResourceHttpRequestHandler。并且添加了标签,专门用于解决静态资源无法访问问题。需要在 springmvc 配置文件中添加如下形式的配置:

    <mvc:resources mapping="/css/**" location="/css/"/>
    <mvc:resources mapping="/js/**" location="/js/"/>
    <mvc:resources mapping="/images/**" location="/images/"/>
    
上一篇:spring5源码分析系列(二)——spring核心容器体系结构


下一篇:5分钟学习maven