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