0.文章起因
最近在搭建Spring MVC + Spring JPA + Hibernate Demo項(xiàng)目時(shí),在配置過(guò)程中不小心將web.xml文件中關(guān)于listener的配置注釋掉了,結(jié)果在項(xiàng)目啟動(dòng)時(shí)報(bào)了如下的錯(cuò)誤信息
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userController': Unsatisfied dependency expressed through field 'userService'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService': Unsatisfied dependency expressed through field 'userRepository'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'cn.xue.repository.UserRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
...
at java.lang.Thread.run(Thread.java:748)
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'userService': Unsatisfied dependency expressed through field 'userRepository'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'cn.xue.repository.UserRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at ...
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:584)
... 67 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'cn.xue.repository.UserRepository' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Autowired(required=true)}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1507)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1104)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1065)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:584)
... 80 more
1.Spring MVC Web項(xiàng)目在tomcat中執(zhí)行過(guò)程
在具體分析Spring MVC Web項(xiàng)目中Servlet和Listener之前,這里先簡(jiǎn)要整理一下Spring MVC Web項(xiàng)目在tomcat中初始化及當(dāng)接收一個(gè)請(qǐng)求后處理過(guò)程。
1.1 初始化過(guò)程
- Tomcat Web容器在啟動(dòng)時(shí),會(huì)為每個(gè)WEB應(yīng)用程序都創(chuàng)建一個(gè)唯一對(duì)應(yīng)的ServletContext對(duì)象,它代表當(dāng)前web應(yīng)用web容器提供其一個(gè)全局的上下文環(huán)境,它為Spring IoC容器提供宿主環(huán)境,用于存放所有的Servlet, Filter, Listener。Spring MVC 啟動(dòng)的時(shí)候主要涉及到DispatcherServlet 與 ContextLoaderListener
- Tomcat啟動(dòng)時(shí)加載web.xml文件,通過(guò)其中的各種配置來(lái)啟動(dòng)項(xiàng)目。web.xml有多項(xiàng)標(biāo)簽,在其加載的過(guò)程中順序依次為:context-param listener fileter servlet,并且需要特別說(shuō)明的是如果同類(lèi)標(biāo)簽多次出現(xiàn)加載順序以出現(xiàn)順序?yàn)闇?zhǔn)
1.2 請(qǐng)求處理過(guò)程
- tomcat接收到一個(gè)具體請(qǐng)求,web容器根據(jù)url-path映射到與之相匹配的DispatcherServlet進(jìn)行處理
- DispatcherServlet將請(qǐng)求交給HandlerMapping,讓它找出對(duì)應(yīng)請(qǐng)求的HandlerExecutionChain對(duì)象,HandlerExecutionChain返回?cái)r截器和處理器。HandlerExecutionChain是一個(gè)執(zhí)行鏈,它包含一個(gè)處理該請(qǐng)求的Handler(處理器,也就是我們常習(xí)慣寫(xiě)作xxxController),同時(shí)還可能包括若干個(gè)對(duì)該請(qǐng)求實(shí)施攔截的Handler Interceptor(攔截器)
2.DispatcherServlet 與 ContextLoaderListener
一個(gè)常見(jiàn)的web.xml中關(guān)于DispatcherServlet 與 ContextLoaderListener的配置如下:
...
<listener>
<!--注冊(cè)Spring的ServletContext監(jiān)聽(tīng)器,監(jiān)聽(tīng)到服務(wù)器啟動(dòng)時(shí),自動(dòng)執(zhí)行ContextLoaderListener的方法初始化Spring-->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<!--加載Spring的配置文件,隨著監(jiān)聽(tīng)器觸發(fā),Spring調(diào)用這里,找到Spring的核心配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/*.xml</param-value>
</context-param>
<!--SpringMVC的相關(guān)設(shè)置-->
<servlet>
<!--SpringMVC是基于Servlet使用中央處理器處理頁(yè)面請(qǐng)求,配置中央處理器的全路徑-->
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--當(dāng)頁(yè)面有請(qǐng)求時(shí),DispatcherServlet對(duì)象調(diào)用這里,獲取到SpringMVC的核心配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC/*.xml</param-value>
</init-param>
<!--優(yōu)先級(jí),數(shù)字越小級(jí)別越高-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<!--指定請(qǐng)求的映射,鏈接為指定形式時(shí),使用Servlet處理,其他鏈接不執(zhí)行Servlet-->
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
...
-
ContextLoaderListener 作為一個(gè)Listener會(huì)首先啟動(dòng),創(chuàng)建一個(gè)WebApplicationContext用于加載除Controller等Web組件以外的所有bean,這個(gè)ApplicationContext作為根容器存在,對(duì)于整個(gè)Web應(yīng)用來(lái)說(shuō),只能存在一個(gè),也就是父容器,會(huì)被所有子容器共享,子容器可以訪問(wèn)父容器里的bean. 簡(jiǎn)單說(shuō)它的作用就是啟動(dòng)Web容器時(shí),自動(dòng)裝配ApplicationContext的配置信息。因?yàn)樗鼘?shí)現(xiàn)了ServletContextListener這個(gè)接口,在web.xml配置這個(gè)監(jiān)聽(tīng)器,啟動(dòng)容器時(shí),就會(huì)默認(rèn)執(zhí)行它實(shí)現(xiàn)的方法。另外在ContextLoaderListener中關(guān)聯(lián)了ContextLoader這個(gè)類(lèi),所以整個(gè)加載配置過(guò)程由ContextLoader來(lái)完成
其初始化過(guò)程對(duì)于xml配置文件方式下和注解方式下略有差異:- i. xml配置下會(huì)直接創(chuàng)建ContextLoaderListener,然后在contextInitialized方法中初始化WebApplicationContext。
- ii. 如果使用的是注解配置的方式,則通過(guò)AnnotationConfigWebApplicationContext獲取一個(gè)WebApplicationContext之后傳給ContextLoadListener,之后再contextInitialized方法中調(diào)用父類(lèi)ContextLoader的initWebApplicationContext進(jìn)行初始化。
DispatcherServlet
-
DispatcherServlet作為ServletContext之中很可能唯一的一個(gè)Servlet被初始化,作為整個(gè)Web應(yīng)用的前端控制器進(jìn)行請(qǐng)求轉(zhuǎn)發(fā)。
初始化過(guò)程如下:- i.XML配置下,生成默認(rèn)的DispatcherServlet,初始化時(shí)通過(guò)init-param中的contextConfigLocation指定的配置文件創(chuàng)建WebApplicationContext。
- ii.注解配置方式下,通過(guò)AnnotationConfigWebApplicationContext讀取JavaConfig后生成WebApplicationContext,傳遞給DispatcherServlet進(jìn)行構(gòu)造。DispatcherServlet構(gòu)造完成之后,調(diào)用init方法進(jìn)行初始化,將DispatcherServlet關(guān)聯(lián)的WebApplicationContext的父容器設(shè)為之前ContextLoaderListener創(chuàng)建的WebApplicationContext。DispatcherServlet關(guān)聯(lián)的WebApplicationContext會(huì)在請(qǐng)求到來(lái)的時(shí)候被設(shè)為request的attribute暴露給handlers和之后的web組件。
3.總結(jié)
對(duì)于一個(gè)使用SpringMVC構(gòu)建的Web應(yīng)用來(lái)說(shuō),ContextLoaderListener雖然只能加載一個(gè)根容器,但通俗些來(lái)說(shuō),如果沒(méi)有必要的bean需要通過(guò)ContextLoader加載,它其實(shí)是可選的,而DispatcherServlet作為請(qǐng)求轉(zhuǎn)發(fā)處理返回結(jié)果的核心是比不可少的,而且可以不唯一