[Spring] 深度解析系列 (1 )- 资源定位

bean 的声明方式,已经在概述中描述,此处就不再赘述, 声明成概述的样子,就已经能满足我们的大多数应用了。

在文章开始是之前,先上一段最精简的代码,本篇文章都是围绕下面这段代码进行的,抽丝拨茧留下的才是精华。

如下:

//首先进行BeanDefinition资源文件的定位,封装为Source的子类
ClassPathResource res = new ClassPathResource("application.xml");
//创建基本的IOC容器
DefaultListableBeanFactory factory = new DefaultListableBeanFactory();
//创建文件读取器,并进行回调设置。
XmlBeanDefinitionReader reader = new XmlBeanDefinitionReader(factory);
//资源加载解析
reader.loadBeanDefinitions(res);
MessageService bean = factory.getBean(MessageService.class);
String message = bean.getMessage();
System.out.println(message);

上面的代码,打破了代码的封装,直接穿透到了底层,执行流程是否是更加的清晰了。为了让读者有一个更清晰的认识,我特地画了一张流程 图。首先有一个宏观上的把控,这也是本篇文章的叙述线索。没有接触过源码的同学,此时看这张 图可能 还是有点模糊,不过没有关系,后面我会进行更加详细的分析,建议读者同学,在阅读文章 的同时,更跟随一下本地代码的执行,这样流程能更加的清晰。
流程图如下:

[Spring] 深度解析系列 (1 )- 资源定位_第1张图片
资源定位执行流程.png

图片有些失真,看不太清,没关系,上传到了github, 点我就可以

启动过程分析

接下来,从ClassPathXmlApplicationContext 的构造函数开始,分析定义Bean的资源文件解析过程。有些人会用FileSystemXmlApplicationContext 作为入口,这里我使用的是ClassPathXmlApplication。 这个 没有关系,无论以哪个类最为入口最终都会流向AbstractApplicationContext 的refresh()方法,效果都是一样的。

为了不脱离主线和节省篇幅,后边的流程我只截取最主干的部分就行解析。ClassPathApplicationContext 提供了多个构造函数,但是最终都会转到下面的方法来执行。

[Spring] 深度解析系列 (1 )- 资源定位_第2张图片
image.png

此构造方法的代码并不是很多,只有四行,并且在该方法中显示的使用了super 关键字,其实在类初始化的过程中,super关键字并不是必须的,编译器会默认帮我们添加上,只不过执行的是无参的构造方法,这里要执行父类参数为ApplicationContext类型的构造方法,才进行了显示的声明。

来看一下上面的构造方法中,最终都执行了那些操作,具体的操作都是由那些类执行的。
通过代码跟随,可以发现,super(parent)这个方法最终是由其基类(AbstractApplicationContext)来执行,执行了AbstractApplicationContext的无参构造方法和setParent()方法。其代码如下,省略了静态代码块的定义:

[Spring] 深度解析系列 (1 )- 资源定位_第3张图片
image.png

调用父类的构造方法执行完成后,返回ClassPathApplicationContext类,执行setConfigLocations(configLocations)方法,该方法的定义在类AbstractRefreshableConfigApplicationContext中:

[Spring] 深度解析系列 (1 )- 资源定位_第4张图片
image.png

在上面的方法中,看到参数采用的数组的方式,也就是说,资源文件支持下面两种实现方式,扩展性更好一些:

  • ClasspathResource res = new ClasspathResource("a.xml,b.xml");
  • ClasspathResource res = new ClasspathResource("new String(){'a.xml' , 'b.xml'}")

我们来看以上,程序执行到此处后,都做了哪些操作:

  1. 在AbstractApplicationContext中初始化了resourcePatternResolver(多资源文件的载入),用于获取Resource,关于何时使用后面再解释
  2. 将资源的定义路径保存在了configLocations数组中

到此,IOC容器根据资源定义路径获取Resouce的准备工作便完成了。

下面来看一下ClassPathXmlApplicationContext 中的关于refresh()方法的调用,实际调用的是AbstractApplicationContext中的refresh()方法,该方法定义如下:

public void refresh() throws BeansException, IllegalStateException {
    synchronized (this.startupShutdownMonitor) {
        //环境准备,获取容器启动的时间,设置活动标志,以及属性的初始化
        prepareRefresh();
        //在子类中启动resreshBeanfactory()方法
        ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
        //为BeanFactory配置容器属性
        prepareBeanFactory(beanFactory);
        try {
            //设置BeanFactory的后置处理
            postProcessBeanFactory(beanFactory);
            //调用后置处理,为这些后置处理器在Bean的定义中向容器注册
            invokeBeanFactoryPostProcessors(beanFactory);
            //注册Bean的后续处理,在Bean创建过程中调用
            registerBeanPostProcessors(beanFactory);
            //初始化上下文消息机制
            initMessageSource();
            //初始化上下文中事件机制
            initApplicationEventMulticaster();
            //初始化其他特殊的Bean
            onRefresh();
            //检查监听Bean并且将这些Bean向容器注册
            registerListeners();
            //初始化所有的singleton beans , 设置lazy-init = true 的bean除外
            finishBeanFactoryInitialization(beanFactory);
            //发布容器事件,结束Refresh过程
            finishRefresh();
        }catch (BeansException ex) {
            // 为防止Bean资源占用,在异常处理中,销毁已经生成的单件Bean
            destroyBeans();
            //重置Rest标志
            cancelRefresh(ex);
            // Propagate exception to caller.
            throw ex;
        }
    }
}

上面的代码便是整个执行的流程。可以看到使用了synchronized 关键字,防止没有初始化 完成,再次进行初始化。

refresh()方法主要为IOC容器Bean的生命周期提供管理条件。Spring IOC容器的生成是从refreshBeanFactory()方法开始的,也就是执行了下面的代码:


[Spring] 深度解析系列 (1 )- 资源定位_第5张图片
image.png

在AbstractApplicationContext抽象类中,只是进行了refreshBeanFactory()方法的定义,方法的实现是在其子类AbstractRefreshableApplicationContext中实现的,在子类的定义如下:


[Spring] 深度解析系列 (1 )- 资源定位_第6张图片
image.png

上面代码loadBeanDefinitions(beanFactory);我们说是一个委派模式,只是进行了方法的定义,具体实现则是由AbstractXmlApplicationContext类实现,在该方法中创建了读取器XmlBeanDefinitionReader的实例,然后把这个读取器在IOC容器中设置好,最后是启动读取器来完成对BeanDefinition在IOC容器中的载入,定义如下:


[Spring] 深度解析系列 (1 )- 资源定位_第7张图片
image.png

在XmlBeanDefinitionReader的初始化过程中,还进行了一些其他的操作,具体如下:
[Spring] 深度解析系列 (1 )- 资源定位_第8张图片
image.png

通过上面代码发现,在创建XmlBeanDefinitionReader的过程中,完成了resourceLoader和eviironment的赋值操作。

首先得到BeanDefinition信息的Resource定位,然后直接调用XmlBeanDefinitionReader来读取,具体的载入过程是委托给BeanDefinitionReader来完成的。因为使用的FileSystemXmlApplicationContext, getConfigResources()方法返回的是null,所以程序会走第二个分支

 //Xml Bean读取器加载Bean定义资源
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
    //获取资源定位
    Resource[] configResources = getConfigResources();
    if (configResources != null) {
        //Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位 
        reader.loadBeanDefinitions(configResources);
    }
    //如果子类获取的Bean定义为空,则获取FileSystemXmlApplication构造方法中setConfigLocations方法设置的资源。
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        reader.loadBeanDefinition(configLocations);
    }
}

程序分析到这,来梳理一下上面的执行流程。

在ClassPathXmlApplicationContext 一共做了三件事

调用了父类的构造器,进行了初始化
设置了BeanDefinition的定义路径
执行了Refresh()方法
refresh()方法来启动整个BeanDefinition的载入过程

创建容器 DefaultListableBeanFactory
创建了XmlBeanDefinitionReader
开始准备通过reader来加载资源
AbstractBeanDefinitionReader读取Bean定义资源,AbstractBeanDefinitionReader的loadBeanDefinitions方法源码如下:

//方法重载
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
    Assert.notNull(locations, "Location array must not be null");
    int counter = 0;
    String[] var3 = locations;
    int var4 = locations.length;
    for(int var5 = 0; var5 < var4; ++var5) {
        String location = var3[var5];
        counter += this.loadBeanDefinitions(location);
    }
    return counter;
}
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
    return this.loadBeanDefinitions(location, (Set)null);
}
//重载的最终执行方法
public int loadBeanDefinitions(String location, Set actualResources) throws BeanDefinitionStoreException {
    //获取资源加载器
    //上面提到过,XmlBeanDefinitionReader初始化时,在其父类中执行了加载器的初始化操作
    //resourceLoader的类型为PathMatchingResourcePatternResolver
    ResourceLoader resourceLoader = getResourceLoader();
    if (resourceLoader == null) {
        throw new BeanDefinitionStoreException("Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
    }
    //判断类型
    if (resourceLoader instanceof ResourcePatternResolver) {
        try {
            //将指定位置的Bean定义资源文件解析转化为Resource
            //加载多个指定位置的资源定义文件
            Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
            //读取Resource
            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;
    }
}

public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
        Assert.notNull(resources, "Resource array must not be null");
        int counter = 0;
        Resource[] var3 = resources;
        int var4 = resources.length;

        for(int var5 = 0; var5 < var4; ++var5) {
            Resource resource = var3[var5];
            counter += this.loadBeanDefinitions((Resource)resource);
        }

        return counter;
    }

上面的方法主要进行了两件事:

调用资源加载器获取资源 resourceLoader.getResource(location)
真正执行加载功能的是子类XmlBeanDefinitionReader的loadBeanDefinitions方法
loadBeanDefinitions()方法在AbstractBeanDefinitionReader中并没有具体的实现,它会转到XmlBeanDefinitionReader中的loadBeanDefinitions(Resource resource)中运行:

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
    //对xml资源进行编码处理
    return this.loadBeanDefinitions(new EncodedResource(resource));
}
//方法重载,转入此方法执行
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
    Assert.notNull(encodedResource, "EncodedResource must not be null");
    if(this.logger.isInfoEnabled()) {
        this.logger.info("Loading XML bean definitions from " + encodedResource.getResource());
    }
    Object currentResources = (Set)this.resourcesCurrentlyBeingLoaded.get();
    if(currentResources == null) {
        currentResources = new HashSet(4);
        this.resourcesCurrentlyBeingLoaded.set(currentResources);
    }
    if(!((Set)currentResources).add(encodedResource)) {
        throw new BeanDefinitionStoreException("Detected cyclic loading of " + encodedResource + " - check your import definitions!");
    } else {
        int var5;
        try {
            //将资源文件转换为类型为InputStream的I/O流
            InputStream ex = encodedResource.getResource().getInputStream();
            try {
                //从InputStream中得到xML的解析源
                InputSource inputSource = new InputSource(ex);
                //编码如果不为null, 则设置inputSource的编码
                if(encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                //读取数据
                var5 = this.doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            } finally {
                ex.close();
            }
        } catch (IOException var15) {
            throw new BeanDefinitionStoreException("IOException parsing XML document from " + encodedResource.getResource(), var15);
        } finally {
            ((Set)currentResources).remove(encodedResource);
            if(((Set)currentResources).isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.remove();
            }
        }
        return var5;
    }
}

protected int doLoadBeanDefinitions(InputSource inputSource, Resource resource)throws BeanDefinitionStoreException {
        try {
            //转化为Document 对象
            Document doc = doLoadDocument(inputSource, resource);
            //启动对Bean定义解析的详细过程,会用到Spring Bean的配置规则
            return registerBeanDefinitions(doc, resource);
        }
        //删除了部分catch语句
        catch (....) {
            throw ex;
        }
    }

将XML文件转换成Document对象,解析过程由documentLoader实现。到此就完成了xml文件的读取工作。
下面详细分析一下,文件是如何读取的? , 回到下面的这段代码

 //Xml Bean读取器加载Bean定义资源
protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
    //获取资源定位
    Resource[] configResources = getConfigResources();
    if (configResources != null) {
        //Xml Bean读取器调用其父类AbstractBeanDefinitionReader读取定位 
        reader.loadBeanDefinitions(configResources);
    }
    //如果子类获取的Bean定义为空,则获取FileSystemXmlApplication构造方法中setConfigLocations方法设置的资源。
    String[] configLocations = getConfigLocations();
    if (configLocations != null) {
        reader.loadBeanDefinition(configLocations);
    }
}

程序有两个分支,一个是获取Resource[] 数组的分支,一个是 String[]数组的分支。在创建ClassPathXmlApplicationContext 的时候,初始化了getConfigLocations()方法,所以程序会走第二个分支。configLoactions的内容就是 classpath:application.xml .之后程序进入到XmlBeanDefinitionReader中将 文件转换为流。

运行 AbstractBeanDefinitionReader中的 loadBeanDefinitions(String location, @Nullable Set actualResources) ,这段代码很长,我只是截取了部分内容 ,定义如下:

[Spring] 深度解析系列 (1 )- 资源定位_第9张图片
image.png

getResourceLoader()方法,返回了 ClassPathXmlApplicationContext ,ClassPathXmlApplicationContext 是 ResourcePatternResolver的子类。所以会执行 Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location); 此处代码就是资源定位的核心。 PathMatchingResourcePatternResolver 是 ResourcePatternResolver 的实现类 ,最终会执行 它的getResource()方法。


[Spring] 深度解析系列 (1 )- 资源定位_第10张图片
image.png

经过一系列判断,最终程序会走 return new Resource[] {getResourceLoader().getResource(locationPattern)}; 这段代码 ,getResourceLoader()返回的是DefaultResourceLoader 类。执行的是它的getResource()方法,方法定义如下:


[Spring] 深度解析系列 (1 )- 资源定位_第11张图片
image.png

程序最终是由这段代码,进行文件的加载:

return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());

getClassLoader()返回的是 AppClassLoader .

ClassPathResource 作为Resource的子类,自然可以得到 Resource[]类型的数组。那么只要调用 getInputStream()方法,便可以得到文件流。
那么下面看一下,是否是这样实现的。

下面的代码我就直接罗列了, 没有什么太复杂的逻辑。

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
                # 在进行一个封装
        return loadBeanDefinitions(new EncodedResource(resource));
    }
[Spring] 深度解析系列 (1 )- 资源定位_第12张图片
image.png

和预想的一样,最后调用了getInputStream()方法,使用了InputStream将流转换成字节流。既然有了流,就可以创建Document了。以便来解析文件中定义的内容。

既然分析到这了,就多说一点,上面的文件加载最终是通过ClassLoader加载的。
ClassLoader: 负责加载类的对象。是一个抽象类。通过一个指定类的全限定名,找到对应的Class字节码文件,然后加载它并转化成一个java.lang.Class类的实例。
ClassLoader 分类:

  • 启动类加载器: 负责加载\lib 目录下的类加载到虚拟机内存中
  • 扩展类加载器:这个类负责加载\lib\ext目录下的类库
  • 应用类加载器:这个类加载器负责加载用户类路径 classpath 下的类库,一般自己编写的类都是由这个加载器进行加载的。

除了上面的三种加载器,还可以自定义类加载器,不过一般用不到。
说到类加载器自然离不开双亲委派模型- 它的工作原理是:如果一个类加载器收到类的加载请求,不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完整,这样层层递进,最后所有的请求被传到顶层的类加载器。只有当父类加载器无法加载,才会交给子类加载器去加载。这样的好处是防止类的重复加载。

ClassLoader是从classPath中读取资源的一个类,一般都是用来加载class, 实际上,但凡是处在classpath中的文件,统称为资源,都可以是classLoader来读取。推荐使用此种方式Thread.currentThread().getContextClassLoader() 获取classLoader , 尽量少用 ClassLoader.getSystemClassLoader()的方式。

本篇文章就到这,下次来写xml文件的读取和标签的解析。

你可能感兴趣的:([Spring] 深度解析系列 (1 )- 资源定位)