spring源码3.2.18解读+spring技术内幕(关于BeanDefinition的定位)
1、导读
以编程的方式使用DefaultListableBeanFactory时,首先定义一个Resource来定位容器使用的BeanDefinition。这时使用的是ClassPathResource,这意味着Spring会在类路径中去寻找以文件形式存在的BeanDefinition信息。
ClassPathResource res = new ClassPathResource(“bean.xml”);
这里定义的Resource并不能由DefaultListableBeanFactory直接使用,Spring通过BeanDefinitionReader来对这些信息进行处理。在这里,我们也可以看到使用ApplicationContext相对于直接使用DefaultListableBeanFactory的好处。因为在ApplicationContext中,Spring已经为我们提供了一系列加载不同Resource的读取器的实现,而DefaultListableBeanFactory只是一个纯粹的容器,需要为它配置特定的读取器才能完成这些功能。当然,有利就有弊,使用DefaultListableBeanFactory这种更底层的容器,能提高定制IOC的灵活性。
回到我们经常使用的ApplicationContext上来,例如FileSystemXmlApplicationContext、ClassPathXmlApplicationContext以及XmlWebApplicationContext等。简单地从这些类的名字上分析,可以清楚地看到他们可以提供哪些不同的Resource读入功能,比如FileSystemXmlApplicationContext可以从文件系统载入Resource,ClassPathXmlApplicationContext可以从Class Path载入Resource,XmlWebApplicationContext可以在Web容器中载入Resource,等等。
下面以FileSystemApplicationContext为例,通过分析这个ApplicationContext的实现来看它是怎样完成这个Resource定位过程。
2、FileSystemXmlApplicationContext继承体系
1、接口实现图、类图
3、BeanDefinition定位主要调用流程
1、主要类图结构
从源码的角度来看,我们可以近距离关心以FileSystemXmlApplicationContext为核心的集成体系。
2、流程图
因此,以下从FileSystemXmlApplicationContext开始构建容器的方法主要调用流程
3、具体java类
从以上流程图我们可以看出BeanDefinition资源定位的主要流程,接下来我们将进行具体的分析。
3.1、FileSystemXmlApplicationContext类
—————————————————————————————————————
public class FileSystemXmlApplicationContext extends AbstractXmlApplicationContext {
//这个构造函数包含的是BeanDefinition所在文件的路径
public FileSystemXmlApplicationContext(String configLocation) throws BeansException {
this(new String[]{configLocation}, true, (ApplicationContext)null);
}
//这个构造函数包含的是多个BeanDefinition所在文件的路径
public FileSystemXmlApplicationContext(String... configLocations) throws BeansException {
this(configLocations, true, (ApplicationContext)null);
}
//这个构造函数包含的是多个BeanDefinition所在文件的路径,同时还可以指定双亲容器
public FileSystemXmlApplicationContext(String[] configLocations, ApplicationContext parent) throws BeansException {
this(configLocations, true, parent);
}
//在对象的初始化过程中,调用这个refresh函数对资源进行定位、载入、注册,这里只讲解资源的定位。
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent) throws BeansException {
super(parent);
this.setConfigLocations(configLocations);//资源文件所在的路径保存在AbstractRefreshableConfigApplicationContext类中string[]类型的字段。至此应用程序传入路径保存在AbstractRefreshableConfigApplicationContext中 。
if(refresh) {
this.refresh();
}
}
//这是应用位于文件系中Resource的实现,通过构造一个FileSystemResource来得到一个系统中定位的BeanDefinition,这个getResourceByPtah是在BeanDefinitionReader的loadBeanDefiniiton中被调用的,loadBeanDefiniiton采用了模板方法模式,具体定位的实现实际是由多个子类来完成
protected Resource getResourceByPath(String path) {
if(path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
}
从上面可以看出,在FileSystemXmlApplicationContext的构造函数中将会调用refresh()函数,这个将会定位到AbstractApplicationContext,AbstractApplicationContext作为顶层接口ApplicationContext的抽象实现,它实现了启动容器的模板方法refresh(),并在该方法中定义一系列算法骨架,用于创建不同类型的容器(其子类具体实现,实现抽象类中的抽象方法,从而创建不同类型的容器)。
3.2、AbstractApplicationContext类
public void refresh() throws BeansException, IllegalStateException {
Object var1 = this.startupShutdownMonitor;
synchronized(this.startupShutdownMonitor) {
//调用容器准备刷新的方法,获取容器的当时时间,同时给容器设置同步标识
prepareRefresh();
//告诉子类启动refreshBeanFactory()方法,Bean定义资源文件的定位、载入从
//子类的refreshBeanFactory()方法启动(这里将会调用本类中的 obtainFreshBeanFactory()方法来调用子类中的refreshBeanFactory())
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
//为BeanFactory配置容器特性,例如类加载器、事件处理器等
prepareBeanFactory(beanFactory);
try {
//为容器的某些子类指定特殊的BeanPost事件处理器
postProcessBeanFactory(beanFactory);
//调用所有注册的BeanFactoryPostProcessor的Bean
invokeBeanFactoryPostProcessors(beanFactory);
//为BeanFactory注册BeanPost事件处理器.
//BeanPostProcessor是Bean后置处理器,用于监听容器触发的事件
registerBeanPostProcessors(beanFactory);
//初始化信息源,和国际化相关.
initMessageSource();
//初始化容器事件传播器.
initApplicationEventMulticaster();
//调用子类的某些特殊Bean初始化方法
onRefresh();
//为事件传播器注册事件监听器.
registerListeners();
//初始化所有剩余的单态Bean.
finishBeanFactoryInitialization(beanFactory);
//初始化容器的生命周期事件处理器,并发布容器的生命周期事件
finishRefresh();
}
catch (BeansException ex) {
//销毁以创建的单态Bean
destroyBeans();
//取消refresh操作,重置容器的同步标识.
cancelRefresh(ex);
throw ex;
}
}
}
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
//在obtainFreshBeanFactory中,这里所调用的refreshBeanFactory具体将会在子类中进行实现
this.refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = this.getBeanFactory();
if(this.logger.isDebugEnabled()) {
this.logger.debug("Bean factory for " + this.getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
从上面的代码可以看出,AbstractApplicationContext主要是定义了启动容器的实现方法的一系列规范,其具体实现由不同的子类进行实现。而在refresh()方法中,将会调用refreshBeanFactory()方法,该方法的具体实现将会定位到子类AbstractRefreshableApplicationContext中的具体实现中。
3.3、AbstractRefreshableApplicationContext类
protected final void refreshBeanFactory()throws BeansException {
if(this.hasBeanFactory()) {//这里判断容器是否存在,如果已经存在则销毁该容器
this.destroyBeans();
this.closeBeanFactory();
}
try {//这里创建一个新的容器,使用的是DefaultListableBeanFactory
DefaultListableBeanFactory ex = this.createBeanFactory();
ex.setSerializationId(this.getId());
this.customizeBeanFactory(ex);
//这里启动对BeanDefinition的定位(载入、注册),这里实际上是调用的一个抽象方法,而实际的载入过程则在该类的子类AbstractXmlApplicationContext中实现。
this.loadBeanDefinitions(ex);
Object var2 = this.beanFactoryMonitor;
synchronized(this.beanFactoryMonitor) {
this.beanFactory = ex;
}
} catch (IOException var5) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + this.getDisplayName(), var5);
}
}
protected abstract void loadBeanDefinitions(DefaultListableBeanFactory var1) throws BeansException, IOException;
从以上方法调用可以看出,refreshBeanFactory()主要负责容器的创建,而对于BeanDefinition的定位,则由loadBeanDefinitions(DefalultListableBeanFactory)方法进行实现,该方法将定位到AbstractXmlApplicationContext类中。
3.4、AbstractXmlApplicationContext类
public abstract class AbstractXmlApplicationContext extends AbstractRefreshableConfigApplicationContext {
private boolean validating = true;
public AbstractXmlApplicationContext() {
}
public AbstractXmlApplicationContext(ApplicationContext parent) {
super(parent);
}
public void setValidating(boolean validating) {
this.validating = validating;
}
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
//创建XmlBeanDefinitionReader,并通过回调设置到BeanFactory中去,创建BeanFactory的过程可以参考上文对编程式使用IOC容器的分析
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
beanDefinitionReader.setEnvironment(this.getEnvironment());
beanDefinitionReader.setResourceLoader(this);
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
//这里启动Bean定义信息的定位和载入,将会调用本类中的loadBeanDefinitions(XmlBeanDefinitionReader reader)方法。
this.initBeanDefinitionReader(beanDefinitionReader);
this.loadBeanDefinitions((XmlBeanDefinitionReader)beanDefinitionReader);
}
protected void initBeanDefinitionReader(XmlBeanDefinitionReader reader) {
reader.setValidating(this.validating);
}
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
Resource[] configResources = this.getConfigResources();
if(configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//这里获配置文件所在的位置,通过之前设置到AbstractRefreshableConfigApplicationContext类中的configlocations字段进行获取
String[] configLocations = this.getConfigLocations();
if(configLocations != null) {
//这里将会调用AbstractBeanDefinitionReader中的具体的实现方法
reader.loadBeanDefinitions(configLocations);
}
}
protected Resource[] getConfigResources() {
return null;
}
}
3.5、AbstractBeanDefinitionReader类
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
Assert.notNull(resources, "Resource array must not be null");
int counter = 0;
Resource[] arr$ = resources;
int len$ = resources.length;
for(int i$ = 0; i$ < len$; ++i$) {
Resource resource = arr$[i$];
counter += this.loadBeanDefinitions((Resource)resource);
}
return counter;
}
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return this.loadBeanDefinitions(location, (Set)null);
}
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
ResourceLoader resourceLoader = this.getResourceLoader();
if(resourceLoader == null) {
throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
} else {
int loadCount;
//这里将会调用ResourcePatternResolver类的子类PathMatchingResourcePatternResource类中的getResource()方法对资源进行定位
if(!(resourceLoader instanceof ResourcePatternResolver)) {
Resource var11 = resourceLoader.getResource(location);
loadCount = this.loadBeanDefinitions((Resource)var11);
if(actualResources != null) {
actualResources.add(var11);
}
if(this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + loadCount + " bean definitions from location [" + location + "]");
}
return loadCount;
} else {
try {
Resource[] resource = ((ResourcePatternResolver)resourceLoader).getResources(location);
loadCount = this.loadBeanDefinitions((Resource[])resource);
if(actualResources != null) {
Resource[] arr$ = resource;
int len$ = resource.length;
for(int i$ = 0; i$ < len$; ++i$) {
Resource resource1 = arr$[i$];
actualResources.add(resource1);
}
}
if(this.logger.isDebugEnabled()) {
this.logger.debug("Loaded " + loadCount + " bean definitions from location pattern [" + location + "]");
}
return loadCount;
} catch (IOException var10) {
throw new BeanDefinitionStoreException("Could not resolve bean definition resource pattern [" + location + "]", var10);
}
}
}
}
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
int counter = 0;
String[] arr$ = locations;
int len$ = locations.length;
for(int i$ = 0; i$ < len$; ++i$) {
String location = arr$[i$];
counter += this.loadBeanDefinitions((String)location);
}
return counter;
}
}
3.6、PathMatchingResourcePatternResource类
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
return findPathMatchingResources(locationPattern);
}
else {
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
else {
int prefixEnd = locationPattern.indexOf(":") + 1;
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
return findPathMatchingResources(locationPattern);
}
else {
//这里将会调用DefaultResourceLoader类中的getResource方法,在
DefaultResourceLoader中的getResource方法中将会调用gerResourceByPath()方法
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
为什么存在PathMatchingResourcePatternResource类,Spring除了提供ResourceLoader之外,还提供了ResourcePatternResolver来扩充ResourceLoader。
引进了一个新的前缀:classpath*:。和classpath:的差别就是,classpath*:可以搜索class path下所有满足
条件的资源(包括同名的资源),而classpath:则只能返回一个资源(即使存在多个)。
同时,Spring也提供了默认的实现:PathMatchingResourcePatternResource。
在它的内部包含一个DefaultResourceLoader的实例,所有ResourceLoader中定义
的方法它都委托给内部的DefaultResourceLoader实例进行处理,而自己只负责处理getResources方法的实现。
getResources(locationPattern)
-> findPathMatchingResources(locationPattern)
-> doFindPathMatchingFileResources(rootDirResource, subPattern)
-> doFindMatchingFileSystemResources(rootDir, subPattern)
-> retrieveMatchingFiles(rootDir, subPattern)
-> doRetrieveMatchingFiles(fullPattern, rootDir, result)
// classpath*:非模式匹配路径
-> findAllClassPathResources(location)
// 非classpath*:非模式匹配路劲
-> getResourceLoader().getResource(locationPattern)//这里将会调用DefaultResourceLoader中的getResource方法
上面列出了getResources方法的大致的执行流程,其中只列举了从文件系统匹配资源的过程。
3.7、DefaultResourceLoader类
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
if (location.startsWith(CLASSPATH_URL_PREFIX)) { // classpath:前缀
// ClassPathResource需要使用Class或ClassLoader,这里传入ClassLoader
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
} else {
try {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
} catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location); //如果不符合URL规范,则当做普通路径(如:test/resource.xml)处理
//这里将会回到最初的FileSystemXmlApplicationContext中getResourceByPath()方法进行调用,至此资源位置的定位已经结束
}
}
}