循序渐进Srping源码-loadBeanDefinitions方法

loadBeanDefinitions


这篇文章只讲一个方法:

public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
   ResourceLoader resourceLoader = getResourceLoader();
   if (resourceLoader == null) {
      throw new BeanDefinitionStoreException(
            "Cannot load bean definitions from location [" + location + "]: no ResourceLoader available");
   }

   if (resourceLoader instanceof ResourcePatternResolver) {
      // Resource pattern matching available.
      try {
         Resource[] resources = ((ResourcePatternResolver) resourceLoader).getResources(location);
         int count = loadBeanDefinitions(resources);
         if (actualResources != null) {
            Collections.addAll(actualResources, resources);
         }
         if (logger.isTraceEnabled()) {
            logger.trace("Loaded " + count + " bean definitions from location pattern [" + location + "]");
         }
         return count;
      }
      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 count = loadBeanDefinitions(resource);
      if (actualResources != null) {
         actualResources.add(resource);
      }
      if (logger.isTraceEnabled()) {
         logger.trace("Loaded " + count + " bean definitions from location [" + location + "]");
      }
      return count;
   }
}

代码逻辑并不复杂,但其背后的概念却值得我们细细推敲。

Resource

以配置的方式描述对象及对象之间的关系是spring IOC的基础,spring支持多种方式的配置,这种各种各样的配置源无疑是需要抽象的,于是spring引入了Resource:

/**
 * Interface for a resource descriptor that abstracts from the actual
 * type of underlying resource, such as a file or class path resource.
 *
 * <p>An InputStream can be opened for every resource if it exists in
 * physical form, but a URL or File handle can just be returned for
 * certain resources. The actual behavior is implementation-specific.
 *
 * @author Juergen Hoeller
 * @since 28.12.2003
 * @see #getInputStream()
 * @see #getURL()
 * @see #getURI()
 * @see #getFile()
 * @see WritableResource
 * @see ContextResource
 * @see UrlResource
 * @see FileUrlResource
 * @see FileSystemResource
 * @see ClassPathResource
 * @see ByteArrayResource
 * @see InputStreamResource
 */
public interface Resource extends InputStreamSource {

注释写的很清楚:

从各种底层资源的实际类型,例如文件或类路径资源等抽象出一个统一的资源描述的接口。

InputStream可以打开每个以物理形式存在的资源,但像URL或文件句柄这样的非物理形式的资源也是可以返回的,实际的行为是特定于实现的。

在这里,我们还看到了那些很熟悉的Resource子类,诸如FileSystemResource、ClassPathResource等。

看看接口的定义,也是很有意思的,上来就是4个判断:

  • 是否存在?
  • 是否打开?
  • 是否可写?
  • 是不是文件?
/**
 * Determine whether this resource actually exists in physical form.
 * <p>This method performs a definitive existence check, whereas the
 * existence of a {@code Resource} handle only guarantees a valid
 * descriptor handle.
 */
boolean exists();

/**
 * Indicate whether non-empty contents of this resource can be read via
 * {@link #getInputStream()}.
 * <p>Will be {@code true} for typical resource descriptors that exist
 * since it strictly implies {@link #exists()} semantics as of 5.1.
 * Note that actual content reading may still fail when attempted.
 * However, a value of {@code false} is a definitive indication
 * that the resource content cannot be read.
 * @see #getInputStream()
 * @see #exists()
 */
default boolean isReadable() {
   return exists();
}

/**
 * Indicate whether this resource represents a handle with an open stream.
 * If {@code true}, the InputStream cannot be read multiple times,
 * and must be read and closed to avoid resource leaks.
 * <p>Will be {@code false} for typical resource descriptors.
 */
default boolean isOpen() {
   return false;
}

/**
 * Determine whether this resource represents a file in a file system.
 * A value of {@code true} strongly suggests (but does not guarantee)
 * that a {@link #getFile()} call will succeed.
 * <p>This is conservatively {@code false} by default.
 * @since 5.0
 * @see #getFile()
 */
default boolean isFile() {
   return false;
}

然后是3种转换:

  • 可不可以转换为URL
  • 可不可以转换为URI
  • 可不可以转换为File
/**
 * Return a URL handle for this resource.
 * @throws IOException if the resource cannot be resolved as URL,
 * i.e. if the resource is not available as descriptor
 */
URL getURL() throws IOException;

/**
 * Return a URI handle for this resource.
 * @throws IOException if the resource cannot be resolved as URI,
 * i.e. if the resource is not available as descriptor
 * @since 2.5
 */
URI getURI() throws IOException;

/**
 * Return a File handle for this resource.
 * @throws java.io.FileNotFoundException if the resource cannot be resolved as
 * absolute file path, i.e. if the resource is not available in a file system
 * @throws IOException in case of general resolution/reading failures
 * @see #getInputStream()
 */
File getFile() throws IOException;

然后是几种关键信息:

  • 内容长度
  • 最后修改时间
  • 文件名
  • 资源描述
/**
 * Determine the content length for this resource.
 * @throws IOException if the resource cannot be resolved
 * (in the file system or as some other known physical resource type)
 */
long contentLength() throws IOException;

/**
 * Determine the last-modified timestamp for this resource.
 * @throws IOException if the resource cannot be resolved
 * (in the file system or as some other known physical resource type)
 */
long lastModified() throws IOException;

/**
 * Determine a filename for this resource, i.e. typically the last
 * part of the path: for example, "myfile.txt".
 * <p>Returns {@code null} if this type of resource does not
 * have a filename.
 */
@Nullable
String getFilename();

/**
 * Return a description for this resource,
 * to be used for error output when working with the resource.
 * <p>Implementations are also encouraged to return this value
 * from their {@code toString} method.
 * @see Object#toString()
 */
String getDescription();

Resource还提供了基于当前资源创建相对资源的方法:

/**
 * Create a resource relative to this resource.
 * @param relativePath the relative path (relative to this resource)
 * @return the resource handle for the relative resource
 * @throws IOException if the relative resource cannot be determined
 */
Resource createRelative(String relativePath) throws IOException;

因为NIO的引入,Spring5后还增加了下面的方法:

/**
 * Return a {@link ReadableByteChannel}.
 * <p>It is expected that each call creates a <i>fresh</i> channel.
 * <p>The default implementation returns {@link Channels#newChannel(InputStream)}
 * with the result of {@link #getInputStream()}.
 * @return the byte channel for the underlying resource (must not be {@code null})
 * @throws java.io.FileNotFoundException if the underlying resource doesn't exist
 * @throws IOException if the content channel could not be opened
 * @since 5.0
 * @see #getInputStream()
 */
default ReadableByteChannel readableChannel() throws IOException {
   return Channels.newChannel(getInputStream());
}

ResourceLoad

提到Resource就不能不提到ResouceLoad:

/**
 * Strategy interface for loading resources (e.. class path or file system
 * resources). An {@link org.springframework.context.ApplicationContext}
 * is required to provide this functionality, plus extended
 * {@link org.springframework.core.io.support.ResourcePatternResolver} support.
 *
 * <p>{@link DefaultResourceLoader} is a standalone implementation that is
 * usable outside an ApplicationContext, also used by {@link ResourceEditor}.
 *
 * <p>Bean properties of type Resource and Resource array can be populated
 * from Strings when running in an ApplicationContext, using the particular
 * context's resource loading strategy.
 *
 * @author Juergen Hoeller
 * @since 10.03.2004
 * @see Resource
 * @see org.springframework.core.io.support.ResourcePatternResolver
 * @see org.springframework.context.ApplicationContext
 * @see org.springframework.context.ResourceLoaderAware
 */
public interface ResourceLoader {

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

注释写得很清楚:

这是加载资源策略的接口

从字符串到Resource和Resource数组

核心的接口方法就一个,那就是怎么从String转换为Resource:

/**
 * Return a Resource handle for the specified resource location.
 * <p>The handle should always be a reusable resource descriptor,
 * allowing for multiple {@link Resource#getInputStream()} calls.
 * <p><ul>
 * <li>Must support fully qualified URLs, e.g. "file:C:/test.dat".
 * <li>Must support classpath pseudo-URLs, e.g. "classpath:test.dat".
 * <li>Should support relative file paths, e.g. "WEB-INF/test.dat".
 * (This will be implementation-specific, typically provided by an
 * ApplicationContext implementation.)
 * </ul>
 * <p>Note that a Resource handle does not imply an existing resource;
 * you need to invoke {@link Resource#exists} to check for existence.
 * @param location the resource location
 * @return a corresponding Resource handle (never {@code null})
 * @see #CLASSPATH_URL_PREFIX
 * @see Resource#exists()
 * @see Resource#getInputStream()
 */
Resource getResource(String location);

顺便提一下,因为Java的特殊性,不同的ClassLoader都有各自独立的类体系,所以有了下面的接口方法:

/**
 * Expose the ClassLoader used by this ResourceLoader.
 * <p>Clients which need to access the ClassLoader directly can do so
 * in a uniform manner with the ResourceLoader, rather than relying
 * on the thread context ClassLoader.
 * @return the ClassLoader
 * (only {@code null} if even the system ClassLoader isn't accessible)
 * @see org.springframework.util.ClassUtils#getDefaultClassLoader()
 * @see org.springframework.util.ClassUtils#forName(String, ClassLoader)
 */
@Nullable
ClassLoader getClassLoader();

ResourcePatternResolver

理解了ResourceLoad,再理解ResourcePatternResolver就是顺理成章的事情了。ResourcePatternResolver是ResourceLoad的子接口,ResourceLoad要解决如何从字符串得到Resource的问题,ResourcePatternResolver又要多解决一个问题:如何从字符串得到Resource数组:

/**
 * Strategy interface for resolving a location pattern (for example,
 * an Ant-style path pattern) into Resource objects.
 *
 * <p>This is an extension to the {@link org.springframework.core.io.ResourceLoader}
 * interface. A passed-in ResourceLoader (for example, an
 * {@link org.springframework.context.ApplicationContext} passed in via
 * {@link org.springframework.context.ResourceLoaderAware} when running in a context)
 * can be checked whether it implements this extended interface too.
 *
 * <p>{@link PathMatchingResourcePatternResolver} is a standalone implementation
 * that is usable outside an ApplicationContext, also used by
 * {@link ResourceArrayPropertyEditor} for populating Resource array bean properties.
 *
 * <p>Can be used with any sort of location pattern (e.g. "/WEB-INF/*-context.xml"):
 * Input patterns have to match the strategy implementation. This interface just
 * specifies the conversion method rather than a specific pattern format.
 *
 * <p>This interface also suggests a new resource prefix "classpath*:" for all
 * matching resources from the class path. Note that the resource location is
 * expected to be a path without placeholders in this case (e.g. "/beans.xml");
 * JAR files or classes directories can contain multiple files of the same name.
 *
 * @author Juergen Hoeller
 * @since 1.0.2
 * @see org.springframework.core.io.Resource
 * @see org.springframework.core.io.ResourceLoader
 * @see org.springframework.context.ApplicationContext
 * @see org.springframework.context.ResourceLoaderAware
 */
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*:";

   /**
    * Resolve the given location pattern into Resource objects.
    * <p>Overlapping resource entries that point to the same physical
    * resource should be avoided, as far as possible. The result should
    * have set semantics.
    * @param locationPattern the location pattern to resolve
    * @return the corresponding Resource objects
    * @throws IOException in case of I/O errors
    */
   Resource[] getResources(String locationPattern) throws IOException;

}

再谈loadBeanDefinitions

有了以上的铺垫,再理解文章开头的loadBeanDefinitions方法就很简单了。

同时我们也能深刻的理解AbstractBeanDefinitionReader中几个重载的loadBeanDefinitions方法:

int loadBeanDefinitions(Resource resource) throws BeanDefinitionStoreException;
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
public int loadBeanDefinitions(String location, @Nullable Set<Resource> actualResources) throws BeanDefinitionStoreException {
public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {

首先支持String… locations数组,然后通过for循环交给String location:

public int loadBeanDefinitions(String... locations) throws BeanDefinitionStoreException {
   Assert.notNull(locations, "Location array must not be null");
   int count = 0;
   for (String location : locations) {
      count += loadBeanDefinitions(location);
   }
   return count;
}

String location直接转交给用actualResources增强的方法,后者将将字符串转换为Resource或Resource数组:

@Override
public int loadBeanDefinitions(String location) throws BeanDefinitionStoreException {
   return loadBeanDefinitions(location, null);
}

如果是Resource数组,就继续for循环:

@Override
public int loadBeanDefinitions(Resource... resources) throws BeanDefinitionStoreException {
   Assert.notNull(resources, "Resource array must not be null");
   int count = 0;
   for (Resource resource : resources) {
      count += loadBeanDefinitions(resource);
   }
   return count;
}

对于最终的int loadBeanDefinitions(Resource resource),这是一个接口方法,一般由子类具体实现。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值