• 【框架学习 | 第五篇】SpringMVC(常用注解、获取请求参数、域对象共享数据、拦截器、异常处理、上传/下载文件)


    在这里插入图片描述

    1.SpringMVC简介

    1.1定义

    基于java的实现MVC设计模式的请求驱动类型的轻量级Web框架,通过注解,无需实现任何接口,处理请求,支持restful。

    • 三层结构:表现层、业务层、持久层
    • 设计模式:Model(模型)、View(视图)、Controller(控制器)

    1.2主要组件

    1. 前端控制器 DispatcherServlet(不需要程序员开发)

      作用:接收请求,处理结果,相当于转发器,有了DispatcherServlet 就减少了其它组件之间的耦合度。

    2. 处理器映射器 HandleMapping ( 不需要程序员开发)

      作用:根据url找到 handler

    3. 处理器适配器HandlerAdapter(适配器模式)

      注意:在编写Handler的时候要按照HandlerAdapter要求的规则去编写,这样适配器HandlerAdapter才可以 正确的去执行Handler。

    4. 处理器Handler(需要程序员开发)

      作用:处理请求的具体过程。

    5. 视图解析器 ViewResolver(不需要程序员开发)

      作用:进行视图的解析,根据视图逻辑名解析成真正的视图(view)

    6. 视图View(需要程序员开发jsp)

      View是一个接口, 它的实现类支持不同的视图类型(jsp,freemarker,pdf等等)

    1.3工作流程

    1.3.1简要流程

    img

    1. 用户发送请求至前端控制器DispatcherServlet
    2. DispatcherServlet收到请求后,调用HandlerMapping处理器映射器,请求获取Handle;
    3. 处理器映射器根据请求url找到具体的处理器,生成处理器对象及处理器拦截器如果有则生成)一并返回给DispatcherServlet;
    4. DispatcherServlet 调用 HandlerAdapter处理器适配器;
    5. HandlerAdapter 经过适配调用 具体处理器(Handler,也叫后端控制器);
    6. Handler执行完成返回ModelAndView;
    7. HandlerAdapter将Handler执行结果ModelAndView返回给DispatcherServlet;
    8. DispatcherServlet将ModelAndView传给ViewResolver视图解析器进行解析;
    9. ViewResolver解析后返回具体View;
    10. DispatcherServlet对View进行渲染视图(即将模型数据填充至视图中)
    11. DispatcherServlet响应用户。

    1.3.2详细流程

    1. 用户向服务器发送请求,请求被SpringMVC 前端控制器 DispatcherServlet捕获。
    2. DispatcherServlet对请求URL进行解析,得到请求资源标识符(URI),判断请求URI对应的映射:

    a) 不存在

    i. 再判断是否配置了mvc:default-servlet-handler

    ii. 如果没配置,则控制台报映射查找不到,客户端展示404错误

    在这里插入图片描述

    iii. 如果有配置,则访问目标资源(一般为静态资源,如:JS,CSS,HTML),找不到客户端也会展示404错误

    在这里插入图片描述

    b) 存在则执行下面的流程

    1. 根据该URI,调用HandlerMapping获得该Handler配置的所有相关的对象(包括Handler对象以及Handler对象对应的拦截器),在doDispatch方法开头先创建了一个HandlerExecutionChain执行链,最后以HandlerExecutionChain执行链对象的形式返回。
    2. DispatcherServlet 根据获得的Handler,选择一个合适的HandlerAdapter。
    3. 如果成功获得HandlerAdapter,此时将开始执行拦截器的preHandler(…)方法【正向】
    4. 提取Request中的模型数据,填充Handler入参,开始执行Handler(Controller)方法,处理请求。在填充Handler的入参过程中,根据你的配置,Spring将帮你做一些额外的工作:

    ​ a) HttpMessageConveter: 将请求消息(如Json、xml等数据)转换成一个对象,将对象转换为指定的响应信息

    ​ b) 数据转换:对请求消息进行数据转换。如String转换成Integer、Double等

    ​ c) 数据格式化:对请求消息进行数据格式化。 如将字符串转换成格式化数字或格式化日期等

    ​ d) 数据验证: 验证数据的有效性(长度、格式等),验证结果存储到BindingResult或Error

    5.Handler执行完成后,向DispatcherServlet 返回一个ModelAndView对象。

    6.此时将开始执行拦截器的postHandle(…)方法【逆向】。

    7.根据返回的ModelAndView(此时会判断是否存在异常:如果存在异常,则执行HandlerExceptionResolver进行异常处理)选择一个适合的ViewResolver进行视图解析,根据Model和View,来渲染视图。

    8.渲染视图完毕执行拦截器的afterCompletion(…)方法【逆向】。

    9.将渲染结果返回给客户端。

    1.4优缺点

    • 优点:
      • 可以支持各种视图技术,而不仅仅局限于JSP;
      • 与Spring框架集成(如IoC容器、AOP等);
      • 清晰的角色分配:
        • 前端控制器(dispatcherServlet)
        • 请求到处理器映射(handlerMapping)
        • 处理器适配器(HandlerAdapter)
        • 视图解析器(ViewResolver)。
      • 支持各种请求资源的映射策略。

    2.常用注解

    1. @Controller : 用于定义控制类

    2. @RequestMapping : 用来处理请求地址映射的注解,可以作用于类和方法上。

      属性:

      • value: 指定请求的实际地址,指定的地址可以是URI Template 模式
      • method: 指定请求的method类型, GET、POST、PUT、DELETE等
      • consumes: 指定处理请求的提交内容类型(Content-Type),例如application/json, text/html;
      • produces: 指定返回的内容类型,仅当request请求头中的(Accept)类型中包含该指定类型才返回;
      • params: 指定request中必须包含某些参数值是,才让该方法处理
      • headers: 指定request中必须包含某些指定的header值,才能让该方法处理请求。
    3. @ResponseBody : 该注解用于将Controller的方法返回的对象,通过适当的HttpMessageConverter转换为指定格式后,写入到Response对象的body数据区。使用时机:返回的数据不是html标签的页面,而是其他 某种格式的数据时(如json、xml等) 使用

    4. @RequestParam : 用于在SpringMVC后台控制层获取参数,类似一种是 request.getParameter(“name”),它有三个常用参数:defaultValue = “0”, required = false, value = “isApp”;defaultValue 表示设置默认值,required 通过boolean设置是否是必须要传入的参数,value 值表示接受的传入的参数类型。

    5. @PathVariable : 用于将请求URL中的模板变量映射到功能处理方法的参数上,即取出uri模板中的变量作为参数。

    6. @ModelAttribute和 @SessionAttributes :代表的是:该Controller的所有方法在调用前,先执行此@ModelAttribute方法,可用于注解和方法参数中,可以把这个@ModelAttribute特性,应用在BaseController当中,所有的Controller继承BaseController,即可实现在调用Controller时,先执行@ModelAttribute方法。

    @SessionAttributes即将值放到session作用域中,写在class上面。

    3.获取请求参数

    • SpringMVC中一共有三种方式可以获取请求参数
      • 通过HttpServletRequest对象获取请求参数
      • 通过控制器方法的形参获取参数
        • 请求路径的参数名和方法的参数名相同(直接赋值)
        • 请求路径的参数名和方法的参数名不相同(使用@RequestParm(value1) String value2)—>将请求路径的参数值value1赋值给方法的参数值value2
      • 通过POJO(对象)获取请求参数

    3.1通过 HttpServletRequest 获取请求参数

    • 登录表单
    <form action="${pageContext.request.contextPath}/login/test1" method="get">
        用户名: <input type="text" name="username"/> <br>
        密 码: <input type="password" name="password"/> <br>
        <input type="submit" />
    form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 控制层
    @Controller
    @RequestMapping("/login")
    public class MyController {
        @RequestMapping(value = "/?est1")
        public String handler1(HttpServletRequest request) {
            System.out.println("处理器1");
            // 通过 HttpServletRequest 获取请求参数
            String username= request.getParameter("username");
            String password = request.getParameter("password");
            System.out.println("username:" + username + " password:" + password);
            return "success";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    3.2通过控制器方法的形参获取请求参数

    3.2.1请求路径参数与方法形参一致

    <input type="text" name="username"/>
    <input type="password" name="password"/>
    
    public String handler1(String username, String password)
    
    • 1
    • 2
    • 3
    • 4

    注意:控制器方法的形参必须和前端 name 的属性值一致,如控制器方法的形参 username 和 password 要和 input 标签中的 name 属性值 username 和 password 一致

    • 举例
    <form action="${pageContext.request.contextPath}/login/test1" method="get">
        用户名: <input type="text" name="username"/> <br>
        密 码: <input type="password" name="password"/> <br>
        <input type="submit" />
    form>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 控制层
    @Controller
    @RequestMapping("/login")
    public class MyController {
        @RequestMapping(value = "/?est1")
        // 通过 形参 获取请求参数
        public String handler1(String username, String password) {
            System.out.println("处理器1");
            System.out.println("username:" + username + " password:" + password);
            return "success";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    3.2.2请求路径参数与方法形参不一致

    @RequestParam 注解用于将请求参数的数据映射到 Handler 方法(控制器方法)的形参上,相当于给请求参数重命名。如有时有一些特殊情况,前端的 name 属性值与后端 Handler 方法中的形参不一致,这个时候就可以通过 @RequestParam 注解来解决。

    • 语法:@RequestParam(value=”参数名”,required=”true|false”,defaultValue=””)
      • value:请求中传入参数的名称
      • required:该参数是否为必传项,默认为 true,表示该请求路径中必须包含该参数,如果不包含就报错;若设置为 false,则表示该请求路径中不必包含该参数,若没有传输该参数,则注解所标识的形参的值为 null
      • defaultValue:设置默认参数值,如果设置了该值,required=true 将失效,自动为 false,如果没有传该参数,就使用默认值

    3.2.3扩展:@RequestHeader与@CookieValue

    3.3通过POJO获取请求参数(重点)

    1. POJO 全称“Plain Old Java Object”,意思是“简单 Java 对象”。POJO 的内在含义是指那些没有从任何类继承、也没有实现任何接口,更没有被其它框架侵入的 Java 对象。
    2. 可以在控制器方法的形参位置设置一个 实体类类型的形参,若浏览器传输的 *请求参数的参数名和实体类中的属性名* 一致,那么请求参数就会为此属性赋值。

    3.3.1举例:User

    • 测试案例:
      • // 普通数据:private String username;
      • // 对象:private UserInfo userInfo;
      • // 数组:private String hobbys[];
      • // 列表:private List titles;
      • // Map:private Map companys
    (1)表单
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>登录title>
    head>
    <body>
        <form action="${pageContext.request.contextPath}/login/test1" method="get">
            用户名: <input type="text" name="username" value="赵飞燕"/> <br>
            年龄: <input type="text" name="userInfo.age" value="18"/> <br>
            身高: <input type="text" name="userInfo.height" value="168"/> <br>
            爱好: <input type="checkbox" name="hobbys" value="追剧" checked="checked">追剧
            <input type="checkbox" name="hobbys" value="画画" checked="checked">画画
            <input type="checkbox" name="hobbys" value="健身" checked="checked">健身<br>
            头衔1: <input type="text" name="titles[0]" value="智慧女神"/> <br>
            头衔2: <input type="text" name="titles[1]" value="幸运之神"/> <br>
            公司1名称: <input type="text" name="companys['公司1'].companyName" value="肯德基"/> <br>
            公司1市值: <input type="text" name="companys['公司1'].values" value="12亿"/> <br>
            公司2名称: <input type="text" name="companys['公司2'].companyName" value="黑马"/> <br>
            公司2市值: <input type="text" name="companys['公司2'].values" value="15亿"/> <br>
            <input type="submit" />
        form>
    body>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    (2)控制层
    @Controller
    @RequestMapping("/login")
    public class MyController {
        @RequestMapping(value = "/?est1")
        // 通过 形参 获取请求参数
        public String handler1(User user) {
            String username = user.getUsername();
            int age = user.getUserInfo().getAge();
            int height = user.getUserInfo().getHeight();
            String hobbys[] = user.getHobbys();
            List<String> titles = user.getTitles();
            Map<String, Company> companys = user.getCompanys();
     
            // 普通参数
            System.out.println("用户姓名:" + username);
            // 对象
            System.out.println("用户年龄:" + age);
            System.out.println("用户身高:" + height);
            // 数组
            System.out.print("用户爱好:");
            for(String hobby : hobbys) {
                System.out.print(" " + hobby);
            }
            System.out.println();
            // List
            System.out.print("称号:");
            for(String title : titles) {
                System.out.print(" " + title);
            }
            System.out.println();
            // Map
            Set<Map.Entry<String, Company>> entries = companys.entrySet();
            for (Map.Entry<String, Company> entry : entries) {
                System.out.println(entry.getKey() + ":" + entry.getValue().getCompanyName() + "市值:" + entry.getValue().getValues());
            }
            return "success";
        }
    }
    
    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    (3)Company类
    public class Company {
        private String companyName;
        private String values;
        
        // setter、getter 方法省略
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    (4)UserInfo类
    public class UserInfo {
        private int age;
        private int height;
     
        // setter、getter 方法省略
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    4.域对象共享数据

    在SpringMVC中常用的域有以下三个:

    1. request:数据在当前请求有效,请求转发后有效,重定向无效
    2. session数据在关闭浏览器前有效,中途关闭服务器,数据钝化(还在),重启浏览器数据又会活化(还能用)
    3. application:数据在关闭服务器前有效(关闭浏览器数据还在)
    4. 注意:pageContext是用在jsp文件中的,但是jsp这种文件现在好像过时了,所以呢,就不用这个了

    4.1 四种共享request域数据

    4.1.1ServletAPI方式

    • 控制层
    @Controller
    public class DomainController {
    
        /*
        *   使用ServletAPI向request域对象共享数据
        * */
        @RequestMapping("/testRequestByServletAPI")
        public String testRequestByServletAPI(HttpServletRequest request){
            request.setAttribute("testRequestScope","Hello ServletAPI");
            return "success";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • success.html:展示request域中对象数据:
    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>成功页面title>
    head>
    <body>
        <h1>跳转成功h1>
        
        <p th:text="${testRequestScope}"/>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    4.1.2ModelAndView方式

    1. ModelAndView有Model和View的功能

      1. Model主要用于向请求域共享数据
      2. View主要用于设置视图,实现页面跳转。
    2. 举例

      	@RequestMapping("/testModelAndView")
          //使用这种方法必须返回ModelAndView对象
          public ModelAndView testModelAndView(){
              ModelAndView mav = new ModelAndView();
              //处理模型数据(向请求域request共享数据)
              mav.addObject("testRequestScope","Hello ModelAndView");
              //设置视图名称(跳转到哪里)
              mav.setViewName("success");
              return mav;
          }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10

    4.1.3Model方式

    	@RequestMapping("/testModel")
        public String testModel(Model model){
            model.addAttribute("testRequestScope", "Hello Model");
            return "success";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.1.4Map方式

     	@RequestMapping("/testMap")
        public String testMap(Map<String, Object> map){
            map.put("testRequestScope","Hello Map");
            return "success";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.1.5ModelMap方式

        @RequestMapping("/testModelMap")
        public String testModelMap(ModelMap modelMap){
            modelMap.addAttribute("testRequestScope", "Hello ModelMap");
            return "success";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    4.2辨别Model、ModelMap、Map的异同

    4.2.1三者本质上都是BindingAwareModelMap类型

    • 测试输出类名:
        @RequestMapping("/testModel")
        public String testModel(Model model){
            model.addAttribute("testRequestScope", "Hello Model");
            System.out.println("Model:"+model.getClass().getName());
            return "success";
        }
    
        /*
        *   使用Map这种就更厉害了,给一个map的形参对象
        *   用法类似于之前的Model方法
        *   主要通过map对象的每一次put,将数据共享到request域中
        * */
        @RequestMapping("/testMap")
        public String testMap(Map<String, Object> map){
            map.put("testRequestScope","Hello Map");
            System.out.println("Map:"+map.getClass().getName());
            return "success";
        }
    
        @RequestMapping("/testModelMap")
        public String testModelMap(ModelMap modelMap){
            modelMap.addAttribute("testRequestScope", "Hello ModelMap");
            System.out.println("ModelMap:"+modelMap.getClass().getName());
            return "success";
        }
    
    • 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
    • 输出结果:

    image-20240311100323591

    4.2.2三者都会返回一个ModelAndView对象

    image-20240311100448036

    4.2向session域共享数据

    image-20240311100623102

    5.拦截器

    5.1概述

    5.1.1定义

    SpringMVC的处理器—>拦截器,类似于Servlet开发中的过滤器Filter,用于对处理器进行预处理和后处理

    5.1.2过滤器和拦截器区别

    1. 过滤器

      • 依赖于servlet容器
      • 在实现上基于函数回调,可以对几乎所有请求进行过滤,
      • 缺点:一个过滤器实例只能在容器初始化时调用一次
      • 目的:做一些过滤操作,比如:在过滤器中修改字符编码;在过滤器中修改HttpServletRequest的一些参数,包括:过滤低俗文字、危险字符等。
    2. 拦截器

      • 依赖于web框架
      • 在实现上基于Java的反射机制,属于==面向切面编程(AOP)==的一种运用。
      • 由于拦截器是基于web框架的调用,因此可以使用Spring的依赖注入(DI)进行一些业务操作,同时一个拦截器实例在一个controller生命周期之内可以多次调用

    5.2拦截器使用

    5.2.1自定义拦截器

    public class MyHandlerInterceptor implements HandlerInterceptor {
    
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
                throws Exception {
            System.out.println("MyHandlerInterceptor->preHandle");
            return true;
        }
    
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
                               ModelAndView modelAndView) throws Exception {
            System.out.println("MyHandlerInterceptor->postHandle");
        }
    
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex)
                throws Exception {
            System.out.println("MyHandlerInterceptor->afterCompletion");
        }
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    1. 拦截器一个有3个回调方法
      • preHandle预处理回调方法,实现处理器的预处理(如登录检查),第三个参数为响应的处理器返回值:true表示继续流程(如调用下一个拦截器或处理器);false表示流程中断(如登录检查失败),不会继续调用其他的拦截器或处理器,此时我们需要通过response来产生响应;
      • postHandle:后处理回调方法,实现处理器的后处理(但==在渲染视图之前==),此时我们可以通过modelAndView(模型和视图对象)对模型数据进行处理或对视图进行处理,modelAndView也可能为null。
      • afterCompletion整个请求处理完毕回调方法,即在视图渲染完毕时回调,如性能监控中我们可以在此记录结束时间并输出消耗时间,还可以进行一些资源清理,类似于try-catch-finally中的finally,但仅调用处理器执行链中preHandle返回true的拦截器才会执行afterCompletion。

    5.2.2controller层

    @Controller
    @RequestMapping("/index")
    public class LoginControl {
        @RequestMapping(value = "/login")
        public String login(){
            System.out.println("LoginControl->login");
            return  "login";
        }
        @RequestMapping(value = "/test")
        @ResponseBody
        public String test(){
            System.out.println("LoginControl->test");
            return "test";
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    5.2.3配置拦截器

    • 拦截所有Controller类里的所有处理方法
    (1)基于xml配置
    • 在Spring的配置文件中添加如下配置
        <!-- 配置拦截器:-->
        <mvc:interceptors>
            <!-- 会拦截所有Controller类里的所有处理方法 -->
            <bean class="com.lucas.controller.MyHandlerInterceptor"></bean>
        </mvc:interceptors>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 拦截指定请求路径
     
        <mvc:interceptors>
            
            <mvc:interceptor>
                
                <mvc:mapping path="/**/login"/>
                
                <bean class="com.lucas.controller.MyHandlerInterceptor">bean>
            mvc:interceptor>
        mvc:interceptors>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    (2)基于注解配置
    public class WebMvcConfig extends WebMvcConfigurerAdapter {
    
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            //创建自定义的拦截器
            MyHandlerInterceptor interceptor = new MyHandlerInterceptor();
            //添加拦截器
            registry.addInterceptor(MyHandlerInterceptor)
            		//添加需要拦截的路径
                    .addPathPatterns("");
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    5.2.4测试

    • 请求路径:

    在这里插入图片描述

    • 测试结果:

    在这里插入图片描述

    6.异常处理

    Spring MVC 有以下 3 种处理异常的方式:

    1. 使用 Spring MVC 提供的简单异常处理器 SimpleMappingExceptionResolver
    2. 实现 Spring 的异常处理接口 HandlerExceptionResolver,自定义自己的异常处理器
    3. 使用 @ExceptionHandler 注解实现局部异常处理

    6.1@ExceptionHandler实现局部异常处理

    1. 局部异常处理仅能处理指定 Controller 中的异常。

    2. 注意:@ExceptionHandler不是加在产生异常的方法上,而是加在处理异常的方法上

    3. @ExceptionHandler 注解定义的方法优先级问题:例如发生的是 NullPointerException,但是声明的异常有 RuntimeException 和 Exception,这时候会根据异常的最近继承关系 找到继承深度最浅的那个@ExceptionHandler 注解方法,即标记了 RuntimeException 的方法。

    4. 例子:

      定义一个处理过程中可能会存在异常情况的 submit 方法,当 i=0 时会产生算术运算异常,在同一个类中定义处理异常的方法controllerExceptionHandler,捕获运算异常。

      @Controller
      @RequestMapping
      public class ExceptionController {
       
          @RequestMapping("/submit") // 抛错方法
          public String submit(HttpServletRequest req,
                               HttpServletResponse resp) throws Exception {
              String num = req.getParameter("num");
              System.out.println(10 / Integer.valueOf(num));
              return "success";
          }
       
          @ExceptionHandler({ArithmeticException.class}) //捕获运算异常
          public String controllerExceptionHandler(Exception e) {
              System.out.println("打印错误信息 ===> ArithmeticException:" + e);
              // 跳转到指定页面
              return "error";
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19

    6.2HandlerExceptionResolver——处理全局异常

    Spring MVC 通过 HandlerExceptionResolver 处理程序异常,包括处理器异常、数据绑定异常以及控制器执行时发生的异常。HandlerExceptionResolver 仅有一个接口方法,源码如下。

    public interface HandlerExceptionResolver {
        ModelAndView resolveException(HttpServletRequest var1, 
                                      HttpServletResponse var2, 
                                      Object var3, 
                                      Exception var4);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    1. 发生异常时,Spring MVC 会调用 resolveException() 方法,并转到 ModelAndView 对应的视图中,返回一个异常报告页面反馈给用户。

    2. 创建一个 HandlerExceptionResolver 接口的实现类 MyExceptionHandler,代码如下

      @Component
      public class MyExceptionHandler implements HandlerExceptionResolver {
          public ModelAndView resolveException(HttpServletRequest httpServletRequest,
                                               HttpServletResponse httpServletResponse,
                                               Object o,
                                               Exception e) {
              Map<String, Object> model = new HashMap<String, Object>();
              model.put("errorMessage", "程序运行出错");
              //根据不同错误转向不同页面(统一处理),即异常与View的对应关系
              if (e instanceof ArithmeticException) {
                  return new ModelAndView("error", model);
              }
              return new ModelAndView("other_error", model);
          }
      }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15

    6.3SimpleMappingExceptionResolver——处理全局异常

    全局异常处理可使用 SimpleMappingExceptionResolver 来实现。它将异常类名映射为视图名,即发生异常时使用对应的视图报告异常。

    6.3.1基于xml配置

        <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
            
            <property name="defaultErrorView" value="other_error">property>
            
            <property name="exceptionAttribute" value="errorMessage">property>
            
            <property name="exceptionMappings">
                <props>
                    <prop key="ArithmeticException">errorprop>
                    
                props>
            property>
        bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    6.4@ControllerAdvice + @ExceptionHandler

    使用@ControllerAdvice 和@ExceptionHandler 可以全局控制异常,使业务逻辑和异常处理分隔开。

    @ControllerAdvice
    public class GlobalExceptionHandler {
        @ExceptionHandler(MyException.class)
        @ResponseBody
        public ResultBean handleMyException(MyException e){
            System.out.println("handleMyException....");
            return new ResultBean(e.getErrorEnum().getCode(),e.getErrorEnum().getMsg());
        }
    
    
    }
    
    public class MyException extends RuntimeException {
    
        private  ErrorEnum errorEnum;
    
        public MyException(ErrorEnum errorEnum){
            this.errorEnum  = errorEnum;
        }
    
    
    
        public ErrorEnum getErrorEnum() {
            return errorEnum;
        }
    }
    
    • 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

    7.上传/下载文件

    SpringMVC上下文中默认没有装配MultipartResolver,因此默认情况下其不能处理文件上传工作。如果想使用Spring的文件上传功能,则需要在上下文中配置MultipartResolver

    7.1前端表单

    前端表单要求:为了能上传文件,必须将表单的method设置为POST,并将enctype设置为multipart/form-data。只有在这样的情况下,浏览器才会把用户选择的文件以二进制数据发送给服务器。

    <form action="" enctype="multipart/form-data" method="post">
        <input type="file" name="file"/>
        <input type="submit">
    form>
    
    • 1
    • 2
    • 3
    • 4

    表单中enctype属性的详细说明:

    • application/x-www=form-urlencoded:默认方式,只处理表单域中的 value 属性值,采用这种编码方式的表单会将表单域中的值处理成 URL 编码方式
    • multipart/form-data:这种编码方式会以二进制流的方式来处理表单数据,这种编码方式会把文件域指定文件的内容也封装到请求参数中,不会对字符编码。
    • text/plain:除了把空格转换为 “+” 号外,其他字符都不做编码处理,这种方式适用直接通过表单发送邮件。

    一旦设置了enctype为multipart/form-data,浏览器即会采用二进制流的方式来处理表单数据,而对于文件上传的处理则涉及在服务器端解析原始的HTTP响应。在2003年,Apache Software Foundation发布了开源的Commons FileUpload组件,其很快成为Servlet/JSP程序员上传文件的最佳选择。

    • Servlet3.0规范已经提供方法来处理文件上传,但这种上传需要在Servlet中完成。而Spring MVC则提供了更简单的封装。
    • Spring MVC为文件上传提供了直接的支持,这种支持是用即插即用的MultipartResolver实现的
    • Spring MVC使用Apache Commons FileUpload技术实现了一个MultipartResolver实现类:CommonsMultipartResolver。因此,SpringMVC的文件上传还需要依赖Apache Commons FileUpload的组件

    7.2文件上传

    【MultipartResolver】用于处理文件上传。当收到请求时,DispatcherServlet 的 checkMultipart() 方法会调用 MultipartResolver 的 isMultipart() 方法判断请求中【是否包含文件】。如果请求数据中包含文件,则调用 MultipartResolver 的 resolveMultipart() 方法对请求的数据进行解析,然后 将文件数据解析成 MultipartFile 并封装在 MultipartHttpServletRequest (继承了 HttpServletRequest) 对象 中,最后传递给 Controller。

    DispatcherServlet的核心方法中第一句就是如下的代码:

    try {
        processedRequest = checkMultipart(request);
        multipartRequestParsed = (processedRequest != request);
        ...
    
    • 1
    • 2
    • 3
    • 4

    注意: MultipartResolver 默认不开启,需要手动开启。

    7.2.1前端页面

    <form action="/upload" enctype="multipart/form-data" method="post">
     <input type="file" name="file"/>
     <input type="submit" value="upload">
    form>
    
    • 1
    • 2
    • 3
    • 4

    7.2.2导入依赖

    • 注意:导入这个【commons-fileupload】jar包,Maven会自动帮我们导入它的依赖包【commons-io】
    
    <dependency>
       <groupId>commons-fileuploadgroupId>
       <artifactId>commons-fileuploadartifactId>
       <version>1.3.3version>
    dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    7.2.3配置bean:multipartResolver

    注意: 这个bena的id必须为:multipartResolver , 否则上传文件会报400的错误!

    
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
       
       <property name="defaultEncoding" value="utf-8"/>
       
       <property name="maxUploadSize" value="10485760"/>
       <property name="maxInMemorySize" value="40960"/>
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • CommonsMultipartFile 的常用方法:
      • String getOriginalFilename():获取上传文件的原名
      • InputStream getInputStream():获取文件流
      • void transferTo(File dest):将上传文件保存到一个目录文件中

    7.2.4controller层

    (1)测试类
    package com.wang.controller;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.multipart.commons.CommonsMultipartFile;
    import javax.servlet.http.HttpServletRequest;
    import java.io.*;
    @Controller
    public class FileController {
        //@RequestParam("file") 将name=file控件得到的文件封装成CommonsMultipartFile 对象
        //批量上传CommonsMultipartFile则为数组即可
        @RequestMapping("/upload")
        public String fileUpload(@RequestParam("file") CommonsMultipartFile file , HttpServletRequest request) throws IOException {
            //获取文件名 : file.getOriginalFilename();
            String uploadFileName = file.getOriginalFilename();
            //如果文件名为空,直接回到首页!
            if ("".equals(uploadFileName)){
                return "redirect:/index.jsp";
            }
            System.out.println("上传文件名 : "+uploadFileName);
            //上传路径保存设置
            String path = request.getServletContext().getRealPath("/upload");
            //如果路径不存在,创建一个
            File realPath = new File(path);
            if (!realPath.exists()){
                realPath.mkdir();
            }
            System.out.println("上传文件保存地址:"+realPath);
            InputStream is = file.getInputStream(); //文件输入流
            OutputStream os = new FileOutputStream(new File(realPath,uploadFileName)); //文件输出流
            //读取写出
            int len=0;
            byte[] buffer = new byte[1024];
            while ((len=is.read(buffer))!=-1){
                os.write(buffer,0,len);
                os.flush();
            }
            os.close();
            is.close();
            return "redirect:/index.jsp";
        }
    }
    
    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    (2)保存文件

    采用file.Transto 来保存上传的文件

    /*
     * 采用file.Transto 来保存上传的文件
     */
    @RequestMapping("/upload2")
    public String  fileUpload2(@RequestParam("file") CommonsMultipartFile file, HttpServletRequest request) throws IOException {
        //上传路径保存设置
        String path = request.getServletContext().getRealPath("/upload");
        File realPath = new File(path);
        if (!realPath.exists()){
            realPath.mkdir();
        }
        //上传文件地址
        System.out.println("上传文件保存地址:"+realPath);
        //通过CommonsMultipartFile的方法直接写文件(注意这个时候)
        file.transferTo(new File(realPath +"/"+ file.getOriginalFilename()));
        return "redirect:/index.jsp";
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    7.3文件下载

    一共有两种文件下载方法:

    • 直接向response的输出流中写入对应的文件流
    • 使用 ResponseEntity来向前端返回文件

    7.3.1传统方式

    1. 设置 response 响应头
    2. 读取文件 — InputStream
    3. 写出文件 — OutputStream
    4. 执行操作
    5. 关闭流 (先开后关)
    @GetMapping("/download1")
    @ResponseBody
    public R download1(HttpServletResponse response){
        FileInputStream fileInputStream = null;
        ServletOutputStream outputStream = null;
        try {
            // 这个文件名是前端传给你的要下载的图片的id
            // 然后根据id去数据库查询出对应的文件的相关信息,包括url,文件名等
            String  fileName = "wang.jpg";
    
            //1、设置response 响应头,处理中文名字乱码问题
            response.reset(); //设置页面不缓存,清空buffer
            response.setCharacterEncoding("UTF-8"); //字符编码
            response.setContentType("multipart/form-data"); //二进制传输数据
            //设置响应头,就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名。
            //Content-Disposition属性有两种类型:inline 和 attachment 
            //inline :将文件内容直接显示在页面 
            //attachment:弹出对话框让用户下载具体例子:
            response.setHeader("Content-Disposition",
                               "attachment;fileName="+ URLEncoder.encode(fileName, "UTF-8"));
    
    		// 通过url获取文件
            File file = new File("D:/upload/"+fileName);
            //2、 读取文件--输入流
            fileInputStream = new FileInputStream(file);
            //3、 写出文件--输出流
            outputStream = response.getOutputStream();
    
            byte[] buffer = new byte[1024];
            int len;
            //4、执行写出操作
            while ((len = fileInputStream.read(buffer)) != -1){
                outputStream.write(buffer,0,len);
                outputStream.flush();
            }
    
            return R.success();
        } catch (IOException e) {
            e.printStackTrace();
            return R.fail();
        }finally {
            if( fileInputStream != null ){
                try {
                    // 5、关闭输入流
                    fileInputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
            if( outputStream != null ){
                try {
                    // 5、关闭输出流
                    outputStream.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    • 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
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59

    7.3.2ResponseEntity方式

    @GetMapping("/download2")
    public ResponseEntity<byte[]> download2(){
        try {
            String fileName = "wang.jpg";
            byte[] bytes = FileUtils.readFileToByteArray(new File("D:/upload/"+fileName));
            HttpHeaders headers=new HttpHeaders();
            // Content-Disposition就是当用户想把请求所得的内容存为一个文件的时候提供一个默认的文件名。
            headers.set("Content-Disposition","attachment;filename="+ URLEncoder.encode(fileName, "UTF-8"));
            headers.set("charsetEncoding","utf-8");
            headers.set("content-type","multipart/form-data");
            ResponseEntity<byte[]> entity=new ResponseEntity<>(bytes,headers, HttpStatus.OK);
            return entity;
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    在这里插入图片描述

  • 相关阅读:
    详解CAN总线:CAN总线报文格式—数据帧
    Vue | Vue.js 高级语法系列
    Golang 单例模式与sync.Once
    RJ45与网络变压器脚位及网线线序的关系?
    R语言计算时间序列数据的逐次差分(successive differences):使用diff函数计算时间序列数据的逐次差分值
    Java面试之Redis篇(offer 拿来吧你)
    【Django | 开发】分离上线环境与开发环境(多settings配置)
    MyBatis-Plus更新字段为null时,update语句为null字段不生效的解决方法
    【光学】Matlab模拟相互垂直的光波叠加
    关心过业务系统里面的sql耗时吗?统计过慢查询吗?对慢查询都怎么优化过?
  • 原文地址:https://blog.csdn.net/weixin_61440595/article/details/136628160