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

在这里插入图片描述

文章目录

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

在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

3、设计模式之工厂模式1(Factory)

工厂模式是什么&#xff1f;     工厂模式是一种创建者模式&#xff0c;用于封装和管理对象的创建&#xff0c;屏蔽了大量的创建细节&#xff0c;根据抽象程度不同&#xff0c;主要分为简单工厂模式、工厂方法模式以及抽象工厂模式。 简单工厂模式 看一个具体的需求 看一个…

【划重点】自动引流软件隐藏风险!?你不知道的网络危机与应对策略

先来看成果&#xff0c;评论888领取 在互联网快速发展的今天&#xff0c;自动引流软件以其高效的推广方式和便捷的操作&#xff0c;成为许多商家和个人提升网络知名度的重要工具。然而&#xff0c;这种看似无害的软件&#xff0c;却潜藏着不容忽视的网络安全风险。本文将深入解…

Linux操作系统启动流程

文章目录 1. Linux操作系统启动流程图2.运行流程&#xff08;1&#xff09; 加载BIOS&#xff08;2&#xff09; 读取MBR&#xff08;3&#xff09; GRUB引导&#xff08;4&#xff09; 加载Kernel&#xff08;5&#xff09; 设定Inittab运行等级&#xff08;6&#xff09; 加载…

如何布局马斯克推特上喊的meme币赛道

2024年的牛市正如火如荼的开展&#xff0c;截止当下&#xff0c;比特币已经站上了7.3万美元&#xff0c;远超2021年高点的6.9万美元&#xff0c;比特币的未来是一片大海。 除了比特币的一枝独秀之外&#xff0c;meme板块可以说是市场资金最青睐的。尤其是马斯克在X分享PEPE相关…

初步了解序列化和反序列化

01什么是序列化和反序列化 序列化是将对象转化为字符串以便存储的一种方式。而反序列化恰好是序列化的逆过程&#xff0c;反序列化会将字符串转化为对象供程序使用。 常见的php系列化和反系列化方式主要有&#xff1a;serialize&#xff0c;unserialize&#xff1b;json_enco…

LVGL移植到ARM开发板(GEC6818开发板)

LVGL移植到ARM开发板&#xff08;GEC6818开发板&#xff09; 一、LVGL概述 LVGL&#xff08;Light and Versatile Graphics Library&#xff09;是一个开源的图形用户界面库&#xff0c;旨在提供轻量级、可移植、灵活和易于使用的图形用户界面解决方案。 它适用于嵌入式系统…

四、MySQL

MySQL MySQL1.初识网站2.安装MySQL2.1 下载&#xff08;最重要的一点是路径中不能有中文&#xff0c;哪怕是同级目录也不行&#xff09;2.2安装补丁2.3安装2.4创建配置文件2.5初始化 3.启动MySQL4.连接测试4.1 设置密码4.2 查看已有的文件夹&#xff08;数据库&#xff09;4.3 …

【SSM】任务列表案例 基本CRUD SSM整合

文章目录 一、案例功能预览二、接口分析三、前端工程导入四、后端程序实现和测试4.1 准备4.2 功能实现4.2.1 分页查询显示4.2.2 添加计划4.2.2 删除计划4.2.3 修改计划 4.3 前后联调 一、案例功能预览 Github 地址 &#xff1a; ssm-integration-part 二、接口分析 学习计划…

【C++初阶】C++入门(上)

C的认识 ①什么是C&#xff1f; ​ C语言是结构化和模块化的语言&#xff0c;适合处理较小规模的程序。对于复杂的问题&#xff0c;规模较大的程序&#xff0c;需要高度的抽象和建模时&#xff0c;C语言则不合适。 ​ 于是1982年&#xff0c;Bjarne Stroustrup&#xff08;本…

激活函数理解

前言 为什么神经网中非要有各种各样的激活函数&#xff1f;他们有什么用&#xff1f;没有他们会怎样&#xff1f;常见的激活函数有哪些&#xff0c;他们都有什么特点&#xff1f; 如果我们不运用激活函数&#xff0c;神经网络的输出信号将仅仅是一个简单的线性函数。线性方程…

【DL经典回顾】激活函数大汇总(七)(CReLU RReLU附代码和详细公式)

激活函数大汇总&#xff08;七&#xff09;&#xff08;CReLU & RReLU附代码和详细公式&#xff09; 更多激活函数见激活函数大汇总列表 一、引言 欢迎来到我们深入探索神经网络核心组成部分——激活函数的系列博客。在人工智能的世界里&#xff0c;激活函数扮演着不可或…

Solidity 智能合约开发 - 基础:基础语法 基础数据类型、以及用法和示例

苏泽 大家好 这里是苏泽 一个钟爱区块链技术的后端开发者 本篇专栏 ←持续记录本人自学两年走过无数弯路的智能合约学习笔记和经验总结 如果喜欢拜托三连支持~ 本篇主要是做一个知识的整理和规划 作为一个类似文档的作用 更为简要和明了 具体的实现案例和用法 后续会陆续给出…

【应急响应靶场web1】

文章目录 前言 一、web1 1、应急响应 1&#xff09;背景 2&#xff09;报错处理 3&#xff09;webshell查杀 4&#xff09;网站日志排查 5&#xff09;隐藏账户 6&#xff09;挖矿程序 2、渗透复现 1&#xff09;弱口令登录 2&#xff09;插件上传 3&#xff09;getshell 总结 …

还有没有免费裁剪音频的软件?15款音乐裁剪软件测评!(不断更新)

市面上有哪些免费裁剪音频的软件呢&#xff1f;今天&#xff0c;我们就来为大家详细介绍15款热门的音乐裁剪软件&#xff0c;并对其进行深度测评。 裁剪音频软件测评1&#xff1a;金舟音频大师 好评指数&#xff1a;4.5/5 优点罗列&#xff1a;支持音频格式转换、裁剪、降噪、…

Linux-vim显示乱码

Linux运维工具-ywtool 目录 一.问题二.解决2.1 编辑VIM的配置文件2.2 添加以下内容 一.问题 用vim编辑的时候,中文显示乱码 二.解决 2.1 编辑VIM的配置文件 vim ~/.vimrc #如果这个文件不存在,创建一个即可2.2 添加以下内容 添加完成以后就不会在出现中文乱码了 set fil…

爬虫的去重

去重基本原理 爬虫中什么业务需要使用去重 防止发出重复的请求防止存储重复的数据 在爬取网页数据时&#xff0c;避免对同一URL发起重复的请求&#xff0c;这样可以减少不必要的网络流量和服务器压力&#xff0c;提高爬虫的效率&#xff0c;在将爬取到的数据存储到数据库或其…

一个简单而绝妙的思维技巧

在文章的最开头&#xff0c;我想先问你一个问题&#xff1a; 你希望未来的你是什么样的&#xff1f;你希望未来的你比现在的你过得更好&#xff0c;还是过得更糟&#xff1f; 我想&#xff0c;应该没有人会选择后者吧&#xff1f; 尽管从客观上说&#xff0c;未来的我们很可能…

配置安装 Kuboard - kubernetes

目录 安装 Kuboard v3 - kubernetes | Kuboard 将官方yaml文件拿到本地 等待 Kuboard v3 就绪 访问 Kuboard 安装 Kuboard v3 - kubernetes | Kuboard Kuboard的官方非常丰富&#xff0c;大家也可以参考官网教程 将官方yaml文件拿到本地 ​kubectl apply -f https://add…

自定义指令控制权限

1.新建directives auth.ts 2.完整的auth.ts import { wmsStore } from "/store/pinia";//判断是否有某个角色的函数 function hasRoles(role: any) {const pinaRoles wmsStore().roles;if (typeof role string) {return pinaRoles.includes(role)} else if (Array…

docker部署Atheos云IDE平台

Codiad 是一个基于 Web 的 IDE 框架 部署 下载镜像 docker pull hlsiira/atheosvim docker-compose.yaml version: 3 services:atheos:image: hlsiira/atheosports:- 8565:80container_name: atheosrestart: always启动 docker-compose up -d访问 http://x.x.x.x:8565