springboot

SpringBoot2.x之HandlerMethodArgum

2022-02-07  本文已影响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)—自定义解析器顺序

上一篇下一篇

猜你喜欢

热点阅读