• springmvc-day03


    springmvc-day03

    第一章 拦截器

    1.概念

    1.1 使用场景
    1.1.1 生活中坐地铁的场景

    为了提高乘车效率,在乘客进入站台前统一检票:
    在这里插入图片描述

    1.1.2 程序中的校验登录场景

    在程序中,使用拦截器在请求到达具体 handler 方法前,统一执行检测。

    在这里插入图片描述

    1.2 拦截器与过滤器的对比
    1.2.1 相同点

    三要素相同

    • 拦截(配置拦截路径):必须先把请求拦住,才能执行后续操作
    • 过滤(根据某种规则/业务逻辑进行筛选):拦截器或过滤器存在的意义就是对请求进行统一处理
    • 放行(满足规则/筛选条件,就让你访问你想访问的资源):对请求执行了必要操作后,放请求过去,让它访问原本想要访问的资源
    1.2.2 不同点
    • 工作平台不同
      • 过滤器工作在 Servlet 容器中
      • 拦截器工作在 SpringMVC 的基础上
    • 拦截的范围
      • 过滤器:能够拦截到的最大范围是整个 Web 应用
      • 拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求(handler方法、view-controller跳转页面、default-servlet-handler处理的静态资源)
    • IOC 容器支持
      • 过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的
      • 拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持

    2. 具体使用

    使用步骤:

    1 编写拦截器
    (1). 编写一个类实现HandlerInterceptor接口
    (2). 重写HandlerInterceptor接口的三个方法:
    ① 在处理器处理请求之前执行preHandle(),该方法的返回值表示是否放行
    ② 在处理器处理请求之后执行postHandle()
    ③ 在视图渲染完毕(整个请求过程完成)之后执行afterCompletion()

    2 配置拦截器的拦截路径(在spring的配置文件)
    mvc:interceptors
    mvc:interceptor




    2.1 导入依赖
      <dependencies> 
          
        <dependency> 
          <groupId>org.springframeworkgroupId>  
          <artifactId>spring-webmvcartifactId>  
          <version>5.3.1version> 
        dependency>  
          
        <dependency> 
          <groupId>ch.qos.logbackgroupId>  
          <artifactId>logback-classicartifactId>  
          <version>1.2.3version> 
        dependency>  
          
        <dependency> 
          <groupId>javax.servletgroupId>  
          <artifactId>javax.servlet-apiartifactId>  
          <version>3.1.0version>  
          <scope>providedscope> 
        dependency>  
          
        <dependency> 
          <groupId>org.thymeleafgroupId>  
          <artifactId>thymeleaf-spring5artifactId>  
          <version>3.0.12.RELEASEversion> 
        dependency> 
      dependencies> 
    
    • 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
    2.2 创建spring-web.xml配置文件
    
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/mvc https://www.springframework.org/schema/mvc/spring-mvc.xsd">
    
    
        <context:component-scan base-package="com.oracle">context:component-scan>
    
        <mvc:annotation-driven>mvc:annotation-driven>
    
        <mvc:default-servlet-handler>mvc:default-servlet-handler>
    
            
                <bean id="viewResolver" class="org.thymeleaf.spring5.view.ThymeleafViewResolver">
                    <property name="order" value="1"/>
                    <property name="characterEncoding" value="UTF-8"/>
                    <property name="templateEngine">
                        <bean class="org.thymeleaf.spring5.SpringTemplateEngine">
                            <property name="templateResolver">
                                <bean class="org.thymeleaf.spring5.templateresolver.SpringResourceTemplateResolver">
    
                                    
                                    <property name="prefix" value="/WEB-INF/templates/"/>
    
                                    
                                    <property name="suffix" value=".html"/>
    
                                    
                                    <property name="templateMode" value="HTML5"/>
                                    
                                    <property name="characterEncoding" value="UTF-8" />
                                bean>
                            property>
                        bean>
                    property>
                bean>
    beans>
    
    • 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
    2.3 web.xml文件添加映射器和解决字符乱码问题
    
    <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
    	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
    	version="4.0">
    
    	<servlet>
    			<servlet-name>dispatcherServletservlet-name>
    			<servlet-class>org.springframework.web.servlet.DispatcherServletservlet-class>
    			<init-param>
    				<param-name>contextConfigLocationparam-name>
    				<param-value>classpath:spring-web.xmlparam-value>
    			init-param>
    			<load-on-startup>1load-on-startup>
    		servlet>
    
    		<servlet-mapping>
    			<servlet-name>dispatcherServletservlet-name>
    			<url-pattern>/url-pattern>
    		servlet-mapping>
    
    	    
    	        
    	        <filter>
    	            <filter-name>CharacterEncodingFilterfilter-name>
    	            <filter-class>org.springframework.web.filter.CharacterEncodingFilterfilter-class>
    	            
    	            <init-param>
    	                <param-name>encodingparam-name>
    	                <param-value>UTF-8param-value>
    	            init-param>
    	            
    	            <init-param>
    	                <param-name>forceRequestEncodingparam-name>
    	                <param-value>trueparam-value>
    	            init-param>
    	            
    	            <init-param>
    	                <param-name>forceResponseEncodingparam-name>
    	                <param-value>trueparam-value>
    	            init-param>
    	        filter>
    	        <filter-mapping>
    	            <filter-name>CharacterEncodingFilterfilter-name>
    	            <url-pattern>/*url-pattern>
    	        filter-mapping>
    web-app>
    
    • 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
    2.4 创建拦截器类
    package com.atguigu.interceptor;
    
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.ModelAndView;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    
    public class Demo01Interceptor implements HandlerInterceptor {
        
        // 在处理请求的目标方法前执行
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            System.out.println("Demo01Interceptor的preHandle方法执行了...");
            // 返回true放行 返回false不放行
            return true;
        }
    
        // 在目标方法之后,渲染视图之前执行
        @Override
        public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
            System.out.println("Demo01Interceptor的postHandle方法执行了...");
    
        }
        
        // 在渲染视图之后执行
        @Override
        public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
            System.out.println("Demo01Interceptor的afterCompletion方法执行了...");
    
        }
    }
    
    
    • 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

    单个拦截器执行顺序

    • preHandle() 方法
    • 目标 handler 方法
    • postHandle() 方法
    • 渲染视图
    • afterCompletion() 方法
    2.5 注册拦截器
    2.5.1 默认拦截全部请求
    
    <mvc:interceptors> 
        
        <bean class="com.atguigu.interceptor.Demo01Interceptor"/>
    mvc:interceptors>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    2.5.2 配置拦截路径
    2.5.2.1 精确匹配
    
    <mvc:interceptor>
        
        <mvc:mapping path="/hello/sayHello"/>
        <bean class="com.atguigu.interceptor.Demo01Interceptor"/>
    mvc:interceptor>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    2.5.2.2 模糊匹配:匹配单层路径
    <mvc:interceptor>
        
        <mvc:mapping path="/hello/*"/>
        <bean class="com.atguigu.interceptor.Demo01Interceptor"/>
    mvc:interceptor>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    2.5.2.3 模糊匹配:匹配多层路径
    <mvc:interceptor>
        
        <mvc:mapping path="/hello/**"/>
    
        
        <mvc:exclude-mapping path="/hello/sayHello"/>
    
        <bean class="com.atguigu.interceptor.Demo01Interceptor"/>
    mvc:interceptor>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    2.6 多个拦截器执行顺序
    • preHandle()方法:和配置的顺序一样
    • 目标handler方法
    • postHandle()方法:和配置的顺序相反
    • 渲染视图
    • afterCompletion()方法:和配置的顺序相反

    第二章 类型转换

    SpringMVC 将『把请求参数注入到 POJO 对象』这个操作称为**『数据绑定』**,英文单词是 binding。数据类型的转换和格式化就发生在数据绑定的过程中。 类型转换和格式化是密不可分的两个过程,很多带格式的数据必须明确指定格式之后才可以进行类型转换。最典型的就是日期类型。

    1. 自动类型转换

    HTTP 协议是一个无类型的协议,我们在服务器端接收到请求参数等形式的数据时,本质上都是字符串类型。请看 javax.servlet.ServletRequest 接口中获取全部请求参数的方法:

    public Map<String, String[]> getParameterMap();
    
    • 1

    而我们在实体类当中需要的类型是非常丰富的。对此,SpringMVC 对基本数据类型提供了自动的类型转换。例如:请求参数传入“100”字符串,我们实体类中需要的是 Integer 类型,那么 SpringMVC 会自动将字符串转换为 Integer 类型注入实体类。

    类型转换的种类:

    类型转换的种类:
    1 自动类型转换(SpringMVC会根据你接收请求参数的类型,进行自动转换)
    2 手动类型转换(如果在类型转换的时候,有一些特殊的要求,我们可以通过手动进行转换)
    (1). 使用SpringMVC提供的某些注解,对特定的类型进行手动转换
    ① DateTimeFormate
    ② NumberFormate注解
    (2). 编写自定义的类型转换器
    ① 创建一个类型转换器类实现Converter接口,该接口有俩泛型(S,T)

    2.手动类型转换(日期和数值类型转换)

    2.1 先导入依赖
      <dependencies> 
          
        <dependency> 
          <groupId>org.springframeworkgroupId>  
          <artifactId>spring-webmvcartifactId>  
          <version>5.3.1version> 
        dependency>  
          
        <dependency> 
          <groupId>ch.qos.logbackgroupId>  
          <artifactId>logback-classicartifactId>  
          <version>1.2.3version> 
        dependency>  
          
        <dependency> 
          <groupId>javax.servletgroupId>  
          <artifactId>javax.servlet-apiartifactId>  
          <version>3.1.0version>  
          <scope>providedscope> 
        dependency>  
          
        <dependency> 
          <groupId>org.thymeleafgroupId>  
          <artifactId>thymeleaf-spring5artifactId>  
          <version>3.0.12.RELEASEversion> 
        dependency>
    
        <dependency>
          <groupId>org.projectlombokgroupId>
          <artifactId>lombokartifactId>
          <version>1.18.24version>
        dependency>
      dependencies> 
    
    • 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
    2.2 创建Product实体类
    package com.atguigu.pojo;
    
    import lombok.Data;
    import org.springframework.format.annotation.DateTimeFormat;
    import org.springframework.format.annotation.NumberFormat;
    
    import java.util.Date;
    
    /**
    
     * SpringMVC提供了一些注解,可以让我们进行一些手动类型转换
     * 1. DateTimeFormat注解:可以对日期时间类型进行转换
     * 2. NumberFormat注解:可以对数值类型进行转换
     */
    @Data
    public class Product {
        
        // 可以根据yyyy-MM-dd格式转换成Date类型显示
        @DateTimeFormat(pattern = "yyyy-MM-dd")
        private Date productDate;
        
        // 可以根据#,#格式显示数字
        @NumberFormat(pattern = "#,#")
        private Double productPrice;
    }
    
    
    • 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
    2.3 创建HelloController类
    package com.atguigu.controller;
    
    import com.atguigu.pojo.Product;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    @RequestMapping("/hello")
    public class HelloController {
    
        @RequestMapping("/demo01")
        public String sayHello(Product product){
            System.out.println("product = " + product);
            return "target";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    2.4 创建index.html页面
    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>首页title>
    head>
    <body>
    <form th:action="@{/hello/demo01}">
        生产日期:<input type="text" name="productDate"> <br>
        产品价格:<input type="text" name="productPrice"> <br>
        <button>提交button>
    form>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    2.5 数据绑定失败后处理方式
    2.5.1 默认结果

    在这里插入图片描述

    2.4.2 BindingResult 接口

    BindingResult 接口和它的父接口 Errors 中定义了很多和数据绑定相关的方法,如果在数据绑定过程中发生了错误,那么通过这个接口类型的对象就可以获取到相关错误信息。
    在这里插入图片描述

    2.4.3 重构 handler 方法
    package com.atguigu.controller;
    
    import com.atguigu.pojo.Product;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import java.util.ArrayList;
    import java.util.List;
    
    @Controller
    @RequestMapping("/hello")
    public class HelloController {
    
        @RequestMapping("/demo01")
        public String sayHello(Product product, BindingResult bindingResult, Model model){
            // 在该方法中声明bindingResult,SpringMVC就不会自己处理绑定错误了
            ArrayList<String> filedNameList = new ArrayList<>();
            // 判断是否有绑定错误
            if (bindingResult.hasErrors()){
                // 如果有绑定错误
                List<FieldError> fieldErrors = bindingResult.getFieldErrors();
                for (FieldError fieldError : fieldErrors) {
                    // 获取出现绑定错误的字段名
                    String fieldName = fieldError.getField();
                    // 获取字段绑定错误的信息
                    String defaultMessage = fieldError.getDefaultMessage();
                    System.out.println(fieldName + ":" + defaultMessage);
                    filedNameList.add(fieldName);
                }
                model.addAttribute("errFieldNames", filedNameList);
                return "bind-error";
            }
            System.out.println("product = " + product);
            return "target";
        }
    }
    
    
    • 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
    2.4.4 在页面上显示错误消息

    页面是vind-error.html,放在Thymeleaf前后缀控制范围之内

    
    
    
        
        数据绑定错误
    
    
    

    传入的参数有问题!

    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3.自定义类型转换器

    现在我们希望通过一个文本框输入约定格式的字符串,然后转换为我们需要的类型,所以必须通过自定义类型转换器来实现,否则 SpringMVC 无法识别。

    3.1 创建自定义类型转换器类

    实现接口:org.springframework.core.convert.converter.Converter

    泛型 S:源类型(本例中是 String 类型)

    泛型 T:目标类型(本例中是 Date类型)

    package com.atguigu.comverter;
    
    import org.springframework.core.convert.converter.Converter;
    
    import java.util.Date;
    
    public class AtguiguDateConverter implements Converter<String, Date> {
        @Override
        public Date convert(String source) {
            // source就是要进行转换的那个字符串,例如"1992-03-03"或者"1992/03/03"
            String[] strs = source.split("-");
            // 如果strs的长度大于1,那说明客户端传的是-格式
            if (strs.length > 1){
                int year = Integer.parseInt(strs[0]);
                int month = Integer.parseInt(strs[1]);
                int day = Integer.parseInt(strs[2]);
                return new Date(year, month, day);
            }
    
            strs = source.split("/");
            if (strs.length > 1){
                int year = Integer.parseInt(strs[0]);
                int month = Integer.parseInt(strs[1]);
                int day = Integer.parseInt(strs[2]);
                return new Date(year, month, day);
            }
            throw new RuntimeException("传入的参数只能是-或者/格式");
        }
    }
    
    
    • 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
    3.2 在springmvc配置文件中注册类型转换器
        
        <mvc:annotation-driven conversion-service="formattingConversionService">mvc:annotation-driven>
    
    
    
    
        
        <bean id="formattingConversionService" class="org.springframework.format.support.FormattingConversionServiceFactoryBean">
            
            <property name="converters">
                <list>
                    <bean class="com.atguigu.converter.AtguiguDateConverter">bean>
                list>
            property>
    
        bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    配置好自定义类型转换器后不管是/格式还是-格式都可以转成Date类型显示

    在这里插入图片描述
    在这里插入图片描述

    在这里插入图片描述

    第三章 数据校验

    在 Web 应用三层架构体系中,表述层负责接收浏览器提交的数据,业务逻辑层负责数据的处理。为了能够让业务逻辑层基于正确的数据进行处理,我们需要在表述层对数据进行检查,将错误的数据隔绝在业务逻辑层之外。

    1. 数据校验概述

    JSR 303 是 Java 为 Bean 数据合法性校验提供的标准,它已经包含在 JavaEE 6.0 标准中。JSR 303 通过在 Bean 属性上标注类似于 @NotNull、@Max 等标准的注解指定校验规则,并通过标准的验证接口对Bean进行验证。

    注解规则
    @Null标注值必须为 null
    @NotNull标注值不可为 null,但是可以为空字符串
    @AssertTrue标注值必须为 true
    @AssertFalse标注值必须为 false
    @Min(value)标注值必须大于或等于 value
    @Max(value)标注值必须小于或等于 value
    @DecimalMin(value)标注值必须大于或等于 value
    @DecimalMax(value)标注值必须小于或等于 value
    @Size(max,min)标注值大小必须在 max 和 min 限定的范围内
    @Digits(integer,fratction)标注值值必须是一个数字,并且指定最大的整数位数和最大的小数位数
    @Past标注值只能用于日期型,且必须是过去的日期
    @Future标注值只能用于日期型,且必须是将来的日期
    @Pattern(value)标注值必须符合指定的正则表达式

    JSR 303 只是一套标准,需要提供其实现才可以使用。Hibernate Validator 是 JSR 303 的一个参考实现,除支持所有标准的校验注解外,它还支持以下的扩展注解:

    注解规则
    @Email标注值必须是格式正确的 Email 地址
    @Length标注值字符串大小必须在指定的范围内
    @NotEmpty标注值字符串不能是空字符串
    @Range标注值必须在指定的范围内

    Spring 4.0 版本已经拥有自己独立的数据校验框架,同时支持 JSR 303 标准的校验框架。Spring 在进行数据绑定时,可同时调用校验框架完成数据校验工作。在SpringMVC 中,可直接通过注解驱动 mvc:annotation-driven 的方式进行数据校验。Spring 的 LocalValidatorFactoryBean 既实现了 Spring 的 Validator 接口,也实现了 JSR 303 的 Validator 接口。只要在Spring容器中定义了一个LocalValidatorFactoryBean,即可将其注入到需要数据校验的 Bean中。Spring本身并没有提供JSR 303的实现,所以必须将JSR 303的实现者的jar包放到类路径下。

    配置 mvc:annotation-driven 后,SpringMVC 会默认装配好一个 LocalValidatorFactoryBean,通过在处理方法的入参上标注 @Validated 注解即可让 SpringMVC 在完成数据绑定后执行数据校验的工作。

    2. 具体操作

    前提:1. springmvc环境 2. Tomcat8及以上版本

    2.1 引入依赖
        
        <dependency>
          <groupId>org.hibernate.validatorgroupId>
          <artifactId>hibernate-validatorartifactId>
          <version>6.2.0.Finalversion>
        dependency>
        
        <dependency>
          <groupId>org.hibernate.validatorgroupId>
          <artifactId>hibernate-validator-annotation-processorartifactId>
          <version>6.2.0.Finalversion>
        dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    2.2 应用校验规则
    2.2.1 给要进行校验的字段加上校验规则注解
    package com.atguigu.pojo;
    
    import lombok.Data;
    import org.hibernate.validator.constraints.Length;
    import org.hibernate.validator.constraints.Range;
    import org.springframework.context.annotation.Bean;
    
    import javax.validation.constraints.Email;
    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Null;
    import javax.validation.constraints.Pattern;
    
    @Data
    public class User {
    
        @Null
        private Integer id;
    
        @NotNull
        @Length(min = 6, max = 10)
        private String username;
    
        @NotNull
        @Pattern(regexp = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$")
        private String phone;
        
        @NotNull
        @Email
        private String email;
    
        @NotNull
        @Range(min = 18, max = 80)
        private Integer age;
    }
    
    
    • 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
    2.2.2 给处理器方法的形参加上Validated
    package com.atguigu.controller;
    
    import com.atguigu.pojo.User;
    import org.springframework.stereotype.Controller;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    @RequestMapping("/user")
    public class UserController {
    
        @RequestMapping("/add")
        public String add(@Validated User user){
    
            return "target";
        }
    
        @RequestMapping("/update")
        public String update(User user){
            System.out.println(user);
            return "target";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    2.3 测试效果
    2.2.3.1 测试add方法 一个参数都没填

    校验生效了 所以报400错误

    在这里插入图片描述

    2.2.3.2 测试update方法 一个参数都没填

    能够成功访问页面 因为update方法中参数中没有加@Validated注解
    在这里插入图片描述

    2.2.3.3 测试add方法 参数全部填写正确

    能够正常访问页面了
    在这里插入图片描述

    2.4 显示友好的错误提示
    2.4.1 重构UserController方法
        @RequestMapping("/add")
        public String add(@Validated User user, BindingResult bindingResult, Model model){
            List<String > fieldNameList = new ArrayList();
            if (bindingResult.hasErrors()){
                List<FieldError> fieldErrors = bindingResult.getFieldErrors();
                for (FieldError fieldError : fieldErrors) {
                    String fieldName = fieldError.getField();
                    String defaultMessage = fieldError.getDefaultMessage();
                    fieldNameList.add(fieldName);
                }
                model.addAttribute("errorFieldNames", fieldNameList);
                return "bind-error";
            }
            System.out.println("user add = " + user);
            return "target";
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    2.4.2 创建错误信息页面
    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>数据绑定错误title>
    head>
    <body>
    <h1>传入的参数有问题!h1>
    <ul th:each="fieldName:${errorFieldNames}">
        <li th:text="${fieldName}">li>
    ul>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    2.4.3 显示效果

    在这里插入图片描述

    2.5 分组校验

    如果想要使用update方法时候id必须为null 使用add方法的时候id必须不能为null的时候该如何处理呢

    使用分组校验解决

    2.5.1 创建分组的接口

    add分组接口

    package com.atguigu.group;
    
    public interface AddGroup {
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    update分组接口

    package com.atguigu.group;
    
    public interface UpdateGroup {
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    2.5.2 重构User实体类校验规则
    package com.atguigu.pojo;
    
    import com.atguigu.group.AddGroup;
    import com.atguigu.group.UpdateGroup;
    import lombok.Data;
    import org.hibernate.validator.constraints.Length;
    import org.hibernate.validator.constraints.Range;
    import org.springframework.context.annotation.Bean;
    
    import javax.validation.constraints.Email;
    import javax.validation.constraints.NotNull;
    import javax.validation.constraints.Null;
    import javax.validation.constraints.Pattern;
    
    @Data
    public class User {
    
        @Null(groups = AddGroup.class)
        @NotNull(groups = UpdateGroup.class)
        private Integer id;
    
        @NotNull(groups = {AddGroup.class,UpdateGroup.class})
        @Length(min = 6,max = 10, groups = {AddGroup.class,UpdateGroup.class})
        private String username;
    
        @NotNull(groups = {AddGroup.class,UpdateGroup.class})
        @Pattern(regexp = "^(13[0-9]|14[01456879]|15[0-35-9]|16[2567]|17[0-8]|18[0-9]|19[0-35-9])\\d{8}$",
                groups = {AddGroup.class,UpdateGroup.class})
        private String phone;
    
        @NotNull(groups = {AddGroup.class,UpdateGroup.class})
        @Email(groups = {AddGroup.class,UpdateGroup.class})
        private String email;
    
        @NotNull(groups = {AddGroup.class,UpdateGroup.class})
        @Range(min = 18, max = 80, groups = {AddGroup.class,UpdateGroup.class})
        private Integer age;
    }
    
    
    • 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
    2.5.3 重构UserController方法
    package com.atguigu.controller;
    
    import com.atguigu.group.AddGroup;
    import com.atguigu.group.UpdateGroup;
    import com.atguigu.pojo.User;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.validation.BindingResult;
    import org.springframework.validation.FieldError;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import java.util.ArrayList;
    import java.util.List;
    
    @Controller
    @RequestMapping("/user")
    public class UserController {
    
        @RequestMapping("/add")
        // add方法使用AddGroup分组的校验
        public String add(@Validated(AddGroup.class) User user, BindingResult bindingResult, Model model){
            List<String > fieldNameList = new ArrayList();
            if (bindingResult.hasErrors()){
                List<FieldError> fieldErrors = bindingResult.getFieldErrors();
                for (FieldError fieldError : fieldErrors) {
                    String fieldName = fieldError.getField();
                    String defaultMessage = fieldError.getDefaultMessage();
                    fieldNameList.add(fieldName);
                }
                model.addAttribute("errorFieldNames", fieldNameList);
                return "bind-error";
            }
            System.out.println("user add = " + user);
            return "target";
        }
    
        @RequestMapping("/update")
        // update方法使用UpdateGroup分组的校验
        public String update(@Validated(UpdateGroup.class) User user){
            System.out.println("user update = " + user);
            return "target";
        }
    }
    
    
    • 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
    2.5.4 测试效果
    2.5.4.1 add方法传入id参数 不能通过校验

    在这里插入图片描述

    2.5.4.1 update方法传入id参数 通过校验

    在这里插入图片描述

    第四章 异常映射

    1. 为什么需要异常映射

    一个项目中会包含很多个模块,各个模块需要分工完成。如果张三负责的模块按照 A 方案处理异常,李四负责的模块按照 B 方法处理异常……各个模块处理异常的思路、代码、命名细节都不一样,那么就会让整个项目非常混乱。

    异常映射可以将异常类型和某个具体的视图关联起来,建立映射关系。好处是可以通过 SpringMVC 框架来帮助我们管理异常。

    • 声明式管理异常:在配置文件中指定异常类型和视图之间的对应关系。在配置文件或注解类中统一管理。
    • 编程式管理异常:需要我们自己手动 try … catch … 捕获异常,然后再手动跳转到某个页面。

    2. 异常映射的优势

    • 使用声明式代替编程式来实现异常管理
      • 让异常控制和核心业务解耦,二者各自维护,结构性更好
    • 整个项目层面使用同一套规则来管理异常
      • 整个项目代码风格更加统一、简洁
      • 便于团队成员之间的彼此协作

    3. 基于 XML 的异常映射

    3.1 XML配置

    SpringMVC 会根据异常映射信息,在捕获到指定异常对象后,将异常对象存入请求域,然后转发到和异常类型关联的视图。

    
    <bean id="exceptionResolver"
          class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver">
    
        
        <property name="exceptionMappings">
            <props>
                
                
                <prop key="java.lang.ArithmeticException">error-arithprop>
    
                <prop key="java.lang.ClassNotFoundException">error-classprop>
    
                <prop key="java.lang.RuntimeException">error-runtimeprop>
            props>
        property>
    bean>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    3.2 异常范围

    如果在配置文件中,发现有多个匹配的异常类型,那么 SpringMVC 会采纳范围上最接近的异常映射关系。

    <prop key="java.lang.ArithmeticException">error-arithprop>
    <prop key="java.lang.RuntimeException">error-runtimeprop>
    
    • 1
    • 2

    4. 基于注解的异常映射

    4.1 创建异常处理器类

    在这里插入图片描述

    4.2 异常处理器加入IOC容器
    4.2.1 包扫描
    
    <context:component-scan base-package="com.atguigu"/>
    
    • 1
    • 2
    4.2.2 给异常处理器类标记注解和处理异常的方法
    package com.atguigu.handler;
    
    import org.springframework.validation.BindException;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    
    import java.util.List;
    
    @ControllerAdvice(basePackages = "com.atguigu.controller")
    public class AtguiguExceptionHandler {
        // @ExceptionHandler注解:标记异常处理方法
        // value属性:指定匹配的异常类型 value可以不写
        // 异常类型的形参:SpringMVC 捕获到的异常对象
        @ExceptionHandler(RuntimeException.class)
        public String handlerRuntimeException(Exception e){
            // 记录异常信息
            System.out.println("记录日志:" + e);
    
            return "error";
        }
    
        
        // 标记匹配的异常类型是数据绑定异常类型
        @ExceptionHandler(BindException.class)
        public String handlerBindingException(BindException e){
            // 记录异常信息
            List<FieldError> fieldErrors = e.getFieldErrors();
            for (FieldError fieldError : fieldErrors) {
                System.out.println("记录日志:" + fieldError.getField() + "字段:" + fieldError.getDefaultMessage());
    
            }
            // 同步请求跳转报错页面
            return "error";
        }
    }
    
    
    • 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
    4.3 处理器方法
    package com.atguigu.controller;
    
    import com.atguigu.pojo.Product;
    import org.springframework.stereotype.Controller;
    import org.springframework.validation.annotation.Validated;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    @Controller
    @RequestMapping("/hello")
    public class HelloController {
    
        @RequestMapping("/sayHello")
        public String sayHello(){
            System.out.println("hello world");
            int num = 10 / 0;
            return "target";
        }
    
        public String sayXixi(@Validated Product product){
            System.out.println("product = " + product);
            return "target";
        }
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    当同一个异常类型在基于 XML 和注解的配置中都能够找到对应的映射,那么以注解为准。

    4.4 测试效果 测试sayHello和sayXixi方法报错是否使用了异常处理器

    在这里插入图片描述

    在这里插入图片描述

    5. 区分请求类型

    5.1 为什么要区分请求类型

    异常处理机制和拦截器机制都面临这样的问题:
    在这里插入图片描述

    5.2 判断依据

    查看请求消息头中是否包含 Ajax 请求独有的特征:

    • Accept 请求消息头:包含 application/json
    • X-Requested-With 请求消息头:包含 XMLHttpRequest

    两个条件满足一个即可。

    5.3 重构异常处理器兼容两种请求的处理方法
    package com.atguigu.handler;
    
    import com.atguigu.result.Result;
    import com.fasterxml.jackson.core.JsonProcessingException;
    import com.fasterxml.jackson.databind.ObjectMapper;
    import org.springframework.validation.BindException;
    import org.springframework.validation.FieldError;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.bind.annotation.ExceptionHandler;
    
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.io.IOException;
    import java.util.List;
    
    @ControllerAdvice(basePackages = "com.atguigu.controller")
    public class AtguiguExceptionHandler {
        // @ExceptionHandler注解:标记异常处理方法
        // value属性:指定匹配的异常类型 value可以不写
        // 异常类型的形参:SpringMVC 捕获到的异常对象
        @ExceptionHandler(RuntimeException.class)
        public String handlerRuntimeException(HttpServletRequest request, HttpServletResponse response, Exception e) throws IOException {
            // 记录异常信息
            System.out.println("记录日志:" + e);
            // 判断是同步请求还是异步请求
            if (request.getHeader("accept").contains("application/json")){
                // 异步请求
                // 将result对象转成json响应给客户端
                response.getWriter().write(new ObjectMapper().writeValueAsString(Result.fail()));
                return null;
            }
            // 同步请求 跳转到错误页面
            return "error";
        }
    
    
        // 标记匹配的异常类型是数据绑定异常类型
        @ExceptionHandler(BindException.class)
        public String handlerBindingException(HttpServletRequest request,HttpServletResponse response,BindException e) throws IOException {
            // 记录异常信息
            List<FieldError> fieldErrors = e.getFieldErrors();
            for (FieldError fieldError : fieldErrors) {
                System.out.println("记录日志:" + fieldError.getField() + "字段:" + fieldError.getDefaultMessage());
            }
            
            // 判断是同步请求还是异步请求
            if (request.getHeader("accept").contains("application/json")){
                // 异步请求
                // 将result对象转成json响应给客户端
                response.getWriter().write(new ObjectMapper().writeValueAsString(Result.fail()));
                return null;
            }
            // 同步请求跳转报错页面
            return "error";
        }
    }
    
    
    • 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
    5.4 测试效果
    5.4.1 sayHello方法测试同步请求

    在这里插入图片描述

    5.4.2 sayHello方法测试异步请求 accept设为application/json

    在这里插入图片描述

    5.4.3 sayXixi方法测试同步请求

    在这里插入图片描述

    5.4.4 sayXixi方法测试异步请求 accept设为application/json

    在这里插入图片描述

    第五章 文件上传

    1.前端表单

    需要满足的要求:

    • 第一点:请求方式必须是 POST
    • 第二点:请求体的编码方式必须是 multipart/form-data(通过 form 标签的 enctype 属性设置)
    • 第三点:使用 input 标签、type 属性设置为 file 来生成文件上传框
    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>首页title>
    head>
    <body>
    <h1>文件上传h1>
    <form enctype="multipart/form-data" th:action="@{/file/upload}" method="post">
        请选择你想上传的文件:<input type="file" name="icon"> <br>
        请输入文件描述:<input type="text" name="desc"> <br>
        <button>提交button>
    form>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.SpringMVC环境

    2.1 映入依赖
        
        <dependency>
          <groupId>commons-fileuploadgroupId>
          <artifactId>commons-fileuploadartifactId>
          <version>1.3.1version>
        dependency>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    2.2 配置

    在 SpringMVC 的配置文件中加入 multipart 类型数据的解析器:

        
        <bean id="multipartResolver"
              class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
            
            <property name="defaultEncoding" value="UTF-8"/>
            
            <property name="maxUploadSize" value="8388608"/>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3 创建FileUtil工具类

    package com.atguigu.utils;
    
    import java.util.Calendar;
    import java.util.Random;
    import java.util.UUID;
    
    
    public class FileUtil {
        /**
         * 随机生成唯一的文件名
         * @param originalFileName
         * @return
         */
        public static String getUUIDName(String originalFileName){
            //1. 获取文件名的后缀
            String suffix = originalFileName.substring(originalFileName.lastIndexOf("."));
            //2. 使用UUID生成一个唯一的字符串
            String str = UUID.randomUUID().toString().replaceAll("-","");
    
            //返回唯一的文件名
            return str + suffix;
        }
    
        /**
         * 生成随机目录的方法
         * @param dirPathLevel
         * @param dirPathNameSize
         * @return
         */
        public static String getRandomDirPath(int dirPathLevel,int dirPathNameSize){
            StringBuilder dirPath = new StringBuilder();
            //我们随机生成的目录就从这个字符串中随机取俩字母
            String source = "abcde0123456789";
            Random random = new Random();
            for (int j=0;j < dirPathLevel;j++) {
                for (int i = 0; i < dirPathNameSize; i++) {
                    //循环两次,每次取出一个字符作为目录名
                    int index = random.nextInt(source.length());
                    dirPath.append(source.charAt(index));
                }
                dirPath.append("/");
            }
            return dirPath.toString();
        }
    
        /**
         * 按照日期生成随机目录:我们每天上传的内容放到一个目录中
         * @return
         */
        public static String getDateDirPath(){
            StringBuilder dirPath = new StringBuilder();
            //获取当前时间:获取当前的年、月、日就可以生成目录路径
            Calendar calendar = Calendar.getInstance();
    
            int year = calendar.get(Calendar.YEAR);
            dirPath.append(year+"/");
    
            //获取到的月是0-11,而真正的月是1-12
            int month = calendar.get(Calendar.MONTH) + 1;
            if (month < 10){
                dirPath.append("0"+month+"/");
            }else {
                dirPath.append(month+"/");
            }
    
            //获取日
            int day = calendar.get(Calendar.DAY_OF_MONTH);
            if(day < 10){
                dirPath.append("0" + day + "/");
            }else {
                dirPath.append(day + "/");
            }
            return dirPath.toString();
        }
    
        public static void main(String[] args) {
            System.out.println(getDateDirPath());
        }
    }
    
    
    • 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
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80

    4 创建FileController处理类接收数据

    package com.atguigu.controller;
    
    import com.atguigu.utils.FileUtil;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RequestParam;
    import org.springframework.web.multipart.MultipartFile;
    
    import javax.servlet.ServletContext;
    import java.io.File;
    import java.io.IOException;
    
    @Controller
    @RequestMapping("/file")
    public class FileController {
    
        @Autowired
        private ServletContext servletContext;
    
        @RequestMapping("/upload")
        public String upload(@RequestParam("desc") String desc,
                             @RequestParam("icon")MultipartFile multipartFile) throws IOException {
            System.out.println("desc = " + desc);
            System.out.println("multipartFile = " + multipartFile);
    
            // 将客户端上传的文件保存起来
            // 1.获取上传时候的文件名
            String originalFilename = multipartFile.getOriginalFilename();
    
            // 文件上传问题1:文件重名问题
            // 2.我们要针对每一个上传的文件给其一个唯一的文件名,但是要保证后缀不变(UUID)
            String uuidName = FileUtil.getUUIDName(originalFilename);
    
            // 文件上传问题2:所有文件都上传同一个目录,不好管理 最少弄两级目录(随机生成或者按照日期生成)
            String randomDirPath = FileUtil.getRandomDirPath(2, 2);
    
            // 方式一:将其保存到当前电脑的D:\\upload中
            // 创建表示目录的file对象
    /*
            File dirFile = new File("D:\\upload");
            // 判断是否存在,如果不存在则创建这个目录
            if (!dirFile.exists()){
                dirFile.mkdirs();
            }
            // 将该文件转存到目录中
            multipartFile.transferTo(new File(dirFile,originalFilename));
    */
    
            // 方式二:使用servletContext获取项目部署路径
            // 动态获取项目部署路径中的upload文件夹
            String dirPath = servletContext.getRealPath(randomDirPath);
            File dirFile = new File(dirPath);
            // 判断是否存在,如果不存在则创建这个目录
            if (!dirFile.exists()){
                dirFile.mkdirs();
            }
            // 将该文件转存到目录中
            multipartFile.transferTo(new File(dirFile,originalFilename));
    
            return "target";
        }
    }
    
    
    • 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
    • 60
    • 61
    • 62
    • 63
    • 64

    5 测试效果

    上传文件

    在这里插入图片描述

    上传到部署目录中了
    在这里插入图片描述

    6. MultipartFile接口介绍

    在这里插入图片描述

    6.1 底层原理

    在这里插入图片描述

    6.2 三种去向
    6.2.1 本地转存

    在这里插入图片描述

    6.2.1.3 缺陷
    • Web 应用重新部署时通常都会清理旧的构建结果,此时用户以前上传的文件会被删除,导致数据丢失。
    • 项目运行很长时间后,会导致上传的文件积累非常多,体积非常大,从而拖慢 Tomcat 运行速度。
    • 当服务器以集群模式运行时,文件上传到集群中的某一个实例,其他实例中没有这个文件,就会造成数据不一致。
    • 不支持动态扩容,一旦系统增加了新的硬盘或新的服务器实例,那么上传、下载时使用的路径都需要跟着变化,导致 Java 代码需要重新编写、重新编译,进而导致整个项目重新部署。

    在这里插入图片描述

    6.2.2 文件服务器

    在这里插入图片描述

    6.2.2.1 优势
    • 不受 Web 应用重新部署影响
    • 在应用服务器集群环境下不会导致数据不一致
    • 针对文件读写进行专门的优化,性能有保障
    • 能够实现动态扩容

    在这里插入图片描述

    5.2.2.2 常见的文件服务器类型
    • 第三方平台:
      • 阿里的 OSS 对象存储服务
      • 七牛云
    • 自己搭建服务器:FastDFS等
    5.2.3 上传到其他模块(了解)

    这种情况肯定出现在分布式架构中,常规业务功能不会这么做,采用这个方案的一定的特殊情况。

    在这里插入图片描述

    第六章 文件下载

    1. 原始形态

    使用链接地址指向要下载的文件。此时浏览器会尽可能解析对应的文件,只要是能够在浏览器窗口展示的,就都会直接显示,而不是提示下载。

    <a href="download/hello.atguigu">下载a><br/>
    <a href="download/tank.jpg">下载a><br/>
    <a href="download/chapter04.zip">下载a><br/>
    
    • 1
    • 2
    • 3

    上面例子中,只有 chapter04.zip 文件是直接提示下载的,其他两个都是直接显示。

    2.明确要求浏览器提示下载

    2.1 前端代码
    DOCTYPE html>
    <html lang="en" xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <title>首页title>
    head>
    <body>
    <h1>文件上传h1>
    <form enctype="multipart/form-data" th:action="@{/file/upload}" method="post">
        请选择你想上传的文件:<input type="file" name="icon"> <br>
        请输入文件描述:<input type="text" name="desc"> <br>
        <button>提交button>
    
    form>
    
        <h1>文件下载h1>
        <a th:href="@{/file/download(fileName='abc.7z')}">下载abc.7za> <br>
        <a th:href="@{/file/download(fileName='demo.txt')}">下载demo.txta> <br>
        <a th:href="@{/file/download(fileName='timg.jpg')}">下载timg.jpga> <br>
    body>
    html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    2.2 处理器方法
        @RequestMapping("/download")
        public ResponseEntity download(@RequestParam("fileName") String fileName) throws IOException {
            // 1.使用字节输入流读取要下载的文件
            InputStream is = servletContext.getResourceAsStream("static/download/" + fileName);
            // 2.将字节输入流中的所有字节读到byte[]
            // is.available() 获取字节输入流中的字节数
            byte[] buffer = new byte[is.available()];
            is.read(buffer);
            // 3.响应:响应行、头、体
            HttpHeaders headers = new HttpHeaders();
            //Content-Disposition:响应头是指示客户端下载内容
            headers.add("content-disposition", "attachment;filename=" + fileName);
            // 获取要下载的那个文件的媒体类型(mime-type)
            String mimeType = servletContext.getMimeType(fileName);
            headers.add("content-type", mimeType);
    
            // 4.使用输出流输出
            return new ResponseEntity(buffer, headers, HttpStatus.OK);
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    3. 典型应用场景举例

    我们目前实现的是一个较为简单的下载,可以用在下面的一些场合:

    • 零星小文件下载
    • 将系统内部的数据导出为 Excel、PDF 等格式,然后以下载的方式返回给用户
  • 相关阅读:
    嵌入式系统中的加密性能:第2部分
    Flink 命令行参数介绍
    LayerNorm的图是不是画错了
    小张刷力扣--第三十五天
    leetcode 606. 根据二叉树创建字符串
    JAVA计算机毕业设计电子商城系统Mybatis+源码+数据库+lw文档+系统+调试部署
    python中的range()函数详解
    机器学习总结(二)
    网站/顶会/工作组
    Kubernetes 能取代 Docker吗?
  • 原文地址:https://blog.csdn.net/Libra_97/article/details/127955108