源码学习之IOC容器初始化三部曲(一)

SpringIOC容器初始化分为三个过程:

  1. BeanDefinition的Resource定位
  2. BeanDefinition的载入
  3. BeanDefinition的注册

这里主要介绍Resource定位过程。

 

 

Resource的资源定位

  我们知道IOC容器中管理的对象是BeanDefinition,这是POJO对象在spring内部的存储形式 ,我们所说的Resource定位指的是BeanDefinition的资源定位,也就是将我们外部的资源(xml,properties .etc)通过ResourceLoader加载进来并以Resource的形式返回。

 

Resource介绍

Java’s standard java.net.URL class and standard handlers for various URL prefixes unfortunately are not quite adequate enough for all access to low-level resources. For example, there is no standardized URL implementation that may be used to access a resource that needs to be obtained from the classpath, or relative to aServletContext. While it is possible to register new handlers for specialized URL prefixes (similar to existing handlers for prefixes such as http:), this is generally quite complicated, and the URL interface still lacks some desirable functionality, such as a method to check for the existence of the resource being pointed to.

          

根据spring文档上的说明,Resource设计的目的是丰富java对资源文件的操作,比如jdk中缺少的对类路径和容器中的资源的操作类。

 

Resource中的主要操作:

public interface Resource extends InputStreamSource {

   boolean exists();

   boolean isReadable();

   boolean isOpen();

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

 

          

这里列出了一部分Resource的类图,其中框出来的ClassPathResource和ServletContextResource就是针对类路径的资源和容器中的资源的Resource对象.

Reource中对于资源的处理策略:

           对于不同路径内容的资源,spring的处理策略:

Prefix

Example

Explanation

classpath:


classpath:com/myapp/config.xml

 

Loaded from the classpath

file:

file:///data/config.xml

Loaded as a URL, from the filesystem.

http:

http://myserver/logo.png

Loaded as a URL.

(none)

/data/config.xml

Depends on the underlying ApplicationContext

同时spring中还内置了一些资源对象,这里简单介绍下常用的几个:

UrlResource:

           对于形如ftp:,http:.file:这种路径的资源,可以使用UrlResource对路径进行包装并对资源进行操作。

eg:

"file:///some/resource/path/myTemplate.txt"
"http://myhost.com/resource/path/myTemplate.txt"
"ftp://XXXXXXX"
         

ClassPathResource:

           对于形如classpath:这种路径的资源,可以使用ClassPathResource对路径进行包装并对资源进行操作。

eg:

"classpath:some/resource/path/myTemplate.txt"

 

FileSystemResource:

           对于给定的文件或者URL,可以使用FileSysTemResource进行包装并对资源进行操作。

eg:

"some/resource/path/myTemplate.txt"
"E:/code/SpringMVC/src/resources/Beans.xml"

          

ServletContextResource:

           针对WEB应用根目录下的资源可以使用ServletContextResource进行包装并对资源进行操作。

eg:

"/WEB-INF/config.xml"
           

同一个资源也可以有多种表达方式,不同的路径表达在spring中也会使用不同的对象来进行包装:

//使用绝对路径

FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("E:/code/SpringMVC/src/resources/Beans.xml");
Resource定位时的返回的对象:

 

//使用classpath描述路径

FileSystemXmlApplicationContext context = new FileSystemXmlApplicationContext("classpath:Beans.xml");
Resource定位时的返回的对象:

 

        Resource可以理解是spring对资源的一种包装,它根据资源路径中的内容包装成对应的Resource对象来供使用。

          

ResourceLoader介绍

           上面提到Resource是spring对资源的一种包装,帮我们完成”包装”这个操作的就是ResourceLoader对象。

           ResourceLoader中的内容:

public interface ResourceLoader {

   /** Pseudo URL prefix for loading from the class path: "classpath:" */
   String CLASSPATH_URL_PREFIX = ResourceUtils.CLASSPATH_URL_PREFIX;


   Resource getResource(String location);


   ClassLoader getClassLoader();

}
 

 里面提供了getResource的方法,将location转化为Resource对象。

 

ResouceLoader的处理路线

 

我们再看下RsourceLoader的结构体系:

 

                                                  

           可以看到对于location的处理,ResourceLoader中有两个处理分支:
第一条是ResourceLoaderèResourcePatternResolverèPathMatchingResourcePatternResolver;

第二条是ResourceLoaderèDefaultResourceLoader。

 

 

 

我们看下ResourcePatternResolver、PathMatchingResourcePatternResolver和DefaultResourceLoader的内容:

ResourcePatternResolver

public interface ResourcePatternResolver extends ResourceLoader {

   /**
    * Pseudo URL prefix for all matching resources from the class path: "classpath*:"
    * This differs from ResourceLoader's classpath URL prefix in that it
    * retrieves all matching resources for a given name (e.g. "/beans.xml"),
    * for example in the root of all deployed JAR files.
    * @see org.springframework.core.io.ResourceLoader#CLASSPATH_URL_PREFIX
    */
   String CLASSPATH_ALL_URL_PREFIX = "classpath*:";


   Resource[] getResources(String locationPattern) throws IOException;

}
 

 

ResourcePatternResolver的实现类PathMatchingResourcePatternResolver:

public class PathMatchingResourcePatternResolver implements ResourcePatternResolver {

//省略部分无关代码

   @Override
   public Resource getResource(String location) {
      return getResourceLoader().getResource(location);
   }

   @Override
   public Resource[] getResources(String locationPattern) throws IOException {
      Assert.notNull(locationPattern, "Location pattern must not be null");
      if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {
         // a class path resource (multiple resources for same name possible)
         if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {
            // a class path resource pattern
            return findPathMatchingResources(locationPattern);
         }
         else {
            // all class path resources with the given name
            return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));
         }
      }
      else {
         // Only look for a pattern after a prefix here
         // (to not get fooled by a pattern symbol in a strange prefix).
         int prefixEnd = locationPattern.indexOf(":") + 1;
         if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {
            // a file pattern
            return findPathMatchingResources(locationPattern);
         }
         else {
            // a single resource with the given name
            return new Resource[] {getResourceLoader().getResource(locationPattern)};
         }
      }
   }
}

DefaultResourceLoader          

public class DefaultResourceLoader implements ResourceLoader {

//省略部分无关代码
  
   @Override
   public Resource getResource(String location) {
      Assert.notNull(location, "Location must not be null");
      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 new UrlResource(url);
         }
         catch (MalformedURLException ex) {
            // No URL -> resolve as resource path.
            return getResourceByPath(location);
         }
      }
   }
}

在介绍这两条路线之前,我们先来了解一下spring中类路径的概念:

classpath:和classpath*:的区别

在web应用中,java文件最终都会编译为class文件,这些class文件连同应用中的配置文件等都会存储在classes文件夹中,类路径的概念就是针对classes来说的。

 

                       classpath:classes找符合条件的文件,只能返回一个文件;

                       classpath*:classesjar中的classes寻找符合条件的文件,可以返回多个文件;

           了解了classpath:和classpath*:的区别后,我们再结合上面的代码来看:

 

DefaultResourceLoader实现ResourceLoader接口,主要针对classpath:为前缀的情况,通过getResource()获取单个资源;

PathMatchingResourcePatternResolver实现ResourcePatternResolver接口,扩展了ResourceLoader的功能,还可以处理classpath*:为前缀的情况,通过getresources()获取所有符合条件的资源。

 

           spring中通常描述文件还会结合Ant-style pattern来一起进行,在spring的配置中,经常可以看到这种类似的描述:

           classpath*:spring/**/config.xml

这种就是类路径结合Ant-style pattern,我们介绍下Ant-style pattern的代表意义:

 

匹配单个字符

*

匹配多个字符

**

匹配多层目录

 

èResource定位过程分析

           之前IOC容器的介绍中说过ApplicationContext作为IOC容器的高级实现,扩展了很多功能,其中就包括了通过ResourceLoader获取Resource的功能,针对不同类型的Resource,spring提供了对应的ApplicationContext的实现类,比如

FileSystemApplicationContext用来读取位于文件系统的资源;

ClassPathApplicationContext用来读取类路径的资源;

XmlWebApplicationContext用于读取Web应用中的资源。

 

我们用FileSystemApplicationContext为例,看下里面的Resource的定位过程:

FileSystemApplicationContext的构造方法:

public FileSystemXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
      throws BeansException {

   super(parent);
   setConfigLocations(configLocations);
   if (refresh) {
      refresh();
   }
}

 

IOC容器的初始化的入口都是调用基类AbstractApplicationContext的refresh()方法:

 

AbstractApplicationContext的refresh中定义ioc容器初始化的整个过程,我们这里暂时只关注Resource定位这一部分,它是在

 
ConfigurableListableBeanFactory beanFactory = this.obtainFreshBeanFactory();

进行.

 

这个方法会返回一个BeanFactory的实例:

AbstractApplicationContext#ObtainFreshBeanFactory

 
/**
 * Tell the subclass to refresh the internal bean factory.
 * @return the fresh BeanFactory instance
 * @see #refreshBeanFactory()
 * @see #getBeanFactory()
 */
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
   refreshBeanFactory();
   ConfigurableListableBeanFactory beanFactory = getBeanFactory();
   if (logger.isDebugEnabled()) {
      logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
   }
   return beanFactory;
}

 

关注refreshBeanFactory方法:

 
/**
 * This implementation performs an actual refresh of this context's underlying
 * bean factory, shutting down the previous bean factory (if any) and
 * initializing a fresh bean factory for the next phase of the context's lifecycle.
 */
@Override
protected final void refreshBeanFactory() throws BeansException {
   if (hasBeanFactory()) {
      destroyBeans();
      closeBeanFactory();
   }
   try {
      DefaultListableBeanFactory beanFactory = createBeanFactory();
      beanFactory.setSerializationId(getId());
      customizeBeanFactory(beanFactory);
      loadBeanDefinitions(beanFactory);
      synchronized (this.beanFactoryMonitor) {
         this.beanFactory = beanFactory;
      }
   }
   catch (IOException ex) {
      throw new ApplicationContextException("I/O error parsing bean definition source for " + getDisplayName(), ex);
   }
}

 

我将需要注意的地方标红,这里使用DeFaultListableBeanFactry作为容器的默认实现,然后调用loadBeanDefinitions(),

 

这个方法的具体执行是在FileSystemApplicationContext的基类AbstractXmlApplicationContext中:

AbstractXmlApplicationContext#loadBeanDefinitions(XmlBeanDefinitionReader):

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

 

这里说明下,在spring中是通过BeanDefinitionReader对资源进行操作,在我们这里,loadBeanDefinitions的执行是在XmlBeanDefinitionReader的基类AbstractBeanDefinitionReader中进行的,传入的参数就是我们的路径描述信息:

AbstractBeanDefinitionReader#loadBeanDefinitions(location,Set<Resource>):

/**
 * Load bean definitions from the specified resource location.
 * <p>The location can also be a location pattern, provided that the
 * ResourceLoader of this bean definition reader is a ResourcePatternResolver.
 * @param location the resource location, to be loaded with the ResourceLoader
 * (or ResourcePatternResolver) of this bean definition reader
 * @param actualResources a Set to be filled with the actual Resource objects
 * that have been resolved during the loading process. May be {@code null}
 * to indicate that the caller is not interested in those Resource objects.
 * @return the number of bean definitions found
 * @throws BeanDefinitionStoreException in case of loading or parsing errors
 * @see #getResourceLoader()
 * @see #loadBeanDefinitions(org.springframework.core.io.Resource)
 * @see #loadBeanDefinitions(org.springframework.core.io.Resource[])
 */
public int loadBeanDefinitions(String location, Set<Resource> 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 (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;
   }
}
可以看到在方法中对于不同分支的处理是通过这个判断来完成的:
resourceLoader instanceof ResourcePatternResolver

对于我们这里的FileSystemApplicationContext而言,使用的resourceLoader是在这个地方被指定的:

AbstractXmlApplicationContext#loadBeanfinitions(DefaultListableBeanFactory)

 
XmlBeanDefinitionReader beanDefinitionReader = new XmlBeanDefinitionReader(beanFactory);

这个调用的是XmlBeanDefinitionReader的基类AbstractBeanDefinitionReader的构造方法:

protected AbstractBeanDefinitionReader(BeanDefinitionRegistry registry) {

    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

    this.registry = registry;

    if(this.registry instanceof ResourceLoader) {

        this.resourceLoader = (ResourceLoader)this.registry;

    } else {

        this.resourceLoader = new PathMatchingResourcePatternResolver();

    }



    if(this.registry instanceof EnvironmentCapable) {

        this.environment = ((EnvironmentCapable)this.registry).getEnvironment();

    } else {

        this.environment = new StandardEnvironment();

    }



}

所以,在我们这边使用的具体的ResourceLoader对象是PathMatchingResourcePatternResolver:

 

 

我们回到AbstractBeanDefinitionReader#loadBeanDefinitions(location,Set<Resource>),去

PathMatchingResourcePatternResolver里面看下getResources的处理:

PathMatchingResourcePatternResolver#getResources(locationPattern)

@Override

public Resource[] getResources(String locationPattern) throws IOException {

   Assert.notNull(locationPattern, "Location pattern must not be null");

   if (locationPattern.startsWith(CLASSPATH_ALL_URL_PREFIX)) {

      // a class path resource (multiple resources for same name possible)

      if (getPathMatcher().isPattern(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()))) {

         // a class path resource pattern

         return findPathMatchingResources(locationPattern);

      }

      else {

         // all class path resources with the given name

         return findAllClassPathResources(locationPattern.substring(CLASSPATH_ALL_URL_PREFIX.length()));

      }

   }

   else {

      // Only look for a pattern after a prefix here

      // (to not get fooled by a pattern symbol in a strange prefix).

      int prefixEnd = locationPattern.indexOf(":") + 1;

      if (getPathMatcher().isPattern(locationPattern.substring(prefixEnd))) {

         // a file pattern

         return findPathMatchingResources(locationPattern);

      }

      else {

         // a single resource with the given name

         return new Resource[] {getResourceLoader().getResource(locationPattern)};

      }

   }

}

这里面的处理逻辑不复杂,通过locationPattern的前缀做第一步处理,然后根据是否是Ant-style,完成第二步处理:

对于是否是Ant-style pattern 的判断则是通过调用AntPathMatcher的isPattern来实现的:

@Override

public boolean isPattern(String path) {

   return (path.indexOf('*') != -1 || path.indexOf('?') != -1);

}

只要路径中出现了’*’或者’?’,那么就是ant-style。

 

1. 单个资源通过DefaultResourceLoadergetResource()来获取;

2. 多个资源且路径包含Ant-style通过PathMatchingResourcePatternResolverfindPathMatchingResources()来获取;

3. 多个资源且路径不包含Ant-style通过PathMatchingResourcePatternResolverfindAllClassPathResources()来获取。

 

 

获取到Resource以后,下面就将执行BeanDefinition的载入和注册工作。

总结

  1. Resouce是spring对资源的包装,也是我们对资源进行操作的对象;
  2. 我们通过ResourceLoader来获取Resource,通过解析资源路径的结构(前缀,是否Ant-style)决定具体调用的方法。

 

猜你喜欢

转载自blog.csdn.net/qq_23585245/article/details/89344254