在上一篇中,说到传统web项目与Spring集成:从web.xml中的ContextLoaderListener
开始,从源码层面分析,追踪底层逻辑,发现web项目通过xml集成spring
时,通过配置文件ContextLoader.properties
中配置的ApplicationContext
的实现类XmlWebApplicationContext
来承担Spring
容器的引入。源码阅读的进度停在XmlWebApplicationContext
的refresh()
方法这里了。
按理说,今天应该接着从这里开始,但现在还不是继续追refresh()
方法的时候,为什么?
因为,我还想说说Spring
集成:通过注解的方式,来集成Spring
.
首先, 以注解方式如何集成spring
? 答案是spring
提供的Java Config
技术.
来看一个官方提供的以注解方式实现spring
集成的例子:
它实现spring
集成, 只需要两个类:
public class MyWebApplicationInitializer implements WebApplicationInitializer {
@Override
public void onStartup(ServletContext servletCxt) {
// Load Spring web application configuration
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
// Create and register the DispatcherServlet
DispatcherServlet servlet = new DispatcherServlet(ac);
ServletRegistration.Dynamic registration = servletCxt.addServlet("app", servlet);
registration.setLoadOnStartup(1);
registration.addMapping("/app/*");
}
}
@Configuration
@ComponentScan("com.springstudy.web")
public class AppConfig {
@Bean
public DataSource getDataSource() {
...
}
@Bean
public SqlSessionFactory getSqlSessionFactory(DataSource datasource) {
...
}
//other bean defintions
...
}
除了直接使用ServletContext API
之外, 还可以通过继承AbstractAnnotationConigDispatcherServletInitializer
并覆写其指定的方法, 也能做到实现Spring
的集成.
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] { AppConfig.class };
}
@Override
protected String[] getServletMappings() {
return new String[] { "/" };
}
/*
* 如果需要Servlet Filter,通过该方法进行Java Config进行注册
*/
@Override
protected Filter[] getServletFilters() {
return new Filter[] {
new HiddenHttpMethodFilter(), new CharacterEncodingFilter() };
}
}
官方解释到, 通过这种Java Config
配置方法,可以注册和实例化DispatcherServlet
.
为什么呢? 因为Servlet
容器能自动探测到. 然后引入spring
容器, 实现容器初始化.
为什么Servlet
容器就能自动探测到上面说到的这个类? 答案是: Servlet 3协议规定的.
在Servlet 3.0+
环境中, 通过Spring Config
注解方式集成spring
时. 用到了Servlet 3
协议规范
在上述协议中, 对于Servlet
容器的加载过程, 有专门的规定和限制要求, 这些约定使得Servlet
容器在启动时, 能调用到上面提到的那个WebApplicationInitializer
类.
针对注解集成spring
时,用到的协议相关内容, 有如下内容:
根据Servlet 3
协议中的说法, 存在一个文件javax.servlet.ServletContainerInitializer
, 定义了一个ServletContainerinitializer
的实现类.
org.springframework.web.SpringServletContainerInitializer
既然找到了这个实现类, 不妨看看它的代码, 有没有协议中说到的onStartup
方法:
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
@Override
public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
throws ServletException {
List<WebApplicationInitializer> initializers = new LinkedList<>();
if (webAppInitializerClasses != null) {
for (Class<?> waiClass : webAppInitializerClasses) {
// Be defensive: Some servlet containers provide us with invalid classes,
// no matter what @HandlesTypes says...
if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
try {
initializers.add((WebApplicationInitializer)
ReflectionUtils.accessibleConstructor(waiClass).newInstance());
}
catch (Throwable ex) {
throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
}
}
}
}
if (initializers.isEmpty()) {
servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
return;
}
servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
AnnotationAwareOrderComparator.sort(initializers);
for (WebApplicationInitializer initializer : initializers) {
initializer.onStartup(servletContext);
}
}
}
果然有!
而且, 根据协议中的说法, 如果这个类上存在@HandleTypes注解, 则表示对其注解中引用的类感兴趣: 被@HandlesTypes
标注的类, 或其实现类, 或其子类, 会被当作参数, 传入onStartup()
结合现在看到的代码, 就是: SpringServletContainerInitializer
在调用onStartup()
方法时, 会传入一个装有WebApplicationInitializer
的实现类, 或其子类的类对象Set, 然后进行判断:
如果传入的WebApplicationInitializer类对象,是其子类, 或者是其实现类对象,
同时又不是接口类,也不是抽象类, 则加入initializers list中
然后, 遍历这个initializers list
, 调用各自的onStartup()
, 并传入ServletContext 对象
现在回到之前最开始集成spring
时, 写的两个类: AppConfig
和 MyWebApplicationInitializer
.
AppConfig
暂时别管, MyWebApplicationInitializer
正是实现了WebApplicationInitializer
接口的实现类, 所以在Servlet
容器启动时, 能调用其onStartup()
方法.
嗯, 不对啊? 之前不是说, 还有一种通过继承类的方式, 也可以集成spring
吗? 那它就不是接口WebApplicationInitializer
的实现类了啊?
好问题! 答案是,其实它也是该接口的实现类!
请看继承类的层级:
public class MyWebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
...//需要再找父类
}
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {
...//需要再找父类
}
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
...//需要再找父类
}
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
...//终于看到最终的接口类了
}
回到前面说到的onStartup()
方法, 如果按方式1(实现接口), spring
容器的引入, 就是通过AnnotationConfigWebApplicationContext
引入的, 因为又看到了熟悉的refresh()
方法了
//通过AnnotationConfigWebApplicationContext来加载spring容器
AnnotationConfigWebApplicationContext ac = new AnnotationConfigWebApplicationContext();
ac.register(AppConfig.class);
ac.refresh();
...
那如果是方式2呢(通过继承抽象类)? 答案也是AnnotationConfigWebApplicationContext
:
//简称这个类为ADSI
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
...
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);//调用父类的onStartup方法
registerDispatcherServlet(servletContext);
}
}
需要继续追踪父类的onStartup方法
//父类的onStartup方法,简称这个类为ACLI
public abstract class AbstractContextLoaderInitializer implements WebApplicationInitializer {
...
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
registerContextLoaderListener(servletContext);
}
protected void registerContextLoaderListener(ServletContext servletContext) {
//创建顶层应用上下文对象
WebApplicationContext rootAppContext = createRootApplicationContext();
if (rootAppContext != null) {
ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
listener.setContextInitializers(getRootApplicationContextInitializers());
servletContext.addListener(listener);
}
else {
logger.debug("No ContextLoaderListener registered, as " +
"createRootApplicationContext() did not return an application context");
}
}
...
}
父类的onStartup()
方法,调用registerContextLoaderListener(servletContext)
方法;在此方法中,会继续调用createRootApplicationContext()
方法。
但是在类AbstractContextLoaderInitializer
, 包括AbstractDispatcherServletInitializer
中, 方法createRootApplicationContext()
都没有实现, 所以需要找其子类中的实现.
在AbstractAnnotationConfigDispatcherServletInitializer
中存在其实现:
//简称这个类为AACDSI
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {
...
@Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
//不为空:有顶层Config配置类
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
context.register(configClasses);
return context;
}
else {
//为空:如果没有顶层Config配置类
return null;
}
}
...
}
由代码可知, 如果没有顶层Config配置类,就返回空;如果有,则通过AnnotationConfigWebApplicationContext
来注册配置类, 从而加载spring
容器。
那,如果没有顶层Config配置类呢?AnnotationConfigWebApplicationContext
不是就没有机会引入了吗?
所以,需要再看代码!再回到之前说到的、会调用父类onStartup()
的那段代码处:
//简称这个类为ADSI
public abstract class AbstractDispatcherServletInitializer extends AbstractContextLoaderInitializer {
...
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
super.onStartup(servletContext);//调用父类的onStartup方法
registerDispatcherServlet(servletContext);
}
}
如果调用完父类的onStartup()
方法,没有发现顶层Config配置类,就不会有顶层上下文,也就不会有AnnotationConfigWebApplicationContext
引入。
但是,不是后面还有一个registerDispatcherServlet(servletContext)
方法吗?继续追呀:
protected void registerDispatcherServlet(ServletContext servletContext) {
String servletName = getServletName();
Assert.hasLength(servletName, "getServletName() must not return null or empty");
//1、创建Servlet应用上下文对象
WebApplicationContext servletAppContext = createServletApplicationContext();
Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
//2、在Servlet应用上下文中,创建DispatcherSerlvet
FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
//3、在DispatcherSerlvet中注册Initializers
dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
//4、在Servlet应用上下文中,注册Servlet
ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
if (registration == null) {
throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
"Check if there is another servlet registered under the same name.");
}
//5、配置Servlet参数
registration.setLoadOnStartup(1);
registration.addMapping(getServletMappings());
registration.setAsyncSupported(isAsyncSupported());
//6、注册ServletFilters
Filter[] filters = getServletFilters();
if (!ObjectUtils.isEmpty(filters)) {
for (Filter filter : filters) {
registerServletFilter(servletContext, filter);
}
}
//7、其它额外的自定义
customizeRegistration(registration);
}
现在重点关注下步骤1:
因为createServletApplicationContext()
在当前类AbstractDispatcherServletInitializer
中是个抽象方法,需要从其子类中寻找实现;
在其子类AbstractAnnotationConfigDispatcherServletInitializer
中,它的实现是这样的:
@Override
protected WebApplicationContext createServletApplicationContext() {
AnnotationConfigWebApplicationContext servletAppContext = new AnnotationConfigWebApplicationContext();
Class<?>[] configClasses = getServletConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
servletAppContext.register(configClasses);
}
return servletAppContext;
}
这下AnnotationConfigWebApplicationContext
的引入,再也没有什么多余的判断逻辑了;终于又看到我们熟悉的……
等等,但是,这里没有看到我们期望的`refresh()·方法啊?在哪里调用的呢?
难道是在其它步骤中?
但是,追踪了其它步骤,也没有发现refresh()
被调用的地方?
怎么回事?刚才是在追没有顶层Config类的代码,如果有顶层Config类的时候呢?
回到判断顶层Config类的那段代码,再看下:
//简称这个类为AACDSI
public abstract class AbstractAnnotationConfigDispatcherServletInitializer
extends AbstractDispatcherServletInitializer {
...
@Override
@Nullable
protected WebApplicationContext createRootApplicationContext() {
Class<?>[] configClasses = getRootConfigClasses();
if (!ObjectUtils.isEmpty(configClasses)) {
//有顶层Config配置类
AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
//奇怪了,不是应该还有个refresh方法的吗?
context.register(configClasses);
return context;
}
else {
//如果没有顶层Config配置类
return null;
}
}
...
}
我去?不是吧?这里也只有register()
,没有调用refresh()
,那这个方法在哪里被调用呢?
到这里,好像没有思路了:
Servlet
容器启动后,从配置文件中获取到ServletContainerInitializer
的实现类SpringServletContainerInitializer
onStartup()
方法;onStartup()
方法根据项目配置,创建ContextLoaderListener
、AnnotationConfigWebApplicationContext
、DispatcherServlet
、ServletFilter
等;整个过程,整理一下,是这样的:
Servlet容器启动类的onStartup()调用过程
//Servlet容器调用容器启动类的onStartup方法
onStartup(Set<Class<?>>, ServletContext) : SpringServletContainerInitializer
|//1、启动:
|--onStartup(ServletContext) : AbstractDispatcherServletInitializer
| |--onStartup(ServletContext servletContext) : AbstractContextLoaderInitializer
| |//注册:上下文加载监听器
| |--registerContextLoaderListener(ServletContext servletContext) : AbstractContextLoaderInitializer
| |//根据是否存在顶层Config配置类,决定是否创建顶层应用上下文对象
| |--createRootApplicationContext() : [$1] : AbstractAnnotationConfigDispatcherServletInitializer
| | |--getRootConfigClasses() : MyWebAppInitializer (自己写的类: 是否指定顶层Config配置类)
| |//如果存在顶层上下文对象,则创建上下文加载监听器
| |--new ContextLoaderListener(rootAppContext) : [$1]+
|
|//2、注册:DispatcherServlet
|--registerDispatcherServlet(ServletContext) : AbstractDispatcherServletInitializer
| |//2.1、创建servletContext
| |--createServletApplicationContext() :
| | |--new AnnotationConfigWebApplicationContext() : [servletAppContext]
| | |--getServletConfigClasses() : [$2] : MyWebAppInitializer
| | |--servletAppContext.register(configClasses) : [$2]+
| |
| |//2.2、创建DipatcherServlet
| |--createDispatcherServlet(servletAppContext) :
| |
| |//2.3、向servletContext中添加servlet
| |--addServlet(servletName, dispatcherServlet)
| |
| |//2.4、向servletContext中添加servlet
| |--registerServletFilter(servletContext, filter)
整个过程中,都没有地方存在调用refresh()
的地方;那么,是否这个refresh()
不在·onStartup()`的作用范围内?
带着这个疑问,看看最初调用onStartup()
方法的后面,是否存在调用refresh()
的可能。
在onStartup()
方法上,·Ctrl + Alt + H`,显示其调用链路:
可见,Servlet
容器——确实是Tomcat
调用onStartup()
,这与之前提到的Servlet 3协议
中的说法是一致的。现在只能去看一下Tomcat
容器的这个startInternal()
方法:
@Override
protected synchronized void startInternal() throws LifecycleException {
...
// Set up the context init params
mergeParameters();
// Call ServletContainerInitializers
//就是从这里开始调用 ServletContainerInitializers 实现类的onStartup()
//那么后续的代码,就有可能存在调用refresh()的可能
for (Map.Entry<ServletContainerInitializer, Set<Class<?>>> entry : initializers.entrySet()) {
try {
entry.getKey().onStartup(entry.getValue(), getServletContext());
} catch (ServletException e) {
log.error(sm.getString("standardContext.sciFail"), e);
ok = false;
break;
}
}
//疑点代码1
//配置和调用web应用的Listener;联想到在xml集成时,通过ContextLoaderListener来完成spring容器的引入,
//这里涉及到的listenerStart()方法可能需要看看
// Configure and call application event listeners
if (ok) {
if (!listenerStart()) {
log.error(sm.getString("standardContext.listenerFail"));
ok = false;
}
}
... ...
// Configure and call application filters
//配置和应用web应用的Filter
if (ok) {
if (!filterStart()) {
log.error(sm.getString("standardContext.filterFail"));
ok = false;
}
}
// Load and initialize all "load on startup" servlets
//疑点代码2
//加载和实例化所有"启动时加载"的Servlet
if (ok) {
if (!loadOnStartup(findChildren())){
log.error(sm.getString("standardContext.servletFail"));
ok = false;
}
}
... ...
}
通过对上面的代码的简单分析,我们可以认定,spring容器有可能在两处代码被引入进来。
为什么?因为:
1)之前xml集成spring
时,容器是通过listener
监听web上下文启动事件而引入的;
2)启动servlet
是startInternal()
方法中的最后一个与容器组件相关的事件了;
OK,先看`listenerStart()·:
public boolean listenerStart() {
... ...
String listeners[] = findApplicationListeners();
Object results[] = new Object[listeners.length];
... ...
for (int i = 0; i < results.length; i++) {
... ...
String listener = listeners[i];
results[i] = getInstanceManager().newInstance(listener);
}
... ...
for (int i = 0; i < results.length; i++) {
... ...
lifecycleListeners.add(results[i]);
}
for (Object lifecycleListener: getApplicationLifecycleListeners()) {
... ...
lifecycleListeners.add(lifecycleListener);
... ...
}
setApplicationLifecycleListeners(lifecycleListeners.toArray());
... ...
Object instances[] = getApplicationLifecycleListeners();
... ...
ServletContextEvent event = new ServletContextEvent(getServletContext());
... ...
for (int i = 0; i < instances.length; i++) {
... ...
ServletContextListener listener = (ServletContextListener) instances[i];
... ...
listener.contextInitialized(event);
}
... ...
}
可见,如果容器注册了listener
就会调用其contextInitialized(event)
;
结合前面分析的代码:只有存在顶层Config类时,才会创建ContextLoadListener,这里才有可能引入spring容器。
但很多时候,有可能是真的没有顶层Config配置类,就比如上面那个AppConfig
类;所以,容器spring
的引入,应该不在这里。
再看疑点代码2:loadOnStartup(findChildren())
//参数 children由方法findChildren()返回
public boolean loadOnStartup(Container children[]) {
TreeMap<Integer, ArrayList<Wrapper>> map = new TreeMap<>();
for (int i = 0; i < children.length; i++) {
Wrapper wrapper = (Wrapper) children[i];
... ...
int loadOnStartup = wrapper.getLoadOnStartup();
... ...
Integer key = Integer.valueOf(loadOnStartup);
... ...
map.put(key, list);
... ...
list.add(wrapper);
}
... ...
... ...
for (ArrayList<Wrapper> list : map.values()) {
for (Wrapper wrapper : list) {
wrapper.load();
... ...
}
}
... ...
}
//children由addChild()方法加入到对象属性中
public Container[] findChildren() {
... ...
Container results[] = new Container[children.size()];
return children.values().toArray(results);
}
@Override
public ServletContext getServletContext() {
if (context == null) {
context = new ApplicationContext(this);
if (altDDName != null)
context.setAttribute(Globals.ALT_DD_ATTR,altDDName);
}
return context.getFacade();
}
这里分析Tomcat的源码有点恶心,我就不说过多的细节,大致的流程说下:
tomcat
调用SpringServletContainerInitializer
的onStartup()
方法,传入ServletContext
对象作为servlet
上下文:ApplicationContextFacade
registerDispatcherServlet()
方法中,通过这个上下文调用addServlet(String, Servlet)
注册DispatcherServlet
;ApplicationContextFacade
底层会调用ApplicationContext.addServlet(String servletName, Servlet servlet)
注册Servlet
;Servlet
,调用createWrapper()
封装成Wrapper : StandardWrapper
,调用StandardContext.addChild(wrapper)
,装入其children[]
属性中;loadOnStartup(children)
时,遍历children[]
,调用其wrapper.load()
方法然后,来看一看wrapper.load()
方法:因为wrapper
对应的类对象是StandardWrapper
,所以要看StandardWrapper.load()
的代码实现:
public synchronized void load() throws ServletException {
instance = loadServlet();
if (!instanceInitialized) {
initServlet(instance);
}
... ...
}
public synchronized Servlet loadServlet() throws ServletException {
... ...
Servlet servlet = (Servlet) instanceManager.newInstance(servletClass);
... ...
initServlet(servlet);
... ...
return servlet;
}
private synchronized void initServlet(Servlet servlet) throws ServletException {
... ...
//protected final StandardWrapperFacade facade = new StandardWrapperFacade(this);
servlet.init(facade);
... ...
}
终于,追到最后,看到调用servlet.init()
方法了,感觉spring
容器要现身了:
public void init(ServletConfig config) throws ServletException {
this.config = config;
this.init();//this是DispatcherServlet对象,其init()在父类HttpServletBean中
}
public final void init() throws ServletException {
... ...
initServletBean();
}
protected final void initServletBean() throws ServletException {
... ...
this.webApplicationContext = initWebApplicationContext();
initFrameworkServlet();
... ...
}
啊,又近了下一步,看到有点熟悉的initWebApplicationContext
方法了;进去确认下:
protected WebApplicationContext initWebApplicationContext() {
WebApplicationContext rootContext =
WebApplicationContextUtils.getWebApplicationContext(getServletContext());
WebApplicationContext wac = null;
if (this.webApplicationContext != null) {
//之前在onStartup()被调用时,那会后面创建过web上下文对象
wac = this.webApplicationContext;
if (wac instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
cwac.setParent(rootContext);
}
configureAndRefreshWebApplicationContext(cwac);//就是这个方法,会刷新上下文对象
}
}
}
if (wac == null) {
wac = findWebApplicationContext();
}
if (wac == null) {
wac = createWebApplicationContext(rootContext);
}
if (!this.refreshEventReceived) {
synchronized (this.onRefreshMonitor) {
onRefresh(wac);
}
}
if (this.publishContext) {
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
}
return wac;
}
//DispatcherServlet的实现
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
// The application context id is still set to its original default value
// -> assign a more useful id based on available information
if (this.contextId != null) {
wac.setId(this.contextId);
}
else {
// Generate default id...
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
}
}
wac.setServletContext(getServletContext());
wac.setServletConfig(getServletConfig());
wac.setNamespace(getNamespace());
wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
}
postProcessWebApplicationContext(wac);
applyInitializers(wac);
wac.refresh();
}
终于,看到最后的refresh()
了!
真心不容易,ServletContainerInitializer.onStartup()
+ Tomcat.startInternal()
,配合起来,中间再穿插一个addServlet()
+ Wrapper.load()
的注册方法,整个调用栈太深了;能追到头,好累啊!
自此, 集成spring
的方式都说清楚了. 下一步, 就是开始分析spring
容器的加载过程了.
加油, 少年! 长征只走完了一小步呐!