当前位置:   article > 正文

SpringMVC 控制层框架-下

SpringMVC 控制层框架-下

五、SpringMVC其他扩展


1. 异常处理机制
1.1 异常处理概念

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

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

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

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

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

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

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

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

1)声明异常处理控制器类

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

  1. /**
  2. * projectName: com.atguigu.execptionhandler
  3. *
  4. * description: 全局异常处理器,内部可以定义异常处理Handler!
  5. */
  6. /**
  7. * @RestControllerAdvice = @ControllerAdvice + @ResponseBody
  8. * @ControllerAdvice 代表当前类的异常处理controller!
  9. */
  10. @RestControllerAdvice
  11. public class GlobalExceptionHandler {
  12. }

2)声明异常处理 handler 方法

异常处理 handler 方法和普通的 handler 方法参数接收和响应都一致。只不过异常处理 handler 方法要映射异常,发生对应的异常会调用。普通的 handler 方法要使用 @RequestMapping 注解映射路径,发生对应的路径调用。

  1. /**
  2. * 异常处理handler
  3. * @ExceptionHandler(HttpMessageNotReadableException.class)
  4. * 该注解标记异常处理Handler,并且指定发生异常调用该方法!
  5. *
  6. *
  7. * @param e 获取异常对象!
  8. * @return 返回handler处理结果!
  9. */
  10. @ExceptionHandler(HttpMessageNotReadableException.class)
  11. public Object handlerJsonDateException(HttpMessageNotReadableException e){
  12. return null;
  13. }
  14. /**
  15. * 当发生空指针异常会触发此方法!
  16. * @param e
  17. * @return
  18. */
  19. @ExceptionHandler(NullPointerException.class)
  20. public Object handlerNullException(NullPointerException e){
  21. return null;
  22. }
  23. /**
  24. * 所有异常都会触发此方法!但是如果有具体的异常处理Handler!
  25. * 具体异常处理Handler优先级更高!
  26. * 例如: 发生NullPointerException异常!
  27. * 会触发handlerNullException方法,不会触发handlerException方法!
  28. * @param e
  29. * @return
  30. */
  31. @ExceptionHandler(Exception.class)
  32. public Object handlerException(Exception e){
  33. return null;
  34. }

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

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

  1. <!-- 扫描controller对应的包,将handler加入到ioc-->
  2. <context:component-scan base-package="com.atguigu.controller,
  3. com.atguigu.exceptionhandler" />
2. 拦截器使用
2.1 拦截器概念

拦截器和过滤器解决问题

  • 生活中

为了提高乘客效率,在乘客进入站台前统一检票

  • 程序中

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

拦截器 VS 过滤器:

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

选择:

功能需要如果用 SpringMVC 的拦截器能够实现,就不使用过滤器。

2.2 拦截器使用

1)创建拦截器类

  1. public class Process01Interceptor implements HandlerInterceptor {
  2. // 在处理请求的目标 handler 方法前执行
  3. @Override
  4. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  5. System.out.println("request = " + request + ", response = " + response + ", handler = " + handler);
  6. System.out.println("Process01Interceptor.preHandle");
  7. // 返回true:放行
  8. // 返回false:不放行
  9. return true;
  10. }
  11. // 在目标 handler 方法之后,handler报错不执行!
  12. @Override
  13. public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {
  14. System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", modelAndView = " + modelAndView);
  15. System.out.println("Process01Interceptor.postHandle");
  16. }
  17. // 渲染视图之后执行(最后),一定执行!
  18. @Override
  19. public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
  20. System.out.println("request = " + request + ", response = " + response + ", handler = " + handler + ", ex = " + ex);
  21. System.out.println("Process01Interceptor.afterCompletion");
  22. }
  23. }

 单个拦截器执行顺序:

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

2)拦截器配置

springmvc.xml

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

 3)配置详解

① 默认拦截全部

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

② 精准配置

  1. <!-- 具体配置拦截器可以指定拦截的请求地址 -->
  2. <mvc:interceptor>
  3. <!-- 精确匹配 -->
  4. <mvc:mapping path="/common/request/one"/>
  5. <bean class="com.atguigu.mvc.interceptor.Process03Interceptor"/>
  6. </mvc:interceptor>
  7. <mvc:interceptor>
  8. <!-- /*匹配路径中的一层 -->
  9. <mvc:mapping path="/common/request/*"/>
  10. <bean class="com.atguigu.mvc.interceptor.Process04Interceptor"/>
  11. </mvc:interceptor>
  12. <mvc:interceptor>
  13. <!-- /**匹配路径中的多层 -->
  14. <mvc:mapping path="/common/request/**"/>
  15. <bean class="com.atguigu.mvc.interceptor.Process05Interceptor"/>
  16. </mvc:interceptor>

③ 排除配置

  1. <mvc:interceptor>
  2. <!-- /**匹配路径中的多层 -->
  3. <mvc:mapping path="/common/request/**"/>
  4. <!-- 使用 mvc:exclude-mapping 标签配置不拦截的地址 -->
  5. <mvc:exclude-mapping path="/common/request/two/bbb"/>
  6. <bean class="com.atguigu.mvc.interceptor.Process05Interceptor"/>
  7. </mvc:interceptor>

 ④ 多个拦截器执行顺序

  1. preHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置顺序调用各个 preHandle() 方法。
  2. postHandle() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 postHandle() 方法。
  3. afterCompletion() 方法:SpringMVC 会把所有拦截器收集到一起,然后按照配置相反的顺序调用各个 afterCompletion() 方法。
2.3 拦截器作用位置图解

 2.4 拦截器案例

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

访问资源的请求地址:

登录资源:/public/resource/login

公共资源1:/public/resource/one

公共资源2:/public/resource/two

私密资源1:/private/resource/one

私密资源2:/private/resource/two

私密资源3:/private/resource/three

案例实现:

1) 声明资源类

① PublicController

  1. /**
  2. * projectName: com.atguigu.controller
  3. * description: 公有资源控制类
  4. */
  5. @RestController
  6. @RequestMapping("public/resource")
  7. public class PublicController {
  8. /**
  9. * 模拟登录,将假用户数据存储到session中!
  10. */
  11. @GetMapping("login")
  12. public Object login(HttpSession session){
  13. session.setAttribute("user","root");
  14. return "login success!!";
  15. }
  16. @GetMapping("one")
  17. public Object one(){
  18. return "public one";
  19. }
  20. @GetMapping("two")
  21. public Object two(){
  22. return "public two";
  23. }
  24. }

② PrivateController

  1. @RestController
  2. @RequestMapping("private/resource")
  3. public class PrivateController {
  4. @GetMapping("one")
  5. public Object one(){
  6. return "private one";
  7. }
  8. @GetMapping("two")
  9. public Object two(){
  10. return "private two";
  11. }
  12. @GetMapping("three")
  13. public Object three(){
  14. return "private two";
  15. }
  16. }

2)声明拦截器类

  1. /**
  2. * projectName: com.atguigu.interceptor
  3. *
  4. * description: 登录保护拦截器
  5. */
  6. public class LoginProtectInterceptor implements HandlerInterceptor {
  7. /**
  8. * 登录保护方法
  9. * @param request current HTTP request
  10. * @param response current HTTP response
  11. * @param handler chosen handler to execute, for type and/or instance evaluation
  12. * @return
  13. * @throws Exception
  14. */
  15. @Override
  16. public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
  17. Object user = request.getSession().getAttribute("user");
  18. if (user == null){
  19. response.setContentType("text/html;charset=utf-8");
  20. //没有登录
  21. response.getWriter().print("请先登录,再访问! <a href='/public/resource/login'>点击此处登录</a>");
  22. //拦截,不到达目标地址
  23. return false;
  24. }
  25. return true;
  26. }
  27. }

3)配置拦截器类 

  1. <!-- 配置拦截器-->
  2. <mvc:interceptors>
  3. <mvc:interceptor>
  4. <mvc:mapping path="/private/**"/>
  5. <bean class="com.atguigu.interceptor.LoginProtectInterceptor" />
  6. </mvc:interceptor>
  7. </mvc:interceptors>
 3. 参数校验

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

1)校验概述

JSR 303 是 Java 为Bean 数据合法性校验提供的标准框架,它已经包含在 JavaEE 6.0标准中。JSR 303 通过在 Bean 属性上标注类似于 @NoNull、@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. <!-- 校验注解 -->
  2. <dependency>
  3. <groupId>jakarta.platform</groupId>
  4. <artifactId>jakarta.jakartaee-web-api</artifactId>
  5. <version>9.1.0</version>
  6. <scope>provided</scope>
  7. </dependency>
  8. <!-- 校验注解实现-->
  9. <!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator -->
  10. <dependency>
  11. <groupId>org.hibernate.validator</groupId>
  12. <artifactId>hibernate-validator</artifactId>
  13. <version>8.0.0.Final</version>
  14. </dependency>
  15. <!-- https://mvnrepository.com/artifact/org.hibernate.validator/hibernate-validator-annotation-processor -->
  16. <dependency>
  17. <groupId>org.hibernate.validator</groupId>
  18. <artifactId>hibernate-validator-annotation-processor</artifactId>
  19. <version>8.0.0.Final</version>
  20. </dependency>

应用校验注解

  1. import jakarta.validation.constraints.Email;
  2. import jakarta.validation.constraints.Min;
  3. import org.hibernate.validator.constraints.Length;
  4. /**
  5. * projectName: com.atguigu.pojo
  6. */
  7. public class User {
  8. //age 1 <= age < = 150
  9. @Min(10)
  10. private int age;
  11. //name 3 <= name.length <= 6
  12. @Length(min = 3,max = 10)
  13. private String name;
  14. //email 邮箱格式
  15. @Email
  16. private String email;
  17. public int getAge() {
  18. return age;
  19. }
  20. public void setAge(int age) {
  21. this.age = age;
  22. }
  23. public String getName() {
  24. return name;
  25. }
  26. public void setName(String name) {
  27. this.name = name;
  28. }
  29. public String getEmail() {
  30. return email;
  31. }
  32. public void setEmail(String email) {
  33. this.email = email;
  34. }
  35. }

 handler 标记和绑定错误收集

  1. @RestController
  2. @RequestMapping("user")
  3. public class UserController {
  4. /**
  5. * @Validated 代表应用校验注解! 必须添加!
  6. */
  7. @PostMapping("save")
  8. public Object save(@Validated @RequestBody User user,
  9. //在实体类参数和 BindingResult 之间不能有任何其他参数, BindingResult可以接受错误信息,避免信息抛出!
  10. BindingResult result){
  11. //判断是否有信息绑定错误! 有可以自行处理!
  12. if (result.hasErrors()){
  13. System.out.println("错误");
  14. String errorMsg = result.getFieldError().toString();
  15. return errorMsg;
  16. }
  17. //没有,正常处理业务即可
  18. System.out.println("正常");
  19. return user;
  20. }
  21. }

 3)易混总结

@NotNull (包装类型不为null)、@NotEmpty(集合类型长度大于0) 、@NotBlank(字符串,不为null,切不为" "字符串) 都是用于在数据校验中检查字段值是否为空的注解,但是它们的用法和校验规则有所不同。

4. 文件上传和下载
4.1 文件上传

1)文件上传表单页面

位置:index.xml

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

 2)springmvc 环境要求

pom.xml 添加依赖

  1. <!-- https://mvnrepository.com/artifact/commons-fileupload/commons-fileupload -->
  2. <dependency>
  3. <groupId>commons-fileupload</groupId>
  4. <artifactId>commons-fileupload</artifactId>
  5. <version>1.3.1</version>
  6. </dependency>

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

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

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

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

3)handler 方法接收数据

  1. /**
  2. * 上传的文件使用 MultipartFile 类型接收其相关数据
  3. * @param nickName
  4. * @param picture
  5. * @param backgroundPicture
  6. * @return
  7. * @throws IOException
  8. */
  9. @PostMapping ("picture")
  10. public String upload(String nickName, @RequestParam("headPicture") MultipartFile picture, @RequestParam("backgroundPicture")MultipartFile backgroundPicture) throws IOException {
  11. System.out.println(nickName);
  12. String inputName = picture.getName();
  13. System.out.println("文件上传表单项的 name 属性值:" + inputName);
  14. // 获取这个数据通常都是为了获取文件本身的扩展名
  15. String originalFilename = picture.getOriginalFilename();
  16. System.out.println("文件在用户本地原始的文件名:" + originalFilename);
  17. String contentType = picture.getContentType();
  18. System.out.println("文件的内容类型:" + contentType);
  19. boolean empty = picture.isEmpty();
  20. System.out.println("文件是否为空:" + empty);
  21. long size = picture.getSize();
  22. System.out.println("文件大小:" + size);
  23. byte[] bytes = picture.getBytes();
  24. System.out.println("文件二进制数据的字节数组:" + Arrays.asList(bytes));
  25. InputStream inputStream = picture.getInputStream();
  26. System.out.println("读取文件数据的输入流对象:" + inputStream);
  27. Resource resource = picture.getResource();
  28. System.out.println("代表当前 MultiPartFile 对象的资源对象" + resource);
  29. return "home";
  30. }
  31. ```

4)MultipartFile 接口

5)文件转存

① 底层机制

② 本地转存

 

 

声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:【wpsshop博客】
推荐阅读
  

闽ICP备14008679号