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

news2024/11/17 7:47:17

在这里插入图片描述

文章目录

  • 1.SpringMVC简介
    • 1.1定义
    • 1.2主要组件
    • 1.3工作流程
      • 1.3.1简要流程
      • 1.3.2详细流程
    • 1.4优缺点
  • 2.常用注解
  • 3.获取请求参数
    • 3.1通过 HttpServletRequest 获取请求参数
    • 3.2通过控制器方法的形参获取请求参数
      • 3.2.1请求路径参数与方法形参一致
      • 3.2.2请求路径参数与方法形参不一致
      • 3.2.3扩展:@RequestHeader与@CookieValue
    • 3.3通过POJO获取请求参数(重点)
      • 3.3.1举例:User
        • (1)表单
        • (2)控制层
        • (3)Company类
        • (4)UserInfo类
  • 4.域对象共享数据
    • 4.1 四种共享request域数据
      • 4.1.1ServletAPI方式
      • 4.1.2ModelAndView方式
      • 4.1.3Model方式
      • 4.1.4Map方式
      • 4.1.5ModelMap方式
    • 4.2辨别Model、ModelMap、Map的异同
      • 4.2.1三者本质上都是BindingAwareModelMap类型
      • 4.2.2三者都会返回一个ModelAndView对象
    • 4.2向session域共享数据
  • 5.拦截器
    • 5.1概述
      • 5.1.1定义
      • 5.1.2过滤器和拦截器区别
    • 5.2拦截器使用
      • 5.2.1自定义拦截器
      • 5.2.2controller层
      • 5.2.3配置拦截器
        • (1)基于xml配置
        • (2)基于注解配置
      • 5.2.4测试
  • 6.异常处理
    • 6.1@ExceptionHandler实现局部异常处理
    • 6.2HandlerExceptionResolver——处理全局异常
    • 6.3SimpleMappingExceptionResolver——处理全局异常
      • 6.3.1基于xml配置
    • 6.4@ControllerAdvice + @ExceptionHandler
  • 7.上传/下载文件
    • 7.1前端表单
    • 7.2文件上传
      • 7.2.1前端页面
      • 7.2.2导入依赖
      • 7.2.3配置bean:multipartResolver
      • 7.2.4controller层
        • (1)测试类
        • (2)保存文件
    • 7.3文件下载
      • 7.3.1传统方式
      • 7.3.2ResponseEntity方式

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>
  • 控制层
@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";
    }

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

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

<input type="text" name="username"/>
<input type="password" name="password"/>

public String handler1(String username, String password)

注意:控制器方法的形参必须和前端 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>
  • 控制层
@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";
    }
}

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<String, Company> 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>
(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";
    }
}
(3)Company类
public class Company {
    private String companyName;
    private String values;
    
    // setter、getter 方法省略
}
(4)UserInfo类
public class UserInfo {
    private int age;
    private int height;
 
    // setter、getter 方法省略
}

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";
    }
}
  • 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>
    <!--使用thymeleaf模块后,可以直接用${域对象存储的键的名称}取出里面的内容-->
    <p th:text="${testRequestScope}"/>
</body>
</html>

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;
        }
    

4.1.3Model方式

	@RequestMapping("/testModel")
    public String testModel(Model model){
        model.addAttribute("testRequestScope", "Hello Model");
        return "success";
    }

4.1.4Map方式

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

4.1.5ModelMap方式

    @RequestMapping("/testModelMap")
    public String testModelMap(ModelMap modelMap){
        modelMap.addAttribute("testRequestScope", "Hello ModelMap");
        return "success";
    }

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";
    }
  • 输出结果:

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. 拦截器一个有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";
    }
}

5.2.3配置拦截器

  • 拦截所有Controller类里的所有处理方法
(1)基于xml配置
  • 在Spring的配置文件中添加如下配置
    <!-- 配置拦截器:-->
    <mvc:interceptors>
        <!-- 会拦截所有Controller类里的所有处理方法 -->
        <bean class="com.lucas.controller.MyHandlerInterceptor"></bean>
    </mvc:interceptors>
  • 拦截指定请求路径
 <!-- 配置拦截器:-->
    <mvc:interceptors>
        <!-- 可以配置多个拦截器  也可以配置bean 拦截器 拦截所有请求 -->
        <mvc:interceptor>
            <!-- 只拦截该路径 -->
            <mvc:mapping path="/**/login"/>
            <!-- 会拦截所有Controller类里的所有处理方法 -->
            <bean class="com.lucas.controller.MyHandlerInterceptor"></bean>
        </mvc:interceptor>
    </mvc:interceptors>
(2)基于注解配置
public class WebMvcConfig extends WebMvcConfigurerAdapter {

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        //创建自定义的拦截器
        MyHandlerInterceptor interceptor = new MyHandlerInterceptor();
        //添加拦截器
        registry.addInterceptor(MyHandlerInterceptor)
        		//添加需要拦截的路径
                .addPathPatterns("");
    }
}

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";
        }
    }
    

6.2HandlerExceptionResolver——处理全局异常

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

public interface HandlerExceptionResolver {
    ModelAndView resolveException(HttpServletRequest var1, 
                                  HttpServletResponse var2, 
                                  Object var3, 
                                  Exception var4);
}
  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);
        }
    }
    

6.3SimpleMappingExceptionResolver——处理全局异常

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

6.3.1基于xml配置

    <bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
        <!-- 定义默认的异常处理页面,当该异常类型注册时使用 -->
        <property name="defaultErrorView" value="other_error"></property>
        <!-- 定义异常处理页面用来获取异常信息的变量名,默认名为exception -->
        <property name="exceptionAttribute" value="errorMessage"></property>
        <!-- 定义需要特殊处理的异常,用类名或完全路径名作为key,异常页名作为值 -->
        <property name="exceptionMappings">
            <props>
                <prop key="ArithmeticException">error</prop>
                <!-- 在这里还可以继续扩展对不同异常类型的处理 -->
            </props>
        </property>
    </bean>

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;
    }
}

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>

表单中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);
    ...

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

7.2.1前端页面

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

7.2.2导入依赖

  • 注意:导入这个【commons-fileupload】jar包,Maven会自动帮我们导入它的依赖包【commons-io】
<!--文件上传-->
<dependency>
   <groupId>commons-fileupload</groupId>
   <artifactId>commons-fileupload</artifactId>
   <version>1.3.3</version>
</dependency>

7.2.3配置bean:multipartResolver

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

<!--文件上传配置-->
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
   <!-- 请求的编码格式,必须和jSP的pageEncoding属性一致,以便正确读取表单的内容,默认为ISO-8859-1 -->
   <property name="defaultEncoding" value="utf-8"/>
   <!-- 上传文件大小上限,单位为字节(10485760=10M) -->
   <property name="maxUploadSize" value="10485760"/>
   <property name="maxInMemorySize" value="40960"/>
</bean>
  • 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";
    }
}
(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";
}

7.3文件下载

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

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

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();
            }
        }
    }
}

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;
    }
}

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1508385.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

笔记本电脑使用时需要一直插电吗?笔记本正确的充电方式

随着科技的不断发展&#xff0c;笔记本电脑已经成为人们日常生活和工作中不可或缺的电子设备。而在使用笔记本电脑时&#xff0c;很多人会有一个疑问&#xff0c;那就是笔记本电脑使用时需要一直插电吗&#xff1f;本文将就此问题展开讨论。 不一定需要一直插电&#xff0c;如果…

C++:2024/3/11

作业1&#xff1a;编程 要求&#xff1a;提示并输入一个字符串&#xff0c;统计该字符中大写、小写字母个数、数字个数、空格个数以及其他字符个数 代码&#xff1a; #include <iostream>using namespace std;int main() {string str;cout << "请输入一个字…

sql-mysql可视化工具Workbench导入sql文件

mysql可视化工具Workbench导入sql文件 1、打开workbench2、导入sql文件3、第一行加上库名4、开始运行 1、打开workbench 2、导入sql文件 3、第一行加上库名 4、开始运行

参与Penpad launch任务,实现Penpad与Scroll的双空投

在比特币 ETF 、BTC 减半等利好消息的持续推动下&#xff0c;加密市场逐渐进入到新一轮牛市周期中。除了以太坊 Layer1 生态 TVL 不断飙升外&#xff0c;Layer2 赛道 TVL 也在不断飙升并且屡创新高。 而在牛市背景下&#xff0c;Layer2 空投所带来的财富效应预期正在被进一步拉…

IAB视频广告标准《数字视频和有线电视广告格式指南》之 目录和概述及视频配套广告 - 我为什么要翻译介绍美国人工智能科技公司IAB系列(2)

写在前面 谈及到中国企业走入国际市场&#xff0c;拓展海外营销渠道的时候&#xff0c;如果单纯依靠一个小公司去国外做广告&#xff0c;拉渠道&#xff0c;找代理公司&#xff0c;从售前到售后&#xff0c;都是非常不现实的。我们可以回想一下40年前&#xff0c;30年前&#x…

Facebook商城号为什么被封?如何防封?

由于Facebook商城的高利润空间&#xff0c;越来越多的跨境电商商家注意到它的存在。Facebook作为全球最大、用户量最大的社媒平台&#xff0c;同时也孕育了一个巨大的商业生态&#xff0c;包括广告投放、商城交易等。依托背后的大流量&#xff0c;Facebook商城起号较快&#xf…

接口测试\接口测试脚本之Jsoup解析HTML

第一次接触jsoup还是在处理收货地址的时候&#xff0c;当时在写一个下单流程&#xff0c;需要省市区id以及详细门牌号等等&#xff0c;因此同事介绍了jsoup,闲来无事&#xff0c;在此闲扯一番&#xff01; 1.我们来看下&#xff0c;什么是jsoup,先来看看官方文档是怎么说的&am…

【深度学习】换脸新科技,InstantID: Zero-shot Identity-Preserving Generation in Seconds

论文&#xff1a;https://arxiv.org/abs/2401.07519 代码:https://github.com/InstantID/InstantID demo&#xff1a;https://huggingface.co/spaces/InstantX/InstantID 文章目录 1 引言2 相关工作2.1 文本到图像扩散模型2.2 主题驱动的图像生成2.3 保持ID的图像生成 3 方法3.…

深入理解Vue.js中的nextTick:实现异步更新的奥秘

&#x1f90d; 前端开发工程师、技术日更博主、已过CET6 &#x1f368; 阿珊和她的猫_CSDN博客专家、23年度博客之星前端领域TOP1 &#x1f560; 牛客高级专题作者、打造专栏《前端面试必备》 、《2024面试高频手撕题》 &#x1f35a; 蓝桥云课签约作者、上架课程《Vue.js 和 E…

【wps】wps与office办公函数储备使用(结合了使用案例 持续更新)

【wps】wps与office办公函数储备使用(结合了使用案例 持续更新) 1、TODAY函数 返回当前电脑系统显示的日期 TODAY函数&#xff1a;表示返回当前电脑系统显示的日期。 公式用法&#xff1a;TODAY() 2、NOW函数 返回当前电脑系统显示的日期和时间 NOW函数&#xff1a;表示返…

群晖NAS使用Docker安装WPS Office并结合内网穿透实现公网远程办公

文章目录 推荐1. 拉取WPS Office镜像2. 运行WPS Office镜像容器3. 本地访问WPS Office4. 群晖安装Cpolar5. 配置WPS Office远程地址6. 远程访问WPS Office小结 7. 固定公网地址 推荐 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff…

美国国家安全局(NSA)和美国政府将Delphi/Object Pascal列为推荐政府机构和企业使用的内存安全编程语言

上周&#xff0c;美国政府发布了《回到构建块&#xff1a;通往安全和可衡量软件的道路》的报告。本报告是美国网络安全战略的一部分&#xff0c;重点关注多个领域&#xff0c;包括内存安全漏洞和质量指标。 许多在线杂志都对这份报告发表了评论&#xff0c;这些杂志强调了对 C…

OpenCV学习笔记(五)——图片的缩放、旋转、平移、裁剪以及翻转操作

目录 图像的缩放 图像的平移 图像的旋转 图像的裁剪 图像的翻转 图像的缩放 OpenCV中使用cv2.resize()函数进行缩放&#xff0c;格式为&#xff1a; resize_imagecv2.resize(image,(new_w,new_h),插值选项) 其中image代表的是需要缩放的对象&#xff0c;(new_w,new_h)表…

Python元组(Tuple)深度解析!

目录 1. 什么是元组&#xff1f; 2. 创建元组 3.访问元组 4.元组的运算 5.修改元组不可行 6.元组的应用场景 前面的博客里&#xff0c;我们详细介绍了列表&#xff08;List&#xff09;这一种数据类型&#xff0c;现在我们来讲讲与列表相似的一种数据类型&#xff0c;元组…

portapack-h2使能hackrf

这两天弄了一块portapack扩展板 &#xff0c;可以用来脱机使能hackrf,简单的说&#xff0c;这是一块自带屏幕&#xff0c;预装gnuradio环境的单片机&#xff0c;通过GPIO插入hackrf,可以使能hackrf&#xff0c;脱机运行一些无线电测试程序。 购置的拓展先不用着急装扩展板&…

Web3 之路:构建开放、透明的数字生态系统

随着区块链技术的迅速发展&#xff0c;Web3正在成为数字世界的新引擎&#xff0c;重新定义着我们的生活和工作方式。在这个数字化的时代&#xff0c;我们目睹着一个全新的网络生态正在逐渐成形&#xff0c;其中开放性和透明性是其核心特征。让我们深入探讨&#xff0c;Web3如何…

一款适合程序员开发复杂系统的通用平台——JNPF 开发平台

在过去&#xff0c;很多开发工具更侧重代码编辑&#xff0c;针对数据库增删改查&#xff08;CRUD&#xff09;类的 Web 系统开发&#xff0c;在界面设计、前后端数据交互等环节主要还是靠写代码&#xff0c;效率比较低。目前很多所谓的低代码开发平台&#xff0c;大多数也都是基…

【格与代数系统】格与哈斯图

【格与代数系统】格与代数系统汇总 目录 常见的偏序关系 覆盖 哈斯图 例1 例2 例3 格与哈斯图 例1 例2 常见的偏序关系 偏序关系&#xff1a;自反性反对称性传递性 整数集合上的小于等于&#xff08;大于等于&#xff09;关系、幂集中的包含关系 、正整数的整除和整…

羊大师分析羊奶的喝法,都有什么讲究?

羊大师分析羊奶的喝法,都有什么讲究&#xff1f; 羊奶的喝法确实有一些讲究&#xff0c;以下是一些主要的注意事项&#xff1a; 温度控制&#xff1a;羊奶不宜煮沸喝&#xff0c;加热时最好保持在50℃&#xff0d;60℃之间&#xff0c;以避免破坏其营养成分。 饮用时间&…

【TypeScript】对TypeScript的理解?与JavaScript的区别?

1 是什么 TypeScript是JS的类型超集&#xff0c;支持ES6语法&#xff0c;支持面向对象编程的概念&#xff0c;如类、接口、继承、泛型等。 是一种静态类型检查的语言&#xff0c;提供了类型注解&#xff0c;在代码编译阶段就可以检查出数据类型的错误 同时扩展了JS语法 &#x…