Как настроить имена параметров при привязке объектов команды spring mvc
У меня есть объект команды:
public class Job {
private String jobType;
private String location;
}
Который связан весной-MVC:
@RequestMapping("/foo")
public Strnig doSomethingWithJob(Job job) {
...
}
Который отлично работает для http://example.com/foo?jobType=permanent&location=Stockholm
, Но теперь мне нужно заставить его работать для следующего URL:http://example.com/foo?jt=permanent&loc=Stockholm
Очевидно, я не хочу менять свой командный объект, потому что имена полей должны оставаться длинными (так как они используются в коде). Как я могу настроить это? Есть ли возможность сделать что-то вроде этого:
public class Job {
@RequestParam("jt")
private String jobType;
@RequestParam("loc")
private String location;
}
Это не работает (@RequestParam
не может быть применено к полям).
Я думаю о том, чтобы преобразователь сообщений был похож на FormHttpMessageConverter
и прочитать пользовательскую аннотацию на целевом объекте
11 ответов
Вот что я получил работать:
Во-первых, преобразователь параметров:
/**
* 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;
}
}
Это решение более краткое, но требует использования 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>
И, наконец, использование (например, решение Божо):
public class Job {
@ParamName("job-type")
private String jobType;
@ParamName("loc")
private String location;
}
В 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");
// ...
}
Спасибо, ответ @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 в первый из преобразователей аргументов:
@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;
}
};
}
}
Param 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);
}
Это все.
Нет ничего хорошего, чтобы сделать это, вы можете только выбрать, какой обходной путь вы примените. Разница между обработкой
@RequestMapping("/foo")
public String doSomethingWithJob(Job job)
а также
@RequestMapping("/foo")
public String doSomethingWithJob(String stringjob)
в том, что работа - это боб, а струнная работа - нет (пока ничего удивительного). Реальное отличие состоит в том, что bean-компоненты разрешаются с помощью стандартного механизма распознавания bean-компонентов Spring, а строковые параметры разрешаются Spring MVC, который знает концепцию аннотации @RequestParam. Короче говоря, в стандартном разрешении 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) {
...
}
ОБНОВИТЬ:
Немного более простой вариант - не расширять, просто добавьте дополнительные методы получения, установки в исходный класс.
public class Job {
private String jobType;
private String location;
public String getJt() {
return jobType;
}
public void setJt(String jt) {
jobType = jt;
}
}
Есть простой способ, вы можете просто добавить еще один метод setter, например, "setLoc,setJt".
Вы можете использовать Jackson com.fasterxml.jackson.databind.ObjectMapper, чтобы конвертировать любую карту в ваш класс DTO/POJO с вложенными подпорками. Вам нужно аннотировать ваши POJO с помощью @JsonUnwrapped для вложенного объекта. Как это:
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.
Я хотел бы указать вам в другом направлении. Но я не знаю, работает ли это.
Я бы попытался манипулировать самой привязкой.
Это сделано WebDataBinder
и будет вызван из HandlerMethodInvoker
метод Object[] resolveHandlerArguments(Method handlerMethod, Object handler, NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception
У меня нет глубокого взгляда на Spring 3.1, но я видел, что эта часть Spring сильно изменилась. Так что возможно обменять WebDataBinder. В Spring 3.0 швы невозможны без переопределения HandlerMethodInvoker
,
Попробуйте перехватить запрос, используя 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>
Отредактировано
Есть небольшое улучшение в ответе 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. Это также не использует initBinder
метод с 2 параметрами, который устарел с весны 5.0. Все остальное в ответе jkee в порядке.
Я попробовал описанное выше, но обнаружил, что мне не хватает некоторых настроенных функций WebBinder, поэтому вместо этого я создал решение, использующее вместо этого Джексона.
Сначала я реализовалArgumentResolver
который ищет конкретную аннотацию уровня классаUsingJacksonMapping
чтобы определить, должен ли он нести ответственность за аргументы этого типа.
public class ObjectMapperParameterArgumentHandler implements HandlerMethodArgumentResolver {
/**
* Marks a Spring method argument parameter class as being marshalled via Jackson INSTEAD of
* Spring's standard WebBinder. This can be helpful in order to use Jackson annotations for
* property names and format. To use the argument resolver must be registered.
*
* <pre>
* @ObjectMapperParameterArgumentHandler.UsingJacksonMapping
* </pre>
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface UsingJacksonMapping {}
private static final ObjectMapper OBJECT_MAPPER = ObjectMapperUtils.createObjectMapper();
static {
OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
OBJECT_MAPPER.configure(DeserializationFeature.UNWRAP_SINGLE_VALUE_ARRAYS, true);
}
@Override
public boolean supportsParameter(MethodParameter parameter) {
Class<?> parameterType = parameter.getParameterType();
boolean result = markedWithUsingJacksonMapping(parameterType);
if (result) {
log.debug("Using Jackson mapping for {}", parameterType.getName());
}
return result;
}
private boolean markedWithUsingJacksonMapping(Class<?> type) {
if (type.equals(Object.class)) {
return false;
} else if (type.getAnnotation(UsingJacksonMapping.class) != null) {
return true;
} else {
return markedWithUsingJacksonMapping(type.getSuperclass());
}
}
@Override
public Object resolveArgument(
MethodParameter parameter,
ModelAndViewContainer mavContainer,
NativeWebRequest webRequest,
WebDataBinderFactory binderFactory) {
Map<String, String[]> parameterMap = webRequest.getParameterMap();
log.trace("Converting params {}", parameterMap);
Class<?> parameterType = parameter.getParameterType();
Object result = OBJECT_MAPPER.convertValue(parameterMap, parameterType);
log.debug("Result {}", result);
return result;
}
}
Затем я добавляю его в качестве аргументаResolver:
@Configuration
public class ObjectMapperParameterArgumentResolverConfiguration extends WebMvcConfigurationSupport {
@Override
protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
argumentResolvers.add(new ObjectMapperParameterArgumentHandler());
}
}
Теперь это позволяет использовать аннотации Джексона следующим образом. Это также позволяетfinal
поля (которые потребуютinitDirectFieldAccess
используя Spring):
@Value
@NoArgsConstructor(force = true)
@ObjectMapperParameterArgumentHandler.UsingJacksonMapping
public class DateBasedQueryParamDto {
@Nullable
@JsonProperty("startDate")
@JsonFormat(pattern = "yyyy-MM-dd")
@Parameter(
required = false,
description =
"Oldest UTC time in ISO date time format (eq 2022-07-08). Mutually exclusive to"
+ " daysPast")
private final LocalDate startDate;
@Nullable
@JsonProperty("endDate")
@JsonFormat(pattern = "yyyy-MM-dd")
@Parameter(
required = false,
description =
"Latest UTC time in ISO date time format (eq 2022-07-08). Mutually exclusive to "
+ "daysPast.")
private final LocalDate endDate;
}