BeanDefinition资源定位
Spring第一步,资源来开路。
Spring资源的加载逻辑比较复杂,我们以相对简单的FileSystemXmlApplicationContext为例来讲解BeanDefinition的定位过程。
后续的文章中,将更进一步的带领大家逐步深入地了解Spring的的运行流程
FileSystemApplicationContext
FileSystemXmlApplicationContext 用于从文件系统中加载指定的Xml文件,来以此作为Spring资源,下面是构造函数
public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
throws BeansException {
//初始化基类,主要是AbstractApplicationContext的初始化
super(parent);
//设置资源(xml文件)
setConfigLocations(configLocations);
if (refresh) {
//调用AbstractApplicationContext的refresh方法,进行容器的刷新
refresh();
}
}
refresh
refresh是Spring容器的核心方法,我们此文中仅仅探讨前两项内容。
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
//准备刷新容器,通知子类刷新容器
prepareRefresh();
// Tell the subclass to refresh the internal bean factory.
//获取BeanFactory,实际是获取子类配置的BeanFactory
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
.....
//下面代码与BeanDefinition资源的定位、载入、注册关系不大,不在此处分析,后续文章中进行分析
prepareRefresh
protected void prepareRefresh() {
this.startupDate = System.currentTimeMillis();
//获取激活锁,设置激活状态
synchronized (this.activeMonitor) {
this.active = true;
}
if (logger.isInfoEnabled()) {
logger.info("Refreshing " + this);
}
// Initialize any placeholder property sources in the context environment
//初始化属性源,交由子类配置(FileSystemXmlApplicationContext没有重写此方法
initPropertySources();
// Validate that all properties marked as required are resolvable
// see ConfigurablePropertyResolver#setRequiredProperties
//验证所有标记为必须的属性,此处没有进行任何必须的配置,所以验证通过
getEnvironment().validateRequiredProperties();
}
obtainFreshBeanFactory
obtainFreshBeanFactory实际是调用了子类AbstractRefreshableApplicationContext的实现,
@Override
protected final void refreshBeanFactory() throws BeansException {
//如果之前有BeanFactory了,就销毁重新构建一个
if (hasBeanFactory()) {
destroyBeans();
closeBeanFactory();
}
try {
//创建一个BeanFactory,默认实现是DefaultListableBeanFactory
DefaultListableBeanFactory beanFactory = createBeanFactory();
//设置id
beanFactory.setSerializationId(getId());
//1.设置一些基本属性 allowBeanDefinitionOverriding,allowCircularReferences
// 是否允许beanDefinition重载,允许循环引用
//2.设置一个自动注入候选者判断器QualifierAnnotationAutowireCandidateResolver
// 专用于@Querifiler @Value的条件判断
customizeBeanFactory(beanFactory);
//定位、加载、注册beanDefinitiion,交由子类实现,因为不同的业务场景下,资源的未知是不同的,所以父类不能确定具体的资源加载形式,所以交由子类实现,对于xml来说是交由子类AbstractXmlApplicationContext实现,
loadBeanDefinitions(beanFactory);
synchronized (this.beanFactoryMonitor) {
this.beanFactory = beanFactory;
}
}
catch (IOException ex) {
throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
}
}
//这里是子类AbstractXmlApplicationContext实现
@Override
protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws BeansException, IOException {
// Create a new XmlBeanDefinitionReader for the given BeanFactory.
//创建一个XmlBeanDefinitionReader,并初始化
//向XmlBeanDefinitionReader
//设置一个BeanDefinitionRegistry
//设置一个ResourceLoader
//因为DefaultListableBeanFactory不是一个ResoueceLoader,所以这里用了默认值PathMatchingResourcePatternResolver
//设置环境,用的默认值StandardEnvironment
//但是不要慌,下面的代码中,就会使用FileSystemXmlApplicationContext来替换这两个值
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
// Configure the bean definition reader with this context's
// resource loading environment.
//使用FileSystemXmlApplicationContext中的环境替换
beanDefinitionReader.setEnvironment(this.getEnvironment());
//使用FileSystemXmlApplicationContext来作为资源加载器
beanDefinitionReader.setResourceLoader(this);
//设置一个实体解析器,用于获取XML中的校验DTD文件,下篇文章中会使用到,这里是一个伏笔
beanDefinitionReader.setEntityResolver(new ResourceEntityResolver(this));
// Allow a subclass to provide custom initialization of the reader,
// then proceed with actually loading the bean definitions.
//设置验证类型
initBeanDefinitionReader(beanDefinitionReader);
//定位、加载、注册
loadBeanDefinitions(beanDefinitionReader);
}
//
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//我们使用的是String配置的资源,不会走这个加载
Resource[] configResources = getConfigResources();
if (configResources != null) {
reader.loadBeanDefinitions(configResources);
}
//从此处进入
String[] configLocations = getConfigLocations();
if (configLocations != null) {
//使用XmlBeanDefinitionReader定位、加载、注册指定的configLocations
reader.loadBeanDefinitions(configLocations);
}
}
//这里传入的String[]类型,所以调用的是XmlBeanDefinitionReader的父类AbstractBeanDefinitionReader的方法
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
Assert.notNull(locations, "Location array must not be null");
//加载的BeanDefinition的个数统计
int counter = 0;
//迭代,加载所有的location
for (String location : locations) {
//加载并统计数量
counter += loadBeanDefinitions(location);
}
return counter;
}
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
return loadBeanDefinitions(location, null);
}
//加载流程的具体实现
public int loadBeanDefinitions(String location, Set<Resource> actualResources) throws BeanDefinitionStoreException {
//获取ResoruceLoader,实际就是上文中传入的FileSystemXmlApplicaitonContext
ResourceLoader resourceLoader = getResourceLoader();
if (resourceLoader == null) {
throw new BeanDefinitionStoreException(
"Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
}
//FileSystemXmlApplicaitonContext实现了这个ResourcePatternResolver接口
if (resourceLoader instanceof ResourcePatternResolver) {
// Resource pattern matching available.
try {
//这行很重要,这里就是资源定位和加载的核心代码,这里是利用FileSystemXmlApplicaitonContext来进行资源的定位和加载,具体分析见下文的资源定位
Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
//资源的加载和BeanDefintiion的注册
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 {
// Can only load single resources by absolute URL.
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;
}
}
资源定位
下面是Spring真正加载资源的逻辑
//FileSystemXmlApplicationContext本身并没有进行资源的加载,而是调用了基类AbstractApplicaiotnContext资源加载的方法,注意此处的方法名是 getResources ,
//内部实际是调用自己内部的resourcePatternResolver,这个resourcePatternResolver是在AbstractApplicationContext实例化是被创建的,是一个PathMatchingResourcePatternResolver
//所以这里资源的加载是先交给PathMatchingResourcePatternResolver来解析
public Resource[] getResources(String locationPattern) throws IOException {
return this.resourcePatternResolver.getResources(locationPattern);
}
PathMatchingResourcePatternResolver的解析
public Resource[] getResources(String locationPattern) throws IOException {
Assert.notNull(locationPattern, "Location pattern must not be null");
//如果以 classpath*: 开头
if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
// a class path resource (multiple resources for same name possible)
//如果是Ant表达式,则进入此
if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
// a class path resource pattern
return findPathMatchingResources(locationPattern);
}
else {
//否则在classpath中寻找资源
// all class path resources with the given name
return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
}
}
//如果不以classpath*:开头
else {
//查看 : 后面的路径
int prefixEnd = locationPattern.indexOf(":") + 1;
//如果:后的路径符合ant表达式
if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
// a file pattern
return findPathMatchingResources(locationPattern);
}
else {
//最后 : 后的表达式不是ant表达式的话,就调用自己的ResourceLoader进行资源的加载
//注意 PathMatchingResourcePatternResolver的构造函数中,已经把AbstractApplicationCotexnt作为了自己的资源加载器,所以此处调用的方法就是AbstractApplicationContext的getResource,注意这个方法的名称,是getResource,不是getResources
//因为AbstractApplicationContext继承了DefaultResourceLoader,所以此处调用的getResource,实际调用的DafaultResourceLoader的getResource方法,
// a single resource with the given name
return new Resource[] {getResourceLoader().getResource(locationPattern)};
}
}
}
DefaultResourceLoader.getResource
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
//如果 location 以 classpath: 开头,就返回一个ClassPathResouce
if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
//不以classpath: 开头的话,就尝试使用url来获取资源,如果不抛出异常,就返回一个UrlResource资源
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
//异常出现,说明url不能正确解析,只好调用 getResourceByPath 来加载资源
//注意 DefaultResourceLoader中已有getResourceByPath的实现,就是把location当作一个ClassPathContextResource来解析,但是在此处并不是,因为FileSystemXmlApplicationContext重写了这个方法,所以getResourceByPath实际是调用的FileSystemXmlApplicationContext中的实现,
return getResourceByPath(location);
}
}
}
FileSystemXmlApplicationContext.getResourceByPath
//可以看出,把资源当作一个FileSystemResource返回,至此,我们就找到了真正的资源位置,完成了资源的定位
protected Resource getResourceByPath(String path) {
if (path != null && path.startsWith("/")) {
path = path.substring(1);
}
return new FileSystemResource(path);
}
总结与回顾
我们可以发现Spring中对于资源的定位是比较复杂的,我大致梳理一下,大致逻辑如下:
使用PathMatchingResourcePatternResolver来解析Ant表达式路径,成功则返回,失败则向下
如果是classpath* 开头的资源 ,
- 符合Ant规则的按照Ant路径解析
- 不符合Ant规则的,解析成ClasspathResource
不是classpath*开头的资源
- 如果 :后面的路径符合Ant规则,按照Ant路径解析
- :后的路径不符合Ant规则,调用传入的ResouceLoader来解析(AbstractApplicaitonContext把这份工作交由DefaultResourceLoader来执行)
使用DefaultResouceLoader加载资源
- 如果资源以 classpath: 开头,返回 ClassPathResource
不是 classpath: 开头
- 按照Url解析不出错,返回UrlResource
- 解析Url出错了,调用getResourceByPath来解析(这个方法被FileSystemXmlApplicationContext重写了)
以上就是FileSystemXmlApplicationContext定位资源的基本流程。