这个学习内容是借鉴的,做笔记是为了不让我遗忘太快。
里面的过程和代码都已经自己实现过了,同时有一些也加了自己的理解。
文章目录
1.SpringMVC入门案例
1.案例程序
1.新建MAVEN工程,并且要选择webapp,建立后在pom.xml
中加入以下代码:
<!-- 版本锁定:指定Spring版本 -->
<properties>
<spring.version>5.0.2.RELEASE</spring.version>
</properties>
<!-- 依赖坐标 -->
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<version>2.5</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.0</version>
<scope>provided</scope>
</dependency>
</dependencies>
2.配置SpringMVC组件
1.将SpringMVC组件注入Spring核心容器: 在工程resource
目录下创建文件bean.xml
如下
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
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
http://www.springframework.org/schema/mvc
http://www.springframework.org/schema/mvc/spring-mvc.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 配置spring创建容器时扫描的包 -->
<context:component-scan base-package="com"></context:component-scan>
<!-- 配置视图解析器,用于解析项目跳转到的文件的位置 -->
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
<!-- 配置spring开启注解mvc的支持 -->
<mvc:annotation-driven></mvc:annotation-driven>
</beans>
2.配置SpringMVC核心控制器DispatcherServlet
,并使Spring容器在TOMCAT初始化时创建. 在工程的webapp/WEB_INF
目录下配置web.xml
如下:
<web-app>
<!-- 配置核心控制器 -->
<servlet>
<servlet-name>dispatcherServlet</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<!-- 使核心控制器初始化时读取bean.xml文件创建Spring核心容器 -->
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:bean.xml</param-value>
</init-param>
<!-- 设置该Servlet的优先级别为最高,使之最早创建 -->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>dispatcherServlet</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
</web-app>
3.编写控制器和视图
1.编写主页视图index.jsp
: 在工程的webapp
目录下创建视图index.jsp
如下
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>主页</title>
</head>
<body>
<h3>入门案例</h3>
<a href="hello">入门案例</a>
</body>
2.在工程的java
目录下创建控制器类com.controller.HelloController
如下:
@Controller
public class HelloController {
@RequestMapping(path="/hello") // 指定方法对应的URL
public String helloHandler() {
System.out.println("Hello SpringMVC!!");
return "success"; // 指定跳转的视图的地址,被ViewResolver(视图解析器)解析为 /WEB-INF/pages/success.jsp
}
}
3.在工程的webapp/WEB_INF/pages
目录下创建成功跳转视图success.jsp
如下:
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>跳转成功</h3>
</body>
</html>
4.配置Tomcat服务器:
过程略。
5.测试: 访问http://localhost:8080/Mvc_day01_01_war/
,可以看到我们的项目主页
点击链接,跳转到success.jsp
页面并在控制台输出"Hello SpringMVC"
2.案例执行流程分析
1.启动Tomcat服务器时,由于配置了标签,所以首先创建DispatcherServlet对象并加载bean.xml配置文件
2.由于bean.xml中开启了注解扫描,HelloController对象被创建并加入Spring容器中
3.浏览器请求index.jsp,请求会先到达DispatcherServlet核心控制器,根据配置@RequestMapping注解找到具体要执行的方法helloHandler
4.执行方法helloHandler,得到返回值的视图解析器解析,查找到对应的JSP文件success.jsp
5.Tomcat服务器渲染页面,做出响应
2.SpringMVC核心组件
1.前端控制器(核心控制器)DispatcherServlet :用户请求最先达到的控制器,前端控制器调用其他组件处理请求,是MVC架构中的C,是整个流程控制的核心.其存在降低了组件间的耦合性.
2.处理器映射器HandlerMapping :负责根据用户请求找到处理器Handler.
3.处理器Handler:具体的业务方法.
4.处理器适配器HandlAdapter : 对处理器进行执行.这是一种适配器模式的应用.
5.视图解析器ViewResolver : 负责将处理结果生成视图. ViewResolver首先根据逻辑视图名解析成物理视图名
即具体的页面地址,再生成View视图对象,最后对View进行渲染将处理结果通过页面展示给用户.
6.视图View : 具体的页面
其中处理器映射器HandlerMapping,处理器适配器HandlAdapter,视图解析器ViewResolver称为SpringMVC三大组件.在bean.xml中声明<mvc:annotation-driven />标签相当于自动配置了处理器映射器和处理器适配。
3.请求路径匹配
1.@RequestMapping
注解
匹配路径与处理器
@RequestMapping注解用于建立请求URL路径和处理器之间的对应关系.
出现位置: 可以出现在类上,也可以出现在方法上.
当它既出现在类上也出现在方法上时,类上注解值为请求URL的一级目录,方法上注解值为请求URL的二级目录
当它只出现在方法上时,该注解值为请求URL的一级目录
其属性如下:
path: value属性的别名,指定请求的URL,支持Ant风格表达式,通配符如下:
通配符 说明
? 匹配文件(路径)名中的一个字符
* 匹配文件(路径)名中的任意数量(包括0个)的字符
** 匹配任意数量(包括0个)的路径
例如
路径/project/*.a匹配项目根路径下所有在/project路径下的.a文件
路径/project/p?ttern匹配项目根路径下的/project/pattern和/project/pXttern,但不能匹配/project/pttern
路径/**/example匹配项目根路径下的/project/example,/project/foo/example,和/example
路径/project/**/dir/file.*匹配项目根路径的/project/dir/file.jsp,/project/foo/dir/file.html,/project/foo/bar/dir/file.pdf
路径/**/*.jsp匹配项目根路径下的所有jsp文件
另外,遵循最长匹配原则,若URL请求了/project/dir/file.jsp,现在存在两个匹配模式:/**/*.jsp和/project/dir/*.jsp,那么会根据/project/dir/*.jsp来匹配.
method: 指定HTTP请求方法(可选RequestMethod.GET,RequestMethod.HEAD,RequestMethod.POST,RequestMethod.PUT等),多个值之间是或的关系.
params: 指定请求参数的限制,支持简单的表达式,如:
@RequestMapping(params={“param1”}),表示请求参数中param1必须出现
@RequestMapping(params={"!param1"}),表示请求参数中param1不能出现
@RequestMapping(params={“param1=value1”}),表示请求参数中param1必须出现且为value1
@RequestMapping(params={“param1!value1”}),表示请求参数中param1必须出现且不为value1
多个值之间是与的关系
headers: 限定HTTP请求中必须包含的请求头,同样支持简单的表达式,属性如下:
其中path和method属性较常用
2.@PathVaribale注解: 绑定URL占位符,支持REST风格URL
1.REST风格URL
REST风格URL有以下特点:
- 资源
Resources
: 通过一个URI来指定一个具体的资源 - 表现层
Representation
: 把资源具体呈现出来的形式 - 状态转化
State Transfer
: 通过HTTP请求方法来决定状态转化
例如:
HTTP请求类型 REST风格URL 对应的方法
GET /accounts 查找所有用户
POST /accounts 新建用户
GET /accounts/{ID} 查找对应ID的用户
PUT /accounts/{ID} 更新对应ID的用户
DELETE /accounts/{ID} 删除对应ID的用户
通过请求类型和REST风格URL来找到控制器里对应的方法
2.@PathVaribale注解的使用
作用:
用于绑定 url 中的占位符。例如:请求 url 中 /delete/{id},这个**{id}**就是 url 占位符。
url 支持占位符是 spring3.0 之后加入的。是 springmvc 支持 rest 风格 URL 的一个重要标志。
属性:
value:用于指定 url 中占位符名称。
required:是否必须提供占位符。
实例:
<a href="anno/testPathVariable/10">pathVariable注解</a>
@RequestMapping("/testPathVariable/{sid}")
private String testPathVariable(@PathVariable(name = "sid") String id){ //id=10
System.out.println("执行了.....");
System.out.println(id);
return "success";
}
4.请求参数绑定
1.参数绑定的示例
SpringMVC将请求参数中的param=value
中的value
传递给控制器方法的param
参数,例如:
对于以下请求和方法:
<a href="account/findAccount?accountId=10">查询账户</a>
// 控制器类
@Controller
@RequestMapping(path = "/account")
public class HelloController {
@RequestMapping(path = "/findAccount")
public void findAccount(Integer accountId) {
// accountId = 10
// 方法体...
}
}
SpringMVC中会将10
传给findAccount
方法的accountID
参数传递给HandlerAdapter
( 处理器适配器 )执行。
2.@RequestParam注解: 为处理器方法参数起别名
@RequestParam注解作用在方法参数上,把请求中指定名称的参数给处理器方法中的形参赋值,相当于给处理器方法起别名.其属性如下:
name : value属性的别名,指定请求参数的名称。
required : 指定该请求参数是否必须的,默认为 true 。
例如jsp中发送请求如下:
<a href="testRequestParam?param1=value">测试requestParam注解</a>
处理器方法中给对应参数加上@RequestParam(name="param1")
注解来接收参数
@RequestMapping("/testRequestParam")
public String handlerMethod(@RequestParam("param1") String username) {
// 方法体...
}
3.各种类型请求参数的绑定
1.SpringMVC内置参数绑定类型
SpringMVC支持三种类型的参数绑定
- 基本数据类型和String类型
- JavaBean类型
- 集合类型
数据绑定要求请求参数名和方法中的参数名相同,或使用@RequestParam
为方法参数起别名.
1.基本数据类型和String类型的参数绑定
对于基本数据类型,只需要以方法参数名作为请求参数名即可.示例如下:
<a href="account/findAccount?accountId=10&accountName=zhangsan">查询账户</a>
// 控制器类
@Controller
@RequestMapping(path = "/account")
public class HelloController {
@RequestMapping("/findAccount")
public String findAccount(Integer accountId, String accountName) {
// accountId = 10, accountName = "zhangsan"
// 方法体...
}
}
2.JavaBean类型的参数绑定
JavaBean类型的参数,要想实现绑定,就必须实现其空参构造函数和所有属性的get,set方法
1.若JavaBean参数的属性中只包含基本数据类型和String类型属性,以属性名作为请求参数名,则SpringMVC会自动将其封装成JavaBean对象.示例如下:
例如JavaBean类的定义如下:
// JavaBean类
public class Account implements Serializable {
private String username;
private Integer age;
// 所有属性的get,set方法...
}
则其对应的请求参数名如下:
<form action="account/updateAccount" method="post">
<label>名称</label><input type="text" name="username"><br/>
<label>年龄</label><input type="text" name="age"><br/>
<input type="submit" value="保存">
</form>
控制器类:
// 控制器类
@Controller
@RequestMapping(path = "/account")
public class HelloController {
@RequestMapping("/findAccount")
public String findAccount(Account account) {
......
}
}
2.若JavaBean参数的属性中包含其它JavaBean对象,则以外层类属性名.内层类属性名
作为请求参数,示例如下:
例如JavaBean类的定义如下:
public class Account implements Serializable {
private String username;
private Intger age;
private User user;
// 所有属性的getset方法...
}
public class User implements Serializable{
private String uname;
private Double umoney;
// 所有属性的getset方法...
}
则其对应的请求参数名如下:
<form action="account/updateAccount" method="post">
<label>名称</label><input type="text" name="username"><br/>
<label>年龄</label><input type="text" name="age"><br/>
<label>用户名</label><input type="text" name="user.uname"><br/>
<label>用户余额</label><input type="text" name="user.umoney"><br/>
<input type="submit" value="保存">
</form>
控制器类:
// 控制器类
@Controller
@RequestMapping(path = "/account")
public class HelloController {
@RequestMapping("/findAccount")
public String findAccount(Account account) {
......
}
}
3.集合类型的参数绑定
对JavaBean类中的集合属性进行参数绑定,可以分为List
类型的参数绑定和Set
类型的参数绑定
对于List类型参数,其请求参数名为:集合名[下标].属性。
对于Set类型参数,其请求参数名为:集合名[键].属性。
例如JavaBean类的定义如下:
public class Account implements Serializable {
private String username;
private Double money;
private List<User> list; // List集合属性
private Map<String, User> map; // Map集合属性
// 所有属性的getset方法...
}
public class User implements Serializable{
private String uname;
private Double age;
// 所有属性的getset方法...
}
则其对应的请求参数名如下:
<form action="account/updateAccount" method="post">
姓名:<input type="text" name="username"/><br/>
密码:<input type="text" name="password"/><br/>
金额:<input type="text" name="money"/><br/>
用户姓名:<input type="text" name="list[0].uname"/><br/>
用户年龄:<input type="text" name="list[0].age"/><br/>
用户姓名:<input type="text" name="map['one'].uname"/><br/>
用户年龄:<input type="text" name="map['one'].age"/><br/>
<input type="submit" value="提交"/>
</form>
控制器类:
// 控制器类
@Controller
@RequestMapping(path = "/account")
public class HelloController {
@RequestMapping("/findAccount")
public String findAccount(Account account) {
......
}
}
2.自定义数据类型参数绑定
表单提交的任何数据类型都是字符串类型,SpringMVC定义了转换器,将字符串转化为我们方法参数的各种类型.我们也可以实现自定义的转换器以实现自定义的参数类型转换
自定义的类型转换器要实现Converter<String, T>接口,并在Spring容器配置bean.xml中配置该实现类. 示例如下:
在工程的java
目录下创建类型转换器类com.util.StringToDateConverter
类,实现Converter<S, T>
接口,完成从String
类到Date
类的转换:
// 自定义的类型转换器,完成从String类到Date类的转换
public class StringToDateConverter implements Converter<String, java.util.Date> {
@Override
public Date convert(String s) {
if (s == null){
throw new RuntimeException("请您传入数据");
}
DateFormat df = new SimpleDateFormat("yyyy-MM-dd");
try {
return df.parse(s);
} catch (ParseException e) {
throw new RuntimeException("数据类型转换出现错误");
}
}
}
在Spring容器配置bean.xml
中加入如下配置:
<!-- 配置的类型转换器工厂 -->
<bean id="conversionService" class="org.springframework.context.support.ConversionServiceFactoryBean">
<property name="converters">
<set>
<!-- 诸如我们自定义的类型转换器 -->
<bean class="com.utils.StringToDateConverter"/>
</set>
</property>
</bean>
<!-- conversion-service 开启类型转换器-->
<mvc:annotation-driven conversion-service="conversionService"></mvc:annotation-driven>
3.通过原始ServletAPI对象处理请求
SpringMVC支持使用原始ServletAPI作为控制器方法的参数,包括HttpServletRequest
,HttpServletResponse
,HttpSession
对象,他们都可以直接用做控制器方法的参数.示例如下:
@RequestMapping("/path")
public void myHandler(HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println(request.getParameter("param1"));
System.out.println(request.getParameter("param1"));
response.getWriter().println("<h3>操作成功</h3>");
}
4.解决请求参数绑定中文乱码问题
在web.xml
中配置编码转换过滤器,即可解决请求参数中文乱码的问题.
<!-- 配置编码转换过滤器 -->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filterclass>
<!-- 指定字符集 -->
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
</filter>
<!-- 过滤所有请求 -->
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
5.常用注解
1.RequestBody
作用:
用于获取请求体内容。直接使用得到是 key=value&key=value…结构的数据。
get 请求方式不适用。
属性:
required:是否必须有请求体。默认值是:true。当取值为 true 时,get 请求方式会报错。如果取值
为 false,get 请求得到是 null。
示例:
JSP中:
<form action="anno/testRequestBody" method="post">
用户姓名:<input type="text" name="username"/><br/>
用户年龄:<input type="text" name="age"/><br/>
<input type="submit" value="提交"/>
</form>
控制器中:
@RequestMapping("/testRequestBody")
private String testRequestBody(@RequestBody String body){
System.out.println("执行了.....");sadjfsaedjoklfsdjf
System.out.println(body); //输出 :username=hehe&age=33
return "success";
}
2.RequestHeader
作用:
用于获取请求消息头。
属性:
value:提供消息头名称
required:是否必须有此消息头
注: 在实际开发中一般不怎么用。
3.CookieValue
不常用,百度吧。
4.ModelAttribute
作用:
该注解是 SpringMVC4.3 版本以后新加入的。它可以用于修饰方法和参数。
出现在方法上,表示当前方法会在控制器的方法执行之前,先执行。它可以修饰没有返回值的方法,也可以修饰有具体返回值的方法。
出现在参数上,获取指定的数据给参数赋值。
属性:
value:用于获取数据的 key。key 可以是 POJO 的属性名称,也可以是 map 结构的 key。
应用场景:
当表单提交数据不是完整的实体类数据时,保证没有提交数据的字段使用数据库对象原来的数据。
例如:
我们在编辑一个用户时,用户有一个创建信息字段,该字段的值是不允许被修改的。在提交表单数据是肯定没有此字段的内容,一旦更新会把该字段内容置为 null,此时就可以使用此注解解决问题。
基于方法有放回值的基本使用:
jsp代码:
<form action="anno/testModelAttribute" method="post">
用户姓名:<input type="text" name="uname"/><br/>
用户年龄:<input type="text" name="age"/><br/>
<input type="submit" value="提交"/>
</form>
控制器代码:
@RequestMapping("/testModelAttribute")
public String testModelAttribute(User user){
System.out.println("ModelAttribute执行了");
System.out.println(user);
return "success";
}
@ModelAttribute //在控制器方法前先执行,因为User有三个属性,jsp表单只能有姓名和年龄,可以自己给日期属性赋值。然后返回值给了上面方法的参数,执行上面方法时jsp表单里的uname和age会把上面方法参数对应的属性覆盖,Date属性不会被覆盖
public User showUser(String uname){ //这个参数会匹配jsp表单里的uname
System.out.println("showUser执行了");
User user = new User();
user.setUname(uname);
user.setAge(20);
user.setDate(new Date());
return user;
}
基于方法无返回值的基本使用 :
jsp代码:
<form action="anno/testModelAttribute" method="post">
用户姓名:<input type="text" name="uname"/><br/>
用户年龄:<input type="text" name="age"/><br/>
<input type="submit" value="提交"/>
</form>
控制器代码:
@RequestMapping("/testModelAttribute")
//属性值要与下面方法的map的key对应
public String testModelAttribute(@ModelAttribute("abc") User user){ //通过Map来匹配后,jsp表单里的uname和age会把上面方法参数对应的属性覆盖,Date属性不会被覆盖
System.out.println("ModelAttribute执行了");
System.out.println(user);
return "success";
}
@ModelAttribute
public void showUser(String uname, Map<String,User> map){
System.out.println("showUser执行了");
User user = new User();
user.setUname(uname);
user.setAge(20);
user.setDate(new Date());
map.put("abc",user);
}
5. SessionAttributes注解
作用:用于多次执行控制器方法间的参数共享
属性:value:指定存入属性的名称
代码如下:
@Controller
@RequestMapping(path="/user")
@SessionAttributes(value= {"username","password","age"}) // 把数据存入到session域对象中
public class HelloController {
/**
* 向session中存入值
* @return
*/
@RequestMapping(path="/save")
public String save(Model model) {
System.out.println("向session域中保存数据");
model.addAttribute("username", "root");
model.addAttribute("password", "123");
model.addAttribute("age", 20);
return "success";
}
/**
* 从session中获取值
* @return
*/
@RequestMapping(path="/find")
public String find(ModelMap modelMap) {
String username = (String) modelMap.get("username");
String password = (String) modelMap.get("password");
Integer age = (Integer) modelMap.get("age");
System.out.println(username + " : "+password +" : "+age);
return "success";
}
/**
* 清除值
* @return
*/
@RequestMapping(path="/delete")
public String delete(SessionStatus status) {
status.setComplete();
return "success";
}
}
调用save(Model model)方法把值存入session域,调用find(ModelMap modelMap)获得session域的值,调用delete(SessionStatus status)方法清除session域中的值。
在success.jsp中:(会显示域中的值)
<%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>入门成功了</h3>
${sessionScope}
</body>
</html>
6.通过处理器方法返回值指定返回视图
SpringMVC中的处理器方法的返回值用来指定页面跳转到哪个视图,处理器的返回值可以为String
,void
,ModelAndView
对象。
1.处理器返回String对象: 转发到字符串指定的URL
处理器方法返回字符串可以指定逻辑视图名,通过视图解析器解析为物理视图地址。
在本例中,因为我们在Spring容器配置文件bean.xml
中配置的视图解析器中注入prefix
和suffix
属性,所以视图解析器会把处理器返回的"字符串值"
解析为"/WEB-INF/pages/字符串值.jsp"
,再请求对应视图.这是一个请求转发过程,浏览器地址栏不会发生变化。
bean.xml中配置的视图解析器如下:
<bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
<property name="prefix" value="/WEB-INF/pages/"></property>
<property name="suffix" value=".jsp"></property>
</bean>
处理器方法如下:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testString")
public String testString(Model model) {
// 执行方法体...向隐式对象添加属性attribute_user,可以在jsp中通过 ${attribute_user} 获取到
model.addAttribute("attribute_user", new User("张三", "123"));
// 经过视图解析器的处理,SpringMVC会将请求转发到/WEB-INF/pages/succeess.jsp,但浏览器地址栏显示的一直是 项目域名/user/testString
return "success";
}
}
2.处理器返回void: 转发到当前URL
若处理器返回void
,表示执行完处理器方法体内代码后,不进行请求转发,而直接转发到当前URL。没有URL对应的文件会发生404。
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testVoid")
public void testVoid(Model model) {
// 执行方法体...向隐式对象添加属性attribute_user,可以在jsp中通过 ${attribute_user} 获取到
model.addAttribute("attribute_user", new User("张三", "123"));
// 处理器没有返回值,则会将请求转发到当前 项目域名/user/testVoid 路径
// 没有URL对应的文件会发生404。
return;
}
}
可以在返回语句之前执行请求转发,重定向或getWriter
方法指定视图,示例如下:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testVoid")
public void testVoid(HttpServletRequest request, HttpServletResponse response) throws Exception {
// 执行方法体...向隐式对象添加属性attribute_user,可以在jsp中通过 ${attribute_user} 获取到
model.addAttribute("attribute_user", new User("张三", "123"));
// 通过下面三个方法之一,可以指定访问的视图
// 指定视图的方式1: 请求转发
request.getRequestDispatcher("/WEB-INF/pages/success.jsp").forward(request,response);
// 指定视图的方式2: 重定向
response.sendRedirect(request.getContextPath() + "/index.jsp");
// 指定视图的方式3: 通过Writer对象写入内容
response.setCharacterEncoding("UTF-8"); //设置中文乱码
response.setContentType("text/html;charset=UTF-8"); //设置中文乱码
response.getWriter().print("你好"); //会在游览器直接显示 你好
return;
}
}
3.处理器返回ModelAndView对象: 更灵活地添加属性和指定返回视图
ModelAndView为我们提供了一种更灵活地为页面添加属性和指定返回视图的方法,其主要方法如下:
1.public ModelMap getModelMap(): 返回当前页面的ModelMap对象.
2.public ModelAndView addObject(Object attributeValue): 向当前页面的ModelMap对象中添加属性
3.public void setViewName(@Nullable String viewName): 指定返回视图,viewName会先被视图解析器处理解析成对应视图.
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testModelAndView")
public ModelAndView testModelAndView() {
// 创建ModelAndView对象
ModelAndView mv = new ModelAndView();
// 向model中存入属性名user,值为User
mv.addObject("user", new User("张三", "123"));
// 指定返回视图,视图解析器将"success"解析为视图URL /WEB-INF/pages/succeess.jsp
mv.setViewName("success");
return mv;
}
}
7.SpringMVC框架提供的请求转发和重定向
1.使用SpringMVC框架提供的请求转发
要使用SpringMVC框架提供的请求转发,只需要在处理器方法返回的viewName
字符串首加上forward:
即可,要注意的是,此时forward:
后的地址不能直接被视图解析器解析,因此要写完整的相对路径.示例如下:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testForward")
public String testForward() {
// 在forward:要写完整的相对路径
// return "forward:success" // 错误,会将请求转发到 /项目名/user/success
return "forward:/WEB-INF/pages/success.jsp";
}
}
2.使用SpringMVC框架提供的重定向
要使用SpringMVC框架提供的请求重定向,只需要在处理器方法返回的viewName
字符串首加上redirect:
即可,要注意的是,此时redirect:
后的地址要写相对于ContextPath
的地址.示例如下:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testRedirct")
public String testRedirct() {
// 在forward:要写完整的相对路径
// return "redirect:" + request.getContextPath() + "/index.jsp"; // 错误,会将请求转发到 /项目名/项目名/index.jsp
return "redirect:/index.jsp";
}
}
8.SpringMVC响应json数据
1.前期准备 :不过滤静态资源
-
jsp在页面上引入jQuery以发送json数据,因此需要向服务器发起一个对jQuery的请求。 DispatcherServlet会拦截到所有的资源,导致一个问题就是像这种对静态资源的请求(img、css、js)也会被拦截到,从而不能被使用。解决问题就是需要配置静态资源不进行拦截,在springmvc.xml配置文件添加如下配置:
我们需要在Spring容器配置
springmvc.xml
中使用<mvc:resources>
标签声明该资源为静态资源,否则请求该资源会报404
错误.该标签的属性如下:location
属性: 表示该资源在服务器上所在的位置,必须是一个有效的目录mapping
属性: 指定匹配的URL
<!-- 配置静态文件的路径于对应的URL --> <!-- location属性必须是一个有效的目录,因此必须以 / 结尾 --> <mvc:resources location="/css/" mapping="/css/**"/> <mvc:resources location="/images/" mapping="/images/**"/> <mvc:resources location="/js/" mapping="/js/**"/>
2.发送ajax的请求
- 在jsp页面中编写代码发送json请求如下:
<html>
<head>
<title>Title</title>
<script src="js/jquery.min.js"></script>
<script>
$("#btn").click(function () {
// 发送ajax请求
$.ajax({
// 配置请求参数
url: "user/testAjax",
contentType: "application/json;charset=UTF-8",
dataType: "json",
type: "post",
// 请求的json数据
data: '{"username":"hehe","password":"1223","age":23}',
// 请求成功后的回调函数 , 即控制器的方法执行成功后才执行的函数
success: function (data) {
// data服务器端响应的json的数据,进行解析
}
});
});
});
</script>
</head>
<body>
<button id="btn">发送ajax的请求</button>
</body>
</html>
-
在控制器中编写代码:
使用**@RequestBody**获取请求体数据,请求体传回来的类型的是字符串,要使用字符串类型接收。
@Controller @RequestMapping("/user") public class UserController { @RequestMapping("/testAjax") public void testAjax(@RequestBody String body){ System.out.println("执行了testAjax"); System.out.println(body); //输出 {"username":"hehe","password":"1223","age":23} } }
3.响应json格式数据:json字符串与JavaBean对象相互转换
1.要将json字符串与JavaBean对象相互转换,我们需要引用jackson
的jar包,在pom.xml
中添加依赖坐标如下:
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
2.在jsp页面中编写代码如下:
<html>
<head>
<title>Title</title>
<script src="js/jquery.min.js"></script>
<script>
$("#btn").click(function () {
// 发送ajax请求
$.ajax({
// 配置请求参数
url: "user/testAjax",
contentType: "application/json;charset=UTF-8",
dataType: "json",
type: "post",
// 请求的json数据
data: '{"username":"hehe","password":"1223","age":23}',
// 请求成功后的回调函数 , 即控制器的方法执行成功后才执行的函数
success: function (data) { //控制器方法使用ResponseBody注解把return的值传给data
// data服务器端响应的json的数据,进行解析
alert(data);
alert(data.username);
alert(data.password);
alert(data.age);
}
});
});
});
</script>
</head>
<body>
<button id="btn">发送ajax的请求</button>
</body>
</html>
3.在控制器中编写代码:
使用**@RequestBody注解把json的字符串转换成JavaBean的对象,使用@ResponseBody**注解把JavaBean对象转换成json字符串,同时将该方法的返回值直接写回到HTTP响应中,而不是存入Model
或解析为视图名.
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testAjax")
public @ResponseBody User testAjax(@RequestBody User user){ //将ajax的数据转换传入user
System.out.println("执行了testAjax");
//客户端发生ajax的请求,传的是json字符串,后端把json字符串封装到user对象中
System.out.println(user);
user.setUsername("haha");
user.setAge(40);
return user; //将user的数据更改后,转换为json字符串传回给ajax的success的参数, //并且执行ajax的success。
}
}
9.文件上传
1.实现文件上传的前提
1.表单的enctype属性取值必须是multipart/form-data(默认值是application/x-www-form-urlencoded),表示表单内容 是分块的.这时request对象的getParameter()方法将失效.
2.表单的method属性取值必须是post,因为get请求长度有限制.
3.提供一个标签,用来选择上传文件.
<h3>文件上传</h3>
<form action="user/fileupload1" method="post" enctype="multipart/form-data">
选择文件:<input type="file" name="upload"/><br/>
<input type="submit" value="上传"/>
</form>
4.引用文件上传的相关jar包
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.4</version>
</dependency>
2.文件表单内容
因为我们设置了enctype属性取值为multipart/form-data,因此在请求参数头中会有一项Content-Type:multipart/form-data; boundary=----WebKitFormBoundaryOMtUEa1sSZ3ayCfC,表示当前表单内容数据是分块的,每两块之间以----WebKitFormBoundaryOMtUEa1sSZ3ayCfC分界.
服务器通过遍历每一块数据,找到文件所在的数据块并执行保存.
3.文件上传的三种实现
1.使用JavaEE进行文件上传
传统的JavaEE文件上传思路是通过解析request
对象,获取表单中的上传文件项并执行保存.
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/fileupload1")
public String fileupload1(HttpServletRequest request) throws Exception {
System.out.println("文件上传...");
// 创建目录保存上传的文件
String path = request.getSession().getServletContext().getRealPath("/uploads/");
File file = new File(path);
if(!file.exists()){
file.mkdirs();
}
// 创建ServletFileUpload来解析request
DiskFileItemFactory factory = new DiskFileItemFactory();
ServletFileUpload upload = new ServletFileUpload(factory);
List<FileItem> items = upload.parseRequest(request);
// 遍历解析的结果,寻找上传文件项
for(FileItem item:items){
if(item.isFormField()){
// 是普通表单项,说明不是是文件上传项
}else{
// 服务器中保存的文件名
String filename = item.getName();
String uuid = UUID.randomUUID().toString().replace("-","");
filename = uuid+"_"+filename;
// 上传文件
item.write(new File(path,filename));
// 删除临时文件
item.delete();
}
}
return "success";
}
}
2.使用SpringMVC进行单服务器文件上传
原理图:
使用SpringMVC提供的文件解析器实现文件上传,在Spring容器中注入文件解析器CommonsMultipartResolver
对象如下:
<!-- 配置文件解析器,其id是固定的,必须为multipartResolver -->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设置文件的最大尺寸 -->
<property name="maxUploadSize" value="10485760"/> <!-- 1M大小 -->
</bean>
只要在处理器方法的参数列表中定义一个与表单文件项同名的**MultipartFile
参数,就可以将上传的文件绑定到该MultipartFile
对象上,调用其transferTo(File file)
**方法即可保存文件.
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/fileupload2")
public String fileupload2(HttpServletRequest request, MultipartFile upload) throws Exception {
System.out.println("springmvc文件上传...");
// 创建目录保存上传的文件
String path = request.getSession().getServletContext().getRealPath("/uploads/");
File file = new File(path);
if(!file.exists()){
file.mkdirs();
}
// 服务器中保存的文件名
String filename = upload.getOriginalFilename();
String uuid = UUID.randomUUID().toString().replace("-","");
filename = uuid+"_"+filename;
// 上传文件
upload.transferTo(new File(path,filename));
return "success";
}
}
3.使用SpringMVC进行跨服务器文件上传
我们可以引入jersey
库进行服务器间通信,实现将文件上传到一个专用的文件服务器,需要在pom.xml
中引入jersey
库的坐标如下:
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-core</artifactId>
<version>1.18.1</version>
</dependency>
<dependency>
<groupId>com.sun.jersey</groupId>
<artifactId>jersey-client</artifactId>
<version>1.18.1</version>
</dependency>
在处理器方法中创建**Client
**对象实现服务器间通信,将文件上传到文件服务器上,代码如下:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/fileupload3")
public String fileupload3(MultipartFile upload) throws Exception {
System.out.println("跨服务器文件上传...");
// 文件服务器URL
String path = "http://localhost:9090/fileuploadserver_war_exploded/uploads/";
// 获取服务器中保存的文件名
String filename = upload.getOriginalFilename();
String uuid = UUID.randomUUID().toString().replace("-","");
filename = uuid+"_"+filename;
//创建客服端对象
Client client = Client.create();
//和图片服务器连接
WebResource webResource = client.resource(path + filename);
//上传图片
webResource.put(upload.getBytes());
return "success";
}
}
**注意:**SpringMVC跨服务器上传文件,上传至文件服务器时报404错误这是目标服务器后端报错。
解决方案是在目标服务器加入注解
因为服务器会默认找这个相关映射去解决,但是上传过来的图片请求没有相关映射,如果没有作映射,就交给 WEB 应用服务器默认的 Servlet 处理,从而找到对应的静态资源,只有再找不到资源时才会报错。
<mvc:default-servlet-handler/>
10.异常处理器
原理及步骤图:
当程序发生错误时,错误最终会传递给DispatcherServlet
,由DispatcherServlet
进行异常处理.
下面演示使用SpringMVC的异常处理机制处理异常
1.创建自定义异常类
package com.exception;
public class SysException extends Exception{
//存储提示信息
private String message;
@Override
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
//构造方法
public SysException(String message) {
this.message = message;
}
}
2.创建异常处理器,异常处理器必须实现**HandlerExceptionResolver
接口,其resolveException()
**方法执行异常处理.
package com.exception;
// 自定义异常处理器
public class SysExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception ex) {
SysException e = null;
//是否正在运行SysException异常
if(ex instanceof SysException){
e = (SysException)ex;
}else{
e = new SysException("系统正在维护...");
}
//创建ModelAndView对象
ModelAndView mv = new ModelAndView();
mv.addObject("errorMsg",e.getMessage()); // 封装错误信息
mv.setViewName("error"); // 跳转页面
return mv;
}
}
3.向Spring容器中注入异常处理器
<!-- 自己定义的异常处理器 -->
<bean id="sysExceptionResolver" class="com.exception.SysExceptionResolver"/>
4.在控制器类中制造异常:
@Controller
@RequestMapping("/user")
public class UserController {
@RequestMapping("/testException")
public String testException()throws SysException{
System.out.println("testException执行了");
try {
int a = 10/0;
} catch (Exception e) {
e.printStackTrace();
throw new SysException("查询所有用户出现错误了...");
}
return "success";
}
}
完成上述操作,控制器发生了异常,会跳转到error提示页面。通过ModelAndView 封装错误信息,然后显示在提示页面。
11.拦截器
Spring MVC 的处理器拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
用户可以自己定义一些拦截器来实现特定的功能。
谈到拦截器,还要向大家提一个词——拦截器链(Interceptor Chain)。拦截器链就是将拦截器按一定的顺
序联结成一条链。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。
拦截器和过滤器是有几分相似,但是也有区别,接下来我们就来说说他们的区别:
过滤器是 servlet 规范中的一部分,任何 java web 工程都可以使用。
拦截器是 SpringMVC 框架自己的,只有使用了 SpringMVC 框架的工程才能用。
过滤器在 url-pattern 中配置了**/***之后,可以对所有要访问的资源拦截。
拦截器它是只会拦截访问的控制器方法,如果访问的是 jsp,html,css,image 或者 js 是不会进行拦截的。
它也是 AOP 思想的具体应用。
我们要想自定义拦截器, 要求必须实现:HandlerInterceptor 接口。
1.拦截器的配置
自定义拦截器需要继承HandlerInterceptor接口,该接口中定义了三个方法,都已有其默认实现:
1.preHandle(…): 该方法在处理器方法实际执行之前执行
2.postHandle(…): 该方法在处理器方法实际执行完毕以后执行
3.afterCompletion(…): 该方法在整个请求处理完成后执行
其中preHandle(…)方法返回一个boolean值,可以通过这个方法来决定是否继续执行处理链中的部件。当方法返回 true时,处理器链会继续执行;若方法返回 false, DispatcherServlet即认为拦截器自身已经完成了对请求的处理(比如说 请求转发/重定向),那么其余的拦截器以及执行链中的其他处理器就不会再被执行了。
1.拦截器1类实现:
/*
自定义拦截器
*/
public class Myintercewptor1 implements HandlerInterceptor {
/*
* 预处理,controller方法执行前
* return true 放行,执行下一个拦截器,如果没有,执行controller中的方法
* return false 不放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("Myinterceptor执行了...前111");
//request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
return true;
}
/*
*后处理方法,controller方法执行后,success.jsp执行之前
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("Myinterceptor执行了...后111");
//request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
}
/*
* success.jsp页面执行后,该方法会执行
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("Myinterceptor执行了...最后111");
}
}
2.向Spring容器中注入拦截器
<mvc:interceptors>
<mvc:interceptor>
<!-- 拦截的方法 -->
<mvc:mapping path="/user/*"/>
<!-- 具体的拦截器 -->
<bean class="com.interceptor.Myintercewptor1"></bean>
</mvc:interceptor>
</mvc:interceptors>
2.多个拦截器的执行顺序
两个拦截器配置如下:
<mvc:interceptors>
<!-- 配置拦截器1 -->
<mvc:interceptor>
<!-- 拦截的方法 -->
<mvc:mapping path="/user/*"/>
<!-- 具体的拦截器 -->
<bean class="com.interceptor.Myintercewptor1"></bean>
</mvc:interceptor>
<!-- 配置拦截器2 -->
<mvc:interceptor>
<!-- 拦截的方法 -->
<mvc:mapping path="/user/**"/>
<!-- 具体的拦截器 -->
<bean class="com.interceptor.Myintercewptor2"></bean>
</mvc:interceptor>
</mvc:interceptors>
访问该域名下Handler
时,服务器输出如下:
Myinterceptor1执行了...前111
Myinterceptor2执行了...前222
testInterceptor执行了
Myinterceptor2执行了...后222
Myinterceptor1执行了...后111
success.jsp执行了...
Myinterceptor2执行了...最后222
Myinterceptor1执行了...最后111
//request.getRequestDispatcher("/WEB-INF/pages/error.jsp").forward(request,response);
}
/*
* success.jsp页面执行后,该方法会执行
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println(“Myinterceptor执行了…最后111”);
}
}
2.向Spring容器中注入拦截器
```xml
<mvc:interceptors>
<mvc:interceptor>
<!-- 拦截的方法 -->
<mvc:mapping path="/user/*"/>
<!-- 具体的拦截器 -->
<bean class="com.interceptor.Myintercewptor1"></bean>
</mvc:interceptor>
</mvc:interceptors>
2.多个拦截器的执行顺序
两个拦截器配置如下:
<mvc:interceptors>
<!-- 配置拦截器1 -->
<mvc:interceptor>
<!-- 拦截的方法 -->
<mvc:mapping path="/user/*"/>
<!-- 具体的拦截器 -->
<bean class="com.interceptor.Myintercewptor1"></bean>
</mvc:interceptor>
<!-- 配置拦截器2 -->
<mvc:interceptor>
<!-- 拦截的方法 -->
<mvc:mapping path="/user/**"/>
<!-- 具体的拦截器 -->
<bean class="com.interceptor.Myintercewptor2"></bean>
</mvc:interceptor>
</mvc:interceptors>
访问该域名下Handler
时,服务器输出如下:
Myinterceptor1执行了...前111
Myinterceptor2执行了...前222
testInterceptor执行了
Myinterceptor2执行了...后222
Myinterceptor1执行了...后111
success.jsp执行了...
Myinterceptor2执行了...最后222
Myinterceptor1执行了...最后111