美文网首页Java面试
spring源码分析(一)资源文件的加载

spring源码分析(一)资源文件的加载

作者: 编程易行 | 来源:发表于2018-07-16 00:22 被阅读18次

    spring是日常开发中用的非常多的一个框架,那么spring究竟是如何帮我们简化开发?短短的几行配置里,spring究竟做了啥?后续几篇博客会分析下spring的源码。

    从一个配置文件开始

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xsi:schemaLocation="
            http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
        <bean id="person" class="com.hdj.learn.spring.demo.Person">
            <property name="name" value="duanji"></property>
        </bean>
    </beans>
    

    使用xml配置spring的话,这个配置可以说非常熟悉了。

    然后如果想通过spring容器来加载配置这个类,简单的代码如下。

    public class TestDemo {
    
        public static void main(String[] args) {
            ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
            Person person = (Person) context.getBean("person");
            System.out.println("person name:" + person.getName());
        }
    }
    

    假想下,如果让我来写spring,那么我要做的第一步是啥?我想会是找到配置文件,加载它(这里先不管使用java配置的方式)

    Spring对资源的封装

    spring对于各种各样的资源抽象了一个接口,比如文件资源或者类路径的资源。

    public interface Resource extends InputStreamSource {
    
       boolean exists();
    
       default boolean isReadable() {
           return true;
       }
    
       default boolean isOpen() {
           return false;
       }
    
       default boolean isFile() {
           return false;
       }
    
       URL getURL() throws IOException;
    
       URI getURI() throws IOException;
    
       File getFile() throws IOException;
    
       long contentLength() throws IOException;
    
       long lastModified() throws IOException;
    
       Resource createRelative(String relativePath) throws IOException;
    
       String getFilename();
    
       String getDescription();
    
    }
    

    所有的资源都会通过这个类来抽象。

    那么简单的说来,spring容器加载资源的第一步,就是加载配置文件,将这个配置文件转换成spring的抽象资源Resource

    源码实现

    源码还是比较简单的
    1)在构造函数里,将路径处理下(替换占位符)存储在成员变量里
    2)将配置文件转换为spring的一个资源(具体步骤在loadBeanDefinition里)

    解析路径代码

    public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {
    
        //1、父类设置ResourcePatternResolver
        super(parent);
        //设置路径到configLocations成员变量里,中间会执行一步,替换${}这样的占位符,
        //比如路径填了 ${path}/application.xml,可以被替换为.properties里的路径
        setConfigLocations(configLocations);
        if (refresh) {
                    //实际启动spring容器
            refresh();
        }
    }
    
    
    public void setConfigLocations(String... locations) {
        if (locations != null) {
            Assert.noNullElements(locations, "Config locations must not be null");
            this.configLocations = new String[locations.length];
            for (int i = 0; i < locations.length; i++) {
                //把占位符给换掉 比如${path.xxx} 换成PropertyPlaceHolder的值
                this.configLocations[i] = resolvePath(locations[i]).trim();
            }
        }
        else {
            this.configLocations = null;
        }
    }
    

    AbstractXmlApplicationContext类

    protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
        Resource[] configResources = getConfigResources();
        if (configResources != null) {
            reader.loadBeanDefinitions(configResources);
        }
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
            reader.loadBeanDefinitions(configLocations);
        }
    }
    

    最后会回调DefaultResourceLoader的getResources方法

    public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
    
        for (ProtocolResolver protocolResolver : this.protocolResolvers) {
            Resource resource = protocolResolver.resolve(location, this);
            if (resource != null) {
                return resource;
            }
        }
    
        if (location.startsWith("/")) {
            return getResourceByPath(location);
        }
        else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            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 (ResourceUtils.isFileURL(url) ? new FileUrlResource(url) : new UrlResource(url));
            }
            catch (MalformedURLException ex) {
                // No URL -> resolve as resource path.
                // 没有填前缀,最后会被解析为 ClassPathContextResource
                return getResourceByPath(location);
            }
        }
    }
    
    protected Resource getResourceByPath(String path) {
        return new ClassPathContextResource(path, getClassLoader());
    }
    

    由于这里没有配置协议前缀(比如classpath:xxx)最后资源会被解析为ClassPathContextResource

    ResourceLoader和ResourcePatternLoader

    总结

    spring启动会去加载配置文件,将配置文件转换为spring可以识别的Resource

    相关文章

      网友评论

        本文标题:spring源码分析(一)资源文件的加载

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