Custom converter for @RequestParam in Spring MVC

后端 未结 3 988
攒了一身酷
攒了一身酷 2021-02-07 20:20

I am getting an encrypted String as Query parameter to a Spring rest controller method.

I wanted to decrypt the string before it reaches the method based on some annotat

3条回答
  •  广开言路
    2021-02-07 21:00

    A custom implementation of org.springframework.format.Formatter is a valid approach for this use case. This is how Spring itself implements formatters for dates, currencies, number styles etc.

    Steps:

    1. Declare an annotation: Decrypt:

      import java.lang.annotation.*;
      
      @Documented
      @Retention(RetentionPolicy.RUNTIME)
      @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
      public @interface Decrypt {
      
      }
      
    2. Declare an AnnotationFormatterFactory which uses the new annotation:

      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 java.text.ParseException;
      import java.util.Collections;
      import java.util.HashSet;
      import java.util.Locale;
      import java.util.Set;
      
      public class DecryptAnnotationFormatterFactory extends EmbeddedValueResolutionSupport
              implements AnnotationFormatterFactory {
      
          @Override
          public Set> getFieldTypes() {
              Set> fieldTypes = new HashSet<>();
              fieldTypes.add(String.class);
              return Collections.unmodifiableSet(fieldTypes);
          }
      
          @Override
          public Printer getPrinter(Decrypt annotation, Class fieldType) {
              return configureFormatterFrom(annotation);
          }
      
          @Override
          public Parser getParser(Decrypt annotation, Class fieldType) {
              return configureFormatterFrom(annotation);
          }
      
          private Formatter configureFormatterFrom(Decrypt annotation) {
              // you could model something on the Decrypt annotation for use in the decryption call
              // in this example the 'decryption' call is stubbed, it just reverses the given String
              // presumaby you implementaion of this Formatter will be different e.g. it will invoke your encryption routine
              return new Formatter() {
                  @Override
                  public String print(String object, Locale locale) {
                      return object;
                  }
      
                  @Override
                  public String parse(String text, Locale locale) throws ParseException {
                      return new StringBuilder(text).reverse().toString();
                  }
              };
          }
      }
      
    3. Register this formatter factory with your web context:

      import org.springframework.context.annotation.Configuration;
      import org.springframework.format.FormatterRegistry;
      import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
      
      @Configuration
      public class WebConfigurer extends WebMvcConfigurerAdapter {
          @Override
          public void addFormatters(FormatterRegistry registry) {
              super.addFormatters(registry);
              registry.addFormatterForFieldAnnotation(new DecryptAnnotationFormatterFactory());
          }
      }
      
    4. That's it.

    With the above in place, all usages of a @RequestParam which are qualified with @Decrypt will be passed through the parse() method declared in DecryptAnnotationFormatterFactory so you can implement your decryption call there.

    To prove this, the following test passes:

    @RunWith(SpringRunner.class)
    @WebMvcTest(controllers = YourController.class)
    public class YourControllerTest {
        @Autowired
        private MockMvc mockMvc;
    
        @Test
        public void theSecretRequestParameterWillBeConverted() throws Exception {
            MvcResult mvcResult = mockMvc.perform(get("/customer?secret=abcdef")).andExpect(status().isOk()).andReturn();
    
            // the current implementation of the 'custom' endpoint returns the value if the secret request parameter and
            // the current decrypt implementation just reverses the given value ...
            assertThat(mvcResult.getResponse().getContentAsString(), is("fedcba"));
        }
    }
    

提交回复
热议问题