美文网首页Spring全家桶编程
绑定SpringMvc GET请求对象时自定义参数名

绑定SpringMvc GET请求对象时自定义参数名

作者: tenlee | 来源:发表于2018-09-12 17:16 被阅读4次

    背景

    定义一个参数对象

    public class Job {
        private String jobType;
        private String location;
    }
    

    Spring MVCGET请求使用它

    @GetMapping("/foo")
    public Job doSomethingWithJob(Job job) {
       ...
    }
    

    请求http://example.com/foo?jobType=permanent&location=Stockholm会得到正确显示,
    请求http://example.com/foo?job_type=permanent&location=Stockholm不会得到正确显示
    这时候就需要自定义GET请求的字段名了,Spring对这样的支持可能不太完美。

    PS:如果有这样的需求,还是建议使用POST JSON这种方式吧。

    解决方案

    参考stackoverflow,但是该解决方法不太完美,不支持类继承情况下的别名映射。
    本人修改后完整代码如下:

    自定义一个注解
    /**
     * Overrides parameter name
     * @author jkee
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface ParamName {
        /**
         * The name of the request parameter to bind to.
         */
        String value();
    }
    
    使用ServletRequestDataBinder给真实的Filed赋值
    /**
     * 将{@link ParamName}注释的field加入MutablePropertyValues,是spring能够注入该值
     * @author tenlee
     */
    public class ParamNameDataBinder extends ExtendedServletRequestDataBinder {
    
        private final Map<String, String> renameMapping;
    
        public ParamNameDataBinder(Object target, String objectName, Map<String, String> renameMapping) {
            super(target, objectName);
            this.renameMapping = renameMapping;
        }
    
        @Override
        protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
            super.addBindValues(mpvs, request);
            for (Map.Entry<String, String> entry : renameMapping.entrySet()) {
                String from = entry.getKey();  // ParamName.value的值,即原请求参数的key,可能和field name不同
                String to = entry.getValue();  // field name
                if (mpvs.contains(from)) {
                    mpvs.add(to, mpvs.getPropertyValue(from).getValue()); // 设置field name 的值,使spring能注入
                }
            }
        }
    }
    
    处理Field和别名的映射关系
    /**
     * Method processor supports {@link ParamName} parameters renaming
     *
     * @author tenlee
     */
    public class RenamingProcessor extends ServletModelAttributeMethodProcessor {
    
        @Autowired
        private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
    
        /**
         * A map caching annotation definitions of command objects (@ParamName-to-fieldname mappings)
         */
        private final Map<Class<?>, Map<String, String>> replaceMap = new ConcurrentHashMap<>();
    
        public RenamingProcessor(boolean annotationNotRequired) {
            super(annotationNotRequired);
        }
    
        @Override
        protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest nativeWebRequest) {
            Object target = binder.getTarget();
            Map<String, String> mapping = getFieldMapping(target.getClass());
    
            ParamNameDataBinder paramNameDataBinder = new ParamNameDataBinder(target, binder.getObjectName(), mapping);
            requestMappingHandlerAdapter.getWebBindingInitializer().initBinder(paramNameDataBinder, nativeWebRequest);
            super.bindRequestParameters(paramNameDataBinder, nativeWebRequest);
        }
    
        private Map<String, String> getFieldMapping(Class<?> targetClass) {
            if (targetClass == Object.class) {
                return Collections.emptyMap();
            }
    
            if (replaceMap.containsKey(targetClass)) {
                return replaceMap.get(targetClass);
            }
    
            Map<String, String> renameMap = new HashMap<>();
            Field[] fields = targetClass.getDeclaredFields();
            for (Field field : fields) {
                ParamName paramNameAnnotation = field.getAnnotation(ParamName.class);
                if (paramNameAnnotation != null && !paramNameAnnotation.value().isEmpty()) {
                    renameMap.put(paramNameAnnotation.value(), field.getName());
                }
            }
    
            // 递归获取全部Field
            renameMap.putAll(getFieldMapping(targetClass.getSuperclass()));
         
             if (renameMap.isEmpty()) {
                renameMap = Collections.emptyMap();
            }
            replaceMap.put(targetClass, renameMap);
            return renameMap;
        }
    }
    
    配置ServletModelAttributeMethodProcessor生效
    /**
     * @author tenlee
     */
    @Configuration
    public class WebContextConfiguration extends WebMvcConfigurerAdapter {
        @Override
        public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
            argumentResolvers.add(renamingProcessor());
        }
    
        @Bean
        protected RenamingProcessor renamingProcessor() {
            return new RenamingProcessor(true);
        }
    }
    

    参考:

    相关文章

      网友评论

        本文标题:绑定SpringMvc GET请求对象时自定义参数名

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