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
 8- axios.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 
 2- requestBody:{"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
 6- FirstInterceptor-->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
 18- package 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(…)方法【逆向】
- 将渲染结果返回给客户端
 
 
- 不存在
 
 
        