想努力成长,欢迎点评。
1.首先介绍tomcat的ServletContext:每个web项目只有一个ServletContext,用于全局配置;
所谓全局配置是因为每个servlet也有自己的独立context,每个servlet可以获取全局的父ServletContext,但是父类却获取不到子类的,这是tomcat的规定,同时他也规定凡是实现ServletContextListener接口的,tomca容器在加载web.xml 上下文环境时会调用该类contextInitialized方法,并以ServletContextEvent为参数。而ServletContextEvent携带了web.xml资源的必要Filter、contextLocation的信息。
接下来开始读ContextLoaderListener的初始化代码
//根据输入入的ServletContext信息进行Spring父容器的初始化工作
this.initWebApplicationContext(event.getServletContext())
这里补充下ServletContext内容信息
2.下面解析initWebApplicationContext方法
//首先判断org.springframework.web.context.WebApplicationContext.ROOT为key的容器是否已经初始化了,也就是这里规定Spring只能有一个父容器
if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
throw new IllegalStateException("Cannot initialize context because there is already a root application context present - check whether you have multiple ContextLoader* definitions in your web.xml!");
} else {
//获取日志工厂,通过上下文类加载器获取日志工厂实例对象,spi规范,获取meta/service找对应的类信息,并反射加载,这里是线程的上下文类加载器打破双亲机制的,此时日志系统就是这个时候实现的。这也就是为什么我们只需要添加jar包以及log4j配合文件就可以完成日志系统的集成,此处不加以详细描述。
Log logger = LogFactory.getLog(ContextLoader.class);
servletContext.log("Initializing Spring root WebApplicationContext");
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
//记录下启动时间
long startTime = System.currentTimeMillis();
try {
if (this.context == null) {
//这里首先去获取servletContext.getInitParameter("contextClass")也就是web.xml中是否配置上下文容器,并根据上下文加载器去加载类,否则就会使用Spring-context默认的XmlWebApplicationContext作为ioc容器。而WebApplicationContext容器也会保存
//request,session,globalSession,application,servletContext,contextParameters,contextAttributes等信息;
//最后会通过反射机制,以ContextClassLoader返回一个实例类XmlWebApplicationContext类;这个类后续会介绍;
this.context = this.createWebApplicationContext(servletContext);
}
//下文会介绍XmlWebApplicationContext继承关系,这里该类是自然实现了ConfigurableWebApplicationContext因此程序进入
if (this.context instanceof ConfigurableWebApplicationContext) {
//强转
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext)this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
//tomcatc初次启动,spring即监听,自然找不到父类的。
ApplicationContext parent = this.loadParentContext(servletContext);
cwac.setParent(parent);
}
//这里会处理在configLocationParam在web.xml配置的spring配置文件信息,并进行加载初始化容器,这里下文会继续深入此方法。
this.configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
//在这里向全局参数注入spring的上下文信息,下面是一些逻辑,就是spring容器加载了多久了,当前的类加载器是否为当前上下文类加载器了等等,不加深入了。
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
if (ccl == ContextLoader.class.getClassLoader()) {
currentContext = this.context;
} else if (ccl != null) {
currentContextPerThread.put(ccl, this.context);
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" + WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return this.context;
} catch (RuntimeException var8) {
logger.error("Context initialization failed", var8);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var8);
throw var8;
} catch (Error var9) {
logger.error("Context initialization failed", var9);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, var9);
throw var9;
}
}
现在补充介绍XmlWebApplicationContext类,根据关系图可见
那么根据上图可知该类具有读取Spring配置文件的功能,及ResourceLoader;加载读取管理Bean及Factory,并且能够感知一切实体Bean变动的能力,而且继承了了生命周期可见其能力作用之大。
3、这里继续深入 this.configureAndRefreshWebApplicationContext(cwac, servletContext),当解析到配置信息后又是如何加载初始化容器的呢?
ContextLoader.class
String configLocationParam;
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
configLocationParam = sc.getInitParameter("contextId");
if (configLocationParam != null) {
wac.setId(configLocationParam);
} else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX + ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
//获取web.xml中配置的contextConfigLocation信息
configLocationParam = sc.getInitParameter("contextConfigLocation");
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
//初始化ioc基础环境
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment)env).initPropertySources(sc, (ServletConfig)null);
}
this.customizeContext(sc, wac);
//这里会进行容器befactory的初始化,以及bean的读取
wac.refresh();
4、 wac.refresh()解析
public void refresh() throws BeansException, IllegalStateException {
//枷锁,听说后续会有dispatchServlet初始化时也会调用该方法
Object var1 = this.startupShutdownMonitor;
synchronized(this.startupShutdownMonitor) {
this.prepareRefresh();
//获取beanFactory 上文程序已完成contextLocation的配置假如,现在会根据这些先前信息加入生成工厂类,这里会会在工厂类中扫描配置文件,并记录所有的包名+类名的全信息,但是此时并未真正装填实例化bean信息
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();
this.prepareBeanFactory(beanFactory);
try {
this.postProcessBeanFactory(beanFactory);
this.invokeBeanFactoryPostProcessors(beanFactory);
this.registerBeanPostProcessors(beanFactory);
this.initMessageSource();
this.initApplicationEventMulticaster();
this.onRefresh();
this.registerListeners();
this.finishBeanFactoryInitialization(beanFactory);
this.finishRefresh();
} catch (BeansException var9) {
if (this.logger.isWarnEnabled()) {
this.logger.warn("Exception encountered during context initialization - cancelling refresh attempt: " + var9);
}
this.destroyBeans();
this.cancelRefresh(var9);
throw var9;
} finally {
this.resetCommonCaches();
}
}
}
在 this.obtainFreshBeanFactory()的时候已经完成了对基本路径的扫描,bean的扫描,跟进去源码是获取各个文件的内容信息了,这个过程只是信息的收集过程,扫描xml信息内容
5. this.registerBeanPostProcessors(beanFactory);则是获取bean的解析器,装载入bean容器了。首先是通过实现Bean工厂存储的,而这些信息放入的内容存储在ConcurrentHashMap,他是线程安全的,线程安全doug lea写的;
private final Map<Class<?>, Object> resolvableDependencies = new ConcurrentHashMap(16);
private final Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap(256);
private final Map<Class<?>, String[]> allBeanNamesByType = new ConcurrentHashMap(64);
private final Map<Class<?>, String[]> singletonBeanNamesByType = new ConcurrentHashMap(64);
6.紧接着各种逻辑获取装栽逻辑bean,满眼望去印象最大的就是getBean\for\BeanPostProcessor本的解析器记住BeanPostProcessor会在aop的作用派上大用处,spring也就是依靠他来实现aspecj的装栽。具体不宜深究,今天主要目的是为了搞清楚spring web\tomcat的装栽流程如对ioc感兴趣建议自行百度,或后期更新。
public static void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
beanFactory.addBeanPostProcessor(new PostProcessorRegistrationDelegate.BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList();
List<BeanPostProcessor> internalPostProcessors = new ArrayList();
List<String> orderedPostProcessorNames = new ArrayList();
List<String> nonOrderedPostProcessorNames = new ArrayList();
String[] var8 = postProcessorNames;
int var9 = postProcessorNames.length;
String ppName;
BeanPostProcessor pp;
//装栽bean解析器并且根据优先级排好队。
for(int var10 = 0; var10 < var9; ++var10) {
ppName = var8[var10];
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
pp = (BeanPostProcessor)beanFactory.getBean(ppName, BeanPostProcessor.class);
priorityOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
} else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
} else {
nonOrderedPostProcessorNames.add(ppName);
}
}
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
//注册解析器
registerBeanPostProcessors(beanFactory, (List)priorityOrderedPostProcessors);
List<BeanPostProcessor> orderedPostProcessors = new ArrayList();
Iterator var14 = orderedPostProcessorNames.iterator();
while(var14.hasNext()) {
String ppName = (String)var14.next();
BeanPostProcessor pp = (BeanPostProcessor)beanFactory.getBean(ppName, BeanPostProcessor.class);
orderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
sortPostProcessors(orderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, (List)orderedPostProcessors);
List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList();
Iterator var17 = nonOrderedPostProcessorNames.iterator();
while(var17.hasNext()) {
ppName = (String)var17.next();
pp = (BeanPostProcessor)beanFactory.getBean(ppName, BeanPostProcessor.class);
nonOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
registerBeanPostProcessors(beanFactory, (List)nonOrderedPostProcessors);
sortPostProcessors(internalPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, (List)internalPostProcessors);
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
7.这段代码将来可以研究,会循环注入bean,调试进入看到 AbstractAutowireCapableBeanFactory。applyPropertyValues有两层while循环,这里循环扫描基本包了,也就是说这个方法内部实现了全部bean 注解类的的加载,本文不在研究设计模式和算法,姑且糊涂一回(其实是菜)。
sharedInstance = this.getSingleton(beanName, new ObjectFactory<Object>() {
public Object getObject() throws BeansException {
try {
return AbstractBeanFactory.this.createBean(beanName, mbd, args);
} catch (BeansException var2) {
AbstractBeanFactory.this.destroySingleton(beanName);
throw var2;
}
}
});
8.Spring父类容器已经渲染完毕了,上述提到ServletContextEvent携带了web.xml资源的必要Filter、contextLocation的信息,那么我们web.xml配置的servlet又是何时登场呢?上文提到tomcat初始化上下文后,就会开始加载servlet并执行init的方法,并设置拦截url应用内截取片段的范围。
首先看图DispatcherServlet.class,这是类和传统的Servlet的功能基础上进行了功能扩展,而当Tomcat容器初始化Servlet容器的时候,会调用Servlet的init方法。
而FrameworkServlet的init方法恰好就是初始化spring FrameWorkServlet对应ContextParam的 mvc配置。 this.webApplicationContext = this.initWebApplicationContext();方法是初始化子servlet容器,并设置父容器是WebapplicationContext因为他可以拿到父容器的公共bean并根据配置文件完成视图解析
protected WebApplicationContext createWebApplicationContext(ApplicationContext parent) {
Class<?> contextClass = this.getContextClass();
if (this.logger.isDebugEnabled()) {
this.logger.debug("Servlet with name '" + this.getServletName() + "' will try to create custom WebApplicationContext context of class '" + contextClass.getName() + "', using parent context [" + parent + "]");
}
if (!ConfigurableWebApplicationContext.class.isAssignableFrom(contextClass)) {
throw new ApplicationContextException("Fatal initialization error in servlet with name '" + this.getServletName() + "': custom WebApplicationContext class [" + contextClass.getName() + "] is not of type ConfigurableWebApplicationContext");
} else {
ConfigurableWebApplicationContext wac = (ConfigurableWebApplicationContext)BeanUtils.instantiateClass(contextClass);
wac.setEnvironment(this.getEnvironment());
wac.setParent(parent);//拿到父类的容器
wac.setConfigLocation(this.getContextConfigLocation());//同样的步骤设置自己的contextConfigLocation信息
this.configureAndRefreshWebApplicationContext(wac);
return wac;
}
}
9.如前文一样ConfigurableWebApplicationContext配置好自己的独属于DispatcherServlet工厂类存储好自己的配置信息后,会以责任链方式触发一个事件ContextRefreshedEvent,事件中包含很多有用信息如图
最后会触发DispatcherServlet.onRefresh方法,继而调用 this.initStrategies(context);
9.DispatcherServlet.initStrategies(context),接下来就是spring mvc的基本组件登场了,适配器,视图解析器等等精力问题下次陈述吧。
this.initMultipartResolver(context);
this.initLocaleResolver(context);
this.initThemeResolver(context);
this.initHandlerMappings(context);//控制器地址保存
this.initHandlerAdapters(context);
this.initHandlerExceptionResolvers(context);
this.initRequestToViewNameTranslator(context);
this.initViewResolvers(context);
this.initFlashMapManager(context);