发布网友 发布时间:2022-04-22 04:31
共1个回答
热心网友 时间:2023-08-12 21:40
先上一张类图:
这里借用在web项目中的加载过程来熟悉Spring ApplicationContext的加载过程:
1. 在一般的Web项目中,我们很多情况下是利用org.springframework.web.context.ContextLoaderListener这个类进行容器的初始化。该类会被Web容器(如Tomcat)自动实例化,并调用contextInitialized方法。
/**
* Initialize the root web application context.
*/
@Override
public void contextInitialized(ServletContextEvent event) {
initWebApplicationContext(event.getServletContext());
}
2. initWebApplicationContext方式是从父类ContextLoader中继承来的。该方法的大致的逻辑是:判定web容器中是否注册了ROOT_APPLICATION_CONTEXT_STTRIBUTE(为WebApplicationContext.class.getName()+ “.ROOT”)的属性,如果有,则抛出异常,以此保证一个Web容器中只有一个Spring根容器;创建容器的时候,要判定需要实例化哪种类来实例化当前web容器的Spring根容器,如果我们设置了名称为“contextClass”的context-param,则取我们设置的类,该类应当实现ConfigurableWebApplicationContext接口或继承自实现了该接口的子类(如XmlWebApplicationContext、GroovyWebApplicationContext和AnnotationConfigWebApplicationContext),通常我们都不会设置,Spring会默认取与ContextLoader同目录下的ContextLoader.properties中记录的类名作为根容器的类型(默认是org.springframework.web.context.support.XmlWebApplicationContext);实例化容器;配置容器;设置容器为Web容器的属性。下面代码中去掉了log和异常等部分。
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
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!");
}
if (this.context == null) {
this.context = createWebApplicationContext(servletContext);
}
if (this.context instanceof ConfigurableWebApplicationContext) {
ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
if (!cwac.isActive()) {
if (cwac.getParent() == null) {
ApplicationContext parent = loadParentContext(servletContext);
cwac.setParent(parent);
}
configureAndRefreshWebApplicationContext(cwac, servletContext);
}
}
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);
}
return this.context;
}
3. configureAndRefreshWebApplicationContext方法负责对容器进行初始化,该方法的逻辑主要有一下几点:设置一个contextId(从contextId这个param获取,如果没有则默认是WebApplicationContext的类名 + “:” + servlet context的路径);设置配置位置(从contextConfigLocation 这个param获取,如果未配置,则默认是/WEB-INF/applicationContext.xml,在XmlWebApplicationContext中可以看出);自定义该congtext;调用该Context的refresh()方法。
protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContextwac, ServletContext sc) {
if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
if (idParam != null) {
wac.setId(idParam);
}
else {
wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
ObjectUtils.getDisplayString(sc.getContextPath()));
}
}
wac.setServletContext(sc);
String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
if (configLocationParam != null) {
wac.setConfigLocation(configLocationParam);
}
ConfigurableEnvironment env = wac.getEnvironment();
if (env instanceof ConfigurableWebEnvironment) {
((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
}
customizeContext(sc, wac);
wac.refresh();
}
4. ConfigurableApplicationContext接口的实现类AbstractApplicationContext中的refresh方法,定义了一个模版,在该方法里,会完成加载资源、配置文件解析、Bean定义的注册、组件的初始化等工作。每一步工作都定义在响应的方法中,清晰明了。下面的方法省略了异常处理。
synchronized (this.startupShutdownMonitor) {.
prepareRefresh(); //准备
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();//获得一个Bean工厂
prepareBeanFactory(beanFactory); //准备好工厂
postProcessBeanFactory(beanFactory);//处理工厂时执行
invokeBeanFactoryPostProcessors(beanFactory);//执行工厂处理
registerBeanPostProcessors(beanFactory);//注册bean产生*
initMessageSource();//初始化消息源
initApplicationEventMulticaster();//初始化应用事件广播器
onRefresh();//初始化该容器子类其他特定的bean
registerListeners();//检查*并注册
finishBeanFactoryInitialization(beanFactory);// 初始化非lazy的singleton的bean
finishRefresh();//发布结束fresh消息
}
5. 这几个方法中比较重要的方法是obtainFreshBeanFactory方法和finishBeanFactoryInitialization方法,一个用来获得bean工厂,一个用来实例化bean并注入的。这里先分析obtainFreshBeanFactory方法。在继承链中,AbstractApplicationContext实现了该方法,并定义了refreshBeanFactory方法让后代实现。AbstractRefreshableApplicationContext又实现了refreshBeanFactory方法,如果有一个BeanFactory销毁它,然后再创建一个。
protected final void refreshBeanFactory() throws BeansException {
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
DefaultListableBeanFactory beanFactory = createBeanFactory();
beanFactory.setSerializationId(getId());
customizeBeanFactory(beanFactory);
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
}
6. loadBeanDefinitions方法会由子类实现,在XmlWebApplicationContext中就实现了该方法。该方法的逻辑为:new一个XmlBeanDefinitionReader;设置该Reader的ResourceLoader(XmlWebApplicationContext设置的是它自己,因为它间接实现了ResourceLoader接口);、使用该Reader加载Bean定义。
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throwsBeansException, IOException {
XmlBeanDefinitionReader beanDefinitionReader = newXmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
initBeanDefinitionReader(beanDefinitionReader);
loadBeanDefinitions(beanDefinitionReader);
}
7. loadeBeanDefinitions方法会使用reader从每个配置中读取bean定义。该方法大概逻辑为:从每个location解析出一些resouce(代表特定的资源,如一个文件)。在AbstractBeanDefinitionReader中实现的loadBeanDefinitions(String,Set<Resource> actualResources) 方法如下:
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throwsBeanDefinitionStoreException {
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
if (resourceLoader instanceof ResourcePatternResolver) {
try {
Resource[] resources = ((ResourcePatternResolver)resourceLoader).getResources(location);
int loadCount = loadBeanDefinitions(resources);
if (actualResources != null) {
for (Resource resource : resources) {
actualResources.add(resource);
}
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" +location + "]");
}
return loadCount;
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"Could not resolve bean definition resource pattern [" + location + "]", ex);
}
}
else {
Resource resource = resourceLoader.getResource(location);
int loadCount = loadBeanDefinitions(resource);
if (actualResources != null) {
actualResources.add(resource);
}
if (logger.isDebugEnabled()) {
logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
}
}
8. 在XmlBeanDefinitionReader中才是真正的加载Bean定义的实现。它从每个Resource中获得输入流,封装成InputSource;调用doLoadBeanDefinitions从该InputSource中获得一个Document,并使用registerBeanDefinitions方法根据该Document注册BeanDefinitions;registerBeanDefinitions方法中先是创建一个DefaultBeanDefinitionDocumentReader实例,再使用该实例来注册BeanDefinition;该实例会从Document的根元素开始注册BeanDefinition。值得注意的是在该类又会委派一个BeanDefinitionParserDelegate来解析Document元素。
protected void parseBeanDefinitions(Element root, BeanDefinitionParserDelegate delegate) {
if (delegate.isDefaultNamespace(root)) {
NodeList nl = root.getChildNodes();
for (int i = 0; i < nl.getLength(); i++) {
Node node = nl.item(i);
if (node instanceof Element) {
Element ele = (Element) node;
if (delegate.isDefaultNamespace(ele)) {
parseDefaultElement(ele, delegate);
}
else {
delegate.parseCustomElement(ele);
}
}
}
}
else {
delegate.parseCustomElement(root);
}
}
9. BeanDefinitionParserDelegate会根据节点的命名空间使用不同的NamespaceHandler进行解析。在XmlBeanDefinitionReader创建DefaultBeanDefinitionDocumentReader时候会传入一个XmlReaderContext,该Context中保存了一个NamespaceHandlerResolver,该HandlerResolver维护一个Map,用来解析对于一个命名空间的具体Handler,其默认使用META-INF/spring.handlers 文件保存Handler的种类,Spring支持的Handler如下图:
BeanDefinitionDelegate使用获得的NamespaceHandler解析元素节点,获得BeanDefinition。
10. 不同的NamespaceHandler解析过程不一样。同时,一个NamespaceHandler中有多个Parser来解析不同种类的元素。以ContextNamspaceHandler为例,它支持的Parser如下图所示: