Spring全家桶编程

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

2018-09-12  本文已影响4人  tenlee

背景

定义一个参数对象

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);
    }
}

参考:

上一篇 下一篇

猜你喜欢

热点阅读