他是幹什麼
RequestMappingHandlerAdapter處理執行我們最常用的@requestMapping 的方法,也是目前mvc提供的最常用最複雜的 handlerAdapter相對於其他的。他功能很簡單:通過反射執行我們標記 @requestMapping 的方法。但是他複雜在,需要我們對執行@requestMapping 入參的綁定(因爲沒有對入參做要求,所以很實用,但是處理起來會面臨各種各樣的情況,所以很複雜),和一些我們定義好的前置處理步驟比如@initbinder,@modeAttribute等標記代碼塊的執行,要給對參數,有的都要給。
初始化
我們先從初始化入手看他的結構,一看到InitializingBean,找afterPropertiesSet如下
@Override
public void afterPropertiesSet() {
initControllerAdviceCache();//
if (this.argumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultArgumentResolvers();
this.argumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.initBinderArgumentResolvers == null) {
List<HandlerMethodArgumentResolver> resolvers = getDefaultInitBinderArgumentResolvers();
this.initBinderArgumentResolvers = new HandlerMethodArgumentResolverComposite().addResolvers(resolvers);
}
if (this.returnValueHandlers == null) {
List<HandlerMethodReturnValueHandler> handlers = getDefaultReturnValueHandlers();
this.returnValueHandlers = new HandlerMethodReturnValueHandlerComposite().addHandlers(handlers);
}
}
先看第一行
//找到@ControllerAdvice 和ModelAttribute,InitBinder的method和bean
initControllerAdviceCache();
/*
* 對應源碼
*/
private void initControllerAdviceCache() {
if (getApplicationContext() == null) {
return;
}
if (logger.isInfoEnabled()) {
logger.info("Looking for @ControllerAdvice: " + getApplicationContext());
}
// 找到所有@ControllerAdvice標記的bean
List<ControllerAdviceBean> beans = ControllerAdviceBean.findAnnotatedBeans(getApplicationContext());
AnnotationAwareOrderComparator.sort(beans);//排序
List<Object> requestResponseBodyAdviceBeans = new ArrayList<Object>();
for (ControllerAdviceBean bean : beans) {
//找到ModelAttribute標記,同時沒有被RequestMapping標記的method
Set<Method> attrMethods = MethodIntrospector.selectMethods(bean.getBeanType(), MODEL_ATTRIBUTE_METHODS);
if (!attrMethods.isEmpty()) {
this.modelAttributeAdviceCache.put(bean, attrMethods);
if (logger.isInfoEnabled()) {
logger.info("Detected @ModelAttribute methods in " + bean);
}
}
//找到@initbinder標記的method
Set<Method> binderMethods = MethodIntrospector.selectMethods(bean.getBeanType(), INIT_BINDER_METHODS);
if (!binderMethods.isEmpty()) {
this.initBinderAdviceCache.put(bean, binderMethods);
if (logger.isInfoEnabled()) {
logger.info("Detected @InitBinder methods in " + bean);
}
}
//看是不是實現了RequestBodyAdvice接口,實現了放到一個list中
if (RequestBodyAdvice.class.isAssignableFrom(bean.getBeanType())) {
requestResponseBodyAdviceBeans.add(bean);
if (logger.isInfoEnabled()) {
logger.info("Detected RequestBodyAdvice bean in " + bean);
}
}
//看是不是實現了ResponseBodyAdvice接口,實現了放到一個list中
if (ResponseBodyAdvice.class.isAssignableFrom(bean.getBeanType())) {
requestResponseBodyAdviceBeans.add(bean);
if (logger.isInfoEnabled()) {
logger.info("Detected ResponseBodyAdvice bean in " + bean);
}
}
}
//把所有的bodyAdvice 放入一個requestResponseBodyAdvice,以備後面使用
if (!requestResponseBodyAdviceBeans.isEmpty()) {
this.requestResponseBodyAdvice.addAll(0, requestResponseBodyAdviceBeans);
}
}
經過以上操作就把所有的spring中的@ControllerAdvice 註解的bean,和對應的@initbinder,@modeAttribute的method還有兩個request/ResponseBodyAdvice都放到我們的RequestMappingHandlerAdapter中的,以備後面使用也就是完成的initControllerAdviceCache 初始化initControllerAdviceCache緩存。
後面三種就是加載各種解析器
調用過程
程序入口是handleInternal方法,看一下源碼
@Override
protected ModelAndView handleInternal(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ModelAndView mav;
//校驗session是否必須存在,還有是否支這個請求
checkRequest(request);
// 是否對session同步
if (this.synchronizeOnSession) {
HttpSession session = request.getSession(false);
if (session != null) {
Object mutex = WebUtils.getSessionMutex(session);
synchronized (mutex) {
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 如果沒有可用的HttpSession ->不需要互斥
mav = invokeHandlerMethod(request, response, handlerMethod);
}
}
else {
// 如果根本不要同步
mav = invokeHandlerMethod(request, response, handlerMethod);
}
//看看response報文頭中是否包含cache_control
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
//判斷當前這handler是否有@sessionAttribute,如果有就解析出來,放到緩存中準備下次使用
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
//設置返回緩存過期時間
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
return mav;
}
我們從頭開始看
checkRequest(request);
protected final void checkRequest(HttpServletRequest request) throws ServletException {
// 得到當前請求的方法類型,看看當前handleradapter是否支持這個方法
String method = request.getMethod();
if (this.supportedMethods != null && !this.supportedMethods.contains(method)) {
throw new HttpRequestMethodNotSupportedException(
method, StringUtils.toStringArray(this.supportedMethods));
}
// 看看是否必須要這個session,如果必須沒有拋異常
if (this.requireSession && request.getSession(false) == null) {
throw new HttpSessionRequiredException("Pre-existing session required but none found");
}
}
第二段執行方法後面細說,剩下的一段就是
if (!response.containsHeader(HEADER_CACHE_CONTROL)) {
if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) {
applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers);
}
else {
prepareResponse(response);
}
}
這段代碼總的意思是:
一般情況下,如果返回報文中沒有對緩存的特殊控制,如果有@sessionAttribute則組織使用緩存,否則就什麼也不做
下面我們看重點,執行方法
mav = invokeHandlerMethod(request, response, handlerMethod);
protected ModelAndView invokeHandlerMethod(HttpServletRequest request,
HttpServletResponse response, HandlerMethod handlerMethod) throws Exception {
ServletWebRequest webRequest = new ServletWebRequest(request, response);
try {
//看下面 1
WebDataBinderFactory binderFactory = getDataBinderFactory(handlerMethod);
//看下面 2
ModelFactory modelFactory = getModelFactory(handlerMethod, binderFactory);
//看下面3
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
//新建傳遞參數的 mavcontainer容器,並把相應的參數設置進去
ModelAndViewContainer mavContainer = new ModelAndViewContainer();
//設置flashMap的參數進去
mavContainer.addAllAttributes(RequestContextUtils.getInputFlashMap(request));
//用modelFactoey把 sessionAttribute和modelAttribute的參數設置進去
modelFactory.initModel(webRequest, mavContainer, invocableMethod);
//這個是在mavcontainer中會用到後面細說
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();
if (logger.isDebugEnabled()) {
logger.debug("Found concurrent result value [" + result + "]");
}
//執行請求
invocableMethod = invocableMethod.wrapConcurrentResult(result);
}
invocableMethod.invokeAndHandle(webRequest, mavContainer);
if (asyncManager.isConcurrentHandlingStarted()) {
return null;
}
//看 4
return getModelAndView(mavContainer, modelFactory, webRequest);
}
finally {
webRequest.requestCompleted();
}
}
我們看一下這個方法,首先封裝了一個webRequest,這個用於argumentResolver解析參數時使用,然後就是對 WebDataBinderFactory, ModelFactory, ServletInvocableHandlerMethod進行定義和初始化
- WebDataBinderFactory
用來創建WebDataBinder,WebDataBinder主要用來參數綁定,實現參數跟String之間的類型轉換,看一下源碼
private WebDataBinderFactory getDataBinderFactory(HandlerMethod handlerMethod) throws Exception {
//取得bean的class
Class<?> handlerType = handlerMethod.getBeanType();
//看看是不是這個controller已經在緩存中有了
Set<Method> methods = this.initBinderCache.get(handlerType);
// 如果沒有
if (methods == null) {
//查找這個controller看看是不是有initbinder,有的話加載到緩存中,下次使用
methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
this.initBinderCache.put(handlerType, methods);
}
//自定義保存initbinder方法的零時變量
List<InvocableHandlerMethod> initBinderMethods = new ArrayList<InvocableHandlerMethod>();
// 先找到符合條件的全局 initbinder,添加到initbindermethods中
for (Entry<ControllerAdviceBean, Set<Method>> entry : this.initBinderAdviceCache.entrySet()) {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
Object bean = entry.getKey().resolveBean();
for (Method method : entry.getValue()) {
// 先添加全局的initbinder
initBinderMethods.add(createInitBinderMethod(bean, method));
}
}
}
//再添加當前這個controller的
for (Method method : methods) {
Object bean = handlerMethod.getBean();
initBinderMethods.add(createInitBinderMethod(bean, method));
}
//創建factory返回
return createDataBinderFactory(initBinderMethods);
}
從上面的源碼可以看出,主要是找到所有的initbinder 來創建WebDataBinderFactory,注意下面這個MethodIntrospector.selectMethods 找不到也會返回一個空set,不是null,這個可以確保我們只用查一次,不用發現發沒有,次次查
methods = MethodIntrospector.selectMethods(handlerType, INIT_BINDER_METHODS);
- ModelFactoey
用來處理model,主要是(1)在處理器處理之前對model初始化 (2)在處理完後對model進行參數更新
先說初始化,初始化主要是做三部分內容
- 將原來的@sessionAttribute 中的值設置到model中。
- 執行註釋了@modelAttribute 的方法,並將值設置到model中。
- 處理器註釋了@modelAttribute 的參數,如果同時在@sessionAttribute中也配置了,而且在mavContainer中還沒有值,則從全部的sessionAttribute中找到值並設置上。
更新是先對SessionAttributes進行設置,設置的規則是如果處理器設置了sessionStatus中的setComplete 就將sessionAttributes清空,否則就將model中的參數設置給sessionAttribute,然後按照需要把給model設置參數對應的bindingResult下面看一下源碼
private ModelFactory getModelFactory(HandlerMethod handlerMethod, WebDataBinderFactory binderFactory) {
// 獲取SessionAttributesHandler,也就是@sessionAttribute標籤的一些信息
SessionAttributesHandler sessionAttrHandler = getSessionAttributesHandler(handlerMethod);
Class<?> handlerType = handlerMethod.getBeanType();
//同上面initbinder的邏輯,找到modeAttribute 的方法
Set<Method> methods = this.modelAttributeCache.get(handlerType);
if (methods == null) {
methods = MethodIntrospector.selectMethods(handlerType, MODEL_ATTRIBUTE_METHODS);
this.modelAttributeCache.put(handlerType, methods);
}
List<InvocableHandlerMethod> attrMethods = new ArrayList<InvocableHandlerMethod>();
// 和上面的一樣,先是公共的,再是私有的
for (Entry<ControllerAdviceBean, Set<Method>> entry : this.modelAttributeAdviceCache.entrySet()) {
if (entry.getKey().isApplicableToBeanType(handlerType)) {
Object bean = entry.getKey().resolveBean();
for (Method method : entry.getValue()) {
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
}
}
for (Method method : methods) {
Object bean = handlerMethod.getBean();
attrMethods.add(createModelAttributeMethod(binderFactory, bean, method));
}
return new ModelFactory(attrMethods, binderFactory, sessionAttrHandler);
}
看一下 new ModelFactory(attrMethods, binderFactory, sessionAttrHandler); 可以看出創建他需要 一個是要用的 initbinder一個是modelAttributes,還有一個是sessionAttributesm給齊了
- ServletInvocableHandlerMethod核心方法請求的處理就是通過他來執行,參數綁定,和請求處理都是他來完成,首先把handlerMethod 封裝爲 servletInvocableHandlermethod,然後再把解析器設置進去
ServletInvocableHandlerMethod invocableMethod = createInvocableHandlerMethod(handlerMethod);
invocableMethod.setHandlerMethodArgumentResolvers(this.argumentResolvers);
invocableMethod.setHandlerMethodReturnValueHandlers(this.returnValueHandlers);
invocableMethod.setDataBinderFactory(binderFactory);
invocableMethod.setParameterNameDiscoverer(this.parameterNameDiscoverer);
4.getModelAndView(mavContainer, modelFactory, webRequest); 主要做三件事1)調用modelFactory更新了model。2)根據mavcontainer 創建了modelandview。3)如果mavcontainer是redirectAttribute類型的就將值設置到 falshmap中
getModelAndView(mavContainer, modelFactory, webRequest);
private ModelAndView getModelAndView(ModelAndViewContainer mavContainer,
ModelFactory modelFactory, NativeWebRequest webRequest) throws Exception {
//更新model
modelFactory.updateModel(webRequest, mavContainer);
if (mavContainer.isRequestHandled()) {
return null;
}
ModelMap model = mavContainer.getModel();
//創建mav
ModelAndView mav = new ModelAndView(mavContainer.getViewName(), model, mavContainer.getStatus());
if (!mavContainer.isViewReference()) {
mav.setView((View) mavContainer.getView());
}
//設置falshMap
if (model instanceof RedirectAttributes) {
Map<String, ?> flashAttributes = ((RedirectAttributes) model).getFlashAttributes();
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
RequestContextUtils.getOutputFlashMap(request).putAll(flashAttributes);
}
return mav;
}
小結
總的來說邏輯是很簡單的,就是用來很多陌生的組件,後面有專門的分析,主要流程的就是綁定參數,執行請求,處理返回值。
1)參數綁定
參數的主要來源就是有6類參數的主要來源就是有6類
- request中參數
- cookie中的參數
- session中參數
- falshMap中的參數
- @sessionAttribute中的參數
- @modelAttributes中的參數
前面三個在request中處理,falshMap是直接在RequestMappingHandlerAdapter處理,後面兩個在ModelFactory中處理
2) 處理請求,用的是ServletInvocableHandlerMethod
3) 處理收尾,主要是更新model中的緩存,和modelAndView的創建