Як налаштувати імена параметрів під час прив’язки об’єктів команди Spring MVC?


76

У мене є об’єкт команди:

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спеціальну анотацію на цільовому об’єкті та читання нею


2
Чи не існує якогось "рідного" рішення вже навесні 4?
Мартін Жділа,

Будь ласка , допоможіть з моїм аналогічним питанням тут stackoverflow.com/questions/38171022 / ...
SourceVisor

Відповіді:


33

Це рішення є більш стислим, але вимагає використання 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;
}

Дуже цінуємо ваше рішення! Одне зауваження: Це зберігає функціональність DateTimeFormatанотації, тобто @ParamNameанотовані Dateполя можуть бути додатково анотовані @DateTimeFormat(pattern = "yyyy-MM-dd").
Саймон

6
Для всіх шанувальників Java-config конфігурація Spring Spring в Java виглядатиме так: @Configuration public class WebContextConfiguration extends WebMvcConfigurationSupport { @Override protected void addArgumentResolvers( List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(renamingProcessor()); } @Bean protected RenamingProcessor renamingProcessor() { return new RenamingProcessor(true); } } Зверніть увагу, що extends WebMvcConfigurationSupportзамінює @EnableWebMvcта <mvc:annotation-driven />.
Саймон

Будь ласка , допоможіть з моїм аналогічним питанням тут stackoverflow.com/questions/38171022 / ...
SourceVisor

2
Чи є що-небудь станом на весну 4.2, що могло б полегшити це?
chrismarx

2
Я addArgumentResolvers
обійшов,

15

Ось що я отримав:

По-перше, вирішувач параметрів:

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

}

Було б дуже корисно мати повний приклад цього, оскільки я не зміг побудувати приклад вище.
Ісмар Сломік

Будь ласка , допоможіть з моїм аналогічним питанням тут stackoverflow.com/questions/38171022 / ...
SourceVisor

Як цей неповний код може бути прийнятою відповіддю? У ньому бракує декількох класів, таких як SupportsAnnotationParameterResolution, @CommandPatternта @SupportsCustomizedBinding, а також імпорт для Maps.*andLists.*
membersound

Гаразд, SupportsAnnotationParameterResolution має бути SupportsCustomizedBinding. Тож під час створення обох анотацій підхід працює!
membersound

9

Навесні 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, а це означає, що я не можу використовувати <mvc:annotation-driven />, так?
Божо

Ласкаво просимо. Так, за допомогою <mvc: annotation-driven /> ви не можете підключити власний RequestMappingHandlerMapping. Однак це можна зробити досить легко за допомогою конфігурації Java MVC.
Россен Стоянчев

@RossenStoyanchev: Ви можете пояснити , як я можу підключити звичай MyWebDataBinderз @EnableWebMvc? Я бачу, що мені доводиться підклас ExtendedServletRequestDataBinderі повертати його шляхом підкласу ServletRequestDataBinderFactory. Тепер я можу повернути цю нову фабрику шляхом підкласифікації RequestMappingHandlerAdapterта заміни createDataBinderFactory(). Але як я можу змусити Spring MVC використовувати мій підклас RequestMappingHandlerAdapter? Він створений у WebMvcConfigurationSupport...
Томаш Нуркевич

1
@TomaszNurkiewicz, можливо, ти це зрозумів, але якщо не бачиш розділ про вдосконалену Java конфігурації Spring MVC на основі XML у довідкових документах .. static.springsource.org/spring/docs/3.1.x/…
Россен Стоянчев

@RossenStoyanchev: насправді це було не терміново, але дякую за вашу пропозицію, нарешті це працює, +1!
Томаш Нуркевич,

5

Дякую за відповідь @jkee.
Ось моє рішення.
По-перше, спеціальна анотація:

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

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

}

Заказчик даних:

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

Це все.


Чим це відрізняється від того, що сприяв @jkee?
Франс

4

для цього немає приємного вбудованого способу, ви можете лише вибрати, який обхідний шлях застосовувати. Різниця між поводженням

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

і

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

полягає в тому, що робота - це боб, а струнний - ні (поки що не дивно). Справжня різниця полягає в тому, що біни вирішуються за допомогою стандартного механізму розпізнавання бін Spring, тоді як параметри рядків вирішуються Spring MVC, який знає концепцію анотації @RequestParam. Коротше кажучи, у стандартній роздільній здатності бінових компонентів (тобто за допомогою класів, таких як 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;
    }

}

@Bozho так це не надто вишукано, але легко читається принаймні :) (я оновив оригінальний пост спрощеним рішенням)
Пітер Санто

1
Нехай це буде просто, так .. Шкода, що весна не підтримує нічого нестандартного = (
Вадим Кирильчук

1
Це не вирішує початкову проблему. тобто автоперетворення параметрів, що містять дефіси "типу" у DTO, за допомогою Spring
Оскар Перес

2

Я хотів би вказати вам на інший напрямок. Але я не знаю, чи це працює .

Я спробував би маніпулювати самим прив'язуванням.

Це робиться методом WebDataBinderі буде викликано з HandlerMethodInvokerметодуObject[] resolveHandlerArguments(Method handlerMethod, Object handler, NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception

Я не глибоко дивлюся на Весну 3.1, але те, що я бачив, полягає в тому, що ця частина Весни сильно змінилася. Тож можливо обмін WebDataBinder. Навесні 3.0 це шви неможливо без перевизначення HandlerMethodInvoker.


Так, саме тут я зараз розслідую. Думаю, у мене є робоче рішення, яке я протестую завтра
Божо

2

Існує простий спосіб, ви можете просто додати ще один метод встановлення, наприклад "setLoc, setJt".


Як щодо параметрів з дефісами? Як Spring автоматично перетворює їх?
Оскар Перес

2

Ви можете використовувати 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);
    ...
}

Це все. Трохи кодування. Крім того, ви можете дати будь-які імена своєму реквізиту usign @JsonProperty.


1

Спробуйте перехопити запит за допомогою 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Дозволяє вказати , який обробник буде interecepted.

<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. ці рядки є параметрами, а не локаціями
Божо

Метод обробника бере об'єкт Job, який є тим самим об'єктом, що зберігається preHandleметодом. Тому перевірка параметра запиту, як показано вище, і повернення true призведе до того, що обробник контролера продовжить запит.
Bitmap

Я відредагував допис, щоб показати, як перехопити обробника вашої роботи.
Bitmap

все-таки, як би це встановило належні параметри для об'єкта Job?
Божо

Дивіться відредаговану відповідь - можливо, це може допомогти. Оберніть 'HttpServletRequestWrapper' навколо запиту, щоб дозволити вам перепрацювати ім'я параметра та пов'язане з ним значення.
Bitmap

1

Відповідь 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 параметрами, який застарів станом на Spring 5.0. Все інше у відповіді jkee нормально.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.