SpringMVC
1、ajax异步交互
Springmvc默认用MappingJackson2HttpMessageConverter对json数据进行转换
- 需要加入jackson的包
<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>
- 同时使用 <mvc:annotation-driven />
<!--处理器映射器-处理器适配器 进行了功能的增强,支持json的读写-->
<mvc:annotation-driven conversion-service="conversionService"/>
1.1、@RequestBody
该注解用于Controller的方法的形参声明,当使用ajax提交并指定contentType为json形式时,通过HttpMessageConverter接口转换为对应的POJO对象。
@RequestMapping("/ajaxRequest")
public void ajaxRequest(@RequestBody List<User> list){
/*
* 1. 首先要导入jackson的包
* 2. 开启处理器映射/适配器的
* 3. 在需要转换格式的地方使用@RequestBody注解
* let data = '[{"id":1 ,"username":"张三"}, {"id":2 ,"username":"李四"}]'
* 这样的话在后端接收到json格式的字符串的时候,就可以自动的将json格式封装成为List<User>
* */
System.out.println(list);
}
<%--
Created by IntelliJ IDEA.
User: li
Date: 2023/1/30
Time: 10:00
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<script src="${pageContext.request.contextPath}/js/jquery-3.5.1.js"></script>
<button id="btn1">ajax异步提交</button>
<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(1);
}
})
})
</script>
</body>
</html>
1.2、 @ResponseBody
该注解用于将Controller的方法返回的对象,通过HttpMessageConverter接口转换为指定格式的数据如:json,xml等,通过Response响应给客户端。
@RequestMapping("/ajaxRequest")
@ResponseBody
public List<User> ajaxRequest(@RequestBody List<User> list){
/*
* 1. 首先要导入jackson的包
* 2. 开启处理器映射/适配器的
* 3. 在需要转换格式的地方使用@RequestBody注解
* let data = '[{"id":1 ,"username":"张三"}, {"id":2 ,"username":"李四"}]'
* 这样的话在后端接收到json格式的字符串的时候,就可以自动的将json格式封装成为List<User>
* */
System.out.println(list);
return list;
}
这样的就是可以简单的实现了,前后端数据的通过json格式的交互了
2、RESTful
2.1、什么是RESTful
Restful是一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更于实现缓存机制等。
Restful风格的请求是使用“url+请求方式”表示一次请求目的的,HTTP 协议里面四个表示操作方式的动词如下:
- GET:读取(Read)
- POST:新建(Create)
- PUT:更新(Update)
- DELETE:删除(Delete)
客户端请求 | 原来风格URL地址 | RESTful风格URL地址 |
---|---|---|
查询所有 | /user/findAll | GET /user |
根据ID查询 | /user/findById?id=1 | GET /user/{1} |
新增 | /user/save | POST /user |
修改 | /user/update | PUT /user |
删除 | 删除 /user/delete?id=1 | DELETE /user/{1} |
2.2、代码实现
@PathVariable
用来接收RESTful风格请求地址中占位符的值
@RestController
RESTful风格多用于前后端分离项目开发,前端通过ajax与服务器进行异步交互,我们处理器通常返回的是json数据所以使用@RestController来替代@Controller@ResponseBody两个注解。组合注解
package com.lzy.controller;
import org.springframework.web.bind.annotation.*;
@RestController // 组合注解:组合了@Controller + @ResponseBody
public class RestFulController {
@GetMapping("/user/{id}")
public String get(@PathVariable Integer id){
return "get: " + id;
}
@PostMapping("/user")
public String post(){
return "post";
}
@PutMapping("/user")
public String put(){
return "put";
}
@DeleteMapping("/user/{id}")
public String delete(@PathVariable Integer id){
return "delete: " + id;
}
}
这就是一种编程风格,了解一下就可以了,后面估计也用不到太多
3、文件上传
3.1、文件上传三要素
- 表单项 type=“file”
- 表单的提交方式 method=“POST”
- 表单的enctype属性是多部分表单形式 enctype=“multipart/form-data"
3.2、文件上传原理
- 当form表单修改为多部分表单时,request.getParameter()将失效。
- 当form表单的enctype取值为 application/x-www-form-urlencoded 时,
- form表单的正文内容格式是: name=value&name=value
- 当form表单的enctype取值为 mutilpart/form-data 时,请求正文内容就变成多部分形式:
3.3、单文件上传
- 导入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>
</dependency>
- 配置文件上传解析器
<!--文件上传解析器-->
<bean id="multipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!-- 设定文件上传的最大值为5MB,5*1024*1024 -->
<property name="maxUploadSize" value="5242880"></property>
<!-- 设定文件上传时写入内存的最大值,如果小于这个参数不会生成临时文件,默认为10240 -->
<property name="maxInMemorySize" value="40960"></property>
</bean>
- 编写文件上传代码
@RequestMapping("/file")
public String fileUpload(String username, MultipartFile filePic) throws IOException {
System.out.println(username);
// 获取文件名
String originalFilename = filePic.getOriginalFilename();
// 保存文件
filePic.transferTo(new File("d:/upload/" + originalFilename));
return "success";
}
<%--
Created by IntelliJ IDEA.
User: li
Date: 2023/1/30
Time: 11:05
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/file" method="post" enctype="multipart/form-data">
名称<input type="text" name="username"><br>
文件<input type="file" name="filePic"><br>
<input type="submit" value="单文件上传">
</form>
</body>
</html>
这里和之前做的那个项目用的那个上传文件的方法底层应该都是一样的,但是springmvc这个好简洁啊
下面是之前的写法,
@WebServlet("/upload")
public class FileUploadServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
// 1、创建磁盘文件工厂对象
DiskFileItemFactory factory = new DiskFileItemFactory();
// 2、创建文件上传核心类
ServletFileUpload upload = new ServletFileUpload(factory);
// 2.1、设置上传文件的编码
upload.setHeaderEncoding("utf-8");
// 2.2、判断表单是否是 文件上传表单
boolean multipartContent = ServletFileUpload.isMultipartContent(req);
// 2.3、是文件上传表单
if(multipartContent){
// 3、解析request -- 获取表单项的集合
List<FileItem> fileItems = upload.parseRequest(req);
// 4、遍历表单项集合
for (FileItem fileItem : fileItems) {
// 5、判断是普通的表单项 还是文件上传项
boolean formField = fileItem.isFormField();
// 普通表单项
if(formField){
String fileFieldName = fileItem.getFieldName();
String fileFieldContent = fileItem.getString("utf-8");
System.out.println(fileFieldName + " = " + fileFieldContent);
}else {
// 文件上传项
// 获取文件名
String fileName = fileItem.getName();
// 保证上传文件没有重复,拼接新的文件名,使用UUID保证不重复
String newFileName = UUIDUtils.getUUID() + "_" + fileName;
// 获取输入流
InputStream inputStream = fileItem.getInputStream();
// 创建输出流
// 1、获取项目的运行目录
String realPath = this.getServletContext().getRealPath("/");
// 2、截取到webapps目录路径
String webappsPath = realPath.substring(0, realPath.indexOf("lagou_edu_home"));
// 3、拼接输出
FileOutputStream fileOutputStream = new FileOutputStream(webappsPath + "/upload/" + newFileName);
// 使用IOUtils 完成文件的copy
IOUtils.copy(inputStream, fileOutputStream);
// 关闭流
inputStream.close();
fileOutputStream.close();
}
}
}
} catch (FileUploadException e) {
e.printStackTrace();
}
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
}
3.4、 多文件上传
@RequestMapping("/files")
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("d:/upload/" + originalFilename));
}
return "success";
}
<%--
Created by IntelliJ IDEA.
User: li
Date: 2023/1/30
Time: 11:05
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<form action="${pageContext.request.contextPath}/files" 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>
</body>
</html>
知识点
:
- 注意文件上传的三要素,是必不可少的
- 新的方法MultipartFile中的getOriginalFilename是获取文件名的,transferTo是将文件保存到哪,以后忘了看看这里应该就可以想起来
4、异常处理
4.1、异常处理的思路
在java中通常由两处异常的方式:
- 一种是当前方法捕获处理(try-catch),这种处理方式会造成业务代码和异常处理代码的耦合
- 另一种是自己不处理,而是抛给调用者处理(throws),调用者再抛给它的调用者,也就是一直向上抛。
在这种方法的基础上,衍生出了SpringMVC的异常处理机制
系统的dao、service、controller出现都通过throws Exception向上抛出,最后由springmvc前端控制器交由异常处理器进行异常处理,如下图:
4.2、自定义异常处理器
4.2.1、创建异常处理器类实现HandlerExceptionResolver
package com.lzy.exception;
import org.springframework.stereotype.Component;
import org.springframework.web.servlet.HandlerExceptionResolver;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class GlobalExceptionResolver implements HandlerExceptionResolver {
@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;
}
}
4.2.2、配置异常处理器
<bean id="globalExecptionResovler"
class="com.lzy.exception.GlobalExceptionResolver">
</bean>
4.2.3、编写异常页面
<%--
Created by IntelliJ IDEA.
User: li
Date: 2023/1/30
Time: 11:51
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h3>这是一个最终异常的显示页面</h3>
<p>${error}</p>
</body>
</html>
4.2.4、测试异常跳转
@RequestMapping("/testException")
public String testException(){
int i = 1 / 0;
return "success";
}
这是最后显示出来的画面
4.3、web的处理异常机制
在web.xml中配置
<!--处理500异常-->
<error-page>
<error-code>500</error-code>
<location>/500.jsp</location>
</error-page>
<!--处理404异常-->
<error-page>
<error-code>404</error-code>
<location>/404.jsp</location>
</error-page>
404.jsp
<%--
Created by IntelliJ IDEA.
User: li
Date: 2023/1/30
Time: 12:02
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>404.jsp页面</h1>
</body>
</html>
500.jsp
<%--
Created by IntelliJ IDEA.
User: li
Date: 2023/1/30
Time: 12:02
To change this template use File | Settings | File Templates.
--%>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>500.jsp页面</h1>
</body>
</html>
也就是当你访问到的资源里面报错会自动的跳转到你自定义异常处理器然后跳转到对应的页面,如果你的路径写错了,没有访问到页面的话就要用那这个web的异常处理机制
5、拦截器
5.1、拦截器(interceptor)的作用
- Spring MVC 的拦截器类似于 Servlet 开发中的过滤器 Filter,用于对处理器进行预处理和后处理
- 将拦截器按一定的顺序联结成一条链,这条链称为拦截器链(InterceptorChain)。在访问被拦截的方法或字段时,拦截器链中的拦截器就会按其之前定义的顺序被调用。拦截器也是AOP思想的具体实现。
5.2、 拦截器和过滤器区别
关于interceptor和filter的区别,如图所示:
区别 | 过滤器 | 拦截器 |
---|---|---|
使用范围 | 是servlet规范中的一部分,任何Java Web工程都可以使用 | 是SpringMVC框架自己的,只有使用了SpringMVC框架的功能才能用 |
拦截范围 | 在url-pattern中配置了/*之后,可以对所有要访问的资源拦截 | 只会拦截访问的控制器方法,如果访问的是jsp,html,image或者js是不会进行拦截的 |
5.3、快速入门
5.3.1、创建拦截器类实现HandlerInterceptor接口
package com.lzy.interceptor;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class MyInterceptor1 implements HandlerInterceptor {
// 在目标方法执行之前 拦截
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("preHendle1");
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");
}
}
5.3.2、配置拦截器
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--对哪些资源执行拦截操作-->
<mvc:mapping path="/**"/>
<bean class="com.lzy.interceptor.MyInterceptor1"/>
</mvc:interceptor>
</mvc:interceptors>
注意:这里的/**就是对一级以及多级目录都进行拦截
5.3.3、测试拦截器的拦截效果
@Controller
public class TargetController {
@RequestMapping("/target")
public String targetMethod(){
System.out.println("目标方法执行了...");
return "success";
}
}
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html >
<head>
<title>success</title>
</head>
<body>
<h3>请求成功!。。。${username}</h3>
<h3>success...</h3>
<% System.out.println("视图执行了....");%>
</body>
</html>
这正好就是这个顺序执行的
5.4、拦截器链
开发中拦截器可以单独使用,也可以同时使用多个拦截器形成一条拦截器链。开发步骤和单个拦截器是一样的,只不过注册的时候注册多个,注意这里注册的顺序就代表拦截器执行的顺序。
同上,再编写一个MyHandlerInterceptor2操作,测试执行顺序:
<!--配置拦截器-->
<mvc:interceptors>
<mvc:interceptor>
<!--对哪些资源执行拦截操作-->
<mvc:mapping path="/**"/>
<bean class="com.lzy.interceptor.MyInterceptor1"/>
</mvc:interceptor>
<mvc:interceptor>
<!--对哪些资源执行拦截操作-->
<mvc:mapping path="/**"/>
<bean class="com.lzy.interceptor.MyInterceptor2"/>
</mvc:interceptor>
</mvc:interceptors>
方法名 | 说明 |
---|---|
preHandel() | 方法将在请求处理之前进行调用,该方法的返回值是布尔值Boolean类型的,当它返回为false时,表示请求结束,后续的Interceptor和Controller都不会再执行;当返回值为true时就会继续调用下一个Interceptor的preHandle方法 |
posthandle() | 该方法是在当前请求进行处理之后被调用,前提是preHandle方法的返回值为true时才能被调用,且它会在DispatcherServlet进行视图返回渲染之前被调用,所以我们可以在这个方法中队Controller处理之后的ModelAndView对象进行操作 |
afterCompletion() | 该方法将在整个请求结束之后,也就是在DispatcherServlet渲染了对应的视图之后执行,前提是preHandle方法的返回值为true时才能被调用 |