??????做了這么多年的web開發(fā)荠耽,對于常用的@RequestMapping抱慌,@RequestParam等;我們是否知道它們實現原理呢勋锤?本文就來做個揭秘。(這篇文章的閱讀建議先了解Spring-MVC請求的基本流程)
????以前開發(fā)中一直有一個疑問:我們的請求的參數@RequestParam和不使用這個注解匹配的參數值有什么不同侥祭?性能有什么不同叁执?持著一份探究源碼的專注分析了源碼,不僅解決了這個疑問矮冬,還順帶把相關參數的解析都了解了谈宛,這里也給大家分享下自己的收獲。
相關學習和知識可以參考:spring-mvc官網
版本:springboot-2.2.6
版本:spring-webmvc-5.2.5
我創(chuàng)建了一個springboot的demo胎署,便于分析
@RestController
@SpringBootApplication
public class RequestparamDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RequestparamDemoApplication.class, args);
}
@RequestMapping("/check")
public String check(HttpServletRequest httpServletRequest, HttpServletResponse response,
@RequestParam(value = "key") String key){
return "ok:"+key;
}
@RequestMapping("/checkNo")
public String checkNo(HttpServletRequest httpServletRequest, HttpServletResponse response,
String keyNo){
return "okNo:"+keyNo;
}
1.知識背景
在分析參數前吆录,先了解這兩個概念。
1.1 回憶下spring-mvc的流程圖
spring-mvc-process.jpg
來自于網絡琼牧,先回憶回憶spring-mvc請求流程恢筝,不做過多解釋哀卫。
1.2 我們請求的流程url資源的存儲
????在具體分析前,還需要知道我們在每次通過一個url請求服務器后撬槽,服務器都能找到這個接口進行執(zhí)行此改,那它的實現是什么呢?不禁我給出一個猜測:在容器初始化的時候會把所有url和所在的控制器關聯起來了侄柔,那具體是不是呢共啃?
這里我們基于spring-boot項目來分析,我們在初始化的項目的中會通過WebMvcAutoConfiguration加載RequestMappingHandlerMapping
暂题,其他幾大DispatcherServlet組件就不一一解析了移剪,這里和原來xml啟動有區(qū)別
@Bean
@Primary
@Override
public RequestMappingHandlerMapping requestMappingHandlerMapping(
@Qualifier("mvcContentNegotiationManager") ContentNegotiationManager contentNegotiationManager,
@Qualifier("mvcConversionService") FormattingConversionService conversionService,
@Qualifier("mvcResourceUrlProvider") ResourceUrlProvider resourceUrlProvider) {
// Must be @Primary for MvcUriComponentsBuilder to work
return super.requestMappingHandlerMapping(contentNegotiationManager, conversionService,
resourceUrlProvider);
}
????這個加載完會進行bean的初始化,RequestMappingHandlerMapping
繼承RequestMappingInfoHandlerMapping
繼承AbstractHandlerMethodMapping
薪者,所以最終通過AfterPropertiesSet()來初始化纵苛,這里也就是我們url的注冊位置
public abstact class AbstractHandlerMethodMapping{
public void afterPropertiesSet() {
initHandlerMethods();
}
/**
* 通過檢測和注冊handler method,官網也給了很清晰的注釋
* Scan beans in the ApplicationContext, detect and register handler methods.
* @see #getCandidateBeanNames()
* @see #processCandidateBean
* @see #handlerMethodsInitialized
*/
protected void initHandlerMethods() {
for (String beanName : getCandidateBeanNames()) {
if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
//這里就是核心流程
processCandidateBean(beanName);
}
}
handlerMethodsInitialized(getHandlerMethods());
}
//接著往下看
protected void processCandidateBean(String beanName) {
Class<?> beanType = null;
try {
beanType = obtainApplicationContext().getType(beanName);
}
catch (Throwable ex) {
// An unresolvable bean type, probably from a lazy bean - let's ignore it.
if (logger.isTraceEnabled()) {
logger.trace("Could not resolve type for bean '" + beanName + "'", ex);
}
}
if (beanType != null && isHandler(beanType)) {
detectHandlerMethods(beanName); //檢測handlerMethod
}
}
//這里判定是否是handler言津,就直接通過是否有Controller或者RequestMapping注解來判定
@Override
protected boolean isHandler(Class<?> beanType) {
return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) ||
AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
}
往下看就是具體的注冊流程邏輯了
/**
* Look for handler methods in the specified handler bean.
* @param handler either a bean name or an actual handler instance
* @see #getMappingForMethod
*/
protected void detectHandlerMethods(Object handler) {
Class<?> handlerType = (handler instanceof String ?
obtainApplicationContext().getType((String) handler) : handler.getClass());
if (handlerType != null) {
Class<?> userType = ClassUtils.getUserClass(handlerType);
//這里遍歷控制器的方法攻人,將含有RequestMapping注解的方法給提取出來
Map<Method, T> methods = MethodIntrospector.selectMethods(userType,
(MethodIntrospector.MetadataLookup<T>) method -> {
try {
return getMappingForMethod(method, userType);
}
catch (Throwable ex) {
throw new IllegalStateException("Invalid mapping on handler class [" +
userType.getName() + "]: " + method, ex);
}
});
if (logger.isTraceEnabled()) {
logger.trace(formatMappings(userType, methods));
}
//這里就是具體注冊邏輯了
methods.forEach((method, mapping) -> {
Method invocableMethod = AopUtils.selectInvocableMethod(method, userType);
registerHandlerMethod(handler, invocableMethod, mapping);
});
}
}
我這里debug把demo中的方法截了個圖:
RequestMappingParse.png
我們繼續(xù)看registerHandlerMethod方法做了些什么:
//RequestMappingHandlerMapping做了方法重寫,不顧還是調用了父類的注冊
@Override
protected void registerHandlerMethod(Object handler, Method method, RequestMappingInfo mapping) {
super.registerHandlerMethod(handler, method, mapping);
//這里會增強處理RequestBody注解
updateConsumesCondition(mapping, method);
}
//發(fā)現這里直接調用mappingRegistry直接注冊了纺念,
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
//繼續(xù)瞧
// MappingRegistry是AbstractHandlerMethodMapping內部類,從名字看也是作為mapping注冊的
下面就是我們平時請求路徑的核心存儲了
class MappingRegistry{
private final Map<T, MappingRegistration<T>> registry = new HashMap<>();
private final Map<T, HandlerMethod> mappingLookup = new LinkedHashMap<>();
private final MultiValueMap<String, T> urlLookup = new LinkedMultiValueMap<>();
private final Map<String, List<HandlerMethod>> nameLookup = new ConcurrentHashMap<>();
private final Map<HandlerMethod, CorsConfiguration> corsLookup = new ConcurrentHashMap<>();
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//針對我們當前的demo情況先把入參說明下:
//@param mapping代表我們之前的RequestMappingInfo,封裝有url信息想括,比如“/check”陷谱,當然還有其他對于請求和返回的封裝
//@param handler 只beanName requestParamDemoApplicaiton
//@param method 只我們bean中的一個方法,比如我們demo中"check"
public void register(T mapping, Object handler, Method method) {
// Assert that the handler method is not a suspending one.
if (KotlinDetector.isKotlinType(method.getDeclaringClass()) && KotlinDelegate.isSuspend(method)) {
throw new IllegalStateException("Unsupported suspending handler method detected: " + method);
}
//讀寫鎖瑟蜈,利用的寫鎖
this.readWriteLock.writeLock().lock();
try {
//handler就是我們的控制器名稱beanName,
//method包含當前的所有信息了
//HandlerMethod會封裝好方法名烟逊,參數,bean等信息
HandlerMethod handlerMethod = createHandlerMethod(handler, method);
//這里驗證對于mapping不會存在多個handlerMethod,否則拋異常
validateMethodMapping(handlerMethod, mapping);
//通過map存儲 url->method的映射
this.mappingLookup.put(mapping, handlerMethod);
//這里直接獲取我們客戶端要請求的url
List<String> directUrls = getDirectUrls(mapping);
for (String url : directUrls) {
//這里存儲 url和mapping的映射管理
this.urlLookup.add(url, mapping);
}
String name = null;
//存儲在nameLookUp的map中
if (getNamingStrategy() != null) {
//name 默認是bean的大寫提取然后通過#連接方法名
//RDA#check (RDA=RequestDemoApplication)
name = getNamingStrategy().getName(handlerMethod, mapping);
addMappingName(name, handlerMethod);
}
//這里就是跨域配置@CrossOrigin的封裝了铺根,存儲起來
CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping);
if (corsConfig != null) {
this.corsLookup.put(handlerMethod, corsConfig);
}
//registry存儲mapping和后面的再次封裝的映射
this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name));
}
finally {
this.readWriteLock.writeLock().unlock();
}
}
}
總結url的存儲:
1.在創(chuàng)建RequestMappingHandlerMapping通過實現的
InitializingBean
的接口初始化所有控制器的url的解析及存儲
- 最終通過MappingRegistry把所有url宪躯,mapping,調用方法信息用map一一映射起來了位迂;
這里我們基本就大概有一個概念了访雪,我們這里有請求url和實際調用方法映,每次請求直接就可以通過url獲取方法掂林,然后通過反射直接調用就是臣缀,接下來理解就更容易了。
2.請求參數適配
先給出請求的處理中心流程
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false;
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
ModelAndView mv = null;
Exception dispatchException = null;
try {
//檢查是否是文件上傳請求
processedRequest = checkMultipart(request);
multipartRequestParsed = (processedRequest != request);
// 根據當前的請求獲取mappedHandlerChain(包含當前的MappingHandler)
mappedHandler = getHandler(processedRequest);
if (mappedHandler == null) {
noHandlerFound(processedRequest, response);
return;
}
//根據handler獲得對應的適配器
// Determine handler adapter for the current request.
HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
// Process last-modified header, if supported by the handler.
String method = request.getMethod();
boolean isGet = "GET".equals(method);
if (isGet || "HEAD".equals(method)) {
long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
return;
}
}
//前置調用泻帮,方法是否攔截(攔截器遍歷執(zhí)行)
if (!mappedHandler.applyPreHandle(processedRequest, response)) {
return;
}
// 實際調用
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//
applyDefaultViewName(processedRequest, mv);
//攔截器后置調用
mappedHandler.applyPostHandle(processedRequest, response, mv);
}
catch (Exception ex) {
dispatchException = ex;
}
catch (Throwable err) {
// As of 4.3, we're processing Errors thrown from handler methods as well,
// making them available for @ExceptionHandler methods and other scenarios.
dispatchException = new NestedServletException("Handler dispatch failed", err);
}
//處理結果信息精置,包含異常,渲染等
processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
}
catch (Exception ex) {
triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
}
catch (Throwable err) {
triggerAfterCompletion(processedRequest, response, mappedHandler,
new NestedServletException("Handler processing failed", err));
}
finally {
if (asyncManager.isConcurrentHandlingStarted()) {
// Instead of postHandle and afterCompletion
if (mappedHandler != null) {
mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
}
}
else {
// Clean up any resources used by a multipart request.
if (multipartRequestParsed) {
cleanupMultipart(processedRequest);
}
}
}
}
2.1 Handler的獲取
mappedHandler = getHandler(processedRequest);
下面的handlerMapping我們在初始化的時候會創(chuàng)建五個锣杂,按照下面順序
RequestMappingHandlerMapping 我
BeanNameUrlHandlerMapping
RouterFunctionMapping
SimpleUrlHandlerMapping
WelcomePageHandlerMapping
//
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
if (this.handlerMappings != null) {
for (HandlerMapping mapping : this.handlerMappings) {
//上面我們也分析了我們RequestMappingHandlerMapping封裝了url和handlerMethod的映射脂倦,往下接著看
HandlerExecutionChain handler = mapping.getHandler(request);
if (handler != null) {
return handler;
}
}
}
return null;
}
public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//這里獲取handler
Object handler = getHandlerInternal(request);
if (handler == null) {
handler = getDefaultHandler();
}
if (handler == null) {
return null;
}
// Bean name or resolved handler?
if (handler instanceof String) {
String handlerName = (String) handler;
handler = obtainApplicationContext().getBean(handlerName);
}
//
HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request);
...
//cors跨域攔截器
if (hasCorsConfigurationSource(handler) || CorsUtils.isPreFlightRequest(request)) {
CorsConfiguration config = (this.corsConfigurationSource != null ? this.corsConfigurationSource.getCorsConfiguration(request) : null);
CorsConfiguration handlerConfig = getCorsConfiguration(handler, request);
config = (config != null ? config.combine(handlerConfig) : handlerConfig);
executionChain = getCorsHandlerExecutionChain(request, executionChain, config);
}
return executionChain;
}
獲取handler最后會調用到AbstractHandlerMethodMapping的getHandlerInternal
protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception {
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
request.setAttribute(LOOKUP_PATH, lookupPath);
this.mappingRegistry.acquireReadLock();
try {
HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request);
return (handlerMethod != null ? handlerMethod.createWithResolvedBean() : null);
}
finally {
this.mappingRegistry.releaseReadLock();
}
}
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<Match> matches = new ArrayList<>();
//獲取適配的url
List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath);
if (directPathMatches != null) {
//這里會獲取對應的handlerMethod并封裝進Match中番宁,
addMatchingMappings(directPathMatches, matches, request);
}
if (matches.isEmpty()) {
// No choice but to go through all mappings...
addMatchingMappings(this.mappingRegistry.getMappings().keySet(), matches, request);
}
//如果當前match有多個滿足,會做一個排序并選出一個最優(yōu)的
if (!matches.isEmpty()) {
Match bestMatch = matches.get(0);
if (matches.size() > 1) {
Comparator<Match> comparator = new MatchComparator(getMappingComparator(request));
matches.sort(comparator);
bestMatch = matches.get(0);
if (logger.isTraceEnabled()) {
logger.trace(matches.size() + " matching mappings: " + matches);
}
if (CorsUtils.isPreFlightRequest(request)) {
return PREFLIGHT_AMBIGUOUS_MATCH;
}
Match secondBestMatch = matches.get(1);
if (comparator.compare(bestMatch, secondBestMatch) == 0) {
Method m1 = bestMatch.handlerMethod.getMethod();
Method m2 = secondBestMatch.handlerMethod.getMethod();
String uri = request.getRequestURI();
throw new IllegalStateException(
"Ambiguous handler methods mapped for '" + uri + "': {" + m1 + ", " + m2 + "}");
}
}
request.setAttribute(BEST_MATCHING_HANDLER_ATTRIBUTE, bestMatch.handlerMethod);
handleMatch(bestMatch.mapping, lookupPath, request);
return bestMatch.handlerMethod;
}
else {
return handleNoMatch(this.mappingRegistry.getMappings().keySet(), lookupPath, request);
}
}
總結下handler這里返回的是HanderExecutionChain
赖阻,包含有我們請求的url最優(yōu)的HanderMethod以及一系列對應的Interceptors
2.2 HandlerAdapter的生成
????這里還是看看HandlerAdapter蝶押,也許曾經的我們有一個疑問:為什么這里會需要用一個HandlerAdapter,后面還要用handlerAdapter來執(zhí)行Method政供?我們上面已經拿到method直接執(zhí)行不就好了嗎播聪?
來自于原作者注釋:
HanderAdapter:MVC framework SPI, allowing parameterization of the core MVC workflow.
This interface is not intended for application developers. It is available
to handlers who want to develop their own web workflow.
看看HanderAdapter接口信息:
public interface HandlerAdapter {
//是否支持當前Adapter
boolean supports(Object handler);
//處理請求
@Nullable
ModelAndView handle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception;
//獲取上一次修改時間
long getLastModified(HttpServletRequest request, Object handler);
}
這里從官方注釋中也發(fā)現了這就是MVC框架提供的一個SPI,我們可以對不同的Handler自己做定制化的Adapter來請求布隔;只要我們實現這個HandlerAdapter注入容器离陶,然后support方法 return (handler instanceof MyHandler)
;
這里默認有四個Adapter,按照如下順序排列
RequestMappingHandlerAdapter
HandlerFunctionAdapter
HttpRequestHandlerAdapter
SimpleControllerHandlerAdatper
稍微總結下:HandlerAdapter是和Handler對應的,我們對于不同的Handler適配不同的適配器來處理這個handler衅檀。MVC框架提供這個SPI可以讓我們自己構建不同的Handler招刨,然后自己來構造自定義的適配器處理自己的請求。
當然RequestMappingHandlerAdapter
就直接適配到了我們的HandlerMethod
//模板方法
public abstract class AbstractHandlerMethodAdapter{
public final boolean supports(Object handler) {
//這里需要支持數據handlerMethod
return (handler instanceof HandlerMethod && supportsInternal((HandlerMethod) handler));
}
//子類RequestMappingHandlerAdapter直接返回true
protected abstract boolean supportsInternal(HandlerMethod handlerMethod);
}
protected boolean supportsInternal(HandlerMethod handlerMethod) {
return true;
}
到這里HanderAdapter就簡單分析到這里
2.3 Handler的調用執(zhí)行
// Actually invoke the handler. 這里RequestMappingHandlerAdatper執(zhí)行
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
上面的handler會調用父類的方法哀军,然后通過模板方法handlerInternel調用實際執(zhí)行方法
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
checkRequest(request);
// Execute invokeHandlerMethod in synchronized block if required.
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// No HttpSession available -> no mutex necessary
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 執(zhí)行方法調用
mav = invokeHandlerMethod(request, response, handlerMethod);
}
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
//執(zhí)行
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
//再次包裝
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
if (this.argumentResolvers != null) {
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
}
if (this.returnValueHandlers != null) {
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
}
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
mavContainer.setIgnoreDefaultModelOnRedirect(this.ignoreDefaultModelOnRedirect);
AsyncWebRequest asyncWebRequest = WebAsyncUtils.createAsyncWebRequest(request, response);
asyncWebRequest.setTimeout(this.asyncRequestTimeout);
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
asyncManager.setTaskExecutor(this.taskExecutor);
asyncManager.setAsyncWebRequest(asyncWebRequest);
asyncManager.registerCallableInterceptors(this.callableInterceptors);
asyncManager.registerDeferredResultInterceptors(this.deferredResultInterceptors);
if (asyncManager.hasConcurrentResult()) {
Object result = asyncManager.getConcurrentResult();
mavContainer = (ModelAndViewContainer) asyncManager.getConcurrentResultContext()[0];
asyncManager.clearConcurrentResult();
LogFormatUtils.traceDebug(logger, traceOn -> {
String formatted = LogFormatUtils.formatValue(result, !traceOn);
return "Resume with async result [" + formatted + "]";
});
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
//重點是這里實心方法的調用沉眶,上面都是為當前的method做保證,封裝移一些屬性:包含參數解析器杉适,返回值解析器谎倔,binderFactory(開發(fā)員可以對于參數做一定的操作,自動轉換數據)等
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
ServletInvocableHandlerMethod
這個類被委托做方法內部的調用和返回數據的封裝猿推。
public void invokeAndHandle(ServletWebRequest webRequest, ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//方法調用
Object returnValue = invokeForRequest(webRequest, mavContainer, providedArgs);
//后面的代碼是對于返回的數據的封裝處理
setResponseStatus(webRequest);
if (returnValue == null) {
if (isRequestNotModified(webRequest) || getResponseStatus() != null || mavContainer.isRequestHandled()) {
disableContentCachingIfNecessary(webRequest);
mavContainer.setRequestHandled(true);
return;
}
}
else if (StringUtils.hasText(getResponseStatusReason())) {
mavContainer.setRequestHandled(true);
return;
}
mavContainer.setRequestHandled(false);
Assert.state(this.returnValueHandlers != null, "No return value handlers");
try {
this.returnValueHandlers.handleReturnValue(
returnValue, getReturnValueType(returnValue), mavContainer, webRequest);
}
catch (Exception ex) {
if (logger.isTraceEnabled()) {
logger.trace(formatErrorForReturnValue(returnValue), ex);
}
throw ex;
}
}
//InvocableHandlerMethod的執(zhí)行請求處理
public Object invokeForRequest(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//這里獲取方法的參數
Object[] args = getMethodArgumentValues(request, mavContainer, providedArgs);
if (logger.isTraceEnabled()) {
logger.trace("Arguments: " + Arrays.toString(args));
}
//通過反射調用數據
return doInvoke(args);
}
做了太多的鋪墊了片习,終于找到這篇文章要做的中心了,參數的解析
getMethodArgumentValules
了
//這里就是需要我們把方法參數的值返回蹬叭;
protected Object[] getMethodArgumentValues(NativeWebRequest request, @Nullable ModelAndViewContainer mavContainer,
Object... providedArgs) throws Exception {
//獲取我們方法的參數對象數據
MethodParameter[] parameters = getMethodParameters();
if (ObjectUtils.isEmpty(parameters)) {
return EMPTY_ARGS; //沒有參數藕咏,返回空數組
}
//創(chuàng)建同樣大小的arg數組用來裝我們相對應的value
Object[] args = new Object[parameters.length];
for (int i = 0; i < parameters.length; i++) {
MethodParameter parameter = parameters[i];
parameter.initParameterNameDiscovery(this.parameterNameDiscoverer);
//我們傳入的providedArgs為null,所以這里可以忽略
args[i] = findProvidedArgument(parameter, providedArgs);
if (args[i] != null) {
continue;
}
//這里通過resolver來判定當前的parameter是否支持當前的parameter的解析秽五,如果支持就用當前resolver解析這個參數孽查,并返回value值
if (!this.resolvers.supportsParameter(parameter)) {
throw new IllegalStateException(formatArgumentError(parameter, "No suitable resolver"));
}
try {
//解析參數值
args[i] = this.resolvers.resolveArgument(parameter, mavContainer, request, this.dataBinderFactory);
}
catch (Exception ex) {
// Leave stack trace for later, exception may actually be resolved and handled...
if (logger.isDebugEnabled()) {
String exMsg = ex.getMessage();
if (exMsg != null && !exMsg.contains(parameter.getExecutable().toGenericString())) {
logger.debug(formatArgumentError(parameter, exMsg));
}
}
throw ex;
}
}
return args;
}
我們發(fā)現這里的resolver和上面的Adapter是不是很相似,如果support給定的某一個handler 那么就handle it坦喘;所以這里也會用大量的resolver來處理不同的參數了盲再,也便于我們自己后期設計框架擴展了。
比如我們在參數中用@RequestParam,@PathVariable等參數了瓣铣,猜測肯定會有不同的解析器resolver了洲胖。
//這是一個對于方法參數解析器的組合了。
HandlerMethodArgumentResolverComposite resolvers = new HandlerMethodArgumentResolverComposite();
public class HandlerMethodArgumentResolverComposite {
public boolean supportsParameter(MethodParameter parameter) {
return getArgumentResolver(parameter) != null;
}
@Nullable
private HandlerMethodArgumentResolver getArgumentResolver(MethodParameter parameter) {
//先從緩存中獲取坯沪,如果有的話就直接返回了绿映。
HandlerMethodArgumentResolver result = this.argumentResolverCache.get(parameter);
if (result == null) {
//遍歷resolver,然后返回支持這個參數的resovler
for (HandlerMethodArgumentResolver resolver : this.argumentResolvers) {
if (resolver.supportsParameter(parameter)) {
result = resolver;
this.argumentResolverCache.put(parameter, result);
break;
}
}
}
return result;
}
}
框架默認的解析器,按照先后有排名叉弦,如下圖:
resolvers.png
框架提供了26個解析器丐一,發(fā)現有些解析器來自相同的類,但是卻有兩個淹冰,比如我們下面要解析的RequestParammethodArgumentResolver库车;還要其他。那么直接給答案了樱拴,這里相同類的不同得對象柠衍,他們的對象不同就用來處理不同的參數了。
分析RequestParamMethodArgumentResolver
晶乔,用來處理帶有@RequestParam注解的參數和沒有注解的參數珍坊,沒有注解解析器order要放在比較靠后的。
public boolean supportsParameter(MethodParameter parameter) {
//1.判定是否含有@RequestParam注解
if (parameter.hasParameterAnnotation(RequestParam.class)) {
if (Map.class.isAssignableFrom(parameter.nestedIfOptional().getNestedParameterType())) {
RequestParam requestParam = parameter.getParameterAnnotation(RequestParam.class);
return (requestParam != null && StringUtils.hasText(requestParam.name()));
}
else {
return true;
}
}
else {
//含有 @RequestPart注解的不處理
if (parameter.hasParameterAnnotation(RequestPart.class)) {
return false;
}
parameter = parameter.nestedIfOptional();
//可以處理文件上傳的
if (MultipartResolutionDelegate.isMultipartArgument(parameter)) {
return true;
}
//useDefaultRelustion默認解析器正罢,這個就是解釋了上面我們?yōu)槭裁磩?chuàng)建了兩個對象阵漏;false表示只處理@RequestParam,另外一個就是默認解析器了翻具,用來解析就沒有特殊類型的參數了
else if (this.useDefaultResolution) {
return BeanUtils.isSimpleProperty(parameter.getNestedParameterType());
}
else {
return false;
}
}
}
上面的parameter.getNestedParameterType表示獲取參數的類型履怯,如果我們定義的參數是(String key)那么就會是java.lang.String
Beanutils.isSimpleProperty
public static boolean isSimpleProperty(Class<?> type) {
Assert.notNull(type, "'type' must not be null");
//這里要么是基本類型,或者基本類型的數組
return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
}
public static boolean isSimpleValueType(Class<?> type) {
return (Void.class != type && void.class != type &&
(ClassUtils.isPrimitiveOrWrapper(type) ||
Enum.class.isAssignableFrom(type) ||
CharSequence.class.isAssignableFrom(type) ||
Number.class.isAssignableFrom(type) ||
Date.class.isAssignableFrom(type) ||
Temporal.class.isAssignableFrom(type) ||
URI.class == type ||
URL.class == type ||
Locale.class == type ||
Class.class == type));
}
找到了參數解析的resolver了裆泳,接著我們看在執(zhí)行解析的過程
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
HandlerMethodArgumentResolver resolver = getArgumentResolver(parameter);
if (resolver == null) {
throw new IllegalArgumentException("Unsupported parameter type [" +
parameter.getParameterType().getName() + "]. supportsParameter should be called first.");
}
return resolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory);
}
這里會調用AbstractNamedValueMethodArgumentResolver的參數解析方法
public final Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) throws Exception {
//通過參數名構建nameValue對象叹洲,這里也是我們分別出唯一@RequestParam和不含注解的區(qū)別了。
NamedValueInfo namedValueInfo = getNamedValueInfo(parameter);
//如果有嵌套的參數需要解析出出來
MethodParameter nestedParameter = parameter.nestedIfOptional();
//解析占位參數 placeholders名
Object resolvedName = resolveStringValue(namedValueInfo.name);
if (resolvedName == null) {
throw new IllegalArgumentException(
"Specified name must not resolve to null: [" + namedValueInfo.name + "]");
}
Object arg = resolveName(resolvedName.toString(), nestedParameter, webRequest);
if (arg == null) {
if (namedValueInfo.defaultValue != null) {
//如果我們有默認值工禾,這里按照默認值處理
arg = resolveStringValue(namedValueInfo.defaultValue);
}
else if (namedValueInfo.required && !nestedParameter.isOptional()) {
//如果我們的required是必須的运提,當沒有值時處理會拋異常
handleMissingValue(namedValueInfo.name, nestedParameter, webRequest);
}
//處理null值,如果是boolean值帜篇,轉為false返回否則拋出異常
arg = handleNullValue(namedValueInfo.name, arg, nestedParameter.getNestedParameterType());
}
else if ("".equals(arg) && namedValueInfo.defaultValue != null) {
arg = resolveStringValue(namedValueInfo.defaultValue);
}
if (binderFactory != null) {
WebDataBinder binder = binderFactory.createBinder(webRequest, null, namedValueInfo.name);
try {
//這里如果我們做了參數轉換糙捺,會走這里把相關的參數诫咱,轉為我們需要的參數
arg = binder.convertIfNecessary(arg, parameter.getParameterType(), parameter);
}
catch (ConversionNotSupportedException ex) {
throw new MethodArgumentConversionNotSupportedException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
catch (TypeMismatchException ex) {
throw new MethodArgumentTypeMismatchException(arg, ex.getRequiredType(),
namedValueInfo.name, parameter, ex.getCause());
}
}
//解析完參數笙隙,再次處理
handleResolvedValue(arg, namedValueInfo.name, parameter, mavContainer, webRequest);
return arg;
}
private NamedValueInfo getNamedValueInfo(MethodParameter parameter) {
//會從緩存中獲取
NamedValueInfo namedValueInfo = this.namedValueInfoCache.get(parameter);
if (namedValueInfo == null) {
namedValueInfo = createNamedValueInfo(parameter);
namedValueInfo = updateNamedValueInfo(parameter, namedValueInfo);
this.namedValueInfoCache.put(parameter, namedValueInfo);
}
return namedValueInfo;
}
@Override
protected NamedValueInfo createNamedValueInfo(MethodParameter parameter) {
//這里會把我們設置的RequestParam包裝起來,而普通的就簡單的new一個對象
RequestParam ann = parameter.getParameterAnnotation(RequestParam.class);
return (ann != null ? new RequestParamNamedValueInfo(ann) : new RequestParamNamedValueInfo());
}
交給子類的模板方法坎缭,RequestParamMethodArgumentResolver實現
@Nullable
protected Object resolveName(String name, MethodParameter parameter, NativeWebRequest request) throws Exception {
HttpServletRequest servletRequest = request.getNativeRequest(HttpServletRequest.class);
//文件處理
if (servletRequest != null) {
Object mpArg = MultipartResolutionDelegate.resolveMultipartArgument(name, parameter, servletRequest);
if (mpArg != MultipartResolutionDelegate.UNRESOLVABLE) {
return mpArg;
}
}
//還是文件處理
Object arg = null;
MultipartRequest multipartRequest = request.getNativeRequest(MultipartRequest.class);
if (multipartRequest != null) {
List<MultipartFile> files = multipartRequest.getFiles(name);
if (!files.isEmpty()) {
arg = (files.size() == 1 ? files.get(0) : files);
}
}
if (arg == null) {
//這里通過參數名獲取參數值竟痰,然后返回
String[] paramValues = request.getParameterValues(name);
if (paramValues != null) {
arg = (paramValues.length == 1 ? paramValues[0] : paramValues);
}
}
return arg;
}
總結:
1.項目初始化將所有的url和HandlerMethod都通過容器存儲起來了,訪問的時候直接通過映射查找
2.獲取后的handlerMethod會封裝為HandlerExecutionChain
3 查找不同的Adapter做操作執(zhí)行
4 先遍歷方法的參數找到需要的Resolver掏呼,然后解析對應的值坏快,而且找到每一個方法參數的resolver也會存儲一個緩存,便于下一次查找憎夷。
5 通過找到的resovler解析參數的值
....
我的疑問也給解決了:@RequestParam和不使用注解是一樣的效果莽鸿,性能上也不會差多少,只是我們通過@RequestParam可以使代碼更加簡潔,可以給出默認值祥得,或者強制必傳而不用在業(yè)務代碼中再次驗證兔沃。