Java 阶段三 Day14 统一响应结果及异常的处理及 Spring Validations 如何实现请求数据校验
一、统一响应结果的处理
首先,先了解一下什么是状态码。
状态码(StatusCode)是一种在计算机通信中用于表示请求的处理结果的数字或字符编码。它们通常用于HTTP协议中,但也可以在其他通信协议和系统中找到。状态码的目的是提供有关请求的处理情况的信息,以便请求发起方能够了解请求是否成功、失败以及失败的原因。
在HTTP协议中,状态码由三位数字组成,通常分为以下五个类别:
-
1xx(Informational): 这一类状态码表示服务器已经接收到请求,但请求的处理尚未完成。最常见的1xx状态码是100(Continue),它表示服务器已经准备好继续处理请求。
-
2xx(Successful): 这一类状态码表示请求已成功接收、理解并接受。最常见的2xx状态码是200(OK),它表示请求已成功处理。
-
3xx(Redirection): 这一类状态码表示客户端需要采取额外的操作以完成请求。通常,这些状态码用于重定向客户端到其他位置。例如,302(Found)状态码表示请求的资源已被临时移动到其他位置。
-
4xx(Client Error): 这一类状态码表示客户端发送了错误的请求或请求无法完成。最常见的4xx状态码是404(Not Found),它表示请求的资源未找到。
-
5xx(Server Error): 这一类状态码表示服务器在处理请求时发生了错误。最常见的5xx状态码是500(Internal Server Error),它表示服务器遇到了未处理的异常。
每个状态码都有特定的含义和用途,它们帮助客户端和服务器之间进行有效的通信,以确保请求的正确处理和错误处理。状态码是HTTP协议中的关键组成部分,它们允许客户端和服务器之间就请求的状态和结果进行明确的交流。
状态码类的定义(StatusCode)
-
需求
当项目中的状态码越来越多时,对状态码的定义没有统一规划,后续对状态的理解就会相当的困难,而且容易导致操作上的失败。 -
解决方案【定义枚举类(enum类),统一封装状态码】
import lombok.Data; import lombok.Getter; /** * 通过此枚举定义一些响应状态码信息, * 实际项目中会基于不同业务定义不同状态码对象 */ @Getter public enum StatusCode { NOT_LOGIN(1000,"未登录"), //所有实例必须是在最上面的 LOGIN_SUCCESS(1001,"登录成功"), PASSWORD_ERROR(1002,"密码错误"),//对象(枚举实例) USERNAME_ERROR(1003,"用户名错误"), OPERATION_SUCCESS(2001,"操作成功"), OPERATION_FAILED(2002,"操作失败"); /**表示状态码*/ private int code; /**状态码信息*/ private String msg; //所有构造方法默认使用private访问修饰符修饰 StatusCode(){ } /**构造方法*/ StatusCode(int code,String msg){ this.code=code; this.msg=msg; } }
响应结果封装类的定义(ResponseVO)
-
需求
为更好定义服务端返回值的格式,统一客户端对服务端响应结果的处理,将服务端返回到客户端的数据再次进行封装。 -
解决方案(定义ResultVO对象)
import lombok.*; /** * 统一响应结果的封装类型 */ @Setter @Getter @NoArgsConstructor @AllArgsConstructor public class ResultVO { /**响应状态码(业务状态码)*/ private int code; /**服务端要返回给客户端的消息(例如,密码不正确)*/ private String msg; /**服务端要返回给客户端的具体数据*/ private Object data; /**通过构造方法对属性初始化*/ public ResultVO(Object data){ this.code=StatusCode.SUCCESS.getCode(); this.msg=StatusCode.SUCCESS.getMsg(); this.data=data; } /**通过构造方法对属性初始化*/ public ResultVO(StatusCode statusCode){ this.code=statusCode.getCode(); this.msg=statusCode.getMsg(); } /**通过构造方法对象属性初始化*/ public ResultVO(StatusCode statusCode,Object data){ this(statusCode);//调用本类中其它方法 this.data=data; } }
-
应用ResultVO对象
在WeiboController中添加如下方法,然后进行访问测试@GetMapping("doSelectIndex") @ApiOperation(value = "微博列表的统一封装") public ResultVO doSelectIndex(){ return new ResultVO(weiboMapper.selectIndex()); }
在UserController中添加如下方法,然后进行访问测试
/**登录功能*/ @PostMapping("doLogin") @ApiOperation(value = "封装登录响应结果") // 1.添加会话参数 public ResultVO doLogin(@RequestBody UserLoginDTO userLoginDTO, HttpSession session){ System.out.println("userLoginDTO = " + userLoginDTO); UserVO userVO = userMapper.selectByUsername(userLoginDTO.getUsername()); if (userVO != null){ // 判断密码 if (userVO.getPassword().equals(userLoginDTO.getPassword())){ // 2.将该用户的登录状态的会话保存起来 session.setAttribute("user", userVO); return new ResultVO(StatusCode.LOGIN_SUCCESS); // 登录成功 } return new ResultVO(StatusCode.PASSWORD_ERROR); // 密码错误 } return new ResultVO(StatusCode.USERNAME_ERROR); // 用户名不存在 }
Tomcat处理流程
Spring 中的统一响应结果的封装
Spring MVC请求响应处理流程
二、统一异常的处理
在Spring MVC中有两个级别的异常处理方式:
- Controller级别:它只能处理当前Controller类中出现的异常。
- 全局的异常处理级别:所有的Spring MVC中的拦截机器(
HandlerInterceptor
),处理器(@Controller
)中的异常。
Spring MVC提供了多种方式来处理异常,以确保在应用程序中发生异常时能够进行适当的处理和响应。以下是一些常见的Spring MVC中的异常处理方式:
-
使用@ControllerAdvice注解处理全局异常:
可以创建一个带有@ControllerAdvice注解的类,用于集中处理全局异常。在这个类中,你可以定义方法来处理不同类型的异常,然后使用@ExceptionHandler注解将它们与特定的异常类型关联起来。这些异常处理方法可以返回适当的HTTP状态码和错误消息。@ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(Exception.class) public ModelAndView handleException(Exception ex) { ModelAndView modelAndView = new ModelAndView("error"); modelAndView.addObject("errorMessage", "An error occurred: " + ex.getMessage()); return modelAndView; } }
-
使用@ExceptionHandler注解处理Controller内的异常:
你还可以在单个Controller内使用@ExceptionHandler注解来处理该Controller内部的异常。这允许你为特定的Controller定义异常处理方法。@Controller public class MyController { @ExceptionHandler(MyCustomException.class) public ModelAndView handleCustomException(MyCustomException ex) { ModelAndView modelAndView = new ModelAndView("error"); modelAndView.addObject("errorMessage", "Custom error: " + ex.getMessage()); return modelAndView; } }
-
使用SimpleMappingExceptionResolver:
Spring提供了SimpleMappingExceptionResolver,可以用于配置全局异常处理。你可以定义异常类型与视图名称的映射,以及其他配置选项。这个解析器可以在Spring配置文件中进行配置。<bean class="org.springframework.web.servlet.handler.SimpleMappingExceptionResolver"> <property name="exceptionMappings"> <props> <prop key="MyCustomException">errorView</prop> </props> </property> </bean>
-
使用自定义异常处理器:
你还可以创建自定义的异常处理器类,实现HandlerExceptionResolver接口。这个自定义异常处理器可以根据需要进行高度定制,例如,将异常信息记录到日志、发送邮件通知等。public class MyExceptionHandler implements HandlerExceptionResolver { @Override public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { // Custom exception handling logic ModelAndView modelAndView = new ModelAndView("error"); modelAndView.addObject("errorMessage", "An error occurred: " + ex.getMessage()); return modelAndView; } }
这些是Spring MVC中处理异常的常见方式。你可以根据应用程序的需求选择合适的方式来处理异常,并提供适当的错误信息和响应。无论选择哪种方式,都能够提高应用程序的稳定性和可维护性。
当然,Spring MVC中有内置的异常处理对象,但是呈现的结果对于用户端不友好,所以实际项目我们一般会自定义异常处理。
全局异常处理对象的定义
我们可以在spring boot启动类所在的包或子包中定义Spring MVC全局异常处理类,这个类的特点是需要使用@RestControllerAdvice
注解进行描述,并且可以在类中定义多个异常处理方法(需要使用@HandlerException
注解进行描述), 例如:
package com.zhikai.exception;
import com.zhikai.common.response.ResultVO;
import com.zhikai.common.response.StatusCode;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.*;
/**
* RestControllerAdvice 描述的类型为一个全局异常处理对象类型,
* 当某个Controller方法中出现了异常,此时在Controller类内部,又没有定义对应的
* 异常处理方法(使用ExcetpionHandler注解描述的方法),系统底层就会查找有没有定义全局异常处理对象。
* 这个全局异常处理对象中有没有定义对应的异常处理方法,假如有就调用此方法处理异常。
*/
@Slf4j //lombok提供的日志注解,在代码层面会为我们提供一个org.slf4j.Logger对象
@RestControllerAdvice //=@ControllerAdvice+@ResponseBody
public class GlobalExceptionHandler {
//private static final Logger log= LoggerFactory.getLogger(GlobalExceptionHandler.class);
/**
* @ExceptionHandler 描述的方法为一个异常处理方法,在此注解内部可以定义具体的异常处理
* 类型(例如RuntimeException.class),此注解描述的方法需要定义一个异常类型的形式参数,
* 通过这个参数接收具体的异常对象(也可以接收其异常类型对应的子类类型的异常)。
* @return
*/
@ExceptionHandler(RuntimeException.class)
public ResultVO doHandleRuntimeException(RuntimeException ex){
log.error("error is {}",ex.getMessage());//日志级别trace<debug<info<warn<error
return new ResultVO(0,ex.getMessage());
}
}
这里的@Slf4j
注解为Lombok
提供,此注解描述类时会在类中创建一个日志对象,基于日志对象可以输出一些日志,
日志的级别可以在application.properties
文件中进行配置,例如logging.level.com.liner=debug
,这里的debug
表示日志级别(trace < debug < info < warn < error
),当配置的日志界别为debug时,当前这个日志级别以及比这个日志级别高的日志级别会输出日志。
三、Spring Validation 如何实现请求数据校验
在实际项目需要对客户端传递到服务端的参数进行校验,用于判定请求参数的合法性,假如请求参数不合法,不可以再去执行后续的业务了。有两种方法:
- 在控制层方法中每次都自己进行参数有效值的判断,不合法可以抛出异常,但是工作量和代码复杂度会比较高。
- 采用市场上主流的 Spring Validation 框架去实现校验。
Spring Validation的应用
Spring Validation 是 Spring 框架提供的一种用于验证数据的机制,它可以帮助你确保用户输入的数据符合预期的规则和约束。Spring Validation 主要用于验证用户提交的表单数据,例如登录信息、注册信息等,以及 API 请求中的数据。
以下是在 Spring 中使用验证的一般步骤:
-
创建验证规则类:
首先,你需要创建一个用于验证数据的 Java 类,通常称为验证规则类。这个类应该实现 Spring 的Validator
接口,它包括两个方法:supports()
和validate()
。import org.springframework.validation.Errors; import org.springframework.validation.ValidationUtils; import org.springframework.validation.Validator; public class UserValidator implements Validator { @Override public boolean supports(Class<?> clazz) { return User.class.isAssignableFrom(clazz); } @Override public void validate(Object target, Errors errors) { User user = (User) target; ValidationUtils.rejectIfEmptyOrWhitespace(errors, "username", "NotEmpty.user.username"); ValidationUtils.rejectIfEmptyOrWhitespace(errors, "password", "NotEmpty.user.password"); // 进一步的自定义验证逻辑 if (user.getUsername().length() < 5) { errors.rejectValue("username", "Size.user.username"); } // 其他验证规则 } }
-
在控制器中应用验证规则:
在 Spring 控制器中,将上述的验证规则类应用到需要验证的数据上。你可以使用@InitBinder
注解来将验证器绑定到控制器方法上。import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.validation.BindingResult; import org.springframework.web.bind.WebDataBinder; import org.springframework.web.bind.annotation.InitBinder; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; @Controller public class UserController { @Autowired private UserValidator userValidator; @InitBinder protected void initBinder(WebDataBinder binder) { binder.setValidator(userValidator); } @RequestMapping(value = "/register", method = RequestMethod.POST) public String register(@ModelAttribute("user") User user, BindingResult result, Model model) { userValidator.validate(user, result); if (result.hasErrors()) { return "registrationForm"; } // 处理注册逻辑 return "registrationSuccess"; } }
-
在视图中显示错误信息:
在视图中,你可以使用标签库或Thymeleaf等模板引擎来显示验证失败的错误信息。Spring 会将错误信息存储在BindingResult
对象中,你可以从中获取错误并在页面上显示。<form:form method="POST" modelAttribute="user" action="/register"> <label for="username">Username:</label> <form:input path="username"/> <form:errors path="username" cssClass="error"/> <label for="password">Password:</label> <form:password path="password"/> <form:errors path="password" cssClass="error"/> <input type="submit" value="Register"/> </form:form>
-
定义错误消息:
在 Spring 中,你可以定义错误消息,以便在验证失败时显示给用户。这些消息可以存储在 properties 文件中,以支持国际化。NotEmpty.user.username=Username is required. NotEmpty.user.password=Password is required. Size.user.username=Username must be at least 5 characters long.
通过以上步骤,你可以使用 Spring Validation 来验证用户输入的数据,并在验证失败时返回错误消息。这有助于确保应用程序接受有效的数据,并提高用户体验。
Spring Validation注解
以下是Spring Framework中常用的Validation注解及其说明:
-
@NotNull:用于标记字段不能为null。
-
@NotEmpty:用于标记字符串、集合或数组字段不能为空,即长度必须大于0。
-
@NotBlank:用于标记字符串字段不能为空且必须至少包含一个非空白字符。
-
@Size:用于指定字符串、集合或数组字段的长度范围。
-
@Min:用于指定数值字段的最小值。
-
@Max:用于指定数值字段的最大值。
-
@Email:用于验证字符串字段是否为有效的电子邮件地址。
-
@Pattern:用于使用正则表达式验证字符串字段。
-
@AssertTrue:用于验证字段是否为true。
-
@AssertFalse:用于验证字段是否为false。
-
@DecimalMin:用于验证数字字段的最小值,通常用于BigDecimal或double类型。
-
@DecimalMax:用于验证数字字段的最大值,通常用于BigDecimal或double类型。
-
@Digits:用于验证数字字段的整数位数和小数位数。
-
@Future:用于验证日期字段是否在将来。
-
@Past:用于验证日期字段是否在过去。
-
@FutureOrPresent:用于验证日期字段是否在现在或将来。
-
@PastOrPresent:用于验证日期字段是否在现在或过去。
-
@CreditCardNumber:用于验证信用卡号的有效性。
-
@Length:用于验证字符串字段的长度。
-
@NotBlank:用于验证字符串字段不能为空且必须至少包含一个非空白字符。
-
@URL:用于验证字符串字段是否为有效的URL。
-
@Valid:用于标记字段应该递归验证,通常用于嵌套对象的验证。
-
@Min 和 @Max:用于验证整数或长整数字段的最小值和最大值。
-
@Size: 检查字符串、集合或数组的长度是否在指定范围内
-
@Positive: 检查数字是否为正数
-
@PositiveOrZero: 检查数字是否为非负数
-
@Negative: 检查数字是否为负数
-
@NegativeOrZero: 检查数字是否为非正数
-
@ConvertGroup: 用于分组校验,可以指定校验的分组,根据不同的分组执行不同的校验规则
-
@GroupSequence: 用于定义校验分组的顺序,指定不同分组的执行顺序
-
@NotNull(message = “{user.name.notnull}”): 使用国际化消息提示
-
@NotBlank(message = “{user.name.notblank}”): 使用国际化消息提示
-
@NotEmpty(message = “{user.name.notempty}”): 使用国际化消息提示
-
@Email(message = “{user.email.format}”): 使用国际化消息提示
-
@Pattern:用于指定一个字符串必须匹配的正则表达式模式。这在输入验证或数据格式化中非常有用。以下是如何在Java中使用它的示例:
import java.util.regex.Pattern; public class Example { @Pattern(regexp = "[A-Za-z0-9]+") private String someField; // 构造函数、getter和setter方法在这里... }
在这个示例中,
@Pattern
注解应用于someField
变量。regexp
参数指定了someField
字符串必须遵循的正则表达式模式。在这种情况下,它要求字符串只包含字母和数字字符。
这些注解允许你在实体类的字段上添加验证规则,以确保输入数据满足特定条件或约束。当在Spring MVC控制器中使用@Valid
注解时,这些注解可以触发验证过程,并在验证失败时生成相应的错误信息。使用这些注解可以简化数据验证的过程,提高应用程序的可读性和可维护性。
附:@ExceptionHandler
@ExceptionHandler
是一个注解,通常用于Spring框架中,用于处理控制器中的异常。当在控制器中发生异常时,@ExceptionHandler
注解允许您指定一个方法来处理该异常,而不是让异常传播到更高层级的异常处理器。
以下是如何在Spring框架中使用 @ExceptionHandler
注解的示例:
@Controller
public class MyController {
@GetMapping("/example")
public String example() {
// 业务逻辑,可能会抛出异常
if (someCondition) {
throw new CustomException("Something went wrong");
}
// ...
return "successPage";
}
@ExceptionHandler(CustomException.class)
public ModelAndView handleCustomException(CustomException ex) {
ModelAndView modelAndView = new ModelAndView("errorPage");
modelAndView.addObject("errorMessage", ex.getMessage());
return modelAndView;
}
}
在上面的示例中,@ExceptionHandler
注解用于处理 CustomException
类型的异常。当在 example
方法中抛出 CustomException
异常时,Spring框架会调用 handleCustomException
方法来处理该异常。在 handleCustomException
方法中,您可以执行自定义的异常处理逻辑,例如,返回一个包含错误消息的视图。
重要注意事项:
@ExceptionHandler
方法的参数可以包括异常类型、HttpServletRequest
、HttpServletResponse
等,以便您可以访问请求和响应对象以及有关异常的信息。- 您可以在同一个控制器中定义多个
@ExceptionHandler
方法,每个方法处理不同类型的异常。 - 如果没有找到与抛出的异常类型匹配的
@ExceptionHandler
方法,异常将传播到更高级别的异常处理器(例如,全局异常处理器)。 @ExceptionHandler
不仅可以用在控制器类上,还可以用在@ControllerAdvice
类中,以全局方式处理异常。