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 方法前执行@Overridepublic 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报错不执行!@Overridepublic 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");}// 渲染视图之后执行(最后),一定执行!@Overridepublic 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*/@Overridepublic 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 地址 | |
@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 邮箱格式@Emailprivate 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> {@Overridepublic void initialize(Gender constraintAnnotation) {ConstraintValidator.super.initialize(constraintAnnotation);}@Overridepublic 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;@Overridepublic 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;//....@Overridepublic 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 官方推荐使用 StandardServletMultipartResolver
或 MockMultipartResolver
来替代。
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接口类型的对象,实现类是HttpHeadersMultiValueMap 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;
}
相关文章:
Spring Web MVC其他扩展(详解下)
文章目录 Spring MVC其他扩展(下)异常处理异常处理机制声明式异常好处基于注解异常声明异常处理 拦截器拦截器概念拦截器使用拦截器作用位置图解拦截器案例拦截器工作原理源码 参数校验校验概述操作演示SpringMVC自定义参数验证ValueObject(VO) 文件上传…...
深度学习之 SegNet
可训练的图像分割引擎,包含一个encoder网络,一个对应的decoder网络,衔接像素级分类层,解码网络与VGG16的13层卷积层相同。解码网络是将低分辨率的编码特征图映射到全分辨率的特征图。解码网络使用最大池化层的池化索引进行非线性上…...
Taro React小程序开发框架 总结
目录 一、安装 二、目录结构 三、创建一个自定义页面 四、路由 1、API 2、传参 3、获取路由参数 4、设置TabBar 五、组件 六、API Taro非常好用的小程序框架,React开发者无缝衔接上。 一、安装 官方文档:Taro 文档 注意,项目创建…...
《Django 5 By Example》阅读笔记:p339-p358
《Django 5 By Example》学习第12天,p339-p358总结,总计20页。 一、技术总结 1.项目(购物网站) django-admin startproject myshop 虽然这里只是示例,但我觉得这种命名为 myxxx 的习惯非常不好,因为在实际应用中,是…...
CSS:Web美学的革新之旅
自HTML的诞生之日起,Web页面设计便踏上了一段不断进化的旅程。起初,HTML作为构建网页的骨架,仅承载着最基本的文本结构与少量显示属性。然而,随着互联网的蓬勃发展和用户对视觉体验需求的日益增长,HTML开始不堪重负&am…...
java全栈day10--后端Web基础(基础知识)之续集
一、Servlet执行流程 二、Http协议(相对Tomcat和servlet重要一点) 2.1Http-概叙 2.2Http-请求协议 2.2.3请求数据格式 2.2.3请求数据获取 先启动服务器 访问/hello Servlet 访问浏览器端Http协议数据 查看数据...
MySQL 与 MongoDB 存储差异分析
MySQL 与 MongoDB 存储差异分析:为什么随机生成数据的存储空间不同? 在实际应用中,我们常常需要选择合适的数据库系统来处理不同类型的数据。在这个过程中,数据库的 存储机制 和 性能优化 起着至关重要的作用。对于很多开发者来说…...
【ArcGIS Pro实操第10期】统计某个shp文件中不同区域内的站点数
统计某个shp文件中不同区域内的站点数 方法 1:使用“空间连接 (Spatial Join)”工具方法 2:使用“点计数 (Point Count)”工具方法 3:通过“选择 (Select by Location)”统计方法 4:通过“Python 脚本 (ArcPy)”实现参考 在 ArcGI…...
Django-Vue3-Admin - 现代化的前后端分离权限管理系统
项目介绍 Django-Vue3-Admin是一个基于RBAC(Role-Based Access Control)模型的综合性基础开发平台,专注于权限控制,支持列级别的细粒度权限管理。该项目采用前后端分离架构,技术栈包括: 后端: Django Django REST …...
【Java基础入门篇】二、控制语句和递归算法
Java基础入门篇 二、控制语句和递归算法 2.1 switch-case多分支选择语句 switch执行case语句块时,若没有遇到break,则运行下一个case直到遇到break,最后的default表示当没有case与之匹配时,默认执行的内容,代码示例如…...
PS的功能学习
背景差色较大,就魔棒 魔棒的连续就是倒水点的跨越问题 魔棒的容差的选择就有点看经验了,看颜色的统一程度选择 Ctrl D 取消当前所有的选区 至于快速选择工具,和对象选择工具也差不多,只不过控制范围变成了一块一块的&#x…...
yolov8的深度学习环境安装(cuda12.4、ubuntu22.04)
目录 一、先安装基础环境包 1.首先给Ubuntu安装Chrome浏览器(搜索引擎换成百度即可) 2、ubuntu 22.04中文输入法安装 3、安装 terminator 4、安装WPS for Linux 5、安装其它之前需要先安装anaconda 6、安装配置anaconda 7、安装完成anaconda后创建…...
《JavaEat:探索 Java 在美食世界的奇妙之旅》
在当今数字化的时代,编程语言的应用领域不断拓展,而 Java 作为一种广泛使用且功能强大的编程语言,其影响力早已超越了传统的软件开发范畴。当我们将目光聚焦在美食领域时,会惊喜地发现 Java 也能在其中发挥独特而重要的作用。本文…...
将excel文件中的信息读取后批量生成word文件
在日常办公过程中,可能需要把excel文件中的信息批量生成成百上千份word文档,便于打印、发邮件或存档等,比如根据excel中的合格人员招聘信息生成word合同文件,或是根据excel中的参会人员名单生成word参会通知等。 首先需要制作wor…...
Android 图形系统之三:SurfaceControl
在 Android 系统中,SurfaceControl 是一个关键的类,用于管理应用窗口和屏幕上的显示内容。它与 SurfaceFlinger 紧密交互,通过 BufferQueue 提供高效的图形缓冲区管理能力。SurfaceControl 是 Android 的显示架构中不可或缺的部分,…...
Nodemailer使用教程:在Node.js中发送电子邮件
目录 1. 简介 2. 安装 3. 基本配置 3.1 创建传输器 3.2 配置说明 4. 发送邮件 4.1 基本发送示例 4.2 发送验证码示例 5. 常见问题解决 5.1 "Greeting never received" 错误 5.2 安全建议 SMTP与邮件加密协议详解 1. SMTP简介 1.1 基本特点 2. 加密协…...
shell第二次作业
1. 使用case实现成绩优良差的判断 read -p "请输入你的成绩:" score if ! [[ "$score" ~ ^[0-9]$ ]];then echo "请输入数字" exit 1 fi if [ "$score" -lt 0 ] || [ "$score" -gt 100 ];then echo …...
MySQL Linux 离线安装
下载 进入官网,下载对应的需要MySQL版本,这里是历史版本。 官网 选择第一个MySQL Community Sever社区版,因为这个是免费的。 选择需要的对应版本: 安装 1.将下载好的安装包上传到服务器端 使用FinalShell 客户端连接服务器 …...
万字长文解读深度学习——多模态模型BLIP2
🌺历史文章列表🌺 深度学习——优化算法、激活函数、归一化、正则化 深度学习——权重初始化、评估指标、梯度消失和梯度爆炸 深度学习——前向传播与反向传播、神经网络(前馈神经网络与反馈神经网络)、常见算法概要汇总 万字长…...
postman使用正则表达式提取数据实战篇!
之前篇章中postman多接口关联使用的是通过JSON提取器的方式进行提取。 除了JSON提取器提取数据外还可通过另一种方式——正则表达式来提取数据。 1、使用正则表达式提取器实现接口关联,match匹配 正则匹配表达式将需要提取的字段key:value都放入表达式中ÿ…...
Docker for Everyone Plus——Unbreakable!
修改一下telnet的端口配置,访问第二小问,sudo -l命令允许提权执行的命令: 发现多了这两个限制--security-optno-new-privileges,表明docker run命令必须带上--security-optno-new-privileges参数,这可以防止通过suid机…...
龙迅#LT6912适用于HDMI2.0转HDMI+LVDS/MIPI,分辨率高达4K60HZ,支持音频和HDCP2.2
1. 描述 LT6912是一款高性能的HDMI2.0转HDMI和LVDS和MIPI转换器。 HDMI2.0 输入和输出均支持高达 6Gbps 的数据速率,为4k60Hz视频提供足够的带宽。此外,还支持 HDCP2.2 进行数据解密(无数据 加密)。 对于 LVDS 输出,…...
Linux自动化部署方法(Linux Automated Deployment Method)
💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 本人主要分享计算机核心技…...
Jmeter测试工具的安装和使用,mac版本,jmeter版本5.2.1
Jmeter测试工具的安装和使用JSON格式请求 一、安装1、安装jdk包和设置java环境2、去官网下载Jmeter3、解压后,打开mac终端,进入apache-jmeter的bin文件开启jmeter 二、使用jmeter1、添加线程2、添加HTTP请求3、配置请求的协议、IP地址、端口号、请求方法…...
2024-2025 ICPC, NERC, Southern and Volga Russian Regional Contest(cf)(个人记录)
A: 思路:一开始有点懵逼,理解错题意了}, 由于是顺序分配,因此前面的人可以选择的条件更多,后面的人更少,我们从后向前遍历即可 #include<bits/stdc.h>using namespace std;typedef long long ll; ty…...
009 STM32 HAL库介绍
STM32 HAL库(Hardware Abstraction Layer)是STMicroelectronics为STM32系列微控制器提供的一套硬件抽象层库,它旨在简化STM32的开发过程,提高代码的可移植性和可维护性。HAL库通过提供一组统一的API接口,使得开发者无需…...
Java的常识
程序员分类 初级程序员(大学毕业一年以内)大概月薪:2-5K 初中级程序员(工作经验2-3年)大概月薪:6-10K 中级程序员(工作经验4-5年)大概月薪:10-15K 高级程序员(工作经验5++)大概月薪:15K++ 普通公司对于程序员的月薪资天花板25K 工作实景 微信小程序、手机APP、写…...
FreeRTOS——列表及列表项
目录 一、概念及其应用 1.1列表List_t 1.2列表项ListItem_t 1.3迷你列表项MiniListItem_t 二、相关API 三、实现原理 3.1列表初始化 3.2列表项初始化 3.3插入列表项 3.4尾插列表项 3.5列表项的删除 3.6列表的遍历 一、概念及其应用 作为多任务的核心,列…...
ChatGPT 网络安全秘籍(三)
第五章:安全意识和培训 在这一章中,我们将深入探讨网络安全培训和教育的迷人领域,强调了 OpenAI 的大型语言模型(LLMs)在增强和丰富这一关键过程中可以发挥的重要作用。我们将踏上一段旅程,发现 ChatGPT 如…...
深度学习与知识图谱嵌入的结合:从理论到实践
知识图谱嵌入方法主要包括两大类: 方法类型描述矩阵分解类方法基于传统矩阵分解思想,将知识图谱的三元组表示为多个矩阵,并通过分解获得低维向量表示。神经网络方法结合深度学习技术,通过神经网络自动学习知识图谱中实体和关系的…...
Java集成Sa-Token进行认证与授权
引言 软件开发过程中都必须要有的一个功能,那就是认证与授权,经过大佬们的不断更新迭代,使得如今实现认证与授权功能变得相对简单,也许你并不能真正的接触到认证与授权这一功能,除非你接触的项目是从0到1的,…...
【附录】Rust国内镜像设置
目录 前言 (1)设置环境变量 (2)安装Rust (3)设置crates镜像 前言 本节课来介绍下如何在国内高速下载安装Rust和Rust依赖,由于网络原因,我们在安装Rust和下载项目依赖时都很慢&am…...
Rust编程语言代码详细运行、编译方法
以下是针对不同类型的 Rust 代码(以常见的命令行程序为例)详细的运行方法: 前提条件 在运行 Rust 代码之前,确保你已经在系统上安装了 Rust 编程语言环境。如果尚未安装,可以通过以下步骤进行安装: 访问…...
Unity ShaderLab 实现交互地毯
实现思路: 将一个位置坐标值传入到shader的顶点着色器中,和这个值位置相同的顶点沿着法线的y轴方向偏移,然后计算这个值与顶点的距离,在这个范围内的顶点,和凸起的点的位置做插值操作。 Shader Graph实现如下&#x…...
Scala模式匹配——高阶用法
(一)scala的模式匹配 (1)常量 (2)变量 (3)构造器 (4)序列 (5)元组 (6)类型 (7)…...
【简单好抄保姆级教学】javascript调用本地exe程序(谷歌,edge,百度,主流浏览器都可以使用....)
javascript调用本地exe程序 详细操作步骤结果 详细操作步骤 在本地创建一个txt文件依次输入 1.指明所使用注册表编程器版本 Windows Registry Editor Version 5.00这是脚本的第一行,指明了所使用的注册表编辑器版本。这是必需的,以确保脚本能够被正确解…...
C#热更原理与HybridCLR
一、Mono的诞生 在Mono之前,C#虽然很好,但是只在windows家族平台上使用,就这点C#与Java就无法比。于是微软公司向ECMA申请将C#作为一种标准。在2001年12月,ECMA发布了ECMA-334 C#语言规范。C#在2003年成为一个ISO标准(ISO/IEC 23270)。意味着只要你遵守CLI(Common Lang…...
arcgis for js点击聚合要素查询其包含的所有要素
功能说明 上一篇讲了实现聚合效果, 但是点击聚合效果无法获取到该聚合点包含的所有点信息 这一篇是对如何实现该功能的案例 实现 各属性说明需要自行去官网查阅 官网案例 聚合API 没空说废话了, 加班到12点,得休息了, 直接运行代码看效果就行, 相关重点和注意事项都在代码注…...
k8s Init:ImagePullBackOff 的解决方法
kubectl describe po (pod名字) -n kube-system 可查看pod所在的节点信息 例如: kubectl describe po calico-node-2lcxx -n kube-system 执行拉取前先把用到的节点的源换了 sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"re…...
HASH256开源代码计算错误问题
计算量超500KB报错 OTA升级中可能会涉及到CRC、hash校验等算法,小编从网上抄到了HASH256的源码,拿来使用的时候却发现了一个问题,当源文件约大于500KB的时候会发现其计算出的hash值出现错误。 经过实际测试得知,当源文件大于约50…...
对象流—ObjectInputStream 和 ObjectOutputStream
对象流(ObjectInputStream和ObjectOutputStream)是Java中用于读写对象的流,可以将对象直接写入到流中,或者从流中读取对象。 ObjectOutputStream将对象序列化为字节流,可以将对象写入文件或网络流中。ObjectInputStream则将字节流反序列化为…...
【Fargo】27:ffmpeg ffprobe 和python分析h264文件并绘制
从帧和包两个层面进行分析。帧级别分析 ffprobe 可以读取264文件信息 -Y9KP MINGW64 /d/XTRANS/thunderbolt/ayame/zhb-bifrost/player-only (main) $ ffprobe test.h264 ffprobe version N-116778-g7e4784e40c-20240827 Copyright (c) 2007-2024 the FFmpeg developersbuilt …...
Debezium Engine监听binlog实现缓存更新与业务解耦
飞书文档 解决缓存与数据源数据不一致的方案有很多, 各有优缺点; 1.0、旁路缓存策略, 直接同步更新 读取流程: 查询缓存。如果缓存命中,则直接返回结果。如果缓存未命中,则查询数据库。将数据库查询到的数据写入缓存,并设置一个…...
mysql_题库详解
1、如何创建和删除数据库? 1)创建数据库 CREATE DATABASE 数据库名; 2)删除数据库 drop database 数据库名; 2、MyISAM与InnoDB的区别? 1)事务:MyISAM 不支持事务 InnoDB 支持 2)行锁/表锁&a…...
docker查询是否运行
您可以通过运行以下命令来检查Docker是否正在运行: docker info 或者: docker ps 如果Docker正在运行,docker info将显示Docker的详细信息,而docker ps将列出当前运行的容器。如果Docker没有运行,这些命令将会返回错误…...
【AI日记】24.11.29 kaggle 比赛 Titanic-2 | 鼓励自己
【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI日记】 核心工作 内容:学习 kaggle 入门比赛 Titanic - Machine Learning from Disaster,学习机器学习课程备注:入门比赛有很多 notebook 适合我这种新手学习,尤其是那…...
SVG无功补偿装置MATLAB仿真模型
“电气仔推送”获得资料(专享优惠) 模型简介 SVG(又称ASVG 或STATCOM)是Static Var Generator 的缩写,叫做静止无功发生器。也是做无功补偿的,比SVC 更加先进。其基本原理是将自换相桥式电路通过电抗器或…...
2039:【例5.6】冒泡排序
【题目描述】 编程输入n(1≤n≤20)个小于1000非负整数,然后自动按从大到小的顺序输出。(冒泡排序) 【输入】 第一行,数的个数n; 第二行,n个非负整数。 【输出】 由大到小的n个非负整数,每个数占一行。 【输…...
yum源问题的解决方案
linux课堂作业 问题描述 yum 直接安装tree的问题截图 这个错误表明你的系统没有正确注册到 Red Hat Subscription Management(这个问题不用管),也没有配置有效的 YUM 软件仓库,因此无法安装或更新软件包。 解决方案(…...
一文解析Kettle开源ETL工具!
ETL(Extract, Transform, Load)工具是用于数据抽取、转换和加载的软件工具,用于支持数据仓库和数据集成过程。Kettle作为传统的ETL工具备受用户推崇。本文就来详细说下Kettle。 一、Kettle是什么? Kettle 是一款开源的 ETL&#x…...