【问题标题】:How to customize parameter names when binding Spring MVC command objects?绑定Spring MVC命令对象时如何自定义参数名称?
【发布时间】:2012-02-17 16:27:12
【问题描述】:

我有一个命令对象:

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

哪个是spring-mvc绑定的:

@RequestMapping("/foo")
public String 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 的自定义消息转换器并读取目标对象上的自定义注释

【问题讨论】:

标签: java spring spring-mvc


【解决方案1】:

这个解决方案更简洁,但需要使用 RequestMappingHandlerAdapter,Spring 在启用<mvc:annotation-driven /> 时使用它。 希望它会帮助某人。 这个想法是像这样扩展 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;
}

【讨论】:

  • 非常感谢您的解决方案!注意:这保留了DateTimeFormat 注释的功能,即@ParamName 带注释的Date 字段可以另外用@DateTimeFormat(pattern = "yyyy-MM-dd") 注释。
  • 对于所有 Java 配置爱好者来说,Java 中的 Spring 上下文配置如下所示:@Configuration public class WebContextConfiguration extends WebMvcConfigurationSupport { @Override protected void addArgumentResolvers( List&lt;HandlerMethodArgumentResolver&gt; argumentResolvers) { argumentResolvers.add(renamingProcessor()); } @Bean protected RenamingProcessor renamingProcessor() { return new RenamingProcessor(true); } } 请注意,extends WebMvcConfigurationSupport 替换了 @EnableWebMvc&lt;mvc:annotation-driven /&gt;
  • 请帮忙解决我的类似问题stackoverflow.com/questions/38171022/…
  • 从 spring 4.2 开始有什么可以帮助使这更容易吗?
  • 我已经通过将addArgumentResolvers 替换为 bean 后处理器:pastebin.com/07ws0uUZ
【解决方案2】:

这是我的工作:

一、参数解析器:

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

然后使用后处理器注册参数解析器。应该注册为&lt;bean&gt;:

/**
 * 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;
    }

}

【讨论】:

  • 如果有一个完整的例子会很有帮助,因为我无法构建上面的例子。
  • 请帮忙解决我的类似问题stackoverflow.com/questions/38171022/…
  • 这个不完整的代码怎么会是公认的答案?它缺少几个类,如 SupportsAnnotationParameterResolution@CommandPattern@SupportsCustomizedBinding,以及 Maps.*Lists.* 的导入
  • 好的,SupportsAnnotationParameterResolution 改成SupportsCustomizedBinding。因此,在创建两个注释时,该方法有效!
【解决方案3】:

在 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");
   // ...
}

【讨论】:

  • 谢谢,但是如果我需要重写.createDtaBinderFactory,这意味着我应该替换RequestMappingHandlerAdapter,也就是说我不能使用&lt;mvc:annotation-driven /&gt;,对吧?
  • 不客气。是的,使用 你不能插入自定义的 RequestMappingHandlerMapping。但是,您可以使用 MVC Java 配置轻松完成。
  • @RossenStoyanchev:你能解释一下如何用@EnableWebMvc 插入自定义MyWebDataBinder 吗?我看到我必须继承 ExtendedServletRequestDataBinder 并通过继承 ServletRequestDataBinderFactory 来返回它。现在我可以通过继承RequestMappingHandlerAdapter 并覆盖createDataBinderFactory() 来返回这个新工厂。但是如何强制 Spring MVC 使用我的子类RequestMappingHandlerAdapter?它创建于WebMvcConfigurationSupport...
  • @TomaszNurkiewicz,也许你已经明白了,但如果没有看到参考文档中关于基于 XML 的 Spring MVC 配置的高级 Java 部分.. static.springsource.org/spring/docs/3.1.x/…
  • @RossenStoyanchev:实际上并不紧急,但感谢您的建议,它终于奏效了,+1!
【解决方案4】:

感谢@jkee 的回答。
这是我的解决方案。
一、自定义注解:

@Inherited
@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ParamName {

  /**
   * The name of the request parameter to bind to.
   */
  String value();

}

客户DataBinder:

public class ParamNameDataBinder extends ExtendedServletRequestDataBinder {

  private final Map<String, String> paramMappings;

  public ParamNameDataBinder(Object target, String objectName, Map<String, String> paramMappings) {
    super(target, objectName);
    this.paramMappings = paramMappings;
  }

  @Override
  protected void addBindValues(MutablePropertyValues mutablePropertyValues, ServletRequest request) {
    super.addBindValues(mutablePropertyValues, request);
    for (Map.Entry<String, String> entry : paramMappings.entrySet()) {
      String paramName = entry.getKey();
      String fieldName = entry.getValue();
      if (mutablePropertyValues.contains(paramName)) {
        mutablePropertyValues.add(fieldName, mutablePropertyValues.getPropertyValue(paramName).getValue());
      }
    }
  }

}

参数解析器:

public class ParamNameProcessor extends ServletModelAttributeMethodProcessor {

  @Autowired
  private RequestMappingHandlerAdapter requestMappingHandlerAdapter;

  private static final Map<Class<?>, Map<String, String>> PARAM_MAPPINGS_CACHE = new ConcurrentHashMap<>(256);

  public ParamNameProcessor() {
    super(false);
  }

  @Override
  public boolean supportsParameter(MethodParameter parameter) {
    return parameter.hasParameterAnnotation(RequestParam.class)
        && !BeanUtils.isSimpleProperty(parameter.getParameterType())
        && Arrays.stream(parameter.getParameterType().getDeclaredFields())
        .anyMatch(field -> field.getAnnotation(ParamName.class) != null);
  }

  @Override
  protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest nativeWebRequest) {
    Object target = binder.getTarget();
    Map<String, String> paramMappings = this.getParamMappings(target.getClass());
    ParamNameDataBinder paramNameDataBinder = new ParamNameDataBinder(target, binder.getObjectName(), paramMappings);
    requestMappingHandlerAdapter.getWebBindingInitializer().initBinder(paramNameDataBinder, nativeWebRequest);
    super.bindRequestParameters(paramNameDataBinder, nativeWebRequest);
  }

  /**
   * Get param mappings.
   * Cache param mappings in memory.
   *
   * @param targetClass
   * @return {@link Map<String, String>}
   */
  private Map<String, String> getParamMappings(Class<?> targetClass) {
    if (PARAM_MAPPINGS_CACHE.containsKey(targetClass)) {
      return PARAM_MAPPINGS_CACHE.get(targetClass);
    }
    Field[] fields = targetClass.getDeclaredFields();
    Map<String, String> paramMappings = new HashMap<>(32);
    for (Field field : fields) {
      ParamName paramName = field.getAnnotation(ParamName.class);
      if (paramName != null && !paramName.value().isEmpty()) {
        paramMappings.put(paramName.value(), field.getName());
      }
    }
    PARAM_MAPPINGS_CACHE.put(targetClass, paramMappings);
    return paramMappings;
  }

}

最后,将 ParamNameProcessor 添加到第一个参数解析器中的 bean 配置:

@Configuration
public class WebConfig {

  /**
   * Processor for annotation {@link ParamName}.
   *
   * @return ParamNameProcessor
   */
  @Bean
  protected ParamNameProcessor paramNameProcessor() {
    return new ParamNameProcessor();
  }

  /**
   * Custom {@link BeanPostProcessor} for adding {@link ParamNameProcessor} into the first of
   * {@link RequestMappingHandlerAdapter#argumentResolvers}.
   *
   * @return BeanPostProcessor
   */
  @Bean
  public BeanPostProcessor beanPostProcessor() {
    return new BeanPostProcessor() {

      @Override
      public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
      }

      @Override
      public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        if (bean instanceof RequestMappingHandlerAdapter) {
          RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean;
          List<HandlerMethodArgumentResolver> argumentResolvers = new ArrayList<>(adapter.getArgumentResolvers());
          argumentResolvers.add(0, paramNameProcessor());
          adapter.setArgumentResolvers(argumentResolvers);
        }
        return bean;
      }
    };
  }

}

参数 pojo:

@Data
public class Foo {

  private Integer id;

  @ParamName("first_name")
  private String firstName;

  @ParamName("last_name")
  private String lastName;

  @ParamName("created_at")
  @DateTimeFormat(pattern = "yyyy-MM-dd")
  private Date createdAt;

}

控制器方法:

@GetMapping("/foos")
public ResponseEntity<List<Foo>> listFoos(@RequestParam Foo foo, @PageableDefault(sort = "id") Pageable pageable) {
  List<Foo> foos = fooService.listFoos(foo, pageable);
  return ResponseEntity.ok(foos);
}

就是这样。

【讨论】:

  • 这与@jkee 贡献的内容有何不同?
【解决方案5】:

有一个简单的方法,你可以多加一个setter方法,比如“setLoc,setJt”。

【讨论】:

  • 带连字符的参数怎么样? Spring 如何自动转换这些?
【解决方案6】:

没有很好的内置方法可以做到这一点,您只能选择您应用的解决方法。处理的区别

@RequestMapping("/foo")
public String doSomethingWithJob(Job job)

@RequestMapping("/foo")
public String doSomethingWithJob(String stringjob)

那个job是一个bean而stringjob不是(到目前为止并不奇怪)。真正的区别在于,bean 是使用标准 Spring bean 解析器机制解析的,而字符串参数是由知道 @RequestParam 注释概念的 Spring MVC 解析的。长话短说,标准 Spring bean 解析(即使用 PropertyValues、PropertyValue、GenericTypeAwarePropertyDescriptor 等类)无法将“jt”解析为名为“jobType”的属性,或者至少我不知道它。

解决方法可以像其他人建议的那样添加自定义 PropertyEditor 或过滤器,但我认为它只会弄乱代码。在我看来,最干净的解决方案是声明一个这样的类:

public class JobParam extends Job {
    public String getJt() {
         return super.job;
    }

    public void setJt(String jt) {
         super.job = jt;
    }

}

然后在你的控制器中使用它

@RequestMapping("/foo")
public String doSomethingWithJob(JobParam job) {
   ...
}

更新:

一个稍微简单的选项是不扩展,只需将额外的getter,setter添加到原始类中

public class Job {

    private String jobType;
    private String location;

    public String getJt() {
         return jobType;
    }

    public void setJt(String jt) {
         jobType = jt;
    }

}

【讨论】:

  • @Bozho 是的,它不是太复杂,但至少易于阅读:)(我用简化的解决方案更新了原始帖子)
  • 保持简单,是啊.. 可惜spring不支持任何开箱即用的东西=(
  • 那并没有解决原来的问题。即使用 Spring 将包含连字符“job-type”的参数自动转换为 DTO
【解决方案7】:

我想为您指出另一个方向。 但我不知道它是否有效

我会尝试操纵绑定本身。

它由WebDataBinder 完成,将从HandlerMethodInvoker 方法Object[] resolveHandlerArguments(Method handlerMethod, Object handler, NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception 调用

我对 Spring 3.1 没有深入了解,但是我看到的是 Spring 的这部分已经发生了很大的变化。所以有可能交换WebDataBinder。在 Spring 3.0 中,如果不覆盖 HandlerMethodInvoker,就不可能接缝。

【讨论】:

  • 是的,这就是我现在正在调查的地方。我想我有一个可行的解决方案,我将在明天进行测试
【解决方案8】:

您可以使用 Jackson com.fasterxml.jackson.databind.ObjectMapper 将任何地图转换为具有嵌套道具的 DTO/POJO 类。您需要在嵌套对象上使用 @JsonUnwrapped 注释您的 POJO。像这样:

public class MyRequest {

    @JsonUnwrapped
    private NestedObject nested;

    public NestedObject getNested() {
        return nested;
    }
}

比这样使用它:

@RequestMapping(method = RequestMethod.GET, value = "/myMethod")
@ResponseBody
public Object myMethod(@RequestParam Map<String, Object> allRequestParams) {

    MyRequest request = new ObjectMapper().convertValue(allRequestParams, MyRequest.class);
    ...
}

就是这样。一点编码。此外,您可以为您的道具命名使用@JsonProperty。

【讨论】:

    【解决方案9】:

    尝试使用InterceptorAdaptor拦截请求,然后使用简单的检查机制决定是否将请求转发给控制器处理程序。还要将 HttpServletRequestWrapper 包裹在请求周围,以使您能够覆盖请求 getParameter()

    通过这种方式,您可以将实际参数名称及其值重新传递回请求以供控制器查看。

    示例选项:

    public class JobInterceptor extends HandlerInterceptorAdapter {
     private static final String requestLocations[]={"rt", "jobType"};
    
     private boolean isEmpty(String arg)
     {
       return (arg !=null && arg.length() > 0);
     }
    
     public boolean preHandle(HttpServletRequest request,
       HttpServletResponse response, Object handler) throws Exception {
    
       //Maybe something like this
       if(!isEmpty(request.getParameter(requestLocations[0]))|| !isEmpty(request.getParameter(requestLocations[1]))
       {
        final String value =
           !isEmpty(request.getParameter(requestLocations[0])) ? request.getParameter(requestLocations[0]) : !isEmpty(request
            .getParameter(requestLocations[1])) ? request.getParameter(requestLocations[1]) : null;
    
        HttpServletRequest wrapper = new HttpServletRequestWrapper(request)
        {
         public String getParameter(String name)
         {
          super.getParameterMap().put("JobType", value);
          return super.getParameter(name);
         }
        };
    
        //Accepted request - Handler should carry on.
        return super.preHandle(request, response, handler);
       }
    
       //Ignore request if above condition was false
       return false;
       }
     }
    

    最后将HandlerInterceptorAdaptor 包裹在您的控制器处理程序周围,如下所示。 SelectedAnnotationHandlerMapping 允许您指定将接收哪个处理程序。

    <bean id="jobInterceptor" class="mypackage.JobInterceptor"/>
    <bean id="publicMapper" class="org.springplugins.web.SelectedAnnotationHandlerMapping">
        <property name="urls">
            <list>
                <value>/foo</value>
            </list>
        </property>
        <property name="interceptors">
            <list>
                <ref bean="jobInterceptor"/>
            </list>
        </property>
    </bean>
    

    已编辑

    【讨论】:

    • 目标方法只是1,它接受一个Job对象作为参数。这些字符串是参数,而不是位置
    • handler 方法采用 Job 对象,该对象与 preHandle 方法持有的对象相同。因此,如上所示检查您的请求参数并返回 true 将导致控制器处理程序继续处理请求。
    • 我已经编辑了帖子以展示如何拦截您的作业处理程序。
    • 仍然,如何在 Job 对象上设置正确的参数?
    • 查看已编辑的答案 - 这可能会有所帮助。将“HttpServletRequestWrapper”包裹在请求周围,使您能够重新传递参数名称和与其关联的值。
    【解决方案10】:

    jkee 的回答有一点改进。

    为了支持继承,您还应该分析父类。

    /**
     * ServletRequestDataBinder which supports fields renaming using {@link ParamName}
     *
     * @author jkee
     * @author Yauhen Parmon
     */
    public class ParamRenamingProcessor extends ServletModelAttributeMethodProcessor {
    
        @Autowired
        private RequestMappingHandlerAdapter requestMappingHandlerAdapter;
    
        //Rename cache
        private final Map<Class<?>, Map<String, String>> replaceMap = new ConcurrentHashMap<>();
    
        public ParamRenamingProcessor(boolean annotationNotRequired) {
           super(annotationNotRequired);
        }
    
        @Override
        protected void bindRequestParameters(WebDataBinder binder, NativeWebRequest nativeWebRequest) {
            Object target = binder.getTarget();
            Class<?> targetClass = Objects.requireNonNull(target).getClass();
            if (!replaceMap.containsKey(targetClass)) {
                replaceMap.put(targetClass, analyzeClass(targetClass));
            }
            Map<String, String> mapping = replaceMap.get(targetClass);
            ParamNameDataBinder paramNameDataBinder = new ParamNameDataBinder(target, binder.getObjectName(), mapping);
            Objects.requireNonNull(requestMappingHandlerAdapter.getWebBindingInitializer())
                    .initBinder(paramNameDataBinder);    
            super.bindRequestParameters(paramNameDataBinder, nativeWebRequest);
        }
    
        private Map<String, String> analyzeClass(Class<?> targetClass) {
            Map<String, String> renameMap = new HashMap<>();
            for (Field field : targetClass.getDeclaredFields()) {
                ParamName paramNameAnnotation = field.getAnnotation(ParamName.class);
                if (paramNameAnnotation != null && !paramNameAnnotation.value().isEmpty()) {
                   renameMap.put(paramNameAnnotation.value(), field.getName());
                }
            }
            if (targetClass.getSuperclass() != Object.class) {
                renameMap.putAll(analyzeClass(targetClass.getSuperclass()));
            }
            return renameMap;
        }
    }
    

    此处理器将分析带有@ParamName 注释的超类字段。它也不使用带有 2 个参数的 initBinder 方法,从 Spring 5.0 开始不推荐使用。 jkee的答案中的所有其余部分都可以。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-01-16
      • 1970-01-01
      • 1970-01-01
      • 2012-01-06
      • 1970-01-01
      相关资源
      最近更新 更多