一、開篇
在使用SpringMvc框架創建工程時,每次都要在xml中添加【<mvc:annotation-driven/>】這一行配置。
到底這行配置起到了什么作用呢?
啟用后,Spring會默認幫我們注冊處理請求,參數和返回值的類。
主要是實現了以下兩個接口:HandlerMapping與HandlerAdapter。
那到底是如何實現的呢,我們在此展開以下。
二、RequestMappingHandlerMapping
首先,由官方幫助文檔可以看到,【<mvc:annotation-driven/>】會幫我們注冊RequestMappingHandlerMapping的Bean。
RequestMappingHandlerMapping實現了InitializingBean接口,那么在初始化Bean時,會調用afterPropertiesSet()方法。如下
public void afterPropertiesSet() { initHandlerMethods(); }
protected void initHandlerMethods() {
// 遍歷候選Bean for (String beanName : getCandidateBeanNames()) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) {
// 執行並注冊候選Bean 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); } }
// 判斷是不是Handler的Bean if (beanType != null && isHandler(beanType)) { detectHandlerMethods(beanName); } }
protected boolean isHandler(Class<?> beanType) {
// 判斷是否被Controller和RequestMapping注解標注 return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class));
}
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 {
// 根據方法上的@RequestMapping來創建RequestMappingInfo實例 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); }); } }
protected void registerHandlerMethod(Object handler, Method method, T mapping) { this.mappingRegistry.register(mapping, handler, method); }
public void register(T mapping, Object handler, Method method) { this.readWriteLock.writeLock().lock(); try { HandlerMethod handlerMethod = createHandlerMethod(handler, method); assertUniqueMethodMapping(handlerMethod, mapping);
// 將處理器方法添加到映射表中 this.mappingLookup.put(mapping, handlerMethod); List<String> directUrls = getDirectUrls(mapping); for (String url : directUrls) { this.urlLookup.add(url, mapping); } String name = null; if (getNamingStrategy() != null) { name = getNamingStrategy().getName(handlerMethod, mapping); addMappingName(name, handlerMethod); } CorsConfiguration corsConfig = initCorsConfiguration(handler, method, mapping); if (corsConfig != null) { this.corsLookup.put(handlerMethod, corsConfig); } this.registry.put(mapping, new MappingRegistration<>(mapping, handlerMethod, directUrls, name)); } finally { this.readWriteLock.writeLock().unlock(); } }
由此可以看到,在對RequestMappingHandlerMapping進行Bean的初始化時,就將被@Controller和@RequestMapping注解的處理器方法放入了映射表
三、RequestMappingHandlerAdapter
因為Handler的格式是不固定的,所以處理之前需要HandlerAdapter來做適配,在此給出了其中最重要的handlerInternal方法
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 { // No synchronization on session demanded at all... mav = invokeHandlerMethod(request, response, handlerMethod); } if (!response.containsHeader(HEADER_CACHE_CONTROL)) { if (getSessionAttributesHandler(handlerMethod).hasSessionAttributes()) { applyCacheSeconds(response, this.cacheSecondsForSessionAttributeHandlers); } else { prepareResponse(response); } } return mav; }
可以看出其中通過invokeHandlerMethod代理執行了方法。
四、HandlerInterceptor
在執行上述三中代理方法時,HandlerIntercepor會對該方法執行前和執行后進行一些准備和善后處理,具體如下
default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { return true; } default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable ModelAndView modelAndView) throws Exception { } default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, @Nullable Exception ex) throws Exception { }
preHandle:在handle執行前調用, 通常是做一些環境准備工作,如登錄檢查
postHandle:在handle執行后,渲染視圖前調用,在此處可以修改ModelAndView
afterCompletion:在渲染視圖后調用,或是preHandle執行出錯后調用,通常是做一些資源回收工作
調用順序如下:
正常情況下:preHandle(1) -> preHandle(2) -> postHandle(2) -> postHandle(1) -> afterCompletion(2) -> afterCompletion(1)
異常情況下(preHandle(2)處理異常)):preHandle(1) -> preHandle(2) -> afterCompletion(1)