Spring boot custom control layer parameter parsing

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.

6, Complete code

Complete code

Keywords: Java Spring Spring Boot Back-end

Added by patrickcurl on Sat, 11 Dec 2021 13:42:11 +0200