SpringMVC(二)
Ajax 异步交互
SpringMVC 默认用 MappingJackson2HttpMessageConverter 对 JSON 数据进行转换,需要加入 Jackson 的包;同时在 spring-mvc.xml 使用 <mvc:annotation-driven />
...
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.9.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.9.0</version>
</dependency>
...
...
<!-- 处理器映射器-处理器适配器。进行了功能的增强:支持 json 的读写 -->
<mvc:annotation-driven />
...
@RequestBody
该注解用于 Controller 的方法的形参声明,当使用 Ajax 提交并指定 contentType 为 JSON 形式时,通过 HttpMessageConverter 接口转换为对应的 POJO 对象。
src\main\webapp\ajax.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Ajax</title>
</head>
<body>
<%-- ajax 异步交互 --%>
<button id="btn1">ajax 异步提交</button>
<script src="${pageContext.request.contextPath}/js/jquery-3.5.1.js"></script>
<script>
$("#btn1").click(function () {
let url = '${pageContext.request.contextPath}/user/ajaxRequest';
let data = '[{"id":1,"username":"张三"},{"id":2,"username":"李四"}]';
$.ajax({
type: 'POST',
url: url,
data: data,
contentType: 'application/json;charset=utf-8',
success: function (resp) {
alert(JSON.stringify(resp));
}
})
});
</script>
</body>
</html>
在 UserController 中添加方法
@RequestMapping("/ajaxRequest")
public List<User> ajaxRequest(@RequestBody List<User> list){
System.out.println(list);
}
@ResponseBody
该注解用于将 Controller 的方法返回的对象,通过 HttpMessageConverter 接口转换为指定格式的数据如:JSON,xml 等,通过 Response 响应给客户端。
@RequestMapping("/ajaxRequest")
@ResponseBody
public List<User> ajaxRequest(@RequestBody List<User> list){
System.out.println(list);
return list;
}
RESTful
-
什么是 RESTful
Restful 是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存机制等。
Restful 风格的请求是使用“URL + 请求方式”表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:
- GET:读取(Read)
- POST:新建(Create)
- PUT:更新(Update)
- DELETE:删除(Delete)
查询所有:/user/findAll GET /user
根据 ID 查询:/user/findById?id=3 GET /user/{1}
新增:/user/save POST /user
修改:/user/update PUT /user
删除:/user/delete?id=3 DELETE /user/{1}
-
代码实现
@PathVariable
用来接收 RESTful 风格请求地址中占位符的值。
@RestController
RESTful 风格多用于前后端分离项目开发,前端通过 Ajax 与服务器进行异步交互,我们处理器通常返回的是 JSON 数据所以使用 @RestController 来替代 @Controller 和 @ResponseBody 两个注解。
/**
* 没有 ResponseBody 的话,会把 return 的值作为逻辑视图进行解析;
* 带有 ResponseBody 则直接进行数据的响应
*/
@RestController // @RestController = @Controller + @ResponseBody
@RequestMapping("/restful")
public class RestfulController {
/**
* 根据 id 进行查询
*/
@GetMapping("/user/{id}") // @RequestMapping(value = "/user/{id}",method = RequestMethod.GET)
public String findById(@PathVariable Integer id) {
// 获取 restful 编程风格中 url 里面占位符的值
return "findById: " + id;
}
/**
* 新增方法
* POST 对应的是新增
*/
@PostMapping("/user") // @RequestMapping(value = "/user",method = RequestMethod.POST)
public String post(){
return "post";
}
/**
* 更新方法
* PUT 对应的是更新操作
*/
@PutMapping("/user")
public String put(){
return "put";
}
/**
* 删除方法
*/
@DeleteMapping("/user/{id}")
public String delete(@PathVariable Integer id){
return "delete" + id;
}
}
文件上传
-
文件上传三要素
- 表单项 type="file"
- 表单的提交方式 method="POST"
- 表单的 enctype 属性是多部分表单形式 enctype=“multipart/form-data"
<form action="${pageContext.request.contextPath}/fileupload" method="post" enctype="multipart/form-data">
名称:<input type="text" name="username"> <br>
文件:<input type="file" name="filePic"> <br>
<input type="submit" value="单文件上传">
</form>
-
文件上传原理
-- 当 form 表单的 enctype 取值为 application/x-www-form-urlencoded 时,form 表单的正文内容格式是: name=value&name=value。
-- 当 form 表单的 enctype 取值为 mutilpart/form-data 时,请求正文内容就变成多部分形式:
-- 当 form 表单修改为多部分表单时,request.getParameter() 将失效。
输入表单项名称 username 为 "张三",上传文件 filePic 为 "a.txt",其中文件的内容为 "test zhangsan";此时表单的 Request Body 的有效载荷 payload 如下:
-----------------------------17656195882531319514853385408
Content-Disposition: form-data; name="username"
张三
-----------------------------17656195882531319514853385408
Content-Disposition: form-data; name="filePic"; filename="a.txt"
Content-Type: text/plain
test zhangsan
-----------------------------17656195882531319514853385408--
-
单文件上传
步骤分析:
- 导入 fileupload 和 io 坐标
- 配置文件上传解析器
- 编写文件上传代码
1. 导入 fileupload 和 io 坐标
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.3</version>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.6</version>
2. 配置文件上传解析器
spring-mvc.xml
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设定文件上传的最大值为 5 MB = 5*1024*1024 B -->
<property name="maxUploadSize" value="5242880"/>
<!-- 设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为 10240 -->
<property name="maxInMemorySize" value="40960"/>
</bean>
3. 编写文件上传代码
<form action="${pageContext.request.contextPath}/fileupload" method="post" enctype="multipart/form-data">
名称:<input type="text" name="username"> <br>
文件:<input type="file" name="filePic"> <br>
<input type="submit" value="单文件上传">
</form>
@RequestMapping("/fileupload")
public String fileUpload(String username, MultipartFile filePic) throws IOException {
// 获取表单的提交参数,完成文件上传
System.out.println(username);
// 获取原始的文件上传名
String originalFilename = filePic.getOriginalFilename();
filePic.transferTo(new File("E:/upload/" + username + "_" + originalFilename));
// 转发到成功页面
return "success";
}
-
多文件上传
<form action="${pageContext.request.contextPath}/filesupload" method="post" enctype="multipart/form-data">
名称:<input type="text" name="username"> <br>
文件1:<input type="file" name="filePic"> <br>
文件2:<input type="file" name="filePic"> <br>
<input type="submit" value="多文件上传">
</form>
@RequestMapping("/filesupload")
public String filesUpload(String username, MultipartFile[] filePic) throws IOException {
//获取表单的提交参数,完成文件上传
System.out.println(username);
// 获取原始的文件上传名
for (MultipartFile multipartFile : filePic) {
String originalFilename = multipartFile.getOriginalFilename();
multipartFile.transferTo(new File("E:/upload/" + username + "_" + originalFilename));
}
// 转发到成功页面
return "success";
}
异常处理
-
异常处理的思路
在 Java 中,对于异常的处理一般有两种方式:
- 一种是当前方法捕获处理(try-catch),这种处理方式会造成业务代码和异常处理代码的耦合。
- 另一种是自己不处理,而是抛给调用者处理(throws),调用者再抛给它的调用者,也就是一直向上抛。在这种方法的基础上,衍生出了 SpringMVC 的异常处理机制。
系统的 Dao、Service、Controller 出现都通过 throws Exception 向上抛出,最后由 SpringMVC 前端控制器交由异常处理器(HandlerExceptionResolver)进行异常处理:
请求往下传:客户端 -> 前端控制器 -> Controller -> Service -> Dao
异常往上抛:Dao -> Service -> Controller -> 前端控制器 -> 异常处理器
-
自定义异常处理器
步骤分析:
- 创建异常处理器类实现 HandlerExceptionResolver
- 配置异常处理器
- 编写异常页面
- 测试异常跳转
1. 创建异常处理器类实现 HandlerExceptionResolver
com.zm.exception.GlobalExceptionResolver
public class GlobalExceptionResolver implements HandlerExceptionResolver {
/**
* @param e 实际抛出的异常对象
*/
@Override
public ModelAndView resolveException(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) {
// 具体的异常处理:产生异常后,跳转到一个最终的异常页面
ModelAndView modelAndView = new ModelAndView();
modelAndView.addObject("error", e.getMessage());
modelAndView.setViewName("error");
return modelAndView;
}
}
2. 配置异常处理器
spring-mvc.xml
<bean id="globalExceptionResolver" class="com.zm.exception.GlobalExceptionResolver"/>
**3. 编写异常页面
src\main\webapp\WEB-INF\pages\error.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>ERROR</title>
</head>
<body>
ERROR:
${error}
</body>
</html>
**4. 测试异常跳转
com.zm.controller.ExceptionController
@Controller
public class ExceptionController {
@RequestMapping("/testException")
public String testException(){
int i = 1/0;
return "success";
}
}
-
web 的处理异常机制
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>404 Error</title>
</head>
<body>
提示: 您请求的资源已经删除
</body>
</html>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>500 Error</title>
</head>
<body>
提示: 网络故障,请稍后再试
</body>
</html>
<!-- 处理 404 异常 -->
<error-page>
<error-code>404</error-code>
<location>/404.jsp</location>
</error-page>
<!-- 处理 500 异常 -->
<error-page>
<error-code>500</error-code>
<location>/500.jsp</location>
</error-page>
拦截器
-
拦截器(interceptor)的作用
Spring MVC 的拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理。
将拦截器按一定的顺序联结成一条链,这条链称为拦截器链 InterceptorChain。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是 AOP 思想的具体实现。
-
拦截器和过滤器区别
使用范围:
filter - 是 servlet 规范中的一部分,任何 Java Web 工程都可以使用 。
interceptor - 是 SpringMVC 框架的一部分,只有使用了 SpringMVC 框架的工程才能用。
拦截范围:
filter - 在 url-pattern 中配置了 /* 后,可以对所有资源进行过滤拦截。
interceptor - 只会拦截访问控制器方法,如果访问的是 JSP、HTML、CSS、Image、JS 就不会进行拦截。
-
快速入门
步骤分析:
- 创建拦截器类实现 HandlerInterceptor 接口
- 配置拦截器
- 测试拦截器的拦截效果
1. 创建拦截器类实现 HandlerInterceptor 接口
com.zm.interceptor.MyInterceptor1
public class MyInterceptor1 implements HandlerInterceptor {
/**
* 在目标方法执行之前进行拦截
*
* @return false:不放行
*/
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle1....");
return true;
}
/**
* 在目标方法执行之后,视图对象返回之前,执行的方法
*/
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle1....");
}
/**
* 在流程都执行完成后,执行的方法
*/
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion1....");
}
}
2. 配置拦截器
spring-mvc.jsp
<mvc:interceptors>
<mvc:interceptor>
<!-- 对所有 controller 类里面的所有方法都进行拦截 -->
<mvc:mapping path="/**"/>
<bean class="com.zm.interceptor.MyInterceptor1"></bean>
</mvc:interceptor>
</mvc:interceptors>
3. 测试拦截器的拦截效果
编写 com.zm.controller.TargetController,请求到 controller,跳转页面
@Controller
public class TargetController {
@RequestMapping("/target")
public String targetMethod() {
System.out.println("targetMethod executed ...");
return "success";
}
}
success.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>SUCCESS</title>
</head>
<body>
<h3>success... ${username}</h3>
<% System.out.println("Success...");%>
</body>
</html>
控制台输出结果:
preHandle1....
targetMethod executed ...
postHandle1....
Success...
afterCompletion1....
-
拦截器链
开发中拦截器可以单独使用,也可以同时使用多个拦截器形成一条拦截器链。开发步骤和单个拦截器是一样的,只不过注册的时候注册多个,注意这里注册的顺序就代表拦截器执行的顺序。
同上,再编写一个 MyHandlerInterceptor2 操作:
public class MyInterceptor2 implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHandle2....");
return true;
}
@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("postHandle2....");
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("afterCompletion2...");
}
}
spring-mvc.jsp
<mvc:interceptors>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.zm.interceptor.MyInterceptor1"></bean>
</mvc:interceptor>
<mvc:interceptor>
<mvc:mapping path="/**"/>
<bean class="com.zm.interceptor.MyInterceptor2"></bean>
</mvc:interceptor>
</mvc:interceptors>
控制台输出结果:
preHandle1....
preHandle2....
targetMethod executed ...
postHandle2....
postHandle1....
Success...
afterCompletion2...
afterCompletion1....
当MyInterceptor1 的 preHandler 方法返回 true,MyHandlerInterceptor2 的 preHandler 方法返回 false;此时,MyInterceptor1 的 preHandler 方法优先执行并返回 true,即便MyHandlerInterceptor2 的 preHandler 方法返回 false,MyInterceptor1 的 afterCompletion 方法仍然被执行了。控制台输出结果:
preHandle1....
preHandle2....
afterCompletion1....
当 MyInterceptor1 的 preHandler 方法返回 false,MyHandlerInterceptor2 的 preHandler 方法返回 true;此时,MyInterceptor1 的 preHandler 方法优先执行并返回 false,方法被拦截,MyHandlerInterceptor2 的 preHandler 方法也无法执行。控制台输出结果:
preHandle1....
所以形成拦截器链时,当拦截器 1 的 preHandler 方法成功执行并返回 true 后,被它拦截的方法即便被另一个拦截器 2 所拦截并返回了 false,拦截器 1 的afterCompletion 方法仍然会被执行。
源码解释:
/**
* Trigger afterCompletion callbacks on the mapped HandlerInterceptors.
* Will just invoke afterCompletion for all interceptors whose preHandle invocation
* has successfully completed and returned true.
*/
void triggerAfterCompletion(HttpServletRequest request, HttpServletResponse response, @Nullable Exception ex)
throws Exception {
...
}
小结
拦截器中的方法说明:
-
preHandle() - 方法将在请求处理之前进行调用,该方法的返回值是布尔值类型的,当它返回为 false 时,表示请求结束,后续的 Interceptor 和 Controller 都不会再执行;当返回值为 true 时就会继续调用下一个 Interceptor 的 preHandler 方法
-
postHandle() - 该方法是在当前请求进行处理之后被调用,前提是 preHandler 方法的返回值为 true 时才能被调用,且它会在 DispatcherServlet 进行视图返回渲染之前被调用,所以可以在这个方法中对 Controller 处理之后的 ModelAndView 对象进行操作
-
afterCompletion() - 该方法在整个请求结束之后,就是在DispatcherServlet 渲染了对应的视图之后执行,前提是 preHandler 方法的返回值为 true 时才能被调用