【问题标题】:Custom converter for @RequestParam in Spring MVCSpring MVC 中 @RequestParam 的自定义转换器
【发布时间】:2018-03-25 12:49:25
【问题描述】:

我正在获取一个加密字符串作为 Spring 休息控制器方法的查询参数。

我想在字符串到达​​方法之前根据下面的一些注释(比如@Decrypt)解密字符串

@RequestMapping(value = "/customer", method = RequestMethod.GET)
public String getAppointmentsForDay(@RequestParam("secret") @Decrypt String customerSecret) {
    System.out.println(customerSecret);  // Needs to be a decrypted value.
   ...
}

自定义Formatter 在这个用例中是正确的方法吗?

或者我应该使用自定义HandlerMethodArgumentResolver

【问题讨论】:

    标签: spring spring-mvc spring-boot


    【解决方案1】:

    org.springframework.format.Formatter 的自定义实现是此用例的有效方法。这就是 Spring 本身为日​​期、货币、数字样式等实现格式化程序的方式。

    步骤:

    1. 声明一个注解:Decrypt:

      import java.lang.annotation.*;
      
      @Documented
      @Retention(RetentionPolicy.RUNTIME)
      @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.ANNOTATION_TYPE})
      public @interface Decrypt {
      
      }
      
    2. 声明一个使用新注解的AnnotationFormatterFactory

      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<Decrypt> {
      
          @Override
          public Set<Class<?>> getFieldTypes() {
              Set<Class<?>> fieldTypes = new HashSet<>();
              fieldTypes.add(String.class);
              return Collections.unmodifiableSet(fieldTypes);
          }
      
          @Override
          public Printer<String> getPrinter(Decrypt annotation, Class<?> fieldType) {
              return configureFormatterFrom(annotation);
          }
      
          @Override
          public Parser<String> getParser(Decrypt annotation, Class<?> fieldType) {
              return configureFormatterFrom(annotation);
          }
      
          private Formatter<String> 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<String>() {
                  @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. 使用您的网络上下文注册此格式化程序工厂:

      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. 就是这样。

    有了上述条件,@RequestParam 的所有使用都将通过parse() 中声明的parse() 方法传递给@Decrypt,因此您可以在那里实现您的解密调用。

    为了证明这一点,下面的测试通过了:

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

    【讨论】:

    • 感谢故障的回复,我正在尝试这个和下面的方法
    【解决方案2】:

    HandlerMethodArgumentResolver 在这方面是最好的。

    1. 创建注释:

    @Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface Decrypt {
        String value();
    }
    
    1. 创建您的自定义 HandlerMethodArgumentResolver:

    public class DecryptResolver implements HandlerMethodArgumentResolver {
    
        @Override
        public boolean supportsParameter(MethodParameter parameter) {
            return parameter.getParameterAnnotation(Decrypt.class) != null;
        }
    
        @Override
        public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
                WebDataBinderFactory binderFactory) throws Exception {
            Decrypt attr = parameter.getParameterAnnotation(Decrypt.class);
            String encrypted = webRequest.getParameter(attr.value());
            String decrypted = decrypt(encrypted);
    
            return decrypted;
        }
    
        private String decrypt(String encryptedString) {
            // Your decryption logic here
    
            return "decrypted - "+encryptedString;
        }
    }
    
    1. 注册解析器:

    @Configuration
    @EnableMvc // If you're not using Spring boot
    public class WebConfig extends WebMvcConfigurerAdapter {
    
        @Override
        public void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
              argumentResolvers.add(new DecryptResolver());
        }
    }
    
    1. 瞧,你有你的解密参数。请注意,您不再需要使用 @RequestParam。

    @RequestMapping(value = "/customer", method = RequestMethod.GET)
    public String getAppointmentsForDay(@Decrypt("secret") String customerSecret) {
    System.out.println(customerSecret);  // Needs to be a decrypted value.
       ...
    }
    

    【讨论】:

    • 感谢@Olantobi 的回复,我正在尝试这种及以上的方法
    【解决方案3】:

    您可以尝试在web.xml 文件中添加CharacterEncodingFilterinit-param encoding UTF-8。查看this example

    但是,如果它仍然不起作用,您可以通过添加下面的参数和上面的init-param 来强制编码。

    <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
    </init-param>
    

    如果它适合你,请告诉我。

    【讨论】:

      猜你喜欢
      • 2022-11-23
      • 2014-04-26
      • 2016-08-17
      • 1970-01-01
      • 2012-02-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-09-16
      相关资源
      最近更新 更多