0.文章起因
最近在搭建Spring MVC + Spring JPA + Hibernate Demo项目时,在配置过程中不小心将web.xml文件中关于listener的配置注释掉了,结果在项目启动时报了如下的错误信息
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项目在tomcat中执行过程
在具体分析Spring MVC Web项目中Servlet和Listener之前,这里先简要整理一下Spring MVC Web项目在tomcat中初始化及当接收一个请求后处理过程。
1.1 初始化过程
- Tomcat Web容器在启动时,会为每个WEB应用程序都创建一个唯一对应的ServletContext对象,它代表当前web应用web容器提供其一个全局的上下文环境,它为Spring IoC容器提供宿主环境,用于存放所有的Servlet, Filter, Listener。Spring MVC 启动的时候主要涉及到DispatcherServlet 与 ContextLoaderListener
- Tomcat启动时加载web.xml文件,通过其中的各种配置来启动项目。web.xml有多项标签,在其加载的过程中顺序依次为:context-param listener fileter servlet,并且需要特别说明的是如果同类标签多次出现加载顺序以出现顺序为准
1.2 请求处理过程
- tomcat接收到一个具体请求,web容器根据url-path映射到与之相匹配的DispatcherServlet进行处理
- DispatcherServlet将请求交给HandlerMapping,让它找出对应请求的HandlerExecutionChain对象,HandlerExecutionChain返回拦截器和处理器。HandlerExecutionChain是一个执行链,它包含一个处理该请求的Handler(处理器,也就是我们常习惯写作xxxController),同时还可能包括若干个对该请求实施拦截的Handler Interceptor(拦截器)
2.DispatcherServlet 与 ContextLoaderListener
一个常见的web.xml中关于DispatcherServlet 与 ContextLoaderListener的配置如下:
...
<listener>
<!--注册Spring的ServletContext监听器,监听到服务器启动时,自动执行ContextLoaderListener的方法初始化Spring-->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<context-param>
<!--加载Spring的配置文件,随着监听器触发,Spring调用这里,找到Spring的核心配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring/*.xml</param-value>
</context-param>
<!--SpringMVC的相关设置-->
<servlet>
<!--SpringMVC是基于Servlet使用中央处理器处理页面请求,配置中央处理器的全路径-->
<servlet-name>SpringMVC</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<!--当页面有请求时,DispatcherServlet对象调用这里,获取到SpringMVC的核心配置文件-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVC/*.xml</param-value>
</init-param>
<!--优先级,数字越小级别越高-->
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<!--指定请求的映射,链接为指定形式时,使用Servlet处理,其他链接不执行Servlet-->
<servlet-name>SpringMVC</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>
...
-
ContextLoaderListener 作为一个Listener会首先启动,创建一个WebApplicationContext用于加载除Controller等Web组件以外的所有bean,这个ApplicationContext作为根容器存在,对于整个Web应用来说,只能存在一个,也就是父容器,会被所有子容器共享,子容器可以访问父容器里的bean. 简单说它的作用就是启动Web容器时,自动装配ApplicationContext的配置信息。因为它实现了ServletContextListener这个接口,在web.xml配置这个监听器,启动容器时,就会默认执行它实现的方法。另外在ContextLoaderListener中关联了ContextLoader这个类,所以整个加载配置过程由ContextLoader来完成
其初始化过程对于xml配置文件方式下和注解方式下略有差异:- i. xml配置下会直接创建ContextLoaderListener,然后在contextInitialized方法中初始化WebApplicationContext。
- ii. 如果使用的是注解配置的方式,则通过AnnotationConfigWebApplicationContext获取一个WebApplicationContext之后传给ContextLoadListener,之后再contextInitialized方法中调用父类ContextLoader的initWebApplicationContext进行初始化。
DispatcherServlet
-
DispatcherServlet作为ServletContext之中很可能唯一的一个Servlet被初始化,作为整个Web应用的前端控制器进行请求转发。
初始化过程如下:- i.XML配置下,生成默认的DispatcherServlet,初始化时通过init-param中的contextConfigLocation指定的配置文件创建WebApplicationContext。
- ii.注解配置方式下,通过AnnotationConfigWebApplicationContext读取JavaConfig后生成WebApplicationContext,传递给DispatcherServlet进行构造。DispatcherServlet构造完成之后,调用init方法进行初始化,将DispatcherServlet关联的WebApplicationContext的父容器设为之前ContextLoaderListener创建的WebApplicationContext。DispatcherServlet关联的WebApplicationContext会在请求到来的时候被设为request的attribute暴露给handlers和之后的web组件。
3.总结
对于一个使用SpringMVC构建的Web应用来说,ContextLoaderListener虽然只能加载一个根容器,但通俗些来说,如果没有必要的bean需要通过ContextLoader加载,它其实是可选的,而DispatcherServlet作为请求转发处理返回结果的核心是比不可少的,而且可以不唯一