Spring Web MVC其他扩展(详解下)

文章目录

  • Spring MVC其他扩展(下)
    • 异常处理
      • 异常处理机制
      • 声明式异常好处
      • 基于注解异常声明异常处理
    • 拦截器
      • 拦截器概念
      • 拦截器使用
      • 拦截器作用位置图解
      • 拦截器案例
      • 拦截器工作原理源码
    • 参数校验
      • 校验概述
      • 操作演示
      • SpringMVC自定义参数验证
      • ValueObject(VO)
    • 文件上传和下载
      • 文件上传
      • 文件下载

Spring MVC其他扩展(下)

异常处理

异常处理机制

异常处理概念

开发过程中是不可避免地会出现各种异常情况的,例如网络连接异常、数据格式异常、空指针异常等等。异常的出现可能导致程序的运行出现问题,甚至直接导致程序崩溃。因此,在开发过程中,合理处理异常、避免异常产生、以及对异常进行有效的调试是非常重要的。

对于异常的处理,一般分为两种方式:

  • 编程式异常处理:是指在代码中显式地编写处理异常的逻辑。它通常涉及到对异常类型的检测及其处理,例如使用 try-catch 块来捕获异常,然后在 catch 块中编写特定的处理代码,或者在 finally 块中执行一些清理操作。在编程式异常处理中,开发人员需要显式地进行异常处理,异常处理代码混杂在业务代码中,导致代码可读性较差。
  • 声明式异常处理:则是将异常处理的逻辑从具体的业务逻辑中分离出来,通过配置等方式进行统一的管理和处理。在声明式异常处理中,开发人员只需要为方法或类标注相应的注解(如 @Throws@ExceptionHandler),就可以处理特定类型的异常。相较于编程式异常处理,声明式异常处理可以使代码更加简洁、易于维护和扩展。

站在宏观角度来看待声明式事务处理:

整个项目从架构这个层面设计的异常处理的统一机制和规范。

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

使用声明式异常处理,可以统一项目处理异常思路,项目更加清晰明了!

声明式异常好处

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

基于注解异常声明异常处理

声明异常处理控制器类

异常处理控制类,统一定义异常处理handler方法!

/**
 * projectName: com.gj.execptionhandler
 * 
 * description: 全局异常处理器,内部可以定义异常处理Handler!
 */

/**
 * @RestControllerAdvice = @ControllerAdvice + @ResponseBody
 * @ControllerAdvice 代表当前类的异常处理controller! 
 */
@RestControllerAdvice
public class GlobalExceptionHandler {

}

声明异常处理hander方法

异常处理handler方法和普通的handler方法参数接收和响应都一致!

只不过异常处理handler方法要映射异常,发生对应的异常会调用!

普通的handler方法要使用@RequestMapping注解映射路径,发生对应的路径调用!

/**
 * 异常处理handler 
 * @ExceptionHandler(HttpMessageNotReadableException.class) 
 * 该注解标记异常处理Handler,并且指定发生异常调用该方法!
 * 
 * 
 * @param e 获取异常对象!
 * @return 返回handler处理结果!
 */
@ExceptionHandler(HttpMessageNotReadableException.class)
public Object handlerJsonDateException(HttpMessageNotReadableException e){
    
    return null;
}

/**
 * 当发生空指针异常会触发此方法!
 * @param e
 * @return
 */
@ExceptionHandler(NullPointerException.class)
public Object handlerNullException(NullPointerException e){

    return null;
}

/**
 * 所有异常都会触发此方法!但是如果有具体的异常处理Handler! 
 * 具体异常处理Handler优先级更高!
 * 例如: 发生NullPointerException异常!
 *       会触发handlerNullException方法,不会触发handlerException方法!
 * @param e
 * @return
 */
@ExceptionHandler(Exception.class)
public Object handlerException(Exception e){

    return null;
}

配置文件扫描控制器类配置

确保异常处理控制类被扫描

 <!-- 扫描controller对应的包,将handler加入到ioc-->
 <context:component-scan base-package="com.gj.controller,
    com.gj.exceptionhandler" />

拦截器

拦截器概念

拦截器和过滤器解决问题

  • 生活中

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

  • 程序中

    在程序中,使用拦截器在请求到达具体 handler 方法前,统一执行检测
    在这里插入图片描述

拦截器 VS 过滤器:

  • 相似点
    • 拦截:必须先把请求拦住,才能执行后续操作
    • 过滤:拦截器或过滤器存在的意义就是对请求进行统一处理
    • 放行:对请求执行了必要操作后,放请求过去,让它访问原本想要访问的资源
  • 不同点
    • 工作平台不同
      • 过滤器工作在 Servlet 容器中
      • 拦截器工作在 SpringMVC 的基础上
    • 拦截的范围
      • 过滤器:能够拦截到的最大范围是整个 Web 应用
      • 拦截器:能够拦截到的最大范围是整个 SpringMVC 负责的请求
    • IOC 容器支持
      • 过滤器:想得到 IOC 容器需要调用专门的工具方法,是间接的
      • 拦截器:它自己就在 IOC 容器中,所以可以直接从 IOC 容器中装配组件,也就是可以直接得到 IOC 容器的支持

选择:

功能需要如果用 SpringMVC 的拦截器能够实现,就不使用过滤器。
在这里插入图片描述

拦截器使用

创建拦截器类

public class Process01Interceptor implements HandlerInterceptor {

    // 在处理请求的目标 handler 方法前执行
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        System.out.println("request = " + request + ", response = " + response + ", handler = " + handler);
        System.out.println("Process01Interceptor.preHandle");
         
        // 返回true:放行
        // 返回false:不放行
        return true;
    }
 
    // 在目标 handler 方法之后,handler报错不执行!
    @Override
    public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
        System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", modelAndView = " + modelAndView);
        System.out.println("Process01Interceptor.postHandle");
    }
 
    // 渲染视图之后执行(最后),一定执行!
    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", ex = " + ex);
        System.out.println("Process01Interceptor.afterCompletion");
    }
}

单个拦截器执行顺序:

  • preHandle() 方法
  • 目标 handler 方法
  • postHandle() 方法
  • 渲染视图(返回json没有此步骤)
  • afterCompletion() 方法

拦截器配置

springmvc.xml

<!-- 配置拦截器-->
<mvc:interceptors>
  <!-- 默认拦截器,拦截所有请求-->
  <bean class="com.gj.interceptor.Process01Interceptor" />
</mvc:interceptors>

配置详解

默认拦截全部

<!-- 具体配置拦截器可以指定拦截的请求地址 -->
<mvc:interceptor>
    <!-- 精确匹配 -->
    <mvc:mapping path="/common/request/one"/>
    <bean class="com.gj.mvc.interceptor.Process03Interceptor"/>
</mvc:interceptor>

精准配置

<!-- 具体配置拦截器可以指定拦截的请求地址 -->
<mvc:interceptor>
    <!-- 精确匹配 -->
    <mvc:mapping path="/common/request/one"/>
    <bean class="com.gj.mvc.interceptor.Process03Interceptor"/>
</mvc:interceptor>

<mvc:interceptor>
    <!-- /*匹配路径中的一层 -->
    <mvc:mapping path="/common/request/*"/>
    <bean class="com.gj.mvc.interceptor.Process04Interceptor"/>
</mvc:interceptor>

<mvc:interceptor>
    <!-- /**匹配路径中的多层 -->
    <mvc:mapping path="/common/request/**"/>
    <bean class="com.gj.mvc.interceptor.Process05Interceptor"/>
</mvc:interceptor>

排除配置

<mvc:interceptor>
    <!-- /**匹配路径中的多层 -->
    <mvc:mapping path="/common/request/**"/>

    <!-- 使用 mvc:exclude-mapping 标签配置不拦截的地址 -->
    <mvc:exclude-mapping path="/common/request/two/bbb"/>

    <bean class="com.gj.mvc.interceptor.Process05Interceptor"/>
</mvc:interceptor>

多个拦截器执行顺序

  • preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置顺序调用各个 preHandle() 方法。
  • postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 postHandle() 方法。
  • afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 afterCompletion() 方法。

拦截器作用位置图解

在这里插入图片描述

拦截器案例

一个网站有 56个资源,其中一个为登陆资源,两个无须登录即可访问,另外三个需要登录后才能访问。如果不登录就访问那三个资源,需要拦截,并且提示登录后访问访问!

提示:登陆为模拟登陆,存储一个user可以到session即可!

访问资源的请求地址可参考:

  • 登陆资源:/public/resource/login
  • 公共资源1:/public/resource/one
  • 公共资源2:/public/resouce/two
  • 私密资源1:/private/resouce/one
  • 私密资源2:/private/resouce/two
  • 私密资源3:/private/resouce/three

案例实现:

声明资源类

/**
 * projectName: com.gj.controller
 * description: 公有资源控制类
 */
@RestController
@RequestMapping("public/resource")
public class PublicController {
    /**
     * 模拟登录,将假用户数据存储到session中!
     */
    @GetMapping("login")
    public Object login(HttpSession session){
        session.setAttribute("user","root");
        return "login success!!";
    }

    @GetMapping("one")
    public Object one(){
        return "public one";
    }

    @GetMapping("two")
    public Object two(){
        return "public two";
    }
}

PrivateController

@RestController
@RequestMapping("private/resource")
public class PrivateController {
    
    @GetMapping("one")
    public Object one(){
        return "private one";
    }
    
    @GetMapping("two")
    public Object two(){
        return "private two";
    }

    @GetMapping("three")
    public Object three(){
        return "private two";
    }
}

声明拦截器类

/**
 * projectName: com.gj.interceptor
 *
 * description: 登录保护拦截器
 */
public class LoginProtectInterceptor implements HandlerInterceptor {

    /**
     * 登录保护方法
     * @param request current HTTP request
     * @param response current HTTP response
     * @param handler chosen handler to execute, for type and/or instance evaluation
     * @return
     * @throws Exception
     */
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

        Object user = request.getSession().getAttribute("user");

        if (user == null){
            response.setContentType("text/html;charset=utf-8");
            //没有登录
            response.getWriter().print("请先登录,再访问! <a href='/public/resource/login'>点击此处登录</a>");
            //拦截,不到达目标地址
            return false;
        }
        return true;
    }
}

配置拦截器类

<!-- 配置拦截器-->
<mvc:interceptors>
   <mvc:interceptor>
       <mvc:mapping path="/private/**"/>
       <bean class="com.gj.interceptor.LoginProtectInterceptor" />
   </mvc:interceptor>
</mvc:interceptors>

拦截器工作原理源码

springMVC断点入口
在这里插入图片描述在这里插入图片描述

preHandle()正序执行
在这里插入图片描述

postHandle()倒序执行
在这里插入图片描述

afterCompletion()倒序执行
在这里插入图片描述

参数校验

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

校验概述

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 在完成数据绑定后执行数据校验的工作。

操作演示

导入依赖

<!-- 校验注解 -->
<dependency>
    <groupId>jakarta.platform</groupId>
    <artifactId>jakarta.jakartaee-web-api</artifactId>
    <version>9.1.0</version>
    <scope>provided</scope>
</dependency>
        
<!-- 校验注解实现-->        
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>8.0.0.Final</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator-annotation-processor</artifactId>
    <version>8.0.0.Final</version>
</dependency>

应用校验注解

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import org.hibernate.validator.constraints.Length;

/**
 * projectName: com.gj.pojo
 */
public class User {
    //age   1 <=  age < = 150
    @Min(10)
    private int age;

    //name 3 <= name.length <= 6
    @Length(min = 3,max = 10)
    private String name;

    //email 邮箱格式
    @Email
    private String email;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

handler标记和绑定错误收集

@RestController
@RequestMapping("user")
public class UserController {

    /**
     * @Validated 代表应用校验注解! 必须添加!
     */
    @PostMapping("save")
    public Object save(@Validated @RequestBody User user,
                       //在实体类参数和 BindingResult 之间不能有任何其他参数, BindingResult可以接受错误信息,避免信息抛出!
                       BindingResult result){
       //判断是否有信息绑定错误! 有可以自行处理!
        if (result.hasErrors()){
            System.out.println("错误");
            String errorMsg = result.getFieldError().toString();
            return errorMsg;
        }
        //没有,正常处理业务即可
        System.out.println("正常");
        return user;
    }
}

测试效果
在这里插入图片描述

易混总结

@NotNull、@NotEmpty、@NotBlank 都是用于在数据校验中检查字段值是否为空的注解,但是它们的用法和校验规则有所不同。

  • @NotNull (包装类型不为null)

    @NotNull 注解是 JSR 303 规范中定义的注解,当被标注的字段值为 null 时,会认为校验失败而抛出异常。该注解不能用于字符串类型的校验,若要对字符串进行校验,应该使用 @NotBlank 或 @NotEmpty 注解。

  • @NotEmpty (集合类型长度大于0)

    @NotEmpty 注解同样是 JSR 303 规范中定义的注解,对于 CharSequence、Collection、Map 或者数组对象类型的属性进行校验,校验时会检查该属性是否为 Null 或者 size()==0,如果是的话就会校验失败。但是对于其他类型的属性,该注解无效。需要注意的是只校验空格前后的字符串,如果该字符串中间只有空格,不会被认为是空字符串,校验不会失败。

  • @NotBlank (字符串,不为null,且不为" "字符串)

    @NotBlank 注解是 Hibernate Validator 附加的注解,对于字符串类型的属性进行校验,校验时会检查该属性是否为 Null 或 “” 或者只包含空格,如果是的话就会校验失败。需要注意的是,@NotBlank 注解只能用于字符串类型的校验。
    总之,这三种注解都是用于校验字段值是否为空的注解,但是其校验规则和用法有所不同。在进行数据校验时,需要根据具体情况选择合适的注解进行校验。

SpringMVC自定义参数验证

定义注解

package com.gj.annotation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;

import java.lang.annotation.*;

@Documented
@Constraint(
    validatedBy = {GenderValidate.class}
)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.ANNOTATION_TYPE, ElementType.CONSTRUCTOR, ElementType.PARAMETER, ElementType.TYPE_USE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Gender {

    String message() default "性别只能是男或女!";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};

}

定义注解验证规则

package com.gj.annotation;

import jakarta.validation.ConstraintValidator;
import jakarta.validation.ConstraintValidatorContext;

/**
 * @Author zhangchunsheng
 * @CreateTime: 2024/11/20
 */
public class GenderValidate implements ConstraintValidator<Gender,String> {
    @Override
    public void initialize(Gender constraintAnnotation) {
        ConstraintValidator.super.initialize(constraintAnnotation);
    }

    @Override
    public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
        return value.equals("男") || value.equals("女");
    }
}

ValueObject(VO)

VO:ValueObject通常用于业务层和表示层之间的数据传输。VO对象通常包含用户界面所需的数据。

package com.gj.pojo;

import com.atguigu.annotation.Gender;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import org.hibernate.validator.constraints.Length;

/**
 * @Author zhangchunsheng
 * @CreateTime: 2024/11/20
 */
public class Student {

    private Integer stuId;
    private String stuName;
    private Integer stuAge;
    private String stuEmail;
    private String stuGender;

    @Override
    public String toString() {
        return "Student{" +
                "stuId=" + stuId +
                ", stuName='" + stuName + '\'' +
                ", stuAge=" + stuAge +
                ", stuEmail='" + stuEmail + '\'' +
                ", stuGender='" + stuGender + '\'' +
                '}';
    }

    public String getStuGender() {
        return stuGender;
    }

    public void setStuGender(String stuGender) {
        this.stuGender = stuGender;
    }

    public Integer getStuId() {
        return stuId;
    }

    public void setStuId(Integer stuId) {
        this.stuId = stuId;
    }

    public String getStuName() {
        return stuName;
    }

    public void setStuName(String stuName) {
        this.stuName = stuName;
    }

    public Integer getStuAge() {
        return stuAge;
    }

    public void setStuAge(Integer stuAge) {
        this.stuAge = stuAge;
    }

    public String getStuEmail() {
        return stuEmail;
    }

    public void setStuEmail(String stuEmail) {
        this.stuEmail = stuEmail;
    }

    public Student(Integer stuId, String stuName, Integer stuAge, String stuEmail, String stuGender) {
        this.stuId = stuId;
        this.stuName = stuName;
        this.stuAge = stuAge;
        this.stuEmail = stuEmail;
        this.stuGender = stuGender;
    }

    public Student(Integer stuId, String stuName, Integer stuAge, String stuEmail) {
        this.stuId = stuId;
        this.stuName = stuName;
        this.stuAge = stuAge;
        this.stuEmail = stuEmail;
    }

    public Student() {
    }
}
package com.gj.pojo.vo;

import com.atguigu.annotation.Gender;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import org.hibernate.validator.constraints.Length;

/**
 * @Author zhangchunsheng
 * @CreateTime: 2024/11/20
 */
public class StudentVO {

    @NotNull(message = "id不能为空!!!")
    private Integer stuId;
    @Length(min = 3, max = 6, message = "长度在3-6之间!!!")
    private String stuName;
    @Min(value = 18, message = "年龄最小18岁!!!")
    @Max(value = 120,message = "年龄最大120岁!!!")
    private Integer stuAge;
    @Email(message = "邮箱格式不正确!!!")
    private String stuEmail;
    @Gender(message = "请求输入正确的性别!")
    private String stuGender;

    //....

    @Override
    public String toString() {
        return "Student{" +
                "stuId=" + stuId +
                ", stuName='" + stuName + '\'' +
                ", stuAge=" + stuAge +
                ", stuEmail='" + stuEmail + '\'' +
                ", stuGender='" + stuGender + '\'' +
                '}';
    }

    public String getStuGender() {
        return stuGender;
    }

    public void setStuGender(String stuGender) {
        this.stuGender = stuGender;
    }

    public Integer getStuId() {
        return stuId;
    }

    public void setStuId(Integer stuId) {
        this.stuId = stuId;
    }

    public String getStuName() {
        return stuName;
    }

    public void setStuName(String stuName) {
        this.stuName = stuName;
    }

    public Integer getStuAge() {
        return stuAge;
    }

    public void setStuAge(Integer stuAge) {
        this.stuAge = stuAge;
    }

    public String getStuEmail() {
        return stuEmail;
    }

    public void setStuEmail(String stuEmail) {
        this.stuEmail = stuEmail;
    }

    public StudentVO(Integer stuId, String stuName, Integer stuAge, String stuEmail, String stuGender) {
        this.stuId = stuId;
        this.stuName = stuName;
        this.stuAge = stuAge;
        this.stuEmail = stuEmail;
        this.stuGender = stuGender;
    }

    public StudentVO(Integer stuId, String stuName, Integer stuAge, String stuEmail) {
        this.stuId = stuId;
        this.stuName = stuName;
        this.stuAge = stuAge;
        this.stuEmail = stuEmail;
    }

    public StudentVO() {
    }

}

文件上传和下载

文件上传

文件上传表单页面

位置:index.html

  • 第一点:请求方式必须是 POST
  • 第二点:请求体的编码方式必须是 multipart/form-data(通过 form 标签的 enctype 属性设置)
  • 第三点:使用 input 标签、type 属性设置为 file 来生成文件上传框
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
    <form action="/save/picture" method="post" enctype="multipart/form-data">
      昵称:<input type="text" name="nickName" value="龙猫" /><br/>
      头像:<input type="file" name="headPicture" /><br/>
      背景:<input type="file" name="backgroundPicture" /><br/>
      <button type="submit">保存</button>
    </form>
</body>
</html>

springmvc环境要求

pom.xml添加依赖

<!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
<dependency>
    <groupId>commons-fileupload</groupId>
    <artifactId>commons-fileupload</artifactId>
    <version>1.3.1</version>
</dependency>

配置文件上传处理器(springmvc配置)

<!-- 文件上传处理器,可处理 multipart/* 请求并将其转换为 MultipartFile 对象-->
<bean id="multipartResolver"
      class="org.springframework.web.multipart.support.StandardServletMultipartResolver">
</bean>

CommonsMultipartResolver的bean的id,必须是:multipartResolver
如果不是这个值,会在上传文件时报错
web.xml 文件中添加 Multipart 配置

<servlet>
    <servlet-name>yourAppServlet</servlet-name>
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
    <multipart-config>
        <!-- 定义文件上传时所需的最大值,单位为字节 -->
        <max-file-size>10485760</max-file-size>
        <!-- 定义单个上传文件的最大值,单位为字节 -->
        <max-request-size>20971520</max-request-size>
        <!-- 定义内存中存储文件的最大值,超过此大小的文件会写入到硬盘中 -->
        <file-size-threshold>5242880</file-size-threshold>
    </multipart-config>
    <load-on-startup>1</load-on-startup>
</servlet>

低版本web.xml约束文件,会爆红,不管担心,继续启动即可!

历史:Spring MVC 6之前,通常使用的是 CommonsMultipartResolver 来解析文件上传请求。但是在 Spring MVC 6中,此类已被移除,Spring 官方推荐使用 StandardServletMultipartResolverMockMultipartResolver 来替代。

handler方法接收数据

/**
 * 上传的文件使用 MultipartFile 类型接收其相关数据
 * @param nickName
 * @param picture
 * @param backgroundPicture
 * @return
 * @throws IOException
 */
@PostMapping ("picture")
public String upload(String nickName, @RequestParam("headPicture") MultipartFile picture, @RequestParam("backgroundPicture")MultipartFile backgroundPicture) throws IOException {
    System.out.println(nickName);
     String inputName = picture.getName();
    System.out.println("文件上传表单项的 name 属性值:" + inputName);

    // 获取这个数据通常都是为了获取文件本身的扩展名
    String originalFilename = picture.getOriginalFilename();
    System.out.println("文件在用户本地原始的文件名:" + originalFilename);

    String contentType = picture.getContentType();
    System.out.println("文件的内容类型:" + contentType);

    boolean empty = picture.isEmpty();
    System.out.println("文件是否为空:" + empty);

    long size = picture.getSize();
    System.out.println("文件大小:" + size);

    byte[] bytes = picture.getBytes();
    System.out.println("文件二进制数据的字节数组:" + Arrays.asList(bytes));

    InputStream inputStream = picture.getInputStream();
    System.out.println("读取文件数据的输入流对象:" + inputStream);

    Resource resource = picture.getResource();
    System.out.println("代表当前 MultiPartFile 对象的资源对象" + resource);
    return "home";
}

MultipartFile接口
在这里插入图片描述

文件转存

底层机制
在这里插入图片描述

本地转存
在这里插入图片描述

转存代码演示:

……
 
// 1、准备好保存文件的目标目录
// ①File 对象要求目标路径是一个物理路径(在硬盘空间里能够直接找到文件的路径)
// ②项目在不同系统平台上运行,要求能够自动兼容、适配不同系统平台的路径格式
//      例如:Window系统平台的路径是 D:/aaa/bbb 格式
//      例如:Linux系统平台的路径是 /ttt/uuu/vvv 格式
//      所以我们需要根据『不会变的虚拟路径』作为基准动态获取『跨平台的物理路径』
// ③虚拟路径:浏览器通过 Tomcat 服务器访问 Web 应用中的资源时使用的路径
String destFileFolderVirtualPath = "/head-picture";
 
// ④调用 ServletContext 对象的方法将虚拟路径转换为真实物理路径
String destFileFolderRealPath = servletContext.getRealPath(destFileFolderVirtualPath);
 
// 2、生成保存文件的文件名
// ①为了避免同名的文件覆盖已有文件,不使用 originalFilename,所以需要我们生成文件名
// ②我们生成文件名包含两部分:文件名本身和扩展名
// ③声明变量生成文件名本身
String generatedFileName = UUID.randomUUID().toString().replace("-","");
 
// ④根据 originalFilename 获取文件的扩展名
String fileExtname = originalFilename.substring(originalFilename.lastIndexOf("."));
 
// ⑤拼装起来就是我们生成的整体文件名
String destFileName = generatedFileName + "" + fileExtname;
 
// 3、拼接保存文件的路径,由两部分组成
//      第一部分:文件所在目录
//      第二部分:文件名
String destFilePath = destFileFolderRealPath + "/" + destFileName;
 
// 4、创建 File 对象,对应文件具体保存的位置
File destFile = new File(destFilePath);
 
// 5、执行转存
picture.transferTo(destFile);
 
……

缺陷

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

文件服务器转存(推荐)
在这里插入图片描述

好处

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

文件服务器类型

  • 第三方平台:
    • 阿里的 OSS 对象存储服务
    • 七牛云
  • 自己搭建服务器:FastDFS 等

上传到其他模块

这种情况肯定出现在分布式架构中,常规业务功能不会这么做,采用这个方案的一定是特殊情况,这种情况极其少见。
在这里插入图片描述

在 MultipartFile 接口中有一个对应的方法:

/**
 * Return a Resource representation of this MultipartFile. This can be used
 * as input to the {@code RestTemplate} or the {@code WebClient} to expose
 * content length and the filename along with the InputStream.
 * @return this MultipartFile adapted to the Resource contract
 * @since 5.1
 */
default Resource getResource() {
  return new MultipartFileResource(this);
}

注释中说:这个 Resource 对象代表当前 MultipartFile 对象,输入给 RestTemplate 或 WebClient。而 RestTemplate 或 WebClient 就是用来在 Java 程序中向服务器端发出请求的组件。

文件下载

在 Spring MVC 中,ResponseEntity 是用于表示 HTTP 响应的一个类,它既能设置响应体的内容,也能设置响应头相关的信息。

ResponseEntity 可以封装一个 HTTP 响应,包括响应体、响应头和响应状态码等属性,并将其发送回客户端。它提供了一种灵活的方式来表示 HTTP 响应,可以用于处理 RESTful API、文件下载、异常处理等应用场景。

演示json数据返回:

@GetMapping("/users/{age}")
public ResponseEntity<User> getUser(@PathVariable("age") int age) {
    User user = new User();
    user.setAge(age);
    user.setEmail("test");
    user.setName("二狗子");
    return ResponseEntity.ok(user);
}

演示文件下载代码:

@Autowired
private ServletContext servletContext;

@RequestMapping("/download/file")
public ResponseEntity<byte[]> downloadFile() {

    // 1.获取要下载的文件的输入流对象
    // 这里指定的路径以 Web 应用根目录为基准
    InputStream inputStream = servletContext.getResourceAsStream("/images/mi.jpg");

    try {
        // 2.将要下载的文件读取到字节数组中
        // ①获取目标文件的长度
        int len = inputStream.available();

        // ②根据目标文件长度创建字节数组
        byte[] buffer = new byte[len];

        // ③将目标文件读取到字节数组中
        inputStream.read(buffer);

        // 3.封装响应消息头
        // ①创建MultiValueMap接口类型的对象,实现类是HttpHeaders
        MultiValueMap responseHeaderMap = new HttpHeaders();

        // ②存入下载文件所需要的响应消息头
        responseHeaderMap.add("Content-Disposition", "attachment; filename=mi.jpg");

        // ③创建ResponseEntity对象
        ResponseEntity<byte[]> responseEntity = new ResponseEntity<>(buffer, responseHeaderMap, HttpStatus.OK);

        // 4.返回responseEntity对象
        return responseEntity;
    } catch (IOException e) {
        e.printStackTrace();
    } finally {

        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

    }
    return null;
}

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

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

相关文章

【线上问题记录 | 排查网络连接问题】

问题描述 现在有我们程序是部署在服务器A的&#xff0c;A链接的是B。程序从B的redis进行存储和取数据的。 我们的业务是: 信息展示&#xff0c;也就是如果发现机器有异常了&#xff0c;实时进行监控。突然发现有一天&#xff0c;信息显示延迟了。 然后我们就开始排查究竟什么原…

如何保护LabVIEW程序免遭反编译

在正常情况下&#xff0c;LabVIEW程序&#xff08;即编译后的可执行文件或运行时文件&#xff0c;如 .exe 或 .llb&#xff09;无法直接被反编译出源码。然而&#xff0c;有一些需要特别注意的点&#xff1a; 1. LabVIEW的编译机制 LabVIEW编译器会将源码&#xff08;.vi文件&a…

求助:selenium.common.exceptions.SessionNotCreatedException: x x x

1.背景 想要使用python代码接管已打开的浏览器&#xff0c;减少重复登录或者selenium用例执行前的又臭又长的流程 2.报错截图 3.场景 目前是已开启浏览器调试模式且终端未关闭&#xff0c;执行上图中的代码后没有自动输入url且报错 4. 分析 我尝试了add_experimental_optio…

40分钟学 Go 语言高并发:【实战课程】工作池(Worker Pool)实现

工作池&#xff08;Worker Pool&#xff09;实战实现 一、知识要点概述 模块核心功能实现难点重要程度池化设计管理协程生命周期并发安全、资源控制⭐⭐⭐⭐⭐动态扩缩容根据负载调整池大小平滑扩缩、性能优化⭐⭐⭐⭐任务分发合理分配任务到worker负载均衡、任务优先级⭐⭐⭐…

Could not locate device support files.

报错信息&#xff1a;Failure Reason: The device may be running a version of iOS (13.6.1 17G80) that is not supported by this version of Xcode.[missing string: 869a8e318f07f3e2f42e11d435502286094f76de] 问题&#xff1a;xcode15升级到xcode16之后&#xff0c;13.…

Ubantu系统docker运行成功拉取失败【成功解决】

解决docker运行成功拉取失败 失败报错 skysky-Legion-Y7000-IRX9:~$ docker run hello-world docker: permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Head “http://%2Fvar%2Frun%2Fdocker.sock/_ping”: dial uni…

git rebase-优雅合并与修改提交

文章目录 简介rebase用于合并使用rebase修改提交cherry-pick 简介 在Git核心概念图例与最常用内容操作(reset、diff、restore、stash、reflog、cherry-pick)中我们已经介绍了git的最常用实用的命令。 在上面说的那篇文章中&#xff0c;我们只是简单提了一下rebase。 是因为r…

TongRDS分布式内存数据缓存中间件

命令 优势 支持高达10亿级的数据缓冲&#xff0c;内存优化管理&#xff0c;避免GC性能劣化。 高并发系统设计&#xff0c;可充分利用多CPU资源实现并行处理。 数据采用key-value多索引方式存储&#xff0c;字段类型和长度可配置。 支持多台服务并行运行&#xff0c;服务之间可互…

【大数据学习 | Spark调优篇】Spark之内存调优

1. 内存的花费 1&#xff09;每个Java对象&#xff0c;都有一个对象头&#xff0c;会占用16个字节&#xff0c;主要是包括了一些对象的元信息&#xff0c;比如指向它的类的指针。如果一个对象本身很小&#xff0c;比如就包括了一个int类型的field&#xff0c;那么它的对象头实…

pageoffice最新版本浏览器点击没反应解决办法

一、问题现象 最新版本的谷歌、火狐浏览器&#xff0c;调用pageoffice时&#xff0c;点击后没反应&#xff08;旧的谷歌浏览器不受影响&#xff09;。 二、产生原因 服务器返回pageOffice的客户端唤起链接格式为&#xff1a; PageOffice://|http://192.168.1.120:8080/xxx …

知行合一:实践中的技术分享与学习

随着科技的不断发展&#xff0c;技术的更新迭代也在不断加速。在这个信息化、数字化的时代&#xff0c;技术人员之间的交流与合作显得尤为重要。为了帮助广大技术爱好者、从业者和专家们相互学习、分享经验、解决技术难题&#xff0c;涵盖了数据库、容器化技术、运维、研发、网…

使用经典的Java,还是拥抱新兴的Rust?

在当代互联网时代的企业级开发中&#xff0c;技术栈的选择往往牵动着每个团队的神经。随着Rust语言的崛起&#xff0c;许多开发团队开始重新思考&#xff1a;是继续坚持使用经典的Java&#xff0c;还是拥抱新兴的Rust&#xff1f;这个问题背后&#xff0c;折射出的是对技术演进…

【Qt】图片绘制不清晰的问题

背景 实现一个图片浏览器&#xff0c;可以支持放大/缩小查看图片。主要组件如下&#xff1a; // canvaswidget.h #ifndef CANVASWIDGET_H #define CANVASWIDGET_H#include <QWidget>class CanvasWidget : public QWidget {Q_OBJECT public:explicit CanvasWidget(QImag…

vscode 怎么下载 vsix 文件?

参考&#xff1a;https://marketplace.visualstudio.com/items?itemNameMarsCode.marscode-extension 更好的办法&#xff1a;直接去相关插件的 github repo 下载老版本 https://github.com/VSCodeVim/Vim/releases?page5 或者&#xff0c;去 open-vsx.org 下载老版本 点击这…

学习笔记043——HashMap源码学习1

文章目录 1、HashMap2、Hashtable3、TreeMap4、HashMap 底层结构4.1、什么是红黑树&#xff1f; 1、HashMap HashMap key 是不能重复的&#xff0c;value 可以重复 底层结构 key-value 进行存储&#xff0c;key-value 存入到 Set 中&#xff0c;再将 Set 装载到 HashMap pack…

火语言RPA流程组件介绍--键盘按键

&#x1f6a9;【组件功能】&#xff1a;模拟键盘按键 配置预览 配置说明 按键 点击后,在弹出的软键盘上选择需要的按键 执行后等待时间(ms) 默认值300,执行该组件后等待300毫秒后执行下一个组件. 输入输出 输入类型 万能对象类型(System.Object)输出类型 万能对象类型…

电子应用设计方案-30:智能扫地机器人系统方案设计

智能扫地机器人系统方案设计 一、引言 随着人们生活节奏的加快和对生活品质的追求&#xff0c;智能家居产品越来越受到消费者的青睐。智能扫地机器人作为一种能够自动清扫地面的智能设备&#xff0c;为人们节省了大量的时间和精力。本方案旨在设计一款功能强大、智能化程度高、…

从简单的自动化脚本到复杂的智能助手:Agent技术的实践与应用

现代软件开发中&#xff0c;Agent技术正在悄然改变着我们构建应用程序的方式。一个Agent就像是一个能独立完成特定任务的智能助手&#xff0c;它可以感知环境、作出决策并采取行动。让我们通过实际案例&#xff0c;深入了解如何运用Agent技术来构建智能系统。 想象你正在开发一…

Ubuntu Server 22.04.5 从零到一:详尽安装部署指南

文章目录 Ubuntu Server 22.04.5 从零到一&#xff1a;详尽安装部署指南一、部署环境二、安装系统2.1 安装2.1.1 选择安装方式2.1.2 选择语言2.1.3 选择不更新2.1.4 选择键盘标准2.1.5 选择安装版本2.1.6 设置网卡2.1.7 配置代理2.1.8 设置镜像源2.1.9 选择装系统的硬盘2.1.10 …

定时/延时任务-ScheduledThreadPoolExecutor的使用

文章目录 1. 概要2. 固定速率和固定延时2.1 固定速率2.2 固定延时 3. API 解释3.1 schedule3.2 固定延时 - scheduleWithFixedDelay3.2 固定速率 - scheduleWithFixedDelay 4. 小结 1. 概要 前三篇文章的地址&#xff1a; 定时/延时任务-自己实现一个简单的定时器定时/延时任…