1. 为什么会有SpringMVC
1.1 引言
之前我们有Servlet处理用户请求的时候,基本需要以下步骤:
- 配置web.xml
- 继承HttpServlet,重写doGet和doPost方法
- 获取请求操作
每个Servlet都需要继承HttpServlet并且重写doGet和doPost方法,还需要写一大堆的getParameter()方法获取请求参数,而且还要做数据类型的转换。
在这样的背景写,SpringMVC就诞生了。
1.2 什么是SpringMVC
SpringMVC是一种轻量级的、基于Web层的应用框架,属于Spring的一部分。SpringMVC对Servlet进行封装,通过一套注解,让一个简单的Java类成为处理请求的控制器,而且无需实现任何接口,同时还支持RestFul编程的请求。
1.3 SpringMVC的优点
- 支持各种视图技术,不仅仅是局限于JSP
- 与Spring框架集成(如IOC容器、AOP等)
- 清晰的角色分配:前端控制器、请求到处理响应、处理器适配器、视图解析器
- 支持各种请求资源的映射策略
2. SpringMVC的工作流程
- 用户发送请求至前端控制器DispatcherServlet (Dispatcher调度)
- DispatcherServlet收到请求,调用HandlerMapping处理器映射器,匹配由HandlerMapping决定。
- 处理器映射器找到具体的处理器,生成处理器对象及处理器拦截器(如果有则生成)一并返回给DispatcherServlet。
- DispatcherServlet调用HandlerAdapter处理器适配器 。
- HandlerAdapter经过适配,知道要调用具体的处理器(Controller(后端控制器),开发者自定义,用来处理用户请求,返回视图)。
- 用具体的处理器Controller执行完成返回ModelAndView 。
- HandlerAdapter将controller执行结果ModelAndView返回给DispatcherServlet。
- DispatcherServlet将ModelAndView传给ViewReslover视图解析器。
- ViewReslover解析后返回具体View。
- DispatcherServlet根据View进行渲染视图(即将模型数据填充至视图中)。
- DispatcherServlet响应用户。
3. 常用的注解
3.1 @RequestMapping
处理 URL 请求,可标注在类上或方法上,标注在类上相当于web应用的根目录,若类上没标注那么则方法上标注的URL相当于web应用的根目录。
3.2 @GetMapping
接收请求方式为GET
3.3 @PostMapping
接收请求方式为POST
3.4 @PathVariable
-
获取URL中携带的参数值,处理RESTful风格的路径参数
-
请求中的productId,会赋值给标注了注释@PathVariable的参数productId。参数productId的类型是String的,像这种简单类型在进行赋值的时候Spring 是会帮我们自动转换的。
@RequestMapping(value="/product/{productId}",method = RequestMethod.GET) public String getProduct(@PathVariable("productId") String productId){ System.out.println("Product Id : " + productId); return "hello"; }
3.5 @RequestBody(请求体)
@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的) ;GET方式无请求体,所以使用@RequestBody主要用来接收前端传递给后端的json字符串中的数据的(请求体中的数据的) ;GET方式无请求体,所以使用@RequestBody与@RequestParam()可以同时使用,@RequestBody最多只能有一个,而@RequestParam()可以有多个。
3.6 @ResponseBody
改变返回逻辑视图的默认行为,返回具体的数据,比如json @ResponseBody注解使用简介
3.7 @Controller
Spring定义的,作用就是标明这是一个controller类
3.8 @RestController
@Controller+@ResponseBody的组合
3.9 @RequestHeader
可以把Request请求header部分的值绑定到方法的参数上。
这是一个Request 的header部分:
示例代码:
上面的代码,把request header部分的 Accept-Encoding的值,绑定到参数encoding上了, Keep-Alive header的值绑定到参数 keepAlive上。
3.10 @CookieValue
可以把Request header中关于cookie的值绑定到方法的参数上。即获取浏览器传递cookie值
例如有如下Cookie值:
JSESSIONID=415A4AC178C59DACE0B2C9CA727CDD84
@RequestMapping("/displayHeaderInfo.do")
public void displayHeaderInfo(@CookieValue("JSESSIONID") String cookie) {
}
即把JSESSIONID的值绑定到参数cookie上。
4. SpringMVC的数据响应
4.1 页面跳转
ModelAndView对象: 它可以通过ModelAndView的addObject()方法设置,向request域存储数据,也可以进行页面跳转
@RequestMapping(value="/quick3")
//如果传有ModelAndView作为形参,MVC框架会帮你注入,那么MVC会将自动给你创建一个ModelAndView对象供你使用
public ModelAndView save3(ModelAndView modelAndView){
modelAndView.addObject("username","itheima");
modelAndView.setViewName("success");
return modelAndView;
}
返回字符串形式:
返回的字符串与视图解析器的前后缀拼接后跳转。
4.2 回写数据
直接返回对象或集合,或者字符串:
在方法上添加@ResponseBody就可以返回json格式的字符串。因此,我们可以使用mvc的注解驱动代替配置,让SpringMVC自动将User转换成json格式的字符串。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:mvc="http://www.springframework.org/schema/mvc"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/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">
<!--mvc的注解驱动-->
<mvc:annotation-driven/>
</beans>
@RequestMapping("/quick")
@ResponseBody //告知SpringMVC框架 不进行视图跳转 直接进行数据响应
public String quickMethod7() throws IOException {
User user = new User();
user.setUsername("zhangsan");
user.setAge(18);
return user;
}
5. SpringMVC获得请求数据
5.1 基本数据类型、POJO类型参数和数组类型参数的获取
//数组数据类型的获取
@RequestMapping("/test01")
@ResponseBody
public void quickMethod11(String[] strs) throws IOException {
System.out.println(Arrays.asList(strs));
}
//POJO数据类型的获取
public class User {
private String username;
private int age;
getter/setter…
}
@RequestMapping("/test02")
@ResponseBody
public void quickMethod10(User user) throws IOException {
System.out.println(user);
}
//基本数据类型的获取
@RequestMapping("/test03")
@ResponseBody
public void quickMethod9(String username,int age) throws IOException {
System.out.println(username);
System.out.println(age);
}
5.2 集合参数类型的获取
一般情况:获得集合参数时,要将集合参数包装到一个POJO中才可以。
//用户类
public class User {
private String username;
private int age;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "User{" +
"username='" + username + '\'' +
", age=" + age +
'}';
}
}
//用于封装的类
public class VO {
private List<User> userList;
public List<User> getUserList() {
return userList;
}
public void setUserList(List<User> userList) {
this.userList = userList;
}
@Override
public String toString() {
return "VO{" +
"userList=" + userList +
'}';
}
}
<!--前端表单,userList[0].username-----userList要与封装类中的属性名对应,[0]表示第几个对象,username表示对象中的属性。-->
<form action="${pageContext.request.contextPath}/quick12" method="post">
<input type="text" name="userList[0].username"><br>
<input type="text" name="userList[0].age"><br>
<input type="text" name="userList[1].username"><br>
<input type="text" name="userList[1].age"><br>
<input type="submit" value="提交"><br>
</form>
//测试程序
@RequestMapping("/quick12")
@ResponseBody
public void quickMethod12(Vo vo) throws IOException {
System.out.println(vo.getUserList());
}
注意:
当使用ajax提交时,可以指定contentType为json形式,那么在方法参数位置使用@RequestBody可以 直接接收集合数据而无需使用POJO进行包装。
<script>
//模拟数据
var userList = new Array();
userList.push({username: "zhangsan",age: "20"});
userList.push({username: "lisi",age: "20"});
$.ajax({
type: "POST",
url: "/itheima_springmvc1/quick13",
data: JSON.stringify(userList),
contentType : 'application/json;charset=utf-8'
});
</script>
@RequestMapping("/quick13")
@ResponseBody
public void quickMethod13(@RequestBody List<User> userList) throws
IOException {
System.out.println(userList);
}
5.3 RestFul风格参数的获取
RestFul是一种软件架构风格、设计风格,只是提供了一组设计原则和约束条件,基于这个风格设计的软件可以更简介。
Restful风格的请求是使用“url+请求方式”表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:
- GET:用于获取资源
- POST:用于新建资源
- PUT:用于更新资源
- DELETE:用于删除资源
例如:
- /user/1 GET : 得到 id = 1 的 user
- /user/1 DELETE: 删除 id = 1 的 user
- /user/1 PUT: 更新 id = 1 的 user
- /user POST: 新增 user
上述url地址/user/1中的1就是要获得的请求参数,在SpringMVC中可以使用占位符进行参数绑定。
地址/user/1可以写成 /user/{id},占位符{id}对应的就是1的值。在业务方法中我们可以使用@PathVariable注解进行占位符的匹配获取工作。
5.4 请求乱码的问题
post请求出现乱码的时候,设置过滤器来进行编码的过滤
<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>
</filter>
<filter-mapping>
<filter-name>CharacterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
6. SpringMVC拦截器(interceptor)
6.1 拦截器(interceptor)的作用
Spring MVC也可以使用拦截器对请求进行拦截处理,用户可以自定义拦截器来实现特定的功能,自定义的拦截器可以实现HandlerInterceptor接口,或者可以继承HandlerInterceptorAdapter适配器类,拦截器也是AOP思想的具体实现。
① preHandle():这个方法在业务处理器处理请求之前被调用,在该方法中对用户请求 request 进行处理。如果程序员决定该拦截器对请求进行拦截处理后还要调用其他的拦截器,或者是业务处理器去进行处理,则返回true;如果程序员决定不需要再调用其他的组件去处理请求,则返回false。
② postHandle():这个方法在业务处理器处理完请求后,但是DispatcherServlet 向客户端返回响应前被调用,在该方法中对用户请求request进行处理。
③ afterCompletion():这个方法在 DispatcherServlet 完全处理完请求后被调用,可以在该方法中进行一些资源清理的操作。
6.2 配置单个拦截器
一、自定义拦截类
public class FirstHandlerInterceptor implements HandlerInterceptor** {
@Override
public void afterCompletion(HttpServletRequest arg0,
HttpServletResponse arg1, Object arg2, Exception arg3) throws Exception {
System.out.println(this.getClass().getName() + " - afterCompletion");
}
@Override
public void postHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2, ModelAndView arg3) throws Exception {
System.out.println(this.getClass().getName() + " - postHandle");
}
@Override
public boolean preHandle(HttpServletRequest arg0, HttpServletResponse arg1,
Object arg2) throws Exception {
System.out.println(this.getClass().getName() + " - preHandle");
return true;
}
}
二、配置拦截器
<mvc:interceptors>
<!-- 声明自定义拦截器 -->
<bean id="firstHandlerInterceptor"
class="com.atguigu.springmvc.interceptors.FirstHandlerInterceptor"></bean>
</mvc:interceptors>
三、执行流程
6.3 配置多个拦截器
一、自定义两个拦截类
二、配置拦截器
<mvc:interceptors>
<!-- 声明自定义拦截器 -->
<bean id="firstHandlerInterceptor"
class="com.atguigu.springmvc.interceptors.FirstHandlerInterceptor ">
</bean>
<!-- 配置拦截器引用 -->
<mvc:interceptor>
<mvc:mapping path="/empList"/>
<!-- <mvc:exclude-mapping path="/empList"/> -->
<bean id="secondHandlerInterceptor"
class="com.atguigu.springmvc.interceptors.SecondHandlerInterceptor"> </bean>
</mvc:interceptor>
</mvc:interceptors>
三、执行顺序
com.springmvc.interceptors.FirstHandlerInterceptor - preHandle
com.springmvc.interceptors.SecondHandlerInterceptor – preHandle
com.springmvc.interceptors.SecondHandlerInterceptor - postHandle
com.springmvc.interceptors.FirstHandlerInterceptor - postHandle
com.springmvc.interceptors.SecondHandlerInterceptor - afterCompletion
com.springmvc.interceptors.FirstHandlerInterceptor - afterCompletion
7. SpringMVC异常处理
系统中的异常分为:预期异常和运行时异常RuntimeException,前者通过捕获异常得到,后者主要通过代码开发、测试等手段减少运行时异常的发生。
系统各层出现的异常通过throws Exception向上抛出,最后由前端控制器交由异常处理器进行处理。
7.2 异常处理两种方式
- 使用SpringMVC提供的简单异常处理器SimpleMappingExceptionResolver。
- 实现Spring的异常处理接口HandlerExceptionResolver自定义自己的异常处理器。
7.3 SimpleMappingExceptionResolver
<!--配置简单映射异常处理器-->
<bean
class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="defaultErrorview" value="error"/>
<property name= "exceptionMappings"/>
<map>
<entry key="com.itheima.exception. MyException" value="error"/>
<entry key="java.lang.classCastException" value="error"/>
</ map>
</bean>
7.4 自定义异常处理类
一、创建异常处理器类实现HandlerExceptionResolver
public class MyExceptionResolver implements HandlerExceptionResolver {
@Override
public ModelAndView resolveException(HttpServletRequest request,
HttpServletResponse response, Object handler, Exception ex) {
//处理异常的代码实现
//创建ModelAndView对象
ModelAndView modelAndView = new ModelAndView();
modelAndView.setViewName("exceptionPage");
return modelAndView;
}
}
二、配置异常处理器
<bean id="exceptionResolver"
class="com.itheima.exception.MyExceptionResolver"/>
三、编写异常页面
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
这是一个最终异常的显示页面
</body>
</html>
四、测试异常跳转
@RequestMapping("/test")
@ResponseBody
public void quickMethod22() throws IOException, ParseException {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd");
simpleDateFormat.parse("abcde");
}