资讯专栏INFORMATION COLUMN

Spring源码深度解析-BeanDefinition资源定位

jsdt / 2906人阅读

摘要:后续的文章中,将更进一步的带领大家逐步深入地了解的的运行流程用于从文件系统中加载指定的文件,来以此作为资源,下面是构造函数初始化基类,主要是的初始化设置资源文件调用的方法,进行容器的刷新是容器的核心方法,我们此文中仅仅探讨前两项内容。

BeanDefinition资源定位
Spring第一步,资源来开路。
链接:https://juejin.im/post/5d2945...

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 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定位资源的基本流程。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/75329.html

相关文章

  • Spring专题之IOC源码分析

    摘要:前言以下源码基于版本解析。实现源码分析对于的实现,总结来说就是定位加载和注册。定位就是需要定位配置文件的位置,加载就是将配置文件加载进内存注册就是通过解析配置文件注册。下面我们从其中的一种使用的方式一步一步的分析的实现源码。 前言 以下源码基于Spring 5.0.2版本解析。 什么是IOC容器? 容器,顾名思义可以用来容纳一切事物。我们平常所说的Spring IOC容器就是一个可以容...

    不知名网友 评论0 收藏0
  • Spring-IOC容器容器

    摘要:使用别名时,容器首先将别名元素所定义的别名注册到容器中。调用的方法向容器注册解析的通过对对象的解析和封装返回一个通过这个来注册对象当调用向容器注册解析的时,真正完成注册功能的是。 文章参考来自:https://www.cnblogs.com/ITtan... 文章代码来自 spring-boot 1.4.1 Release版本 Spring IoC容器对Bean定义资源的载入是从ref...

    BigTomato 评论0 收藏0
  • 一起来读Spring源码吧(一)容器的初始化

    摘要:对于开发者来说,无疑是最常用也是最基础的框架之一。概念上的东西还是要提一嘴的用容器来管理。和是容器的两种表现形式。定义了简单容器的基本功能。抽象出一个资源类来表示资源调用了忽略指定接口的自动装配功能委托解析资源。 对于Java开发者来说,Spring无疑是最常用也是最基础的框架之一。(此处省略1w字吹Spring)。相信很多同行跟我一样,只是停留在会用的阶段,比如用@Component...

    libxd 评论0 收藏0
  • 【小家SpringSpring IoC是如何使用BeanWrapper和Java内省结合起来给Be

    摘要:从层层委托的依赖关系可以看出,的依赖注入给属性赋值是层层委托的最终给了内省机制,这是框架设计精妙处之一。当然分享到你的朋友圈让更多小伙伴看到也是被作者本人许可的若对技术内容感兴趣可以加入群交流高工架构师群。 每篇一句 具备了技术深度,遇到问题可以快速定位并从根本上解决。有了技术深度之后,学习其它技术可以更快,再深入其它技术也就不会害怕 相关阅读 【小家Spring】聊聊Spring中的...

    waruqi 评论0 收藏0
  • Spring IOC源码深度解析

    摘要:这个读取器可以读取注解标注下的所有定义,并最终添加到的中。处理注解的配置类读取每一个配置类中定义的,加入到容器中。 IOC的核心就是代码入口就在AbstractApplictionContext public void refresh() throws BeansException, IllegalStateException { synchronized (t...

    blastz 评论0 收藏0

发表评论

0条评论

jsdt

|高级讲师

TA的文章

阅读更多
最新活动
阅读需要支付1元查看
<