目录
- 第八章 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)。区分层次的目的就是为了解除各个模块之间的耦合关系,提高代码复用性。
三层架构的优势:
- 结构清晰,耦合度低
- 可维护性高,可扩展性高
- 利于开发任务同步进行,容易适应需求变化
三层架构的劣势:
- 降低了系统的性能
- 有时会导致级联的修改
- 增加了代码量,增加了工作量
各层之间的方案:
- 用户界面层: Spring MVC、Struts 2
- 业务逻辑层: Spring
- 数据访问层: Hibernate、MyBatis、JdbcTemplate
MVC
MVC最开始是存在于桌面程序中的,全名是Model View Controller,是数据模型(model)-用户界面(view)-控制器(controller)的缩写,它是一种软件设计模式。使用MVC的目的在于将M(数据模型)和V(用户界面)的实现代码分离,从而使同一个程序可以使用不同的表现形式。C(控制器)存在的目的则是确保M和V的同步,一旦M改变,V应该同步更新。
我用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的原理
请求流程:
父子容器:
8.3、SpringMVC的入门
8.3.1、项目创建
修改 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
打开控制台:
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
@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;
}
如果说,我们当前控制器中的所有方法均是返回 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 中资源的。
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、单个拦截器执行顺序
单个拦截器中方法与处理器方法的执行顺序如下图:
8.8.5、多个拦截器执行顺序
多个拦截器中方法与处理器方法的执行顺序如下图:
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进行访问
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
请求地址:http://localhost:8080/hello/error.do?age=20
请求地址:http://localhost:8080/hello/error.do?name=%E5%BC%A0%E4%B8%89age=90
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
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/"/>