SpringMVC:Ajax处理、文件传输、拦截器与异常处理
处理Ajax
Ajax
Ajax:实现异步请求,不重新加载整个页面的情况下,与服务器交换数据并更新部分网页内容(利用axios.min.js)
基本的参数:参见请求配置 | Axios 中文文档
- url:请求路径(绝对路径,需要加上tomcat的上下文路径)
- method:请求方式
- params:请求参数——以
name=value & name=value
的方式传输,存储位置为url中,可以直接通过request.getParameter()获取 - data:请求参数——以json格式发送,存储的位置为请求报文的请求体中(注意,get没有请求体,因此使用该参数时,请求方式通常为post)
常用的函数:
1
2
3
4
5
6
7
8axios.request(config)
axios.get(url[, config])
axios.delete(url[, config])
axios.head(url[, config])
axios.options(url[, config])
axios.post(url[, data[, config]])
axios.put(url[, data[, config]])
axios.patch(url[, data[, config]])- 注意,post方法默认使用data参数来传递请求参数
- 如果要使用params的方式传输,则可以直接将参数拼接到url中,例如传递参数id=1时,url为
/targetAddress?id=1
(不推荐)
例如:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28<head>
<meta charset="UTF-8">
<title>首页</title>
<script type="text/javascript" th:src="@{/static/js/vue.js}"></script>
<script type="text/javascript" th:src="@{/static/js/axios.min.js}"></script>
</head>
<body>
<div id="app">
<h1>index.html</h1>
<input type="button" value="测试SpringMVC处理ajax" @click="testAjax()"><br>
</div>
<script type="text/javascript">
var vue = new Vue({
el:"#app",
methods:{
testAjax(){
axios.post(
console.log(response.data);
});
}
}
});
</script>
</body>此时,相应的控制器方法:
1
2
3
4
5
6
public void testAjax(Integer id, String requestBody, HttpServletResponse response)throws IOException {
System.out.println("requestBody:"+requestBody); // 请求体中的请求数据
System.out.println("id:"+id); // param中的请求参数
response.getWriter().write("hello,axios"); // console.log(response.data)打印结果为"hello,axios"
}不返回字符串,因为只是局部更新网页,不需要新的模板
输出:
1
2requestBody:{"username":"admin","password":"123456"}
id:1001
RequestBody注解
数据从前端传向后端
方式1:如上例,请求体的内容被转换为string
方式2:转换为实体对象
添加依赖
1
2
3
4
5<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.12.1</version>
</dependency>SpringMVC配置文件开启注解驱动
在处理请求的控制器方法的形参位置,直接设置json格式的请求参数要转换的java类型的形参,使用@RequestBody注解标识即可
1
2
3
4
5@RequestMapping("/test/RequestBody/jsonPojo")
public void testRequestBody(@RequestBody User user, HttpServletResponse response) throws IOException {
System.out.println(user);
response.getWriter().write("hello,RequestBody");
}
方式3:转化为map
1
2
3
4
5@RequestMapping("/test/RequestBody/jsonMap")
public void testRequestBody(@RequestBody Map<String, Object> map, HttpServletResponse response) throws IOException {
System.out.println(map);
response.getWriter().write("hello,RequestBody");
}
ResponseBody注解
数据从后端传向前端
标识一个控制器方法,将该方法的返回值直接作为响应报文的响应体,响应到浏览器(没有该注解,则为页面跳转)
通常需要将java对象转换为json字符串,再响应回浏览器,因此需要导入jackson依赖。此时能够直接返回一个java对象——这里的java对象可以为map、list、实体类
需要jackson依赖
例如:
1
2
3
4
5
6
7
8
public List<User> testResponseBodyJson(){
User user1 = new User("admin1", "123456", 20, "男");
User user2 = new User("admin2", "123456", 20, "男");
User user3 = new User("admin3", "123456", 20, "男");
return Arrays.asList(user1, user2, user3);
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17<div id="app">
...
<input type="button" value="ResponseBody" @click="testResponseBody()"><br>
</div>
<script type="text/javascript">
var vue = new Vue({
el:"#app",
methods:{
...
testResponseBody(){
axios.post("/Demo4_war_exploded/test/ResponseBody/json").then(response=>{
console.log(response.data);
});
}
}
});
</script>小结:@ResponseBody和@RequestBody封装了响应数据和请求数据。注意区分之前的请求数据处理方法和响应数据处理方法
- @ResponseBody和ModelAndView:
- @ResponseBody将方法的返回值,以特定的格式写入到response的body区域,再将数据返回给客户端
- 当方法上面没有写ResponseBody,底层会将方法的返回值封装为ModelAndView对象——该对象需要设置返回视图,此时会导致页面刷新
- @RequestParam、@RequestBody、@PathVariable:
- @RequestParam一般用于get请求,参数是一个个的参数。此时的请求url一般形式如
http://localhost:8089/test/getDataById?id=1
- @RequestBody一般用于post请求,参数是一个对象或者集合,并且请求一般为json类型的请求体
- @PathVariable一般用于get请求,参数是一个个的参数。能够体现RestFul风格。此时的请求url一般为
http://localhost:8089/test/getDataById/1
- 参考@RequestParam、@RequestBody、@PathVariable区别和案例分析
- @RequestParam一般用于get请求,参数是一个个的参数。此时的请求url一般形式如
- @ResponseBody和ModelAndView:
@RestController注解是springMVC提供的一个复合注解,标识在控制器的类上,就相当于为类添加了 @Controller注解,并且为其中的每个方法添加了@ResponseBody注解
文件上传与下载
文件下载
ResponseEntity为控制器方法的返回类型,此时返回值就是响应到浏览器的响应报文——如果响应报文为文件,自然就是文件下载了
处理器方法:具体文件名可以通过请求参数确定
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public ResponseEntity<byte[]> testResponseEntity(HttpSession session) throws IOException {
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取服务器中文件的真实路径——war包下的路径,在项目中的路径为src/main/webapp/static/img
String realPath = servletContext.getRealPath("/static/image");
realPath = realPath + File.separator + "1.png";
//创建输入流
InputStream is = new FileInputStream(realPath);
//创建字节数组,is.available()获取输入流所对应文件的字节数
byte[] bytes = new byte[is.available()];
is.read(bytes); //将流读到字节数组中
//创建HttpHeaders对象设置响应头信息
MultiValueMap<String, String> headers = new HttpHeaders();
//设置要下载方式以及下载文件的名字
headers.add("Content-Disposition", "attachment;filename=1.png");
//设置响应状态码
HttpStatus statusCode = HttpStatus.OK;
//创建ResponseEntity对象
ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(bytes, headers, statusCode);
is.close(); //关闭输入流
return responseEntity;
}- session参数:用于获得上下文,以得到某个资源的真实路径
- 请求头本质是键值对
- Content-Disposition:设置下载文件的方式
- attachment;filename=1.png:以附件的方式下载,下载文件的默认名字为1.png
- Content-Disposition:设置下载文件的方式
文件上传
form表单的请求方式必须为post,属性enctype=”multipart/form-data”(以二进制的方式传输数据)
1
2
3
4<form th:action="@{/test/upload}" method="post" enctype="multipart/form-data">
头像:<input type="file" name="photo"><br>
<input type="submit" value="上传">
</form>添加依赖
1
2
3
4
5
6<!--上传文件-->
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>1.3.1</version>
</dependency>SpringMVC的配置文件中添加配置——通过文件解析器的解析才能将文件转换为MultipartFile对象,配置中的bean id必须为multipartResolver
1
2
3
4
5
6
7
8<!--文件上传解析器-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<!--文件默认编码-->
<property name="defaultEncoding" value="UTF-8"></property>
<!-- 上传文件大小上限,单位为字节(10485760=10M) -->
<property name="maxUploadSize" value="10485760"/>
<property name="maxInMemorySize" value="40960"/>
</bean>处理器方法:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public String testUp(MultipartFile photo, HttpSession session) throws IOException {
//获取上传的文件的文件名
String fileName = photo.getOriginalFilename();
//获取上传的文件的后缀名
String hzName = fileName.substring(fileName.lastIndexOf("."));
//解决重名问题
String uuid = UUID.randomUUID().toString();
fileName = uuid + hzName;
//获取ServletContext对象
ServletContext servletContext = session.getServletContext();
//获取当前工程下photo目录的真实路径
String photoPath = servletContext.getRealPath("/static/photo");
//创建photoPath所对应的File对象
File file = new File(photoPath);
//判断file所对应目录是否存在
if(!file.exists()){
file.mkdir();
}
String finalPath = photoPath + File.separator + fileName;
//上传文件
photo.transferTo(new File(finalPath));
return "index";
}- 获得上传的文件:上传文件被封装为MultipartFile对象,形参名和表单中的名字相同
拦截器
拦截器用于拦截控制器方法的执行
- 需要实现接口有HandlerInterceptor
- 三个方法:
- preHandle:控制器方法执行之前执行preHandle(),其boolean类型的返回值表示是否拦截或放行,返回true为放行,即调用控制器方法;返回false表示拦截
- postHandle:控制器方法执行之后执行postHandle(),无返回值
- afterCompletion:处理完视图和模型数据,渲染视图完毕后执行afterCompletion(),无返回值
范例:
在SpringMVC配置文件添加相关配置(将某一个类设置为拦截器)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15<mvc:interceptors>
<!--已经通过@Component注解添加了这个bean,因此只需要引用即可-->
<ref bean="firstInterceptor"/>
<!--第二种配置方式,精确设置某个拦截器的拦截请求对象——mvc:mapping设置需要拦截的请求,通过
mvc:exclude-mapping设置需要排除的请求,即不需要拦截的请求-->
<mvc:interceptor>
<!--/*表示上下文后的一层的请求,不会拦截/a/b-->
<!--/**表示拦截所有的请求-->
<mvc:mapping path="/**"/>
<mvc:exclude-mapping path="/testRequestEntity"/>
<ref bean="firstInterceptor"/>
</mvc:interceptor>
</mvc:interceptors>拦截器的实现:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class FirstInterceptor implements HandlerInterceptor {
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
System.out.println("FirstInterceptor-->preHandle");
return true;
}
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
System.out.println("FirstInterceptor-->postHandle");
}
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
System.out.println("FirstInterceptor-->afterCompletion");
}
}
多个拦截器的执行顺序:
多个拦截器的配置:
1
2
3
4<mvc:interceptors>
<ref bean="firstInterceptor"/>
<ref bean="secondInterceptor"/>
</mvc:interceptors>输出:
1
2
3
4
5
6FirstInterceptor-->preHandle
SecondInterceptor-->preHandle
SecondInterceptor-->postHandle
FirstInterceptor-->postHandle
SecondInterceptor-->afterCompletion
FirstInterceptor-->afterCompletion执行顺序与在SpringMVC配置文件中配置的顺序有关
若拦截器中某个拦截器preHandle返回了false,该拦截器和它之前的拦截器的preHandle都会执行,所有的拦截器postHandle都不执行,该拦截器之前的返回true的拦截器afterCompletion会执行
异常处理器
SpringMVC提供了一个处理控制器方法执行过程中所出现异常的接口:HandlerExceptionResolver
HandlerExceptionResolver接口的实现类:
- DefaultHandlerExceptionResolver(SpringMVC默认使用该实现类处理异常)
- SimpleMappingExceptionResolve(可自定义)
返回一个ModelAndView,实现页面的跳转
基于配置的异常处理
1
2
3
4
5
6
7
8
9
10
11
12
13<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
<property name="exceptionMappings">
<props>
<!--
properties的键表示处理器方法执行过程中出现的异常
properties的值表示若出现指定异常时,跳转到的新视图名称
-->
<prop key="java.lang.ArithmeticException">error</prop>
</props>
</property>
<!--exceptionAttribute属性:设置一个属性名,将出现的异常信息在请求域中进行共享-->
<property name="exceptionAttribute" value="ex"></property>
</bean>这里ex为共享在request域中的错误信息
1
<p th:text="${ex}" />
基于注解的异常注解
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18package Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
// 标识一个异常处理组件
public class ExceptionController {
//@ExceptionHandler用于设置所标识方法处理的异常
public String handleArithmeticException(Exception ex, Model model){ //ex表示当前请求处理中出现的异常对象
model.addAttribute("ex", ex);
return "error";
}
}
SpringMVC执行流程
基本组件:
- DispatcherServlet:前端控制器,框架提供:统一处理请求和响应,整个流程控制的中心,由它调用其它组件处理用户的请求
- HandlerMapping:处理器映射器,框架提供;根据请求的url、method等信息查找Handler
- Handler:处理器;在DispatcherServlet的控制下,Handler对具体的用户请求进行处理
- HandlerAdapter:处理器适配器,框架提供;通过HandlerAdapter执行控制器方法
- ViewResolver:视图解析器,框架提供;进行视图解析,得到相应的视图
- View:视图;将模型数据通过页面展示给用户
执行流程:
- 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获
- DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:
- 不存在
- 再判断是否配置了mvc:default-servlet-handler
- 如果没配置,则控制台报映射查找不到,客户端展示404错误
- 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误
- 存在则执行下面的流程
- 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),最后以HandlerExecutionChain执行链对象的形式返回
- DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter
- 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】
- 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据配置,Spring将做一些额外的工作:
- HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息
- 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等
- 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等
- 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error中
- Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象
- 此时将开始执行拦截器的postHandle(…)方法【逆向】
- 根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的 ViewResolver 进行视图解析,根据ModelAndView,来渲染视图
- 渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】
- 将渲染结果返回给客户端
- 不存在