首页 > 解决方案 > 将(自定义)解码器添加到 WebMVC 端点

问题描述

我有一个 WebMVC 端点:

@RequestMapping(path = "/execution/{id}", method = RequestMethod.POST)
public ResponseEntity<...> execute(@PathVariable String id) {
   ...
}

在这里,提供的id应该首先被解码。是否可以定义一个“在后台”执行此操作的注释;也就是说,在调用端点之前?类似的东西:

@RequestMapping(path = "/execution/{id}", method = RequestMethod.POST)
public ResponseEntity<...> execute(@PathVariable @DecodedIdentifier String id) {
   ...
}

注意@DecodedIdentifier注释。我知道它不存在,但它希望能解释我的意图。我知道这可以通过 Jersey 的 JAX-RS 实现来实现,但是 Spring 的 WebMVC 呢?

在这里,我使用的是 base64 解码,但我想知道是否也可以注入自定义解码器。

标签: javaspringspring-mvc

解决方案


尽管您可以使用注释,但我建议您Converter为此目的使用自定义。

按照你的例子,你可以做这样的事情。

首先,您需要定义一个适合转换的自定义类。例如:

public class DecodedIdentifier {
  private final String id;

  public DecodedIdentifier(String id) {
    this.id = id;
  }

  public String getId() {
    return this.id;
  }
}

然后,Converter为您的自定义类定义一个。它可以执行 Base64 解码:

public class DecodedIdentifierConverter implements Converter<String, DecodedIdentifier> {

  @Override
  public DecodedIdentifier convert(String source) {
    return new DecodedIdentifier(Base64.getDecoder().decode(source));
  }
}

为了告诉 Spring 这个转换器你有几个选择。

如果您正在运行 Spring Boot,您所要做的就是将类注释为 a@Component并且自动配置逻辑将负责Converter注册。

@Component
public class DecodedIdentifierConverter implements Converter<String, DecodedIdentifier> {

  @Override
  public DecodedIdentifier convert(String source) {
    return new DecodedIdentifier(Base64.getDecoder().decode(source));
  }
}

请务必配置您的组件扫描,以便 Spring 可以检测到@Component类中的注释。

如果您在没有 Spring Boot 的情况下使用 Spring MVC,则需要“手动”注册Converter

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addConverter(new DecodedIdentifierConverter());
    }
}

注册后Converter,您可以在您的Controller

@RequestMapping(path = "/execution/{id}", method = RequestMethod.POST)
public ResponseEntity<...> execute(@PathVariable DecodedIdentifier id) {
   ...
}

您还可以遵循其他选项。请考虑阅读这篇文章,它将为您提供有关该问题的更多信息。

作为旁注,上述文章表明您可以直接valueOf在类中定义一个方法,该方法将存储转换服务的结果,DecodedIdentifier在您的示例中,它可以让您摆脱Converter该类:老实说,我从来没有尝试过这种方法,我不知道它在什么条件下可以工作。话虽如此,如果它有效,它可以简化您的代码。请,如果您认为合适,请尝试一下。

更新

感谢@Aman 评论,我仔细查看了 Spring 文档。之后,我发现,尽管我认为上述转换方法更适合用例——您实际上是在执行转换——另一种可能的解决方案可能是使用自定义Formatter.

我已经知道 Spring 使用这种机制来执行多次转换,但我不知道可以基于 annotation 注册自定义格式化程序,这是答案中提出的原始想法。考虑像这样的注释DateTimeFormat,这很有意义。事实上,这种方法之前已经在 Stackoverflow 中描述过(请参阅此问题中接受的答案)。

在您的情况下(基本上是上述针对您的情况的答案的转录):

首先,定义您的DecodedIdentifier注释:

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
public @interface DecodedIdentifier {
}

事实上,您可以考虑通过包括例如处理信息的编码来丰富注释。

然后,创建相应的AnnotationFormatterFactory

import java.text.ParseException;
import java.util.Base64;
import java.util.Collections;
import java.util.Locale;
import java.util.Set;

import org.springframework.context.support.EmbeddedValueResolutionSupport;
import org.springframework.format.AnnotationFormatterFactory;
import org.springframework.format.Formatter;
import org.springframework.format.Parser;
import org.springframework.format.Printer;
import org.springframework.stereotype.Component;

@Component
public class DecodedIdentifierFormatterFactory extends EmbeddedValueResolutionSupport
  implements AnnotationFormatterFactory<DecodedIdentifier> {

  @Override
  public Set<Class<?>> getFieldTypes() {
    return Collections.singleton(String.class);
  }

  @Override
  public Printer<?> getPrinter(DecodedIdentifier annotation, Class<?> fieldType) {
    return this.getFormatter(annotation);
  }

  @Override
  public Parser<?> getParser(DecodedIdentifier annotation, Class<?> fieldType) {
    return this.getFormatter(annotation);
  }

  private Formatter getFormatter(DecodedIdentifier annotation) {
    return new Formatter<String>() {
      @Override
      public String parse(String text, Locale locale) throws ParseException {
        // If the annotation could provide some information about the
        // encoding to be used, this logic will be highly reusable
        return new String(Base64.getDecoder().decode(text));
      }

      @Override
      public String print(String object, Locale locale) {
        return object;
      }
    };
  }
}

在 Spring MVC 配置中注册工厂:

@Configuration
@EnableWebMvc
public class WebConfig implements WebMvcConfigurer {

    @Override
    public void addFormatters(FormatterRegistry registry) {
        registry.addFormatterForFieldAnnotation(new DecodedIdentifierFormatterFactory());
    }
}

Controller最后,完全按照您在问题中指出的那样使用您的 s 中的注释:

@RequestMapping(path = "/execution/{id}", method = RequestMethod.POST)
public ResponseEntity<...> execute(@PathVariable @DecodedIdentifier String id) {
   ...
}

推荐阅读