为了账号安全,请及时绑定邮箱和手机立即绑定

绑定Spring MVC命令对象时如何自定义参数名称?

/ 猿问

绑定Spring MVC命令对象时如何自定义参数名称?

幕布斯6054654 2019-12-13 09:02:06

我有一个命令对象:


public class Job {

    private String jobType;

    private String location;

}

这受spring-mvc约束:


@RequestMapping("/foo")

public Strnig doSomethingWithJob(Job job) {

   ...

}

哪个适合http://example.com/foo?jobType=permanent&location=Stockholm。但是现在我需要使它适用于以下网址:


http://example.com/foo?jt=permanent&loc=Stockholm

显然,我不想更改命令对象,因为字段名称必须保持较长(因为它们在代码中使用)。我该如何自定义?是否可以执行以下操作:


public class Job {

    @RequestParam("jt")

    private String jobType;

    @RequestParam("loc")

    private String location;

}

这不起作用(@RequestParam不能应用于字段)。


我正在考虑的事情是类似于FormHttpMessageConverter目标对象并读取目标对象上的自定义注释的自定义消息转换器


查看完整描述

3 回答

?
慕仰1329654

该解决方案更加简洁,但是需要使用RequestMappingHandlerAdapter,该<mvc:annotation-driven />功能在启用时由Spring使用。希望它能帮助到别人。想法是像这样扩展ServletRequestDataBinder:


 /**

 * ServletRequestDataBinder which supports fields renaming using {@link ParamName}

 *

 * @author jkee

 */

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

            String to = entry.getValue();

            if (mpvs.contains(from)) {

                mpvs.add(to, mpvs.getPropertyValue(from).getValue());

            }

        }

    }

}

合适的处理器:


/**

 * Method processor supports {@link ParamName} parameters renaming

 *

 * @author jkee

 */


public class RenamingProcessor extends ServletModelAttributeMethodProcessor {


    @Autowired

    private RequestMappingHandlerAdapter requestMappingHandlerAdapter;


    //Rename cache

    private final Map<Class<?>, Map<String, String>> replaceMap = new ConcurrentHashMap<Class<?>, Map<String, String>>();


    public RenamingProcessor(boolean annotationNotRequired) {

        super(annotationNotRequired);

    }


    @Override

    protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest nativeWebRequest) {

        Object target = binder.getTarget();

        Class<?> targetClass = target.getClass();

        if (!replaceMap.containsKey(targetClass)) {

            Map<String, String> mapping = analyzeClass(targetClass);

            replaceMap.put(targetClass, mapping);

        }

        Map<String, String> mapping = replaceMap.get(targetClass);

        ParamNameDataBinder paramNameDataBinder = new ParamNameDataBinder(target, binder.getObjectName(), mapping);

        requestMappingHandlerAdapter.getWebBindingInitializer().initBinder(paramNameDataBinder, nativeWebRequest);

        super.bindRequestParameters(paramNameDataBinder, nativeWebRequest);

    }


    private static Map<String, String> analyzeClass(Class<?> targetClass) {

        Field[] fields = targetClass.getDeclaredFields();

        Map<String, String> renameMap = new HashMap<String, String>();

        for (Field field : fields) {

            ParamName paramNameAnnotation = field.getAnnotation(ParamName.class);

            if (paramNameAnnotation != null && !paramNameAnnotation.value().isEmpty()) {

                renameMap.put(paramNameAnnotation.value(), field.getName());

            }

        }

        if (renameMap.isEmpty()) return Collections.emptyMap();

        return renameMap;

    }

}

注解:


/**

 * 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();


}

春季配置:


<mvc:annotation-driven>

    <mvc:argument-resolvers>

        <bean class="ru.yandex.metrika.util.params.RenamingProcessor">

            <constructor-arg name="annotationNotRequired" value="true"/>

        </bean>

    </mvc:argument-resolvers>

</mvc:annotation-driven> 

最后,用法(如Bozho解决方案):


public class Job {

    @ParamName("job-type")

    private String jobType;

    @ParamName("loc")

    private String location;

}


查看完整回答
反对 回复 2019-12-13
?
四季花海

这是我的工作:


首先,一个参数解析器:


/**

 * This resolver handles command objects annotated with @SupportsAnnotationParameterResolution

 * that are passed as parameters to controller methods.

 * 

 * It parses @CommandPerameter annotations on command objects to

 * populate the Binder with the appropriate values (that is, the filed names

 * corresponding to the GET parameters)

 * 

 * In order to achieve this, small pieces of code are copied from spring-mvc

 * classes (indicated in-place). The alternative to the copied lines would be to

 * have a decorator around the Binder, but that would be more tedious, and still

 * some methods would need to be copied.

 * 

 * @author bozho

 * 

 */

public class AnnotationServletModelAttributeResolver extends ServletModelAttributeMethodProcessor {


    /**

     * A map caching annotation definitions of command objects (@CommandParameter-to-fieldname mappings)

     */

    private ConcurrentMap<Class<?>, Map<String, String>> definitionsCache = Maps.newConcurrentMap();


    public AnnotationServletModelAttributeResolver(boolean annotationNotRequired) {

        super(annotationNotRequired);

    }


    @Override

    public boolean supportsParameter(MethodParameter parameter) {

        if (parameter.getParameterType().isAnnotationPresent(SupportsAnnotationParameterResolution.class)) {

            return true;

        }

        return false;

    }


    @Override

    protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest request) {

        ServletRequest servletRequest = request.getNativeRequest(ServletRequest.class);

        ServletRequestDataBinder servletBinder = (ServletRequestDataBinder) binder;

        bind(servletRequest, servletBinder);

    }


    @SuppressWarnings("unchecked")

    public void bind(ServletRequest request, ServletRequestDataBinder binder) {

        Map<String, ?> propertyValues = parsePropertyValues(request, binder);

        MutablePropertyValues mpvs = new MutablePropertyValues(propertyValues);

        MultipartRequest multipartRequest = WebUtils.getNativeRequest(request, MultipartRequest.class);

        if (multipartRequest != null) {

            bindMultipart(multipartRequest.getMultiFileMap(), mpvs);

        }


        // two lines copied from ExtendedServletRequestDataBinder

        String attr = HandlerMapping.URI_TEMPLATE_VARIABLES_ATTRIBUTE;

        mpvs.addPropertyValues((Map<String, String>) request.getAttribute(attr));

        binder.bind(mpvs);

    }


    private Map<String, ?> parsePropertyValues(ServletRequest request, ServletRequestDataBinder binder) {


        // similar to WebUtils.getParametersStartingWith(..) (prefixes not supported)

        Map<String, Object> params = Maps.newTreeMap();

        Assert.notNull(request, "Request must not be null");

        Enumeration<?> paramNames = request.getParameterNames();

        Map<String, String> parameterMappings = getParameterMappings(binder);

        while (paramNames != null && paramNames.hasMoreElements()) {

            String paramName = (String) paramNames.nextElement();

            String[] values = request.getParameterValues(paramName);


            String fieldName = parameterMappings.get(paramName);

            // no annotation exists, use the default - the param name=field name

            if (fieldName == null) {

                fieldName = paramName;

            }


            if (values == null || values.length == 0) {

                // Do nothing, no values found at all.

            } else if (values.length > 1) {

                params.put(fieldName, values);

            } else {

                params.put(fieldName, values[0]);

            }

        }


        return params;

    }


    /**

     * Gets a mapping between request parameter names and field names.

     * If no annotation is specified, no entry is added

     * @return

     */

    private Map<String, String> getParameterMappings(ServletRequestDataBinder binder) {

        Class<?> targetClass = binder.getTarget().getClass();

        Map<String, String> map = definitionsCache.get(targetClass);

        if (map == null) {

            Field[] fields = targetClass.getDeclaredFields();

            map = Maps.newHashMapWithExpectedSize(fields.length);

            for (Field field : fields) {

                CommandParameter annotation = field.getAnnotation(CommandParameter.class);

                if (annotation != null && !annotation.value().isEmpty()) {

                    map.put(annotation.value(), field.getName());

                }

            }

            definitionsCache.putIfAbsent(targetClass, map);

            return map;

        } else {

            return map;

        }

    }


    /**

     * Copied from WebDataBinder.

     * 

     * @param multipartFiles

     * @param mpvs

     */

    protected void bindMultipart(Map<String, List<MultipartFile>> multipartFiles, MutablePropertyValues mpvs) {

        for (Map.Entry<String, List<MultipartFile>> entry : multipartFiles.entrySet()) {

            String key = entry.getKey();

            List<MultipartFile> values = entry.getValue();

            if (values.size() == 1) {

                MultipartFile value = values.get(0);

                if (!value.isEmpty()) {

                    mpvs.add(key, value);

                }

            } else {

                mpvs.add(key, values);

            }

        }

    }

}

然后使用后处理器注册参数解析器。它应该注册为<bean>:


/**

 * Post-processor to be used if any modifications to the handler adapter need to be made

 * 

 * @author bozho

 *

 */

public class AnnotationHandlerMappingPostProcessor implements BeanPostProcessor {


    @Override

    public Object postProcessAfterInitialization(Object bean, String arg1)

            throws BeansException {

        return bean;

    }


    @Override

    public Object postProcessBeforeInitialization(Object bean, String arg1)

            throws BeansException {

        if (bean instanceof RequestMappingHandlerAdapter) {

            RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean;

            List<HandlerMethodArgumentResolver> resolvers = adapter.getCustomArgumentResolvers();

            if (resolvers == null) {

                resolvers = Lists.newArrayList();

            }

            resolvers.add(new AnnotationServletModelAttributeResolver(false));

            adapter.setCustomArgumentResolvers(resolvers);

        }


        return bean;

    }


}


查看完整回答
反对 回复 2019-12-13
?
偶然的你

在Spring 3.1中,ServletRequestDataBinder为附加绑定值提供了一个钩子:


protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {

}

ExtendedServletRequestDataBinder子类使用它来添加URI模板变量作为绑定值。您可以进一步扩展它,以便添加特定于命令的字段别名。


您可以重写RequestMappingHandlerAdapter.createDataBinderFactory(..)以提供自定义WebDataBinder实例。从控制器的角度来看,它可能看起来像这样:


@InitBinder

public void initBinder(MyWebDataBinder binder) {

   binder.addFieldAlias("jobType", "jt");

   // ...

}


查看完整回答
反对 回复 2019-12-13

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信