美文网首页
[Spring] 深度解析系列 (1 )- 资源定位

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

作者: 起个名忒难 | 来源:发表于2019-11-15 23:59 被阅读0次

    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);
    

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

    资源定位执行流程.png
    图片有些失真,看不太清,没关系,上传到了github, 点我就可以
    启动过程分析

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

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

    image.png

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

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

    image.png

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

    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()方法开始的,也就是执行了下面的代码:


    image.png

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


    image.png

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


    image.png
    在XmlBeanDefinitionReader的初始化过程中,还进行了一些其他的操作,具体如下:
    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<Resource> 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<Resource> actualResources) ,这段代码很长,我只是截取了部分内容 ,定义如下:

    image.png

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


    image.png

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


    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));
        }
    
    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 )- 资源定位

          本文链接:https://www.haomeiwen.com/subject/tqxrbctx.html