欢迎光临我的博客[http://poetize.cn],前端使用Vue2,聊天室使用Vue3,后台使用Spring Boot
@ControllerAdvice 对Controller进行"切面"环绕
结合方法型注解 @ExceptionHandler
用于捕获Controller中抛出的指定类型的异常,从而达到不同类型的异常区别处理的目的。
@ControllerAdvice(basePackages = "mvc")
public class ControllerAdvice {
@ExceptionHandler(RuntimeException.class)
public ModelAndView runtimeException(RuntimeException e) {
e.printStackTrace();
return new ModelAndView("error");
}
}
结合方法型注解 @InitBinder
用于request中自定义参数解析方式进行注册,从而达到自定义指定格式参数的目的。
@ControllerAdvice(basePackages = "mvc")
public class ControllerAdvice {
@InitBinder
public void globalInitBinder(WebDataBinder binder) {
binder.addCustomFormatter(new DateFormatter("yyyy-MM-dd"));
}
}
结合方法型注解 @ModelAttribute
表示其标注的方法将会在目标Controller方法执行之前执行。
@ControllerAdvice(basePackages = "mvc")
public class ControllerAdvice {
@ModelAttribute(value = "message") //name或value属性则指定的是返回值的名称
public String globalModelAttribute() {
return "ld";
}
}
@Valid @Validated 参数校验
@Valid 注解会导致 MethodArgumentNotValidException 验证失败时抛出该异常
1. 参数前加注解:@Valid
2. JavaBean属性注解:@NotNull,@Max,@Size
@Validated 导致 ConstraintViolationException 抛出该异常
@RequestParam或者@PathVariable结合@NotNull进行参数检验
1. 类上加注解:@Validated
2. 参数前加注解:@NotBlank(message = "姓名不能为空") @RequestParam("name") String name
3. 给SpringMVC注入org.springframework.validation.beanvalidation.MethodValidationPostProcessor
@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
return new MethodValidationPostProcessor();
}
Controller 异常捕获
@RestControllerAdvice({"run.halo.app.controller.admin.api", "run.halo.app.controller.content.api"})
@Slf4j
public class ControllerExceptionHandler {
//试图插入或更新数据时引发异常(Dao异常)
@ExceptionHandler(DataIntegrityViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse handleDataIntegrityViolationException(DataIntegrityViolationException e) {
BaseResponse<?> baseResponse = handleBaseException(e);
if (e.getCause() instanceof org.hibernate.exception.ConstraintViolationException) {
baseResponse = handleBaseException(e.getCause());
}
baseResponse.setMessage("字段验证错误,请完善后重试!");
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
return baseResponse;
}
//参数校验异常
@ExceptionHandler(ConstraintViolationException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse handleConstraintViolationException(ConstraintViolationException e) {
BaseResponse<Map<String, String>> baseResponse = handleBaseException(e);
baseResponse.setMessage("字段验证错误,请完善后重试!");
//违反属性约束的Map(key是变量名,value是错误信息)
baseResponse.setData(ValidationUtils.mapWithValidError(e.getConstraintViolations()));
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
return baseResponse;
}
//参数校验异常
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
BaseResponse<Map<String, String>> baseResponse = handleBaseException(e);
baseResponse.setMessage("字段验证错误,请完善后重试!");
//违反属性约束的Map(key是变量名,value是错误信息)
baseResponse.setData(ValidationUtils.mapWithFieldError(e.getBindingResult().getFieldErrors()));
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
return baseResponse;
}
//缺少Servlet请求参数异常
@ExceptionHandler(MissingServletRequestParameterException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public BaseResponse handleMissingServletRequestParameterException(MissingServletRequestParameterException e) {
BaseResponse<?> baseResponse = handleBaseException(e);
baseResponse.setMessage(String.format("请求字段缺失,类型为 %s,名称为 %s", e.getParameterType(), e.getParameterName()));
baseResponse.setStatus(HttpStatus.BAD_REQUEST.value());
return baseResponse;
}
/**
* 异常处理基础方法
*/
private <T> BaseResponse<T> handleBaseException(Throwable t) {
log.error("捕获一个异常:", t);
//构造响应体BaseResponse
BaseResponse<T> baseResponse = new BaseResponse<>();
//设置响应信息Message
baseResponse.setMessage(t.getMessage());
if (log.isDebugEnabled()) {
//设置开发信息(堆栈跟踪信息)
baseResponse.setDevMessage(ExceptionUtils.getStackTrace(t));
}
return baseResponse;
}
}
参数校验工具类
public class ValidationUtils {
private static Validator VALIDATOR;
private ValidationUtils() {
}
/** 获取验证器 */
@NonNull
public static Validator getValidatorOrCreate() {
if (VALIDATOR == null) {
synchronized (ValidationUtils.class) {
//初始化验证器
VALIDATOR = Validation.buildDefaultValidatorFactory().getValidator();
}
}
return VALIDATOR;
}
/**
* 手动校验Bean
*/
public static void validate(Object obj, Class<?>... groups) {
Validator validator = getValidatorOrCreate();
Set<ConstraintViolation<Object>> constraintViolations = validator.validate(obj, groups);
if (!CollectionUtils.isEmpty(constraintViolations)) {
throw new ConstraintViolationException(constraintViolations);
}
}
/**
* ConstraintViolationException.class
*
* 将字段验证错误转换为标准的map型,key:value = field:message
*/
@NonNull
public static Map<String, String> mapWithValidError(Set<ConstraintViolation<?>> constraintViolations) {
if (CollectionUtils.isEmpty(constraintViolations)) {
return Collections.emptyMap();
}
Map<String, String> errMap = new HashMap<>(4);
//格式化错误信息
constraintViolations.forEach(
constraintViolation ->
//key:变量名(constraintViolation.getPropertyPath()),value:错误信息
errMap.put(constraintViolation.getPropertyPath().toString(), constraintViolation.getMessage()));
return errMap;
}
/**
* MethodArgumentNotValidException.class
*
* 将字段验证错误转换为标准的map型,key:value = field:message
*/
public static Map<String, String> mapWithFieldError(@Nullable List<FieldError> fieldErrors) {
if (CollectionUtils.isEmpty(fieldErrors)) {
return Collections.emptyMap();
}
Map<String, String> errMap = new HashMap<>(4);
fieldErrors.forEach(
//key:变量名(constraintViolation.getPropertyPath()),value:错误信息
filedError -> errMap.put(filedError.getField(), filedError.getDefaultMessage()));
return errMap;
}
}
堆栈跟踪信息
public class ExceptionUtils {
/** 从Throwable获取堆栈跟踪 */
public static String getStackTrace(final Throwable throwable) {
final StringWriter sw = new StringWriter();
final PrintWriter pw = new PrintWriter(sw, true);
//将异常信息打印到StringWriter中
throwable.printStackTrace(pw);
return sw.getBuffer().toString();
}
}
ResponseBodyAdvice接口 + @ControllerAdvice 处理返回结果
/**
* 封装请求体body,解决JS跨域请求
*/
@ControllerAdvice("run.halo.app.controller")
public class CommonResultControllerAdvice implements ResponseBodyAdvice<Object> {
/**
* 拦截条件:拦截Json数据
*
* @param returnType 返回类型
* @param converterType 转换器类型
*/
@Override
public boolean supports(MethodParameter returnType, Class<? extends HttpMessageConverter<?>> converterType) {
//拦截转换器 AbstractJackson2HttpMessageConverter子类的Controller方法
return AbstractJackson2HttpMessageConverter.class.isAssignableFrom(converterType);
}
@Override
@NonNull
public final Object beforeBodyWrite(@Nullable Object body, MethodParameter returnType,
MediaType contentType, Class<? extends HttpMessageConverter<?>> converterType,
ServerHttpRequest request, ServerHttpResponse response) {
//将返回体body包装成MappingJacksonValue
MappingJacksonValue container = getOrCreateContainer(body);
//处理返回体body,设置状态码
beforeBodyWriteInternal(container, response);
return container;
}
/**
* 将返回体body包装成MappingJacksonValue
*/
private MappingJacksonValue getOrCreateContainer(Object body) {
//JSONP:JS跨域请求数据的一中解决方案
return (body instanceof MappingJacksonValue ? (MappingJacksonValue) body : new MappingJacksonValue(body));
}
/**
* 处理返回体body,设置状态码
*/
private void beforeBodyWriteInternal(MappingJacksonValue bodyContainer,
ServerHttpResponse response) {
//返回体body
Object returnBody = bodyContainer.getValue();
//如果返回体body是BaseResponse及其子类,设置状态码并返回
if (returnBody instanceof BaseResponse) {
BaseResponse<?> baseResponse = (BaseResponse) returnBody;
//HttpStatus.resolve(baseResponse.getStatus():将给定的状态码解析为HttpStatus
//response.setStatusCode:设置 response 状态码
response.setStatusCode(HttpStatus.resolve(baseResponse.getStatus()));
return;
}
//如果返回体body不是BaseResponse及其子类,将返回体包装成BaseResponse,设置状态码并返回
BaseResponse<?> baseResponse = BaseResponse.ok(returnBody);
bodyContainer.setValue(baseResponse);
response.setStatusCode(HttpStatus.valueOf(baseResponse.getStatus()));
}
}
自定义序列化器
/**
* 分页对象的序列化
*/
public class PageJacksonSerializer extends JsonSerializer<Page> {
@Override
public void serialize(Page page, JsonGenerator generator, SerializerProvider serializers) throws IOException {
//写开始标记:'{'
generator.writeStartObject();
//写内容:属性是"content",值是page.getContent()
generator.writeObjectField("content", page.getContent());
generator.writeNumberField("pages", page.getTotalPages()); //总页数
generator.writeNumberField("total", page.getTotalElements()); //总元素数
generator.writeNumberField("page", page.getNumber()); //第几页
generator.writeNumberField("rpp", page.getSize()); //当前页元素数
generator.writeBooleanField("hasNext", page.hasNext()); //是否后面还有页
generator.writeBooleanField("hasPrevious", page.hasPrevious()); //是否前面还有页
generator.writeBooleanField("isFirst", page.isFirst()); //是否是第一页
generator.writeBooleanField("isLast", page.isLast()); //是否是最后一页
generator.writeBooleanField("isEmpty", page.isEmpty()); //当前页内容是否为空
generator.writeBooleanField("hasContent", page.hasContent()); //当前页是否有内容
//处理评论页
if (page instanceof CommentPage) {
CommentPage commentPage = (CommentPage) page;
generator.writeNumberField("commentCount", commentPage.getCommentCount()); //总评论数(包含子评论)
}
//写结束标记:'}'
generator.writeEndObject();
}
}
使用1
@JsonSerialize(using = Date2LongSerialize.class)
private Date time;
使用2
/**
* Http请求和响应报文本质上都是一串字符串(有格式文本)。
*
* Spring Boot底层通过HttpMessageConverter(消息转换器)将请求报文与响应报文转换为对象。
*
* MappingJackson2HttpMessageConverter处理application/json。
*/
@Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.stream()
.filter(c -> c instanceof MappingJackson2HttpMessageConverter)
.findFirst().ifPresent(converter -> {
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter = (MappingJackson2HttpMessageConverter) converter;
Jackson2ObjectMapperBuilder builder = Jackson2ObjectMapperBuilder.json();
// JsonComponentModule 来扫描被 @JsonComponent 注解的类
// 并自动注册 JsonSerializer 和 JsonDeserializer。
JsonComponentModule module = new JsonComponentModule();
//指定PageImpl类型字段使用自定义的PageJacksonSerializer序列化器
module.addSerializer(PageImpl.class, new PageJacksonSerializer());
ObjectMapper objectMapper = builder.modules(module).build();
mappingJackson2HttpMessageConverter.setObjectMapper(objectMapper);
});
}
Controller层日志AOP切面类
@Aspect
@Component
@Slf4j
public class ControllerLogAop {
//所有Controller方法
@Pointcut("execution(* *..controller..*.*(..))")
public void controller() {
}
@Around("controller()")
public Object controller(ProceedingJoinPoint joinPoint) throws Throwable {
//类名
String className = joinPoint.getTarget().getClass().getSimpleName();
//方法名
String methodName = joinPoint.getSignature().getName();
//参数数组
Object[] args = joinPoint.getArgs();
//获取HttpServletRequest对象
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
HttpServletRequest request = Objects.requireNonNull(requestAttributes).getRequest();
//打印请求日志
printRequestLog(request, className, methodName, args);
long start = System.currentTimeMillis();
//处理目标方法
Object returnObj = joinPoint.proceed();
//打印响应日志
printResponseLog(className, methodName, returnObj, System.currentTimeMillis() - start);
return returnObj;
}
private void printRequestLog(HttpServletRequest request, String clazzName, String methodName, Object[] args) throws JsonProcessingException {
log.info("打印请求信息-----Request URL:[{}], URI:[{}], Request Method:[{}], IP:[{}]",
request.getRequestURL(),
request.getRequestURI(),
request.getMethod(),
ServletUtil.getClientIP(request));
//将参数转为Json字符串
String requestBody = JsonUtils.objectToJson(args);
log.info("打印请求参数信息-----{}.{}的请求体:[{}]", clazzName, methodName, requestBody);
}
private void printResponseLog(String className, String methodName, Object returnObj, long usage) throws JsonProcessingException {
String returningData = null;
if (returnObj != null) {
if (returnObj.getClass().isAssignableFrom(byte[].class)) {
returningData = "byte[]二进制数据";
} else {
returningData = JsonUtils.objectToJson(returnObj);
}
}
log.info("打印响应信息-----{}.{}的响应体:[{}], 处理时长:[{}]ms", className, methodName, returningData, usage);
}
}