上篇給你們介紹了Spring MVC父子容器的概念,主要提到的知識點是:html
Spring MVC容器是Spring容器的子容器,當在Spring MVC容器中找不到bean的時候就去父容器找.
複製代碼
在文章最後我也給你們也留了一個問題,既然子容器找不到就去父容器找,那乾脆把bean定義都放在父容器不就好了?是這樣嗎,咱們作個實驗。java
咱們把<context:component-scan base-package="xx.xx.xx"/> 這條語句從spring-mvc.xml文件中挪到spring.xml中,重啓應用。會發現報404,以下圖:node
404說明請求的資源沒有找到,爲何呢?react
使用Spring MVC的同窗通常都會如下方式定義請求地址:web
@Controller
@RequestMapping("/test")
public class Test {
@RequestMapping(value="/handle", method=RequestMethod.POST)
public void handle();
}
複製代碼
@Controller註解用來把一個類定義爲Controller。spring
@RequestMapping註解用來把web請求映射到相應的處理函數。express
@Controller和@RequestMapping結合起來完成了Spring MVC請求的派發流程。api
爲何兩個簡單的註解就能完成這麼複雜的功能呢?又和<context:component-scan base-package="xx.xx.xx"/>的位置有什麼關係呢?spring-mvc
讓咱們開始分析源碼。bash
@RequestMapping流程能夠分爲下面6步:
爲何是這6步,咱們展開分析。
第一步仍是先找程序入口。
使用Spring MVC的同窗都知道,要想使@RequestMapping註解生效,必須得在xml配置文件中配置< mvc:annotation-driven/>。所以咱們以此爲突破口開始分析。
在Spring系列(一):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 {
//若是該元素屬於自定義namespace走此邏輯 ,好比AOP,MVC等。
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);
return handler.parse(ele, new ParserContext(this.readerContext, this, containingBd));
}
複製代碼
進入NamespaceHandlerSupport類的parse()方法。
@Override
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);
return parser;
}
複製代碼
Spring MVC中含有多種命名空間,此方法會根據元素所屬命名空間獲得相應解析類,其中< mvc:annotation-driven/>對應的是AnnotationDrivenBeanDefinitionParser解析類。
(2)解析< mvc:annotation-driven/>元素
進入AnnotationDrivenBeanDefinitionParser類的parse()方法。
@Override
public BeanDefinition parse(Element element, ParserContext context) {
Object source = context.extractSource(element);
XmlReaderContext readerContext = context.getReaderContext();
//生成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類名
context.registerComponent(new BeanComponentDefinition(handlerMappingDef, HANDLER_MAPPING_BEAN_NAME));
}
複製代碼
能夠看到上面方法在Spring MVC容器中註冊了一個名爲「HANDLER_MAPPING_BEAN_NAME」,類型爲RequestMappingHandlerMapping的bean。
至於這個bean能幹嘛,繼續往下分析。
bean註冊完後的下一步就是實例化。
在開始分析實例化流程以前,咱們先介紹一下RequestMappingHandlerMapping是個什麼樣類。
上圖信息比較多,咱們查找關鍵信息。能夠看到這個類間接實現了HandlerMapping接口,是HandlerMapping類型的實例。
除此以外還實現了ApplicationContextAware和IntitalzingBean 這兩個接口。
在這裏簡要介紹一下這兩個接口:
下面是官方介紹:
public interface ApplicationContextAware extends Aware
Interface to be implemented by any object that wishes to be notified of the 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.
複製代碼
歸納一下上面表達的信息:若是一個類實現了ApplicationContextAware接口,Spring容器在初始化該類時候會自動回調該類的setApplicationContext()方法。這個接口主要用來讓實現類獲得Spring 容器上下文信息。
下面是官方介紹:
public interface InitializingBean
Interface to be implemented by beans that need to react once all their properties have been set by a BeanFactory: e.g. to perform custom initialization, or merely to check that all mandatory properties have been set.
複製代碼
該接口只包含如下方法:
void afterPropertiesSet() throws Exception
Invoked by the containing BeanFactory after it has set all bean properties and satisfied BeanFactoryAware, ApplicationContextAware etc.
複製代碼
歸納一下上面表達的信息:若是一個bean實現了該接口,Spring 容器初始化bean時會回調afterPropertiesSet()方法。這個接口的主要做用是讓bean在初始化時能夠實現一些自定義的操做。
介紹完RequestMappingHandlerMapping類後咱們開始對這個類的源碼進行分析。
既然RequestMappingHandlerMapping實現了ApplicationContextAware接口,那實例化時候確定會執行setApplicationContext方法,咱們查看其實現邏輯。
@Override
public final void setApplicationContext(ApplicationContext context) throws BeansException {
if (this.applicationContext == null) {
this.applicationContext = context;
}
}
複製代碼
能夠看到此方法把容器上下文賦值給applicationContext變量,由於如今是Spring MVC容器建立流程,所以此處設置的值就是Spring MVC容器 。
RequestMappingHandlerMapping也實現了InitializingBean接口,當設置完屬性後確定會回調afterPropertiesSet方法,再看afterPropertiesSet方法邏輯。
@Override
public void afterPropertiesSet()
super.afterPropertiesSet();
}
複製代碼
上面調用了父類的afterPropertiesSet()方法,沿調用棧繼續查看。
@Override
public void afterPropertiesSet() {
//初始化handler函數
initHandlerMethods();
}
複製代碼
進入initHandlerMethods初始化方法查看邏輯。
protected void initHandlerMethods() {
//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) {
//2.判斷bean是否含有@Controller或者@RequestMappin註解
if (beanType != null && isHandler(beanType)) {
//3.對含有註解的bean進行處理,獲取handler函數信息。
detectHandlerMethods(beanName);
}
}
複製代碼
上面函數分爲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);
}
});
//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) {
//將生成的RequestMappingInfo保存到methodMap中
methodMap.put(specificMethod, result);
}
}
}, ReflectionUtils.USER_DECLARED_METHODS);
}
//返回保存函數映射信息後的methodMap
return methodMap;
}
複製代碼
上面邏輯中doWith()回調了inspect(),inspect()又回調了getMappingForMethod()方法。
咱們看看getMappingForMethod()是如何生成函數信息的。
protected RequestMappingInfo getMappingForMethod(Method method, Class<?> handlerType) {
//建立函數信息
RequestMappingInfo info = createRequestMappingInfo(method);
return info;
}
複製代碼
查看createRequestMappingInfo()方法。
private RequestMappingInfo createRequestMappingInfo(AnnotatedElement element) {
//若是該函數含有@RequestMapping註解,則根據其註解信息生成RequestMapping實例,
//若是該函數沒有@RequestMapping註解則返回空
RequestMapping requestMapping = AnnotatedElementUtils.findMergedAnnotation(element, RequestMapping.class);
//若是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和處理函數映射關係。
protected void registerHandlerMethod(Object handler, Method method, T mapping) {
this.mappingRegistry.register(mapping, handler, method);
}
複製代碼
看到把全部的handler方法都註冊到了mappingRegistry這個變量中。
到此就把RequestMappingHandlerMapping bean的實例化流程就分析完了。
這裏咱們回到Spring MVC容器初始化流程,查看initWebApplicationContext方法。
protected WebApplicationContext initWebApplicationContext() {
//1.得到rootWebApplicationContext
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (wac == null) {
//2.建立 Spring 容器
wac = createWebApplicationContext(rootContext);
}
//3.初始化容器
onRefresh(wac);
return wac;
}
複製代碼
前兩步咱們在Spring 系列(二):Spring MVC的父子容器一文中分析過,主要是建立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) {
//容器中查找name爲"ANDLER_MAPPING_BEAN_NAME"的實例
HandlerMapping hm = context.getBean(HANDLER_MAPPING_BEAN_NAME, HandlerMapping.class);
//把找到的bean放到hanlderMappings中。
this.handlerMappings = Collections.singletonList(hm);
}
複製代碼
此處咱們看到從容器中獲取了name爲 「HANDLER_MAPPING_BEAN_NAME」的bean,這個bean你們應該還記得吧,就是前面註冊並實例化了的RequestMappingHandlerMapping bean。
DispatchServlet繼承自Servlet,那全部的請求都會在service()方法中進行處理。
查看service()方法。
@Override
protected void service(HttpServletRequest request, HttpServletResponse response) {
//獲取請求方法
HttpMethod httpMethod = HttpMethod.resolve(request.getMethod());
//如果patch請求執行此邏輯
if (httpMethod == HttpMethod.PATCH || httpMethod == null) {
processRequest(request, response);
}
//其它請求走此邏輯
else {
super.service(request, response);
}
}
複製代碼
咱們以get、post請求舉例分析。查看父類service方法實現。
protected void service(HttpServletRequest req, HttpServletResponse resp){
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
//處理get請求
doGet(req, resp);
} else if (method.equals(METHOD_POST)) {
//處理post請求
doPost(req, resp)
}
}
複製代碼
查看doGet()、doPost()方法實現。
@Override
protected final void doGet(HttpServletRequest request, HttpServletResponse response){
processRequest(request, response);
}
@Override
protected final void doPost(HttpServletRequest request, HttpServletResponse response {
processRequest(request, response);
}
複製代碼
能夠看到都調用了processRequest()方法,繼續跟蹤。
protected final void processRequest(HttpServletRequest request, HttpServletResponse response){
//處理請求
doService(request, response);
}
複製代碼
查看doService()方法。
@Override
protected void doService(HttpServletRequest request, HttpServletResponse response) {
//處理請求
doDispatch(request, response);
}
複製代碼
最終全部的web請求都由doDispatch()方法進行處理,查看其邏輯。
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) {
HttpServletRequest processedRequest = request;
// 根據請求得到真正處理的handler
mappedHandler = getHandler(processedRequest);
//用獲得的handler處理請求,此處省略
。。。。
}
複製代碼
查看getHandler()。
protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
//獲取HandlerMapping實例
for (HandlerMapping hm : this.handlerMappings) {
//獲得處理請求的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) {
Object handler = getHandlerInternal(request);
}
複製代碼
進入getHandlerInternal()方法。
@Override
protected HandlerMethod getHandlerInternal(HttpServletRequest request) {
//獲取函數url
String lookupPath = getUrlPathHelper().getLookupPathForRequest(request);
//查找HandlerMethod
handlerMethod = lookupHandlerMethod(lookupPath, request);
}
複製代碼
進入lookupHandlerMethod()。
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) {
this.mappingRegistry.getMappingsByUrl(lookupPath);
}
複製代碼
能夠看到上面方法中從mappingRegistry獲取handler,這個mappingRegistry的值還記得是從哪裏來的嗎?
就是前面RequestMappingHandlerMapping 實例化過程的最後一步調用registerHandlerMethod()函數時設置進去的。
獲取到相應的handler後剩下的事情就是進行業務邏輯。處理後返回結果,這裏基本也沒什麼好說的。
到此整個@RequestMapping的流程也分析完畢。
認真讀完上面深刻分析@RequestMapping註解流程的同窗,相信此時確定對Spring MVC有了更深一步的認識。
如今回到文章開頭的那個問題,爲何把<context:component-scan base-package="xx.xx.xx"/>挪到spring.xml文件中後就會404了呢?
我想看明白此文章的同窗確定已經知道答案了。
答案是:
當把<context:component-scan base-package="xx.xx.xx"/>寫到spring.xml中時,全部的bean其實都實例化在了Spring父容器中。
可是在@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文件中。寫法以下:
spring-mvc.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註解。
若是想得到更多,歡迎關注公衆號:七分熟pizza
公衆號裏我會分享更多技術以及職場方面的經驗,你們有什麼問題也能夠直接在公衆號向我提問交流。