来看图:
都说DispatcherServlet 是MVC的核心, 从component(一系列adapters, handlers...), context,request生命周期 都是维护在dispatcherServlet中的角度看,是正确的
但是我今天碰到的问题,恰恰与这关系不大
1. 我这边碰到一个问题, project都多个module, 然后在web module中配置了 XXX-servlet.xml中 声明了一组mvc:interceptor, 然后发现这些interceptor 只对web module下的controller 有效, 对于 其他module的controller不能intercept
2. 然后我在web下配置了一个config, 用java的方式把 interceptors注入spring 容器, 去掉XXX-servlet.xml中的mvc:interceptors
图2
结果: 其他module的controller可以intercept,但是web下的不行了。。。。
3. 把mvc:interceptors 配置,移动到application-context.xml中, 然后就可以了, 一切ok了,所有的api都能用了
但是why?什么原因导致的问题。。。。我还没发现
1. 看看web 在启动的init逻辑也许有帮助:
找到一篇介绍的文章,讲的比较细,上面的图也是从里面来的,文章比较老,现在spring已经有了变化, 但是其中核心的概念不变
DispatcherServlet 说到底是个Servlet,主要任务:
1. 初始化init
2. http请求处理
现在重点来看init, 从而理解我碰到的问题:
init过程的简单流程,如图1
1. 由HttpServletBean.init()
主要是wrap servlet --- 这里主要目的是把servlet 包装成一个标准bean (隐藏不同的细节,同一属性的设置和访问等),具体的实现逻辑可以在研究
然后最重要的就是 initServletBean() 这个方法,会调用FrameworkServlet
2. FrameworkServlet.initServletBean()
包含真正的spring 容器: webApplicationContext, 然后初始化它
关键在于初始化的WebApplicationContext 有 2 个
2.1. 都知道在web.xml 中一般这么配置:
这里的ContextLoaderListener 将会调用 ContextLoader. initWebApplicationContext 来初始化 ROOT spring 容器
这里提一句,spring的容器context的结构设计 PARENT-CHILD, CHILD 共享PARENT的bean, CHILD的bean对PARENT不可见
ContextLoader. initWebApplicationContext:
然后调用 createWebApplicationContext, 这里都知道初始化的class 是 XmlWebApplicationContext
然后就是XmlWebApplicationContext 的 loadBeanDefinitions
这里主要就是用reader来load 定义的bean, 在具体: reader.loadBeanDefinitions
在具体就在doLoadBeanDefinitions
在register: registerBeanDefinitions
doRegisterBeanDefinitions:
到这里ROOT的WebApplicationContext 就初始化完了
2.2 web中 servelt mapping:
这里对应dispatcherServlet 会默认找classpath下的nafweb-servlet.xml, 然后会初始化dispatcherServlet (这才是真正从dispatcherServlet发起的init 初始化的对象), 同样是WebApplicationContext, parent就是上面的ROOT
这里才回到原来的地方:
FrameworkServlet.initServletBean
然后基本都会走到:createWebApplicationContext
这里的contextClasss 也是XmlWebApplicationContext, 然后是正常的xml读取,解析,生成bean
创建完之后会config: configureAndRefreshWebApplicationContext
之后在initWebApplicationContext 就会 onRefresh(wac); //这里就是DispatcherServlet的初始化 kick-in
从这里就是一些组件的初始化。。。。后面的就不讲了
总之,处理两个WebApplicationContext之间的关系,最好把大部分bean都放在ROOT中,避免出现我这边出现的问题,是我目前发现的最好的解决办法,至于spring 为什么要搞两个ROOT/CHILD呢?
事实上,spring的设计思想是,隔离所有web相关的bean,最好都在DispatcherServlet中,然后ROOT中放一些共用的,基础的bean,但是在实现的时候,因为模块的问题,一些controller被放到分散的模块中,然后在引入xml文档的时候都是在application-context-web.xml中import resource 导致这部分controller 被 注入到ROOT Context中, 那么正确的方式是咋样?
1. 应该把controller的扫描xml 分开, 单独扫描, 比如<context:component-scan base-package="com.XXX.controller" /> 不要直接到com.XXX
然后把这个定义 放到单独的xml中
2. 把这个xml 在XXX-servlet.xml中import
就能解决这次碰到的问题了
PS: 所有spring代码基于最新的spring master (spring5)