SpringBoot自定義控制層參數解析


一、背景

在Spring的Controller中,我們通過@RequestParam@RequestBody就可以將請求中的參數映射到控制層具體的參數中,那么這個是怎么實現的呢?如果我現在控制層中的某個參數的值是從Redis中來,那么應該如何實現呢?

二、參數是如何解析的

在這里插入圖片描述

從上圖中可以我們的參數最終會通過HandlerMethodArgumentResolver來解析,那么知道了這個后,我們就可以實現自己的參數解析了。

三、需求

如果我們控制層方法的參數中存在@Redis標注,那么此參數的值應該從redis中獲取,不用從請求參數中獲取。

@Redis標注的參數

從上圖中可知@Redis(key = "redisKey") String redisValue這個參數就需要從Redis中獲取。

四、實現

此處我們不會真的從Redis中獲取值,模擬從Redis中獲取值即可。

1、編寫一個Redis注解

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
public @interface Redis {
    
    /**
     * redis中的Key
     */
    String key();
    
}

在控制層的方法上,被此處注解標注的方法參數,都從Redis中獲取,都走我們自己定義的參數解析器。

2、編寫參數解析類

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;

/**
 * 從redis中獲取值放入到參數中
 *
 */
public class RedisMethodArgumentResolver implements HandlerMethodArgumentResolver {
    
    private static final Logger log = LoggerFactory.getLogger(RedisMethodArgumentResolver.class);
    
    /**
     * 處理參數上存在@Redis注解的
     */
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
        return parameter.hasParameterAnnotation(Redis.class);
    }
    
    /**
     * 解析參數 
     */
    @Override
    public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest,
                                  WebDataBinderFactory binderFactory) throws Exception {
        // 獲取http request
        HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
        log.info("當前請求的路徑:[{}]", request.getRequestURI());
        // 獲取到這個注解
        Redis redis = parameter.getParameterAnnotation(Redis.class);
        // 獲取在redis中的key
        String redisKey = redis.key();
        // 模擬從redis中獲取值
        String redisValue = "從redis中獲取的值:" + new Random().nextInt(100);
        log.info("從redis中獲取到的值為:[{}]", redisValue);
        // 返回值
        return redisValue;
    }
}

1、通過supportsParameter方法判斷我們應該處理哪些參數,此處處理的是參數上存在@Redis注解的。
2、通過resolveArgument方法,獲取到參數的具體的值。比如從Redis中獲取,代碼中沒有真的從Redis中獲取,只是模擬從Redis中獲取。

3、配置到Spring的上下文中

此處我們最好將我們自己的參數解析器放置在第一位,否則可能會有問題。下方提供了2種方式,第一種方式無法達到我們的需求、我們采用第二種方式來實現

1、通過WebMvcConfigurer實現

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    /**
     * 這個地方加載的順序是在默認的HandlerMethodArgumentResolver之后的
     */
    @Override
    public void addArgumentResolvers(List<HandlerMethodArgumentResolver> resolvers) {
        resolvers.add(new RedisMethodArgumentResolver());
    }
}

參數解析器位置

從上圖可知,我們自己的參數解析器不是在第一位,這樣可能達不到我們想要的效果,此處不考慮這種方式。

2、通過BeanPostProcessor來實現

BeanPostProcessor可以在一個Bean完全初始化之后來執行一些操作,此處我們通過這種方式,將我們自己的參數解析器放置在第一位。

@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);
            // 將我們自己的參數解析器放置到第一位
            handlerMethodArgumentResolvers.add(0, new RedisMethodArgumentResolver());
            adapter.setArgumentResolvers(Collections.unmodifiableList(handlerMethodArgumentResolvers));
            return adapter;
        }
        return bean;
    }
}

參數解析器

從上圖可知,我們自己的參數解析器在第一位,這樣就達到我們想要的效果,此處使用這種方式。

4、編寫一個簡單的控制層

/**
 * @author huan.fu 2021/12/7 - 下午3:36
 */
@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("控制層獲取到的參數值: hello:[{}],redisValue:[{}]", hello, redisValue);
    }
}

該控制層比較簡單,對外提供來一個簡單的apihttp://localhost:8080/redisArgumentResolver?hello=123。該api存在2個參數helloredisValue,其中hello參數的值是從請求參數中獲取,redisValue的值是從我們自己定義的參數
解析器中獲取。

五、測試

curl http://localhost:8080/redisArgumentResolver?hello=123

測試自定義參數解析器

由上圖可知我們自己定義的參數解析器工作了。

六、完整代碼

完整代碼


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM