一、背景
在Spring的Controller中,我們通過@RequestParam
或@RequestBody
就可以將請求中的引數對映到控制層具體的引數中,那麼這個是怎麼實現的呢?如果我現在控制層中的某個引數的值是從Redis
中來,那麼應該如何實現呢?
二、引數是如何解析的
從上圖中可以我們的引數最終會通過HandlerMethodArgumentResolver
來解析,那麼知道了這個後,我們就可以實現自己的引數解析了。
三、需求
如果我們控制層方法的引數中存在@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個引數hello
和redisValue
,其中hello
引數的值是從請求引數中獲取,redisValue
的值是從我們自己定義的引數
解析器中獲取。
五、測試
curl http://localhost:8080/redisArgumentResolver?hello=123
由上圖可知我們自己定義的引數解析器工作了。