欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Spring资源定位和载入

程序员文章站 2022-04-26 08:33:38
...

一、资源(Resource)

        资源就是我们程序需要得到的信息,这些信息通常都是以各式各样的文件的形式存在。有二进制的、文本的、加密的,或者本地的、网络的,从不同的维度可以分成很多中类型。Spring中为了我们提供一个统一的资源接口Resource,它提供了访问资源的统一操作,并且为我们提供了一些资源的默认实现类,如下所示:


Spring资源定位和载入
            
    
    博客分类: spring resource springresource

 

         我个人理解,资源定位就是将各种资源文件封装成相应的Resource类,这样我们可以通过Resource接口提供的操作,对资源进行统一的访问。

          我们在使用XmlBeanFactroy或者DefaultListableBeanFactory的时候,首先需要定义一个Resource来定位容器使用的BeanDefinition。这是使用ClassPathResource,这意味着Spring会在类路径中去寻找以文件形式存在的BeanDefinition。

ClassPathResource res = new ClassPathResource("bean.xml");

         这里定义的Resource并不能由XmlBeanFactory或者DefaultListableBeanFactory直接使用,Spring通过BeanDefinitionReader来对这些信息进行处理。从这里我们可以看出ApplicationContext相对这些容器的好处了,因为在ApplicationContext中,Spring已经为我们提供了一系列加载不同Resource的读取器的实现,而XmlBeanFactory或者DefaultListableBeanFactory只是一个纯粹的IoC容器,需要为它配置特定的读取器才可以。当然,有利有弊,使用XmlBeanFactory或者DefaultListableBeanFactory这些更底层的容器,能提高定制IoC容器的灵活性。

        下面就以实现ApplicationContext的实现类来谈谈Spring为我们提供的这些容器中是如何来进行资源定位和加载的。

 

二、资源定位

         Spring为我们提供了许多默认的ApplicationContext的实现类,如FileSystemXmlApplicationContext、ClassPathXmlApplicationContext以及XmlWebApplicationContext等。简单从这些名字上看,可以清楚的看到他们都分别提供了哪些不同Resource的读入功能,FileSystemXmlApplicationContext可以从文件系统载入Resource,ClassPathXmlApplicationContext可以从Class Path载入Resource,XmlWebApplicationContext可以从web容器中载入Resource等等。下面我们给出有关ApplicationContext继承体系的类图。


Spring资源定位和载入
            
    
    博客分类: spring resource springresource
         资源的定位以及载入是发生在何时的?以FileSystemXmlApplicationContext为例,我们具体谈谈ApplicationContext容器的实现类是在何时、如何定位以及加载资源的。从FileSystemXmlApplicationContext的构造函数开始,不管是调用哪个构造函数,FileSystemXmlApplicationContext都会最终调用到下面构造函数

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
            throws BeansException {
 
        super(parent);
                //配置文件位置
        setConfigLocations(configLocations);
        if (refresh) {
                       //调用AbstractApplicationContext中默认的实现方法,refresh()实现了容器初始化的过程
            refresh();
        }
    }

         Spring提供的ApplicationContext的大多数默认实现类(除了GenericApplicationContext这个继承分支的类除外,这个类将在最后做单独说明)的构造函数中最终都会调用到refresh(),refresh()是AbstractApplicationContext提供的一个默认实现,它实现了容器初始化所经历的一系列的操作,接下来我们就来看看这个方法的具体实现。

public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
            // Prepare this context for refreshing.
            prepareRefresh();
 
            // Tell the subclass to refresh the internal bean factory.
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
 
            // Prepare the bean factory for use in this context.
            prepareBeanFactory(beanFactory);
 
            try {
                // Allows post-processing of the bean factory in context subclasses.
                postProcessBeanFactory(beanFactory);
 
                // Invoke factory processors registered as beans in the context.
                invokeBeanFactoryPostProcessors(beanFactory);
 
                // Register bean processors that intercept bean creation.
                registerBeanPostProcessors(beanFactory);
 
                // Initialize message source for this context.
                initMessageSource();
 
                // Initialize event multicaster for this context.
                initApplicationEventMulticaster();
 
                // Initialize other special beans in specific context subclasses.
                onRefresh();
 
                // Check for listener beans and register them.
                registerListeners();
 
                // Instantiate all remaining (non-lazy-init) singletons.
                finishBeanFactoryInitialization(beanFactory);
 
                // Last step: publish corresponding event.
                finishRefresh();
            }
 
            catch (BeansException ex) {
                // Destroy already created singletons to avoid dangling resources.
                beanFactory.destroySingletons();
 
                // Reset 'active' flag.
                cancelRefresh(ex);
 
                // Propagate exception to caller.
                throw ex;
            }
        }
    }

         资源的定位和载入就发生在obtainFreshBeanFactory()方法里面,这这个方法里面会调用到一个模板方法refreshBeanFactory(),这个方法由实现了ApplicationContext的子类去提供,以用于定制自己想要的BeanFactroy,在FileSystemXmlApplicationContext所调用的是处于这个继承分支上其父类AbstractXmlApplicationContext所提供的默认实现,如下:

protected void loadBeanDefinitions(DefaultListableBeanFactory beanFactory) throws IOException {
        // Create a new XmlBeanDefinitionReader for the given BeanFactory.
                //创建一个读取器
        XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);
 
        // Configure the bean definition reader with this context's
        // resource loading environment.
                //未读取器指定资源加载器,因为ApplicationContext是继承了DefaultResourceLoader,所以这里参数未this
        beanDefinitionReader.setResourceLoader(this);
        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(XmlBeanDefinitionReader reader)
        loadBeanDefinitions(beanDefinitionReader);
    }

         从上面方法可以知道,在容器初始化的过程中容器会创建一个读取器用来读取相应的Resource,那么具体是怎么定位以及读取的,还需要接着看loadBeanDefinitions(XmlBeanDefinitionReader reader)中所做的工作了

protected void loadBeanDefinitions(XmlBeanDefinitionReader reader) throws BeansException, IOException {
//getConfigResources()是个模板方法,用于获得配置的Resources,默认实现放回null,可以在子类中重写这个方法为配置Resources做定制,ClassPathXmlApplicationContext就实现了这个方法用来获取配置的Resources
Resource[] configResources = getConfigResources();
        if (configResources != null) {
                        //由于直接指定了Resources,因此将文件定位为Resources的过程就不用了,直接调用AbstractBeanDefinitionReader提供的默认实现方法loadBeanDefinitions(Resource[] resources)载入资源
            reader.loadBeanDefinitions(configResources);
        }
               //同上,getConfigLocations()这个方法也可以被子类重写,做定制处理。当然也可以直接利用默认实现做定制处理,查看下面的默认实现方法。
        String[] configLocations = getConfigLocations();
        if (configLocations != null) {
                        //这里的参数是String[]类型的资源,需要定位成Resources供加载,因此相对于上面直接加载,这里会调用AbstractBeanDefinitionReader提供的默认实现方法loadBeanDefinitions(String[] locations),这个方法会先定位资源,然后在与上面那种情况一样,再调用loadBeanDefinitions(Resource[] resources)载入资源
            reader.loadBeanDefinitions(configLocations);
        }
    }
protected String[] getConfigLocations() {
               //可以重写getDefaultConfigLocations()方法做定制处理,XmlWebApplicationContext中就重写了该方法。
        return (this.configLocations != null ? this.configLocations : getDefaultConfigLocations());
    }

         我们接下来先看看资源是如何定位的吧,loadBeanDefinitions(String[] locations)中的方法很简单,就是循环这个String[],调用loadBeanDefinitions(String location)

public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException { 
           //资源定位的过程就发生在下面这个方法中
             return loadBeanDefinitions(location, null); 
}

         那我们来详细看看AbstractBeanDefinitionReader提供的loadBeanDefinitions(String location, Set actualResources)方法

 

public int loadBeanDefinitions(String location, Set actualResources) throws BeanDefinitionStoreException {
        ResourceLoader resourceLoader = getResourceLoader();
        if (resourceLoader == null) {
            throw new BeanDefinitionStoreException(
                    "Cannot import bean definitions from location [" + location + "]: no ResourceLoader available");
        }
                //如果资源加载器实现了自己的资源路径解析器,那么按照实现的模式定位资源
        if (resourceLoader instanceof ResourcePatternResolver) {
            // Resource pattern matching available.
            try {
                                //定位资源
                Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
                                //载入资源
                int loadCount = loadBeanDefinitions(resources);
                if (actualResources != null) {
                    for (int i = 0; i < resources.length; i++) {
                        actualResources.add(resources[i]);
                    }
                }
                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;
        }
    }

         如何实现了自己的资源路径解析器的classLoader将调用自己实现的getResource(String location)方法,在这里就不多说了,我们来看看为实现自己的资源路径解析器的classLoader是如何定位的。跟踪代码我们可以看到,这个时候将调用DefaultResourceLoader这个Spring提供的ClassLoader的默认实现类中的getResource(String location)方法。

public Resource getResource(String location) {
        Assert.notNull(location, "Location must not be null");
        //是不是classpath资源
               if (location.startsWith(CLASSPATH_URL_PREFIX)) {
            return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
        }
        else {
                       //不是classpath资源,那么尝试构造成一个网络资源
            try {
                // Try to parse the location as a URL...
                URL url = new URL(location);
                return new UrlResource(url);
            }
                         //既不是classpath资源,又不是网络资源,将调用用户自己的处理方式
            catch (MalformedURLException ex) {
                // No URL -> resolve as resource path.
                                //这是一个模板方法,在子类中重写该方法,将根据用户的方式进行资源定位
                return getResourceByPath(location);
            }
        }
    }

         从上面代码可以看到,Spring提供的默认资源加载器DefaultResourceLoader,会首先判断是不是classpath或者url资源,不过不是,将调用用户自己的getResourceByPath(location),不管哪种资源,最后返回的都是统一的资源接口Resource供程序调用。那我们在具体啊看看FileSystemXmlApplicationContext是如何重写getResourceByPath(String location)方法的吧。从名字来看,FileSystemXmlApplicationContext实现的getResourceByPath(String path)方法应该返回一个文件系统的资源Resource,从下面代码我们可以看到这个方法实现,最后retrun的确实是一个FileSystemResource。

protected Resource getResourceByPath(String path) {
        if (path != null && path.startsWith("/")) {
            path = path.substring(1);
        }
                //FileSystemXmlApplicationContext中得到的是一个FileSystemResource
        return new FileSystemResource(path);
    }

 

三、资源载入

        我们同样接着上面的例子来讲,我们可以看到,最后资源加载都会调用到读取器中的loadBeanDefinitions(Resource resource)方法,从我们给的例子FileSystemXmlApplicationContext中可以看到,Spring为FileSystemXmlApplicationContext提供的读取器是XmlBeanDefinitionReader,因此最终会调用到我们提供的这个读取器的loadBeanDefinitions(Resource resource)放进行资源载入。

public int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException {
        return loadBeanDefinitions(new EncodedResource(resource));
    }

         我们继续看调用的这个方法

public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
        Assert.notNull(encodedResource, "EncodedResource must not be null");
        if (logger.isInfoEnabled()) {
            logger.info("Loading XML bean definitions from " + encodedResource.getResource());
        }
 
        Set currentResources = (Set) this.resourcesCurrentlyBeingLoaded.get();
        if (currentResources == null) {
            currentResources = new HashSet(4);
            this.resourcesCurrentlyBeingLoaded.set(currentResources);
        }
        if (!currentResources.add(encodedResource)) {
            throw new BeanDefinitionStoreException(
                    "Detected recursive loading of " + encodedResource + " - check your import definitions!");
        }
        try {
                       //通过Resource提供的统一接口,将资源读取为InputStream来进行处理
            InputStream inputStream = encodedResource.getResource().getInputStream();
            try {
                                //将流转换成InputSource进行处理
                InputSource inputSource = new InputSource(inputStream);
                if (encodedResource.getEncoding() != null) {
                    inputSource.setEncoding(encodedResource.getEncoding());
                }
                                //对读取的资源进行处理(解析,bean的注册等)
                return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
            }
            finally {
                inputStream.close();
            }
        }
        catch (IOException ex) {
            throw new BeanDefinitionStoreException(
                    "IOException parsing XML document from " + encodedResource.getResource(), ex);
        }
        finally {
            currentResources.remove(encodedResource);
            if (currentResources.isEmpty()) {
                this.resourcesCurrentlyBeingLoaded.set(null);
            }
        }
    }

         从上面可以看到,资源都通过统一的资源接口Resource提供的操作,读为InputStream流进行处理的。然后接下来的工作就完全是对读取的数据进行解析、bena的注册等后续操作了。

  • Spring资源定位和载入
            
    
    博客分类: spring resource springresource
  • 大小: 20.8 KB
  • Spring资源定位和载入
            
    
    博客分类: spring resource springresource
  • 大小: 118.1 KB
相关标签: spring resource