@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);
}
}

最新文章

  1. mysql CREATE USER
  2. java 笔试题 字符串旋转
  3. 浅析对象访问属性的&quot;.&quot;和&quot;[]&quot;方法区别
  4. 【对比分析八】null和undefined的区别
  5. IntelliJ IDEA currently
  6. 引用、return
  7. 认识k_BackingField【转】
  8. 什么是RAW?
  9. mybatis遇见的奇葩问题(返回null)
  10. 网络安全实验室 脚本关通关writeup
  11. [翻译] 编写高性能 .NET 代码--第二章 GC -- 减少分配率, 最重要的规则,缩短对象的生命周期,减少对象层次的深度,减少对象之间的引用,避免钉住对象(Pinning)
  12. hdfs 架构
  13. Objective-C RunTime 学习笔记 之 atomic/nonatomic 关键字
  14. jmeter元素
  15. Laravel 怎么使用资源控制器delete方法
  16. [pig] pig 基础使用
  17. java学习笔记(十):scanner输入
  18. js 获取北京时间
  19. Python基础笔记(一)
  20. Android图片处理:识别图像方向并显示

热门文章

  1. HTML与CSS中的文本个人分享
  2. 影视感悟专题---1、B站-魔兽世界代理及其它乱七八糟
  3. HDU6669 Game(思维,贪心)
  4. MySQL5.6多实例安装
  5. DB-MDM:MDM/主数据管理 百科
  6. spring-boot BUG 集锦
  7. mysql 数据库表结构对比语句
  8. Go语言格式化字符串
  9. SpringMvc.xml
  10. Struts2之上传