美文网首页
SpringMVC自定义参数解析-解决 xxx.N.xxx 和x

SpringMVC自定义参数解析-解决 xxx.N.xxx 和x

作者: Java及SpringBoot | 来源:发表于2019-12-09 20:15 被阅读0次

    个人专题目录

    ActiviMQ专题

    链路追踪

    Dubbo专题

    Docker专题

    Git专题

    Idea专题

    Java阿里P6+必会专题

    Java工具类

    Kafka专题

    Linux专题

    Maven专题

    Markdown专题

    Mysql专题

    Netty专题

    Nginx专题

    Openstack专题

    Redis专题

    Spring专题

    SpringBoot专题

    SpringCloud专题

    Zookeeper专题

    个人随笔专题

    数据结构专题

    单点登录专题

    设计模式专题

    架构优化专题


    SpringMVC自定义参数解析-解决 xxx.N.xxx 和xxx.N的参数传递

    1. 项目中遇到的问题

    spring mvc默认不支持xxx.N.xxx 和xxx.N的参数传递 {@code HaveNAttribute}作用是把 xxx.N.xxx转换成* xxx[N].xxx 把 xxx.N转换成 xxx[N] 转换成spring mvc能处理的格式。

    解决方案:

    通过自定义注解HaveNAttribute来决定是否开启自定义参数解析:

    /**
     * 解决 xxx.N.xxx 和xxx.N的参数传递<br>
     * 如果参数加了该注解则开启自定义的参数解析
     */
    public @interface HaveNAttribute {
    
        String value() default "";
    }
    

    对象参数绑定扩展

    对象参数解析绑定会交给ServletModelAttributeMethodProcessor这个类,在初始化argumentResolvers的时候。

    所以我们只要扩展ServletModelAttributeMethodProcessor这个类即可。自定义的处理器也是处理复杂对象,只是扩展了可以处理名称映射,扩展这个ServletModelAttributeMethodProcessor即可。然后重写父类的bindRequestParameters方法,这个方法就是绑定数据对象的时候调用的方法。核心代码如下:

    public class HaveNAttributeHandlerMethodArgumentResolver extends ServletModelAttributeMethodProcessor {
    
        private HaveNAttribute haveNAttribute;
    
        public HaveNAttributeHandlerMethodArgumentResolver() {
            this(false);
        }
    
        public HaveNAttributeHandlerMethodArgumentResolver(boolean annotationNotRequired) {
            super(annotationNotRequired);
        }
    
        /**
         * 该解析器是否支持parameter参数的解析
         */
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            if (parameter.hasParameterAnnotation(HaveNAttribute.class)) {
                haveNAttribute = parameter.getParameterAnnotation(HaveNAttribute.class);
                return true;
            }
            return false;
        }
    
        /**
         * 将请求绑定至目标binder的target对象
         */
        @Override
        protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {
            ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);
            Assert.state(servletRequest != null, "No ServletRequest");
            HaveNAttributeDataBinder attrBinder = new HaveNAttributeDataBinder(binder.getTarget(), binder.getObjectName());
            attrBinder.bind(servletRequest);
        }
    }
    

    在bindRequestParameters这个方法中,新建了一个自定义的DataBinder-HaveNAttributeDataBinder,然后调了DataBinder的bind方法。DataBinder就是实际去把请求参数和对象绑定的类,自定义的DataBinder继承自ExtendedServletRequestDataBinder,子类复写的方法是addBindValues,有两个参数,一个是MutablePropertyValues类型的,这里面存的就是请求参数的key-value对,还有一个参数是request对象本身。

    核心代码DataBinder扩展

    @Slf4j
    public class HaveNAttributeDataBinder extends ExtendedServletRequestDataBinder {
    
        private final static String THREE_LEVEL = "^(\\w+)\\.(-?\\d+)\\.(\\w+)$";
        private final static String TWO_LEVEL = "^(\\w+)\\.(-?\\d+)$";
        private final static String FOUR_LEVEL = "^(\\w+)\\.(-?\\d+)\\.(\\w+)\\.(-?\\d+)$";
        private final static String DIGITS = "^-?\\d+$";
        private Map<PropertyValue, String> pvMap;
    
        public HaveNAttributeDataBinder(Object target) {
            super(target);
        }
    
        public HaveNAttributeDataBinder(Object target, String objectName) {
            super(target, objectName);
        }
    
        @Override
        protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
            super.addBindValues(mpvs, request);
            // 处理自己的逻辑
            PropertyValue[] pvArray = mpvs.getPropertyValues();
            pvMap = new HashMap<>();
            StringBuilder beforeBuffer = new StringBuilder();
            for (PropertyValue pv : pvArray) {
                String field = pv.getName();
                if (field.matches(THREE_LEVEL) || field.matches(TWO_LEVEL) || field.matches(FOUR_LEVEL)) {
                    pvMap.put(pv, field);
                }
                beforeBuffer.append(field).append("=======before========").append(pv.getValue()).append("\n");
            }
            log.info("handle before PropertyValues {},size {}", beforeBuffer.toString(), pvArray.length);
            // 替换map中的key *.N.* replace *[N].*
            this.replaceMapKey(pvMap, null);
            this.handlePropertyValue(pvMap, mpvs);
            PropertyValue[] pvArrays = mpvs.getPropertyValues();
            StringBuilder afterBuffer = new StringBuilder();
            for (PropertyValue pv : pvArrays) {
                afterBuffer.append(pv.getName()).append("=======after========").append(pv.getValue()).append("\n");
            }
            log.info("handle after PropertyValues {},size {}", afterBuffer.toString(), pvArrays.length);
        }
    
        /**
         * 替换map中的key *.N.* replace *[N].*
         */
        public void replaceMapKey(Map<PropertyValue, String> handleResult, Map<String, List<String>> countMap) {
            boolean isReturn = true;
            if (countMap == null) {
                countMap = new HashMap<>();
            }
            if (!handleResult.isEmpty()) {
                for (Map.Entry<PropertyValue, String> entry : handleResult.entrySet()) {
                    String str = entry.getValue();
                    if (str.lastIndexOf(".") > -1) {
                        isReturn = false;
                        String handleStr = this.handleKey(str, countMap);
                        entry.setValue(handleStr);
                    }
                }
                if (!isReturn) {
                    replaceMapKey(handleResult, countMap);
                }
            }
        }
    
        private String handleKey(String str, Map<String, List<String>> countMap) {
            int index = str.lastIndexOf(".");
            String prefixStr = StringUtils.capitalize(str.substring(0, index));
            String suffixStr = str.substring(index + 1);
    
            if (suffixStr.matches(DIGITS)) {
                List<String> list = countMap.computeIfAbsent(prefixStr, k -> new ArrayList<>());
                if (!list.contains(suffixStr)) {
                    list.add(suffixStr);
                }
                prefixStr += "[" + list.indexOf(suffixStr) + "]";
                return prefixStr;
            } else {
                suffixStr = StringUtils.capitalize(suffixStr);
                return handleKey(prefixStr, countMap) + suffixStr;
            }
    
        }
    
        public void handlePropertyValue(Map<PropertyValue, String> handleResult, MutablePropertyValues mpvs) {
            if (handleResult != null && !handleResult.isEmpty()) {
                for (Map.Entry<PropertyValue, String> entry : handleResult.entrySet()) {
                    PropertyValue pv = entry.getKey();
                    String str = entry.getValue();
                    String value = str.replace("]", "].");
                    int index = value.lastIndexOf(".");
                    if (index == value.length() - 1) {
                        value = value.substring(0, index);
                    }
                    entry.setValue(value);
                    mpvs.removePropertyValue(pv);
                    mpvs.addPropertyValue(value, Objects.requireNonNull(pv.getValue()));
                }
            }
        }
    
        public Map<PropertyValue, String> getPvMap() {
            return pvMap;
        }
    
        public void setPvMap(Map<PropertyValue, String> pvMap) {
            this.pvMap = pvMap;
        }
    }
    

    注册自定义解析器

    核心代码如下: 添加解析器以支持自定义控制器方法参数类型。该方法不会覆盖用于解析处理程序方法参数的内置支持。 要自定义内置的参数解析支持, 同样可以通过 RequestMappingHandlerAdapter 直接配置 RequestMappingHandlerAdapter

    @Configuration
    public class WebMvcConfig implements WebMvcConfigurer {
    
        @Override
        public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
            //添加一个自定义的参数解析对象
            resolvers.add(new HaveNAttributeHandlerMethodArgumentResolver());
        }
    
    }
    

    功能测试

    测试核心代码如下:

    Controller

    @RestController
    @RequestMapping(value = "/", produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
    @Slf4j
    public class TestController {
    
        @RequestMapping
        public void testAction(HttpServletRequest request, @Validated @HaveNAttribute TestParam param) {
            log.info("param={}", param);
        }
    
    }
    

    Model

    @Data
    public class TestParam {
    
        @NotNull
        @Min(1)
        private Integer age;
    
        @NotBlank
        @Size(min = 2, max = 14)
        private String name;
    
        private Map<@NotBlank String, @NotBlank String> relationship;
    
        private List<@NotBlank String> instanceId;
    
        private List<@NotNull @Valid Dog> dog;
    
        private Map<String, Dog> mapDag;
    
        @Valid
        private Map<Integer, Filter> mapFilter;
    
        @Valid
        private List<Filter> filter;
    
    
        @Data
        public static class Dog {
            @NotBlank
            private String name;
            @NotNull
            private Integer age;
        }
    
        @Data
        public static class Filter {
            @NotBlank
            private String name;
            @NotNull
            private List<String> value;
        }
    }
    
    相关测试postman截图如下:读者可以自行测试

    注意:Content-Type:application/x-www-form-urlencoded

    image-20191209201254943.png

    相关文章

      网友评论

          本文标题:SpringMVC自定义参数解析-解决 xxx.N.xxx 和x

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