美文网首页springboot
SpringBoot2.x之HandlerMethodArgum

SpringBoot2.x之HandlerMethodArgum

作者: 小胖学编程 | 来源:发表于2022-02-07 09:56 被阅读0次

    你还在为定义Vo对象而烦恼吗?可以使用自定义注解去获取Json的值,如同@RequestParam那样。

    1. 自定义注解

    /**
     * 自定义注解,将JSON解析为对象。
     * 对没错就是抄袭的@RequestParam
     */
    @Target({ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface JsonParam {
    
        /**
         * Alias for {@link #name}.
         */
        @AliasFor("name")
        String value() default "";
    
        /**
         * The name of the request parameter to bind to.
         * @since 4.2
         */
        @AliasFor("value")
        String name() default "";
    
        /**
         * Whether the parameter is required.
         * <p>Defaults to {@code true}, leading to an exception being thrown
         * if the parameter is missing in the request. Switch this to
         * {@code false} if you prefer a {@code null} value if the parameter is
         * not present in the request.
         * <p>Alternatively, provide a {@link #defaultValue}, which implicitly
         * sets this flag to {@code false}.
         */
        boolean required() default true;
    
        /**
         * The default value to use as a fallback when the request parameter is
         * not provided or has an empty value.
         * <p>Supplying a default value implicitly sets {@link #required} to
         * {@code false}.
         */
        String defaultValue() default ValueConstants.DEFAULT_NONE;
    }
    

    2. 自定义解析器

    2.1 自定义解析器顺序

    @Slf4j
    @Configuration
    public class WebMvcConfiguration implements WebMvcConfigurer {
        @Autowired
        private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
    
        @PostConstruct
        public void init() {
            //获取到自定义requestMappingHandlerAdapter的属性(只读)
            List<HandlerMethodArgumentResolver> resolvers = requestMappingHandlerAdapter.getArgumentResolvers();
            List<HandlerMethodArgumentResolver> newResolvers =
                    new ArrayList<>(resolvers.size() + 2);
            // 添加 解析器 到集合首位
            newResolvers.add(new JsonParamAnnotationResolver());
            // 添加 已注册的 Resolver 对象集合
            newResolvers.addAll(resolvers);
            // 从新设置 Resolver 对象集合
            requestMappingHandlerAdapter.setArgumentResolvers(newResolvers);
        }
    }
    

    2.2 实现自定义解析器

    其实就是参考的@RequestBody注解来实现,@JsonParam可以看着为轻量级的获取Json参数的自定义实现注解。

    @Slf4j
    public class JsonParamAnnotationResolver implements HandlerMethodArgumentResolver {
    
        private static ObjectMapper objectMapper = new ObjectMapper();
    
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.hasParameterAnnotation(JsonParam.class);
        }
    
    
        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer modelAndViewContainer,
                NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) throws Exception {
            JsonParam jsonParam = parameter.getParameterAnnotation(JsonParam.class);
            boolean required = jsonParam.required();
            Class<?> type = parameter.getParameterType();
            String paramName = jsonParam.value();
    
            Object value;
            try {
                //拿到参数。读取请求对象的流
                Object param = RequestBodyScope.get(paramName);
                // 传入type和contextClass对象,得到JavaType
                //org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.getJavaType 源码。
                JavaType javaType =
                        getJavaType(parameter.getGenericParameterType(), parameter.getContainingClass());
                //Jackson+JavaType下支持泛型的返回。
                value = value(param, javaType);
            } catch (Exception e) {
                //出现异常,则抛出参数非法的异常
                throw new IllegalArgumentException(e);
            }
            if (value != null) {
                return value;
            }
            if (required) {
                throw new MissingServletRequestParameterException(paramName, type.getTypeName());
            }
            if (!Objects.equals(jsonParam.defaultValue(), ValueConstants.DEFAULT_NONE)) {
                value = jsonParam.defaultValue();
            }
            return value;
        }
    
        /**
         * 将{@link java.lang.reflect.Type} 转化为Jackson需要的{com.fasterxml.jackson.databind.JavaType}
         */
        public static JavaType getJavaType(Type type, Class<?> contextClass) {
            //MAPPER这个可以使用ObjectMapperUtils中ObjectMapper
            TypeFactory typeFactory = objectMapper.getTypeFactory();
            //这种是处理public <T extends User> T testEnvV3(@JsonParam("users") List<T> user) 这种类型。
            return typeFactory.constructType(GenericTypeResolver.resolveType(type, contextClass));
        }
    
        /**
         * 将Object对象转换为具体的对象类型(支持泛型)
         */
        public static <T> T value(Object rawValue, JavaType javaType) {
            return objectMapper.convertValue(rawValue, javaType);
        }
    }
    

    2.3 一些工具类

    因为流只能读取一次,所以需要在第一次读取的时候,将解析的对象存入ThreadLocal中,以便于多次使用@JsonParam来进行解析。

    @Slf4j
    public class RequestBodyScope {
    
        /**
         * 需要将其设置到ThreadLocal中,以便多个@JsonParam注解多次进行解析。
         * 注意内存泄露的问题。
         */
        private static ThreadLocal<Map<String, Object>> REQUEST_BODY_THREAD_LOCAL =
                ThreadLocal.withInitial(RequestBodyScope::resolveRequestBody);
    
        private static ObjectMapper MAPPER = new ObjectMapper();
    
        /**
         * 获取RequestBody里面的值
         */
        public static Object get(String name) {
            return REQUEST_BODY_THREAD_LOCAL.get().get(name);
        }
    
        /**
         * 回收资源
         */
        public static void clear() {
            REQUEST_BODY_THREAD_LOCAL.remove();
        }
    
    
        /**
         * 解析requestBody对象为Map对象
         */
        private static Map<String, Object> resolveRequestBody() {
            HttpServletRequest request = ((ServletRequestAttributes) currentRequestAttributes()).getRequest();
            if (!isJsonRequest(request)) {
                return Collections.emptyMap();
            }
            try (InputStream input = request.getInputStream()) {
                byte[] bytes = IOUtils.toByteArray(input);
                String encoding = StringUtils.defaultIfBlank(request.getCharacterEncoding(), "UTF-8");
                String content = new String(bytes, encoding);
                return fromJson(content);
    
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 将json串转化为Map对象
         */
        private static Map<String, Object> fromJson(String json) {
            if (StringUtils.isEmpty(json)) {
                json = "{}";
            }
            try {
                //json串转化为特定格式的Map对象
                return MAPPER.readValue(json,
                        defaultInstance().constructMapType(Map.class, String.class, Object.class));
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 判断是否为JSON格式?
         */
        private static boolean isJsonRequest(HttpServletRequest request) {
            String contentType = request.getHeader("Content-Type");
            return contentType != null && contentType.toLowerCase().contains("application/json");
        }
    }
    

    3. 如何测试

    @Slf4j
    @RestController
    public class ResolverController {
    
        @RequestMapping(value = "/r/t1", method = RequestMethod.POST)
        public String tt(@JsonParam("name") String name, @JsonParam("pid") Long id) {
            log.info("输出:{},{}",name,id);
            return "success";
        }
    
    }
    

    相关文章

    SpringBoot2.x之HandlerMethodArgumentResolver实战
    SpringBoot2.x之HandlerMethodArgumentResolver(2)—自定义解析器顺序

    相关文章

      网友评论

        本文标题:SpringBoot2.x之HandlerMethodArgum

        本文链接:https://www.haomeiwen.com/subject/bearhrtx.html