1, Background
In the Spring Controller, we can map the parameters in the request to the specific parameters of the control layer through @ RequestParam or @ RequestBody. How is this implemented? If the value of a parameter in the control layer comes from Redis, how should I implement it?
2, How are parameters resolved
As can be seen from the figure above, our parameters will eventually be resolved through the HandlerMethodArgumentResolver. After knowing this, we can implement our own parameter resolution.
3, Demand
If the @ redis annotation exists in the parameters of our control layer method, the value of this parameter should be obtained from redis instead of from the request parameters.
From the above figure, we can see that the parameter @ Redis(key = "redisKey") String redisValue needs to be obtained from Redis.
4, Realize
Here we won't really get the value from Redis, just simulate getting the value from Redis.
1. Write a Redis annotation
@Target({ElementType.PARAMETER}) @Retention(RetentionPolicy.RUNTIME) public @interface Redis { /** * redis Key in */ String key(); }
In the method of the control layer, the method parameters annotated here are obtained from Redis and go through the parameter parser defined by ourselves.
2. Write parameter resolution class
package com.huan.study.argument.resolver; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.core.MethodParameter; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.method.support.ModelAndViewContainer; import javax.servlet.http.HttpServletRequest; import java.util.Random; /** * Get the value from redis and put it into the parameter * */ public class RedisMethodArgumentResolver implements HandlerMethodArgumentResolver { private static final Logger log = LoggerFactory.getLogger(RedisMethodArgumentResolver.class); /** * The @ Redis annotation exists on the processing parameter */ @Override public boolean supportsParameter(MethodParameter parameter) { return parameter.hasParameterAnnotation(Redis.class); } /** * Analytical parameters */ @Override public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { // Get http request HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); log.info("Current requested path:[{}]", request.getRequestURI()); // Get this annotation Redis redis = parameter.getParameterAnnotation(Redis.class); // Get the key in redis String redisKey = redis.key(); // Simulate getting values from redis String redisValue = "from redis Value obtained in:" + new Random().nextInt(100); log.info("from redis The value obtained in is:[{}]", redisValue); // Return value return redisValue; } }
1. Determine which parameters we should process through the supportsParameter method. Here, we process parameters with @ Redis annotation on them.
2. Obtain the specific value of the parameter through the resolveArgument method. For example, if it is obtained from Redis, the code does not really obtain it from Redis, but only simulates obtaining it from Redis.
3. Configure into the context of Spring
Here we'd better put our own parameter parser first, otherwise there may be problems. Two methods are provided below. The first method cannot meet our needs, and we use the second method to achieve it
1. Implemented via WebMvcConfigurer
@Configuration public class WebMvcConfig implements WebMvcConfigurer { /** * The loading order of this place is after the default HandlerMethodArgumentResolver */ @Override public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) { resolvers.add(new RedisMethodArgumentResolver()); } }
As can be seen from the above figure, our own parameter parser is not in the first place, which may not achieve the desired effect. This method is not considered here.
2. Implemented by BeanPostProcessor
BeanPostProcessor can perform some operations after a Bean is fully initialized. Here, we put our own parameter parser first.
@Component static class CustomHandlerMethodArgumentResolverConfig implements BeanPostProcessor { @Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean instanceof RequestMappingHandlerAdapter) { final RequestMappingHandlerAdapter adapter = (RequestMappingHandlerAdapter) bean; final List<HandlerMethodArgumentResolver> argumentResolvers = Optional.ofNullable(adapter.getArgumentResolvers()) .orElseGet(ArrayList::new); final ArrayList<HandlerMethodArgumentResolver> handlerMethodArgumentResolvers = new ArrayList<>(argumentResolvers); // Put our own parameter parser first handlerMethodArgumentResolvers.add(0, new RedisMethodArgumentResolver()); adapter.setArgumentResolvers(Collections.unmodifiableList(handlerMethodArgumentResolvers)); return adapter; } return bean; } }
As can be seen from the above figure, our own parameter parser is in the first place, so as to achieve the desired effect. This method is used here.
4. Write a simple control layer
/** * @author huan.fu 2021/12/7 - 3:36 PM */ @RestController public class RedisArgumentController { private static final Logger log = LoggerFactory.getLogger(RedisArgumentController.class); @GetMapping("redisArgumentResolver") public void redisArgumentResolver(@RequestParam("hello") String hello, @Redis(key = "redisKey") String redisValue) { log.info("Parameter value obtained by control layer: hello:[{}],redisValue:[{}]", hello, redisValue); } }
The control layer is relatively simple and provides a simple external interface apihttp://localhost:8080/redisArgumentResolver?hello=123 . The api has two parameters: hello and redisValue. The value of the Hello parameter is obtained from the request parameter, and the value of redisValue is obtained from the parameter defined by ourselves
Get from the parser.
5, Testing
curl http://localhost:8080/redisArgumentResolver?hello=123
It can be seen from the figure above that the parameter parser defined by ourselves works.