Wie kann ich Parameternamen beim Binden von Spring MVC-Befehlsobjekten anpassen?

76

Ich habe ein Befehlsobjekt:

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

Welches ist von spring-mvc gebunden:

@RequestMapping("/foo")
public Strnig doSomethingWithJob(Job job) {
   ...
}

Welches funktioniert gut für http://example.com/foo?jobType=permanent&location=Stockholm. Aber jetzt muss es stattdessen für die folgende URL funktionieren:

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

Natürlich möchte ich mein Befehlsobjekt nicht ändern, da die Feldnamen lang bleiben müssen (wie sie im Code verwendet werden). Wie kann ich das anpassen? Gibt es eine Möglichkeit, so etwas zu tun:

public class Job {
    @RequestParam("jt")
    private String jobType;
    @RequestParam("loc")
    private String location;
}

Dies funktioniert nicht ( @RequestParamkann nicht auf Felder angewendet werden).

Ich denke an einen benutzerdefinierten Nachrichtenkonverter FormHttpMessageConverter, der einer benutzerdefinierten Anmerkung zum Zielobjekt ähnelt und diese liest

Bozho
quelle
2
Gibt es nicht schon im Frühjahr 4 eine "native" Lösung?
Martin Ždila
Bitte helfen Sie mit meiner ähnlichen Frage hier stackoverflow.com/questions/38171022/…
SourceVisor

Antworten:

32

Diese Lösung ist prägnanter, erfordert jedoch die Verwendung von RequestMappingHandlerAdapter, den Spring bei Aktivierung verwendet <mvc:annotation-driven />. Hoffe es wird jemandem helfen. Die Idee ist, ServletRequestDataBinder wie folgt zu erweitern:

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

Geeigneter Prozessor:

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

Anmerkung:

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

}

Frühlingskonfiguration:

<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> 

Und schließlich die Verwendung (wie die Bozho-Lösung):

public class Job {
    @ParamName("job-type")
    private String jobType;
    @ParamName("loc")
    private String location;
}
jkee
quelle
Schätzen Sie Ihre Lösung sehr! Ein Hinweis: Dadurch bleibt die Funktionalität der DateTimeFormatAnnotation erhalten, dh @ParamNameannotierte DateFelder können zusätzlich mit Annotationen versehen werden @DateTimeFormat(pattern = "yyyy-MM-dd").
Simon
6
Für alle Java-Konfigurationsfans würde eine Spring-Kontextkonfiguration in Java folgendermaßen aussehen: @Configuration public class WebContextConfiguration extends WebMvcConfigurationSupport { @Override protected void addArgumentResolvers( List<HandlerMethodArgumentResolver> argumentResolvers) { argumentResolvers.add(renamingProcessor()); } @Bean protected RenamingProcessor renamingProcessor() { return new RenamingProcessor(true); } } Beachten Sie, dass und extends WebMvcConfigurationSupportersetzt wird . @EnableWebMvc<mvc:annotation-driven />
Simon
Bitte helfen Sie mit meiner ähnlichen Frage hier stackoverflow.com/questions/38171022/…
SourceVisor
2
Gibt es ab Frühjahr 4.2 irgendetwas, das dies einfacher machen könnte?
Chrismarx
2
Ich habe durch das Ersetzen gearbeitet um addArgumentResolversmit einem Bohne Postprozessor: pastebin.com/07ws0uUZ
dtrunk
15

Folgendes habe ich zum Arbeiten gebracht:

Zunächst ein Parameter-Resolver:

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

Und dann den Parameter Resolver mit einem Postprozessor registrieren. Es sollte registriert sein als <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;
    }

}
Bozho
quelle
Es wäre sehr hilfreich, ein vollständiges Beispiel dafür zu haben, da ich das obige Beispiel nicht erstellen kann.
Ismar Slomic
Bitte helfen Sie mit meiner ähnlichen Frage hier stackoverflow.com/questions/38171022/…
SourceVisor
Wie könnte dieser unvollständige Code die akzeptierte Antwort sein? Es fehlen mehrere Klassen wie SupportsAnnotationParameterResolution, @CommandPatternund @SupportsCustomizedBinding, sowie die Importe für Maps.*undLists.*
Membersound
Ok, die SupportsAnnotationParameterResolution soll sein SupportsCustomizedBinding. Wenn Sie also beide Anmerkungen erstellen, funktioniert der Ansatz!
Membersound
9

In Spring 3.1 bietet ServletRequestDataBinder einen Hook für zusätzliche Bindungswerte:

protected void addBindValues(MutablePropertyValues mpvs, ServletRequest request) {
}

Die ExtendedServletRequestDataBinder-Unterklasse verwendet sie, um URI-Vorlagenvariablen als Bindungswerte hinzuzufügen. Sie können es weiter erweitern, um befehlsspezifische Feldaliasnamen hinzuzufügen.

Sie können RequestMappingHandlerAdapter.createDataBinderFactory (..) überschreiben, um eine benutzerdefinierte WebDataBinder-Instanz bereitzustellen. Aus der Sicht eines Controllers könnte es so aussehen:

@InitBinder
public void initBinder(MyWebDataBinder binder) {
   binder.addFieldAlias("jobType", "jt");
   // ...
}
Rossen Stoyanchev
quelle
Vielen Dank, aber wenn ich .createDtaBinderFactory überschreiben muss, bedeutet dies, dass ich den RequestMappingHandlerAdapter ersetzen sollte, was bedeutet, dass ich ihn nicht verwenden kann <mvc:annotation-driven />, oder?
Bozho
Bitte. Ja, mit <mvc: annotation-powered /> können Sie kein benutzerdefiniertes RequestMappingHandlerMapping anschließen. Sie können dies jedoch ganz einfach mit der MVC Java-Konfiguration tun.
Rossen Stoyanchev
@RossenStoyanchev: Können Sie erklären , wie kann ich in benutzerdefinierten Plug MyWebDataBindermit @EnableWebMvc? Ich sehe, ich muss eine Unterklasse bilden ExtendedServletRequestDataBinderund sie durch Unterklasse zurückgeben ServletRequestDataBinderFactory. Jetzt kann ich diese neue Fabrik durch Unterklassen RequestMappingHandlerAdapterund Überschreiben zurückgeben createDataBinderFactory(). Aber wie kann ich Spring MVC zwingen, meine Unterklasse zu verwenden RequestMappingHandlerAdapter? Es wird erstellt in WebMvcConfigurationSupport...
Tomasz Nurkiewicz
1
@TomaszNurkiewicz, vielleicht haben Sie das herausgefunden, aber wenn nicht, lesen Sie den Abschnitt über fortgeschrittenes Java der XML-basierten Spring MVC-Konfiguration in den Referenzdokumenten. Static.springsource.org/spring/docs/3.1.x/…
Rossen Stoyanchev
@RossenStoyanchev: Eigentlich war es nicht dringend, aber danke für deinen Vorschlag, es funktioniert endlich, +1!
Tomasz Nurkiewicz
5

Danke die Antwort von @jkee.
Hier ist meine Lösung.
Zunächst eine benutzerdefinierte Anmerkung:

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

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

}

Ein Kunde 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());
      }
    }
  }

}

Ein Parameter-Resolver:

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

}

Schließlich eine Bean-Konfiguration zum Hinzufügen von ParamNameProcessor zum ersten der Argumentauflöser:

@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;

}

Controller-Methode:

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

Das ist alles.

Allen Kerr
quelle
Wie unterscheidet sich das von dem, was @jkee beigetragen hat?
Frans
4

Es gibt keine gut eingebaute Methode, Sie können nur auswählen, welche Problemumgehung Sie anwenden. Der Unterschied zwischen Handhabung

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

und

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

ist, dass Job eine Bohne ist und Stringjob nicht (bisher keine Überraschung). Der wirkliche Unterschied besteht darin, dass Beans mit dem Standard-Spring-Bean-Resolver-Mechanismus aufgelöst werden, während String-Parameter von Spring-MVC aufgelöst werden, die das Konzept der @ RequestParam-Annotation kennt. Um es kurz zu machen, es gibt keine Möglichkeit in der Standard-Spring-Bean-Auflösung (die Klassen wie PropertyValues, PropertyValue, GenericTypeAwarePropertyDescriptor verwendet), "jt" in eine Eigenschaft namens "jobType" aufzulösen, oder zumindest weiß ich nichts darüber.

Die Problemumgehungen könnten so sein, wie andere vorgeschlagen haben, einen benutzerdefinierten PropertyEditor oder einen Filter hinzuzufügen, aber ich denke, das bringt den Code nur durcheinander. Meiner Meinung nach wäre die sauberste Lösung, eine Klasse wie diese zu deklarieren:

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

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

}

Verwenden Sie das dann in Ihrem Controller

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

UPDATE:

Eine etwas einfachere Option besteht darin, nicht zu verlängern, sondern nur die zusätzlichen Getter und Setter zur ursprünglichen Klasse hinzuzufügen

public class Job {

    private String jobType;
    private String location;

    public String getJt() {
         return jobType;
    }

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

}
Peter Szanto
quelle
@Bozho ja es ist nicht zu raffiniert, aber zumindest leicht zu lesen :) (Ich habe den ursprünglichen Beitrag mit einer vereinfachten Lösung aktualisiert)
Peter Szanto
1
Halten Sie es einfach, ja. Es ist schade, dass der Frühling nichts Out-of-the-Box unterstützt = (
Vadim Kirilchuk
1
Das löst das ursprüngliche Problem nicht. dh automatische Konvertierung von Parametern mit Bindestrichen "Jobtyp" in DTO unter Verwendung von Spring
Oscar Perez
2

Ich möchte Sie in eine andere Richtung weisen. Aber ich weiß nicht, ob es funktioniert .

Ich würde versuchen, die Bindung selbst zu manipulieren.

Dies erfolgt durch WebDataBinderund wird von der HandlerMethodInvokerMethode aufgerufenObject[] resolveHandlerArguments(Method handlerMethod, Object handler, NativeWebRequest webRequest, ExtendedModelMap implicitModel) throws Exception

Ich habe in Spring 3.1 keinen tiefen Einblick, aber was ich gesehen habe, ist, dass dieser Teil des Frühlings sehr verändert wurde. So ist es möglicherweise möglich, den WebDataBinder auszutauschen. In Spring 3.0 sind Nähte nicht möglich, ohne die zu überschreiben HandlerMethodInvoker.

Ralph
quelle
Ja, dort untersuche ich gerade. Ich glaube, ich habe eine funktionierende Lösung, die ich morgen testen werde
Bozho
2

Es gibt eine einfache Möglichkeit, einfach eine weitere Setter-Methode hinzuzufügen, z. B. "setLoc, setJt".

Jack
quelle
Wie wäre es mit Parametern mit Bindestrichen? Wie konvertiert Spring diese automatisch?
Oscar Perez
2

Sie können Jackson com.fasterxml.jackson.databind.ObjectMapper verwenden, um eine beliebige Karte mit verschachtelten Requisiten in Ihre DTO / POJO-Klasse zu konvertieren. Sie müssen Ihre POJOs mit @JsonUnwrapped für verschachtelte Objekte kommentieren. So was:

public class MyRequest {

    @JsonUnwrapped
    private NestedObject nested;

    public NestedObject getNested() {
        return nested;
    }
}

Und dann benutze es so:

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

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

Das ist alles. Ein bisschen Codierung. Sie können Ihren Requisiten auch beliebige Namen geben, indem Sie @JsonProperty verwenden.

Demel
quelle
1

Versuchen Sie, die Anforderung mit abzufangen InterceptorAdaptor, und entscheiden Sie dann mithilfe eines einfachen Überprüfungsmechanismus, ob die Anforderung an den Controller-Handler weitergeleitet werden soll. Umschließen Sie auch HttpServletRequestWrapperdie Anforderung, damit Sie die Anforderungen überschreiben können getParameter().

Auf diese Weise können Sie den tatsächlichen Parameternamen und seinen Wert auf die Anforderung zurückführen, die von der Steuerung angezeigt werden soll.

Beispieloption:

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

Wickeln Sie zum Schluss den HandlerInterceptorAdaptorController-Handler wie unten gezeigt um. Mit SelectedAnnotationHandlerMappingkönnen Sie angeben, welcher Handler abgefangen werden soll.

<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>

BEARBEITET .

Bitmap
quelle
Die Zielmethode ist nur 1 und verwendet ein Jobobjekt als Argument. Diese Zeichenfolgen sind eher Parameter als Orte
Bozho
Die Handler-Methode verwendet das Job-Objekt, das dasselbe Objekt ist, das von der preHandleMethode gehalten wird. Wenn Sie daher Ihren Anforderungsparameter wie oben gezeigt überprüfen und true zurückgeben, fährt der Controller-Handler mit der Anforderung fort.
Bitmap
Ich habe den Beitrag bearbeitet, um zu zeigen, wie Sie Ihren Job-Handler abfangen können.
Bitmap
Wie würde das die richtigen Parameter für das Job-Objekt festlegen?
Bozho
Siehe bearbeitete Antwort - möglicherweise hilft dies. Umschließen Sie die Anforderung mit 'HttpServletRequestWrapper', damit Sie den Parameternamen und den damit verbundenen Wert erneut eingeben können.
Bitmap
1

Jkees Antwort hat sich ein wenig verbessert.

Um die Vererbung zu unterstützen, sollten Sie auch übergeordnete Klassen analysieren.

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

Dieser Prozessor analysiert Felder von Superklassen, die mit @ParamName versehen sind. Es wird auch keine initBinderMethode mit 2 Parametern verwendet, die ab Spring 5.0 veraltet ist. Der Rest in Jkees Antwort ist in Ordnung.

Yauhen Parmon
quelle