轉載http://www.javashuo.com/article/p-kbctaxqk-gz.htmlhtml
源碼版本spring-webmvc-4.3.7.RELEASEjava
使用Spring MVC的同窗通常都會如下方式定義請求地址:node
@Controller @RequestMapping("/test") public class TestController { @RequestMapping(value = {"/show"}) public String testTest() { return "/jsp/index"; } }
@Controller註解用來把一個類定義爲Controller。react
@RequestMapping註解用來把web請求映射到相應的處理函數。web
@Controller和@RequestMapping結合起來完成了Spring MVC請求的派發流程。spring
爲何兩個簡單的註解就能完成這麼複雜的功能呢?又和<context:component-scan base-package="xx.xx.xx"/>的位置有什麼關係呢?express
@RequestMapping流程能夠分爲下面6步:api
爲何是這6步,咱們展開分析。spring-mvc
第一步仍是先找程序入口。mvc
使用Spring MVC的同窗都知道,要想使@RequestMapping註解生效,必須得在xml配置文件中配置< mvc:annotation-driven/>。所以咱們以此爲突破口開始分析。
在bean 解析、註冊、實例化流程源碼剖析 文中咱們知道xml配置文件解析完的下一步就是解析bean。在這裏咱們繼續對那個方法展開分析。以下:
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) { //若是該元素屬於默認命名空間走此邏輯。Spring的默認namespace爲:http://www.springframework.org/schema/beans「 if (delegate.isDefaultNamespace(root)) { NodeList nl = root.getChildNodes(); for (int i = 0; i < nl.getLength(); i++) { Node node = nl.item(i); if (node instanceof Element) { Element ele = (Element) node; //對document中的每一個元素都判斷其所屬命名空間,而後走相應的解析邏輯 if (delegate.isDefaultNamespace(ele)) { parseDefaultElement(ele, delegate); } else { delegate.parseCustomElement(ele); } } } } else { //若是該元素屬於自定義namespace走此邏輯 ,好比AOP,MVC等。 delegate.parseCustomElement(root); } }
方法中根據元素的命名空間來進行不一樣的邏輯處理,如bean、beans等屬於默認命名空間執行parseDefaultElement()方法,其它命名空間執行parseCustomElement()方法。
< mvc:annotation-driven/>元素屬於mvc命名空間,所以進入到 parseCustomElement()方法。
public BeanDefinition parseCustomElement(Element ele) { return parseCustomElement(ele, null); }
進入parseCustomElement(ele, null)方法。
public BeanDefinition parseCustomElement(Element ele, BeanDefinition containingBd) { //獲取該元素namespace url String namespaceUri = getNamespaceURI(ele); //獲得NamespaceHandlerSupport實現類解析元素 NamespaceHandler handler = this.readerContext.getNamespaceHandlerResolver().resolve(namespaceUri); if (handler == null) { error("Unable to locate Spring NamespaceHandler for XML schema namespace [" + namespaceUri + "]", ele); return null; } return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd)); }
進入NamespaceHandlerSupport類的parse()方法。
public BeanDefinition parse(Element element, ParserContext parserContext) { //此處獲得AnnotationDrivenBeanDefinitionParser類來解析該元素 return findParserForElement(element, parserContext).parse(element, parserContext); }
上面方法分爲兩步,(1)獲取元素的解析類。(2)解析元素。
(1)獲取解析類。
private BeanDefinitionParser findParserForElement(Element element, ParserContext parserContext) { String localName = parserContext.getDelegate().getLocalName(element); BeanDefinitionParser parser = this.parsers.get(localName); if (parser == null) { parserContext.getReaderContext().fatal( "Cannot locate BeanDefinitionParser for element [" + localName + "]", element); } return parser; }
Spring MVC中含有多種命名空間,此方法會根據元素所屬命名空間獲得相應解析類,參考spring xml 配置文件中標籤的解析,其中< mvc:annotation-driven/>對應的是AnnotationDrivenBeanDefinitionParser解析類。
(2)解析< mvc:annotation-driven/>元素
進入AnnotationDrivenBeanDefinitionParser類的parse()方法。
public BeanDefinition parse(Element element, ParserContext parserContext) { Object source = parserContext.extractSource(element); XmlReaderContext readerContext = parserContext.getReaderContext(); CompositeComponentDefinition compDefinition = new CompositeComponentDefinition(element.getTagName(), source); parserContext.pushContainingComponent(compDefinition); RuntimeBeanReference contentNegotiationManager = getContentNegotiationManager(element, source, parserContext); //生成RequestMappingHandlerMapping bean信息 RootBeanDefinition handlerMappingDef = new RootBeanDefinition(RequestMappingHandlerMapping.class); handlerMappingDef.setSource(source); handlerMappingDef.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); handlerMappingDef.getPropertyValues().add("order", 0); handlerMappingDef.getPropertyValues().add("contentNegotiationManager", contentNegotiationManager); ...... //此處HANDLER_MAPPING_BEAN_NAME值爲:RequestMappingHandlerMapping類名 //容器中註冊name爲RequestMappingHandlerMapping類名 parserContext.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME)); ...... }
能夠看到上面方法在Spring MVC容器中註冊了一個名爲「HANDLER_MAPPING_BEAN_NAME」,類型爲RequestMappingHandlerMapping的bean(看此函數的其它代碼,獲得同時也註冊了RequestMappingHandlerAdapter等)。
至於這個bean能幹嘛,繼續往下分析。
bean註冊完後的下一步就是實例化。
在開始分析實例化流程以前,咱們先介紹一下RequestMappingHandlerMapping是個什麼樣類。
上圖信息比較多,咱們查找關鍵信息。能夠看到這個類間接實現了HandlerMapping接口,是HandlerMapping類型的實例。
除此以外還實現了ApplicationContextAware和IntitalzingBean 這兩個接口。
下面是官方介紹:
在這裏簡要介紹一下這兩個接口:
public interface ApplicationContextAware
extends Aware
ApplicationContext
that it runs in.
void setApplicationContext(ApplicationContext applicationContext) throws BeansException;
* Set the ApplicationContext that this object runs in.
* Normally this call will be used to initialize the object.
public interface InitializingBean
BeanFactory
: e.g. to perform custom initialization, or merely to check that all mandatory properties have been set.
void afterPropertiesSet() throws Exception;
* Invoked by a BeanFactory after it has set all bean properties supplied
* (and satisfied BeanFactoryAware and ApplicationContextAware).
歸納一下上面表達的信息:若是一個bean實現了該接口,Spring 容器初始化bean時會回調afterPropertiesSet()方法。這個接口的主要做用是讓bean在初始化時能夠實現一些自定義的操做。
介紹完RequestMappingHandlerMapping類後咱們開始對這個類的源碼進行分析。
既然RequestMappingHandlerMapping實現了ApplicationContextAware接口,那實例化時候確定會執行setApplicationContext方法,咱們查看其實現邏輯。
public final void setApplicationContext(ApplicationContext context) throws BeansException { if (context == null && !isContextRequired()) { // Reset internal context state. this.applicationContext = null; this.messageSourceAccessor = null; } else if (this.applicationContext == null) { // Initialize with passed-in context. if (!requiredContextClass().isInstance(context)) { throw new ApplicationContextException( "Invalid application context: needs to be of type [" + requiredContextClass().getName() + "]"); } this.applicationContext = context; this.messageSourceAccessor = new MessageSourceAccessor(context); initApplicationContext(context); } else { // Ignore reinitialization if same context passed in. if (this.applicationContext != context) { throw new ApplicationContextException( "Cannot reinitialize with different application context: current one is [" + this.applicationContext + "], passed-in one is [" + context + "]"); } } }
能夠看到此方法把容器上下文賦值給applicationContext變量,由於如今是Spring MVC容器建立流程,所以此處設置的值就是Spring MVC容器 。
RequestMappingHandlerMapping也實現了InitializingBean接口,當設置完屬性後確定會回調afterPropertiesSet方法,再看afterPropertiesSet方法邏輯。
public void afterPropertiesSet() { this.config = new RequestMappingInfo.BuilderConfiguration(); this.config.setUrlPathHelper(getUrlPathHelper()); this.config.setPathMatcher(getPathMatcher()); this.config.setSuffixPatternMatch(this.useSuffixPatternMatch); this.config.setTrailingSlashMatch(this.useTrailingSlashMatch); this.config.setRegisteredSuffixPatternMatch(this.useRegisteredSuffixPatternMatch); this.config.setContentNegotiationManager(getContentNegotiationManager()); super.afterPropertiesSet(); }
上面調用了父類的afterPropertiesSet()方法,沿調用棧繼續查看。
public void afterPropertiesSet() { //初始化handler函數 initHandlerMethods(); }
進入initHandlerMethods初始化方法查看邏輯。
protected void initHandlerMethods() { if (logger.isDebugEnabled()) { logger.debug("Looking for request mappings in application context: " + getApplicationContext()); } //1.獲取容器中全部bean 的name。 //根據detectHandlerMethodsInAncestorContexts bool變量的值判斷是否獲取父容器中的bean,默認爲false。所以這裏只獲取Spring MVC容器中的bean,不去查找父容器 String[] beanNames = (this.detectHandlerMethodsInAncestorContexts ? BeanFactoryUtils.beanNamesForTypeIncludingAncestors(getApplicationContext(), Object.class) : getApplicationContext().getBeanNamesForType(Object.class)); //循環遍歷bean for (String beanName : beanNames) { if (!beanName.startsWith(SCOPED_TARGET_NAME_PREFIX)) { Class<?> beanType = null; try { beanType = getApplicationContext().getType(beanName); } catch (Throwable ex) { // An unresolvable bean type, probably from a lazy bean - let's ignore it. if (logger.isDebugEnabled()) { logger.debug("Could not resolve target class for bean with name '" + beanName + "'", ex); } } //2.判斷bean是否含有@Controller或者@RequestMappin註解 if (beanType != null && isHandler(beanType)) { //3.對含有註解的bean進行處理,獲取handler函數信息。 detectHandlerMethods(beanName); } } } handlerMethodsInitialized(getHandlerMethods()); }
上面函數分爲3步。
(1)獲取Spring MVC容器中的bean。
(2)找出含有含有@Controller或者@RequestMappin註解的bean。
(3)對含有註解的bean進行解析。
第1步很簡單就是獲取容器中全部的bean name,咱們對第二、3步做分析。
查看isHandler()方法實現邏輯。
@Override protected boolean isHandler(Class<?> beanType) { return (AnnotatedElementUtils.hasAnnotation(beanType, Controller.class) || AnnotatedElementUtils.hasAnnotation(beanType, RequestMapping.class)); }
上面邏輯很簡單,就是判斷該bean是否有@Controller或@RequestMapping註解,而後返回判斷結果。
若是含有這兩個註解之一就進入detectHandlerMethods()方法進行處理。
查看detectHandlerMethods()方法。
protected void detectHandlerMethods(final Object handler) { //1.獲取bean的類信息 Class<?> handlerType = (handler instanceof String ? getApplicationContext().getType((String) handler) : handler.getClass()); final Class<?> userType = ClassUtils.getUserClass(handlerType); //2.遍歷函數獲取有@RequestMapping註解的函數信息 Map<Method, T> methods = MethodIntrospector.selectMethods(userType, new MethodIntrospector.MetadataLookup<T>() { @Override public T inspect(Method method) { try { //若是有@RequestMapping註解,則獲取函數映射信息 return getMappingForMethod(method, userType); } catch (Throwable ex) { throw new IllegalStateException("Invalid mapping on handler class [" + userType.getName() + "]: " + method, ex); } } }); if (logger.isDebugEnabled()) { logger.debug(methods.size() + " request handler methods found on " + userType + ": " + methods); } //3.遍歷映射函數列表,註冊handler for (Map.Entry<Method, T> entry : methods.entrySet()) { Method invocableMethod = AopUtils.selectInvocableMethod(entry.getKey(), userType); T mapping = entry.getValue(); //註冊handler函數 registerHandlerMethod(handler, invocableMethod, mapping); } }
上面方法中用了幾個回調,可能看起來比較複雜,其主要功能就是獲取該bean和父接口中全部用@RequestMapping註解的函數信息,並把這些保存到methodMap變量中。
咱們對上面方法進行逐步分析,看看如何對有@RequestMapping註解的函數進行解析。
先進入selectMethods()方法查看實現邏輯。
public static <T> Map<Method, T> selectMethods(Class<?> targetType, final MetadataLookup<T> metadataLookup) { final Map<Method, T> methodMap = new LinkedHashMap<Method, T>(); Set<Class<?>> handlerTypes = new LinkedHashSet<Class<?>>(); Class<?> specificHandlerType = null; //把自身類添加到handlerTypes中 if (!Proxy.isProxyClass(targetType)) { handlerTypes.add(targetType); specificHandlerType = targetType; } //獲取該bean全部的接口,並添加到handlerTypes中 handlerTypes.addAll(Arrays.asList(targetType.getInterfaces())); /對本身及全部實現接口類進行遍歷 for (Class<?> currentHandlerType : handlerTypes) { final Class<?> targetClass = (specificHandlerType != null ? specificHandlerType : currentHandlerType); //獲取函數映射信息 ReflectionUtils.doWithMethods(currentHandlerType, new ReflectionUtils.MethodCallback() { //循環獲取類中的每一個函數,經過回調處理 @Override public void doWith(Method method) { //對類中的每一個函數進行處理 Method specificMethod = ClassUtils.getMostSpecificMethod(method, targetClass); //回調inspect()方法給個函數生成RequestMappingInfo T result = metadataLookup.inspect(specificMethod); if (result != null) { Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(specificMethod); if (bridgedMethod == specificMethod || metadataLookup.inspect(bridgedMethod) == null) { //將生成的RequestMappingInfo保存到methodMap中 methodMap.put(specificMethod, result); } } } }, ReflectionUtils.USER_DECLARED_METHODS); } //返回保存函數映射信息後的methodMap return methodMap; }
上面邏輯中doWith()回調了inspect(),inspect()又回調了getMappingForMethod()方法。
咱們看看getMappingForMethod()是如何生成函數信息的。
@Override protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) { //建立函數信息 RequestMappingInfo info = createRequestMappingInfo(method); if (info != null) { RequestMappingInfo typeInfo = createRequestMappingInfo(handlerType); if (typeInfo != null) { info = typeInfo.combine(info); } } return info; }
查看createRequestMappingInfo()方法。
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) { //若是該函數含有@RequestMapping註解,則根據其註解信息生成RequestMapping實例, //若是該函數沒有@RequestMapping註解則返回空 RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class); RequestCondition<?> condition = (element instanceof Class ? getCustomTypeCondition((Class<?>) element) : getCustomMethodCondition((Method) element)); //若是requestMapping不爲空,則生成函數信息MAP後返回 return (requestMapping != null ? createRequestMappingInfo(requestMapping, condition) : null); }
看看createRequestMappingInfo是如何實現的。
protected RequestMappingInfo createRequestMappingInfo( RequestMapping requestMapping, RequestCondition<?> customCondition) { return RequestMappingInfo .paths(resolveEmbeddedValuesInPatterns(requestMapping.path())) .methods(requestMapping.method()) .params(requestMapping.params()) .headers(requestMapping.headers()) .consumes(requestMapping.consumes()) .produces(requestMapping.produces()) .mappingName(requestMapping.name()) .customCondition(customCondition) .options(this.config) .build(); 能夠看到上面把RequestMapping註解中的信息都放到一個RequestMappingInfo實例中後返回。當生成含有@RequestMapping註解的函數映射信息後,最後一步是調用registerHandlerMethod 註冊handler和處理函數映射關係。 protectedvoid registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method); }
看到把全部的handler方法都註冊到了mappingRegistry這個變量中。
到此就把RequestMappingHandlerMapping bean的實例化流程就分析完了。
protected WebApplicationContext initWebApplicationContext() { //1.得到rootWebApplicationContext WebApplicationContext rootContext = WebApplicationContextUtils.getWebApplicationContext(getServletContext()); WebApplicationContext wac = null; ...... if (wac == null) { // No context instance is defined for this servlet -> create a local one //2.建立 Spring 容器 wac = createWebApplicationContext(rootContext); } if (!this.refreshEventReceived) { // Either the context is not a ConfigurableApplicationContext with refresh // support or the context injected at construction time had already been // refreshed -> trigger initial onRefresh manually here. //3.初始化容器 onRefresh(wac); } ...... return wac; }
前兩步咱們在spring與springmvc父子容器一文中分析過,主要是建立Spring MVC容器,這裏咱們重點看第3步。
進入onRefresh()方法。
@Override protected void onRefresh(ApplicationContext context) { //執行初始化策略 initStrategies(context); }
進入initStrategies方法,該方法進行了不少初始化行爲,爲減小干擾咱們只過濾出與本文相關內容。
protected void initStrategies(ApplicationContext context) { ...... //初始化HandlerMapping initHandlerMappings(context); ...... }
進入initHandlerMappings()方法。
private void initHandlerMappings(ApplicationContext context) { this.handlerMappings = null; if (this.detectAllHandlerMappings) { // Find all HandlerMappings in the ApplicationContext, including ancestor contexts. //容器中查找HandlerMapping的實例 Map<String, HandlerMapping> matchingBeans = BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false); if (!matchingBeans.isEmpty()) { //把找到的bean放到hanlderMappings中。 this.handlerMappings = new ArrayList<HandlerMapping>(matchingBeans.values()); // We keep HandlerMappings in sorted order. AnnotationAwareOrderComparator.sort(this.handlerMappings); } } else { try { HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class); this.handlerMappings = Collections.singletonList(hm); } catch (NoSuchBeanDefinitionException ex) { // Ignore, we'll add a default HandlerMapping later. } } // Ensure we have at least one HandlerMapping, by registering // a default HandlerMapping if no other mappings are found. if (this.handlerMappings == null) { this.handlerMappings = getDefaultStrategies(context, HandlerMapping.class); if (logger.isDebugEnabled()) { logger.debug("No HandlerMappings found in servlet '" + getServletName() + "': using default"); } } }
默認有多個實例,其中就有前面註冊並實例化了的RequestMappingHandlerMapping bean
DispatchServlet繼承自Servlet,那全部的請求都會在service()方法中進行處理。
查看service()方法。
@Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { //獲取請求方法 HttpMethod httpMethod = HttpMethod.resolve(request.getMethod()); //如果patch請求執行此邏輯 if (HttpMethod.PATCH == httpMethod || httpMethod == null) { processRequest(request, response); } else { //其它請求走此邏輯 super.service(request, response); } }
咱們跟着源碼,獲得無論get、post最後都會執行到DispatcherServlet#doDispatch(request, response);
最終全部的web請求都由doDispatch()方法進行處理,查看其邏輯。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception { HttpServletRequest processedRequest = request; HandlerExecutionChain mappedHandler = null; ...... //根據請求得到真正處理的handler mappedHandler = getHandler(processedRequest); ...... }
查看getHandler()。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { //獲取HandlerMapping實例 for (HandlerMapping hm : this.handlerMappings) { if (logger.isTraceEnabled()) { logger.trace( "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'"); } //獲得處理請求的handler HandlerExecutionChain handler = hm.getHandler(request); if (handler != null) { return handler; } } return null; }
這裏遍歷handlerMappings得到全部HandlerMapping實例,還記得handlerMappings變量吧,這就是前面initHandlerMappings()方法中設置進去的值。
能夠看到接下來調了用HandlerMapping實例的getHanlder()方法查找handler,看其實現邏輯。
@Override public final HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception { Object handler = getHandlerInternal(request); ...... HandlerExecutionChain executionChain = getHandlerExecutionChain(handler, request); ...... return executionChain; }
進入getHandlerInternal()方法。
@Override protected HandlerMethod getHandlerInternal(HttpServletRequest request) throws Exception { //獲取函數url String lookupPath = getUrlPathHelper().getLookupPathForRequest(request); ...... try { //查找HandlerMethod HandlerMethod handlerMethod = lookupHandlerMethod(lookupPath, request); ...... } finally { this.mappingRegistry.releaseReadLock(); } }
進入lookupHandlerMethod()。
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception { List<Match> matches = new ArrayList<Match>(); List<T> directPathMatches = this.mappingRegistry.getMappingsByUrl(lookupPath); ...... }
能夠看到上面方法中從mappingRegistry獲取handler,這個mappingRegistry的值還記得是從哪裏來的嗎?
就是前面RequestMappingHandlerMapping 實例化過程的最後一步調用registerHandlerMethod()函數時設置進去的。
獲取到相應的handler後剩下的事情就是進行業務邏輯。處理後返回結果,這裏基本也沒什麼好說的。
到此整個@RequestMapping的流程也分析完畢。
認真讀完上面深刻分析@RequestMapping註解流程的同窗,相信此時確定對Spring MVC有了更深一步的認識。
在@ReqestMapping解析過程當中,initHandlerMethods()函數只是對Spring MVC 容器中的bean進行處理的,並無去查找父容器的bean。所以不會對父容器中含有@RequestMapping註解的函數進行處理,更不會生成相應的handler。
因此當請求過來時找不處處理的handler,致使404。
從上面的分析中,咱們知道要使用@RequestMapping註解,必須得把含有@RequestMapping的bean定義到spring-mvc.xml中。
這裏也給你們個建議:
由於@RequestMapping通常會和@Controller搭配使。爲了防止重複註冊bean,建議在spring-mvc.xml配置文件中只掃描含有Controller bean的包,其它的共用bean的註冊定義到spring.xml文件中。寫法以下:
<!-- 只掃描@Controller註解 --> <context:component-scan base-package="com.xxx.controller" use-default-filters="false"> <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan>
spring.xml
<!-- 配置掃描註解,不掃描@Controller註解 --> <context:component-scan base-package="com.xxx"> <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" /> </context:component-scan>。
use-default-filters屬性默認爲true,會掃描全部註解類型的bean 。若是配置成false,就只掃描白名單中定義的bean註解。