zoukankan      html  css  js  c++  java
  • Halo(七)

    @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);
        }
    }
    
  • 相关阅读:
    Django测试开发-20-settings.py中templates配置,使得APP下的模板以及根目录下的模板均可生效
    Django测试开发-19-auth模块之session,cookie
    Django测试开发-19-引入xadmin
    Django测试开发-17-报错:No module named 'django.contrib.staticfiles.templatetags'
    Django测试开发-16-ImportError: cannot import name 'six' from 'django.utils'
    Django测试开发-15-django.utils.encoding未发现 python_2_unicode_compatible包
    Django测试开发-14-数据库表设计:多对多,一对一,一对多
    Django测试开发-13-优化表单提交(GET、POST、登录、注册)
    Django测试开发-12-优化admin (2020-03-13 18:57)
    Django测试开发-11-返回json数据
  • 原文地址:https://www.cnblogs.com/loveer/p/11924017.html
Copyright © 2011-2022 走看看