天天看點

Mybatis源碼學習(8)-資源加載

1、概述

  Mybatis的IO模块主要封装了ClassLoader以及读取资源文件的相关API。

2、类加载器
注:类加载器相关内容来源《Mybatis技术内幕》一书。

  Java虚拟机中的类加载器( ClassLoader )负责加载来自文件系统、网络或其他来源的类文件。Java 虚拟机中的类加载器默认使用的是双亲委派模式。有三种默认使用的类加载器,分别是Bootstrap ClassLoader 、Extension ClassLoader 和System ClassLoader (也

被称为Application ClassLoader ),每种类加载器都己经确定从哪个位置加载类文件。其中,Bootstrap ClassLoader 负责加载JDK 自带的比jar 包中的类文件,它是所有类加载器的父加载器,Bootstrap ClassLoader 没有任何父类加载器。Extension ClassLoader 负责加载Java 的扩展类库,也就是从jre/lib/ ext 目录下或者java.ext.dirs 系统属性指定的目录下加载类。System ClassLoader 负责从classpath 环境变量中加载类文件。

Mybatis源碼學習(8)-資源加載
3、IO模块包结构

  IO模块包结构如下所示。其中,ClassLoaderWrapper是一个ClassLoader的包装器;VFS表示虚拟文件系统(Virtual File System ),它用来查找指定路径下的资源;JBoss6VFS和DefaultVFS两个类是VFS的实现;Resources是一个提供了一系列访问资源的静态方法的工具类;ResolverUtil可以根据指定的条件查找指定包下的类。ExternalResources类是Resources类的扩展,已经废弃。

Mybatis源碼學習(8)-資源加載
4、VFS

  VFS表示虚拟文件系统,提供了一系列访问应用下资源的方法。Mybatis中提供了JBoss6VFS 和DefaultVFS两个VFS的实现。其中,在创建VFS实例的时候,采用了单例模式,且是静态内部类的单例模式写法(该设计模式此处不做详细记录,后续文章单独介绍)。

  • 字段

      其中,IMPLEMENTATIONS 主要用来记录Mybatis提供的两个VFS实现类,分别是JBoss6VFS和DefaultVFS。USER_IMPLEMENTATIONS 用来记录用户自定义的VFS实现类,addimplClass ()方法会将指定的VFS实现对应的Class对象添加到USER_IMPLEMENTATIONS 变量中。

/** The built-in implementations. */
  public static final Class<?>[] IMPLEMENTATIONS = { JBoss6VFS.class, DefaultVFS.class };

  /** The list to which implementations are added by {@link #addImplClass(Class)}. */
  public static final List<Class<? extends VFS>> USER_IMPLEMENTATIONS = new ArrayList<Class<? extends VFS>>();
           
  • 静态内部类

      静态内部类的单例模式写法中的静态内部类,用来实现延迟加载且保证只创建一个单例对象。在静态内部类中,通过createVFS()方法,实现创建静态变量INSTANCE 的值。注意,根据方法中的代码逻辑,可以判断,优先使用用户定义的VFS实现,当用户定义的VFS不存在或者不可用时(根据isValid()方法判断),才加载Mybatis提供的默认实现。

/** Singleton instance holder. */
  private static class VFSHolder {
    static final VFS INSTANCE = createVFS();

    @SuppressWarnings("unchecked")
    static VFS createVFS() {
      // Try the user implementations first, then the built-ins
      List<Class<? extends VFS>> impls = new ArrayList<Class<? extends VFS>>();
      impls.addAll(USER_IMPLEMENTATIONS);
      impls.addAll(Arrays.asList((Class<? extends VFS>[]) IMPLEMENTATIONS));

      // Try each implementation class until a valid one is found
      VFS vfs = null;
      for (int i = 0; vfs == null || !vfs.isValid(); i++) {
        Class<? extends VFS> impl = impls.get(i);
        try {
          vfs = impl.newInstance();
          if (vfs == null || !vfs.isValid()) {
            if (log.isDebugEnabled()) {
              log.debug("VFS implementation " + impl.getName() +
                  " is not valid in this environment.");
            }
          }
        } catch (InstantiationException e) {
          log.error("Failed to instantiate " + impl, e);
          return null;
        } catch (IllegalAccessException e) {
          log.error("Failed to instantiate " + impl, e);
          return null;
        }
      }
      if (log.isDebugEnabled()) {
        log.debug("Using VFS adapter " + vfs.getClass().getName());
      }
      return vfs;
    }
  }
           
  • 获取单例对象的方法
public static VFS getInstance() {
    return VFSHolder.INSTANCE;
  }
           
  • 添加用户自定义的VFS实现
public static void addImplClass(Class<? extends VFS> clazz) {
    if (clazz != null) {
      USER_IMPLEMENTATIONS.add(clazz);
    }
  }
           
  • 根据classname获取对应的Class
/** Get a class by name. If the class is not found then return null. */
  protected static Class<?> getClass(String className) {
    try {
      return Thread.currentThread().getContextClassLoader().loadClass(className);
//      return ReflectUtil.findClass(className);
    } catch (ClassNotFoundException e) {
      if (log.isDebugEnabled()) {
        log.debug("Class not found: " + className);
      }
      return null;
    }
  }
           
  • 根据clazz和methodName、parameterTypes获取对应的Method
/**
   * Get a method by name and parameter types. If the method is not found then return null.
   * 
   * @param clazz The class to which the method belongs.
   * @param methodName The name of the method.
   * @param parameterTypes The types of the parameters accepted by the method.
   */
  protected static Method getMethod(Class<?> clazz, String methodName, Class<?>... parameterTypes) {
    if (clazz == null) {
      return null;
    }
    try {
      return clazz.getMethod(methodName, parameterTypes);
    } catch (SecurityException e) {
      log.error("Security exception looking for method " + clazz.getName() + "." + methodName + ".  Cause: " + e);
      return null;
    } catch (NoSuchMethodException e) {
      log.error("Method not found " + clazz.getName() + "." + methodName + "." + methodName + ".  Cause: " + e);
      return null;
    }
  }
           
  • 调用方法,并返回指定值

      有点儿类似动态代理中的方法调用实现。

@SuppressWarnings("unchecked")
  protected static <T> T invoke(Method method, Object object, Object... parameters)
      throws IOException, RuntimeException {
    try {
      return (T) method.invoke(object, parameters);
    } catch (IllegalArgumentException e) {
      throw new RuntimeException(e);
    } catch (IllegalAccessException e) {
      throw new RuntimeException(e);
    } catch (InvocationTargetException e) {
      if (e.getTargetException() instanceof IOException) {
        throw (IOException) e.getTargetException();
      } else {
        throw new RuntimeException(e);
      }
    }
  }
           
  • getResources()方法,根据路径查找对应的URL集合
/**
   * Get a list of {@link URL}s from the context classloader for all the resources found at the
   * specified path.
   * 
   * @param path The resource path.
   * @return A list of {@link URL}s, as returned by {@link ClassLoader#getResources(String)}.
   * @throws IOException If I/O errors occur
   */
  protected static List<URL> getResources(String path) throws IOException {
    return Collections.list(Thread.currentThread().getContextClassLoader().getResources(path));
  }
           
  • list(String)方法,负责查找指定的资源名称列表

      底层是由重写方法list(URL,String)方法负责实现查找指定的资源名称列表。

public List<String> list(String path) throws IOException {
    List<String> names = new ArrayList<String>();
    for (URL url : getResources(path)) {
      names.addAll(list(url, path));
    }
    return names;
  }
           
  • isValid()方法,验证VFS实现在当前环境下是否有效

      该方法是抽象方法,由具体实现类来实现该方法,并验证在当前环境下,是否支持该类型的VFS实现。

public abstract boolean isValid();
           
  • list(URL,String)方法,该方法负责查找指定的资源名称列表

      该方法是抽象方法,由具体实现类来实现该方法。

protected abstract List<String> list(URL url, String forPath) throws IOException;
           
5、DefaultVFS
Mybatis源碼學習(8)-資源加載

  DefaultVFS是VFS的一个实现。主要实现了抽象类中的isValid()和list(URL,String)方法。

  • isValid()方法

      默认返回true,表示都所有环境下都可以使用该实现类。

@Override
  public boolean isValid() {
    return true;
  }
           
  • list(URL, String)方法

      主要提供了查找指定的资源名称列表的方法。URL参数指查找资源的路径,String指在指定的资源路径下,查找指定的目录。该方法提供了读取jar包中资源的方法。

@Override
  public List<String> list(URL url, String path) throws IOException {
    InputStream is = null;
    try {
      List<String> resources = new ArrayList<String>();

      // First, try to find the URL of a JAR file containing the requested resource. If a JAR
      // file is found, then we'll list child resources by reading the JAR.
      URL jarUrl = findJarForResource(url);
      if (jarUrl != null) {
        is = jarUrl.openStream();
        if (log.isDebugEnabled()) {
          log.debug("Listing " + url);
        }
        resources = listResources(new JarInputStream(is), path);
      }
      else {
        List<String> children = new ArrayList<String>();
        try {
          if (isJar(url)) {
            // Some versions of JBoss VFS might give a JAR stream even if the resource
            // referenced by the URL isn't actually a JAR
            is = url.openStream();
            JarInputStream jarInput = new JarInputStream(is);
            if (log.isDebugEnabled()) {
              log.debug("Listing " + url);
            }
            for (JarEntry entry; (entry = jarInput.getNextJarEntry()) != null;) {
              if (log.isDebugEnabled()) {
                log.debug("Jar entry: " + entry.getName());
              }
              children.add(entry.getName());
            }
            jarInput.close();
          }
          else {
            /*
             * Some servlet containers allow reading from directory resources like a
             * text file, listing the child resources one per line. However, there is no
             * way to differentiate between directory and file resources just by reading
             * them. To work around that, as each line is read, try to look it up via
             * the class loader as a child of the current resource. If any line fails
             * then we assume the current resource is not a directory.
             */
            is = url.openStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(is));
            List<String> lines = new ArrayList<String>();
            for (String line; (line = reader.readLine()) != null;) {
              if (log.isDebugEnabled()) {
                log.debug("Reader entry: " + line);
              }
              lines.add(line);
              if (getResources(path + "/" + line).isEmpty()) {
                lines.clear();
                break;
              }
            }

            if (!lines.isEmpty()) {
              if (log.isDebugEnabled()) {
                log.debug("Listing " + url);
              }
              children.addAll(lines);
            }
          }
        } catch (FileNotFoundException e) {
          /*
           * For file URLs the openStream() call might fail, depending on the servlet
           * container, because directories can't be opened for reading. If that happens,
           * then list the directory directly instead.
           */
          if ("file".equals(url.getProtocol())) {
            File file = new File(url.getFile());
            if (log.isDebugEnabled()) {
                log.debug("Listing directory " + file.getAbsolutePath());
            }
            if (file.isDirectory()) {
              if (log.isDebugEnabled()) {
                  log.debug("Listing " + url);
              }
              children = Arrays.asList(file.list());
            }
          }
          else {
            // No idea where the exception came from so rethrow it
            throw e;
          }
        }

        // The URL prefix to use when recursively listing child resources
        String prefix = url.toExternalForm();
        if (!prefix.endsWith("/")) {
          prefix = prefix + "/";
        }

        // Iterate over immediate children, adding files and recursing into directories
        for (String child : children) {
          String resourcePath = path + "/" + child;
          resources.add(resourcePath);
          URL childUrl = new URL(prefix + child);
          resources.addAll(list(childUrl, resourcePath));
        }
      }

      return resources;
    } finally {
      if (is != null) {
        try {
          is.close();
        } catch (Exception e) {
          // Ignore
        }
      }
    }
  }
           
/**
   * List the names of the entries in the given {@link JarInputStream} that begin with the
   * specified {@code path}. Entries will match with or without a leading slash.
   * 
   * @param jar The JAR input stream
   * @param path The leading path to match
   * @return The names of all the matching entries
   * @throws IOException If I/O errors occur
   */
  protected List<String> listResources(JarInputStream jar, String path) throws IOException {
    // Include the leading and trailing slash when matching names
    if (!path.startsWith("/")) {
      path = "/" + path;
    }
    if (!path.endsWith("/")) {
      path = path + "/";
    }

    // Iterate over the entries and collect those that begin with the requested path
    List<String> resources = new ArrayList<String>();
    for (JarEntry entry; (entry = jar.getNextJarEntry()) != null;) {
      if (!entry.isDirectory()) {
        // Add leading slash if it's missing
        String name = entry.getName();
        if (!name.startsWith("/")) {
          name = "/" + name;
        }

        // Check file name
        if (name.startsWith(path)) {
          if (log.isDebugEnabled()) {
            log.debug("Found resource: " + name);
          }
          // Trim leading slash
          resources.add(name.substring(1));
        }
      }
    }
    return resources;
  }

           
6、ClassLoaderWrapper

  ClassLoaderWrapper是一个ClassLoader的包装器。其中包含了多个ClassLoader对象。通过调整多个类加载器的使用顺序, ClassLoaderWrapper 可以确保返回给系统使用的是正确的类加载器。使用ClassLoaderWrapper 就如同使用一个ClassLoader 对象,ClassLoaderWrapper 会按照指定的顺序依次检测其中封装的ClassLoader 对象,并从中选取第一个可用的ClassLoader 完成相关功能。

Mybatis源碼學習(8)-資源加載
  • 字段

      ClassLoaderWrapper有两个字段defaultClassLoader( 默认类加载器)、systemClassLoader(系统类加载器)。其中,systemClassLoader字段由构造函数直接赋值;defaultClassLoader字段由使用该类的对象进行赋值,因为该字段不是私有字段,所以可以通过对象.字段的格式进行赋值,在Resources类中,有关于ClassLoaderWrapper对象的defaultClassLoader字段赋值的方法。

/**
   * 默认类加载器
   */
  ClassLoader defaultClassLoader;
  /**
   * 系统类加载器
   */
  ClassLoader systemClassLoader;
           
  • 构造函数

      主要实现了systemClassLoader字段的赋值,即实现了系统类加载器的赋值。

/**
   * 构造函数
   */
  ClassLoaderWrapper() {
    try {
      systemClassLoader = ClassLoader.getSystemClassLoader();
    } catch (SecurityException ignored) {
      // AccessControlException on Google App Engine   
    }
  }
           
  • getClassLoaders() 获取加载器列表

      定义了可能用到的所有类加载器。

ClassLoader[] getClassLoaders(ClassLoader classLoader) {
    return new ClassLoader[]{
        classLoader,
        defaultClassLoader,
        Thread.currentThread().getContextClassLoader(),
        getClass().getClassLoader(),
        systemClassLoader};
  }
           
  • getResourceAsStream()方法

      根据resource获取InputStream的方法,底层是通过InputStream returnValue = classLoader.getResourceAsStream(resource);实现。有三个重载方法:

public InputStream getResourceAsStream(String resource) {
    return getResourceAsStream(resource, getClassLoaders(null));
  }
           
public InputStream getResourceAsStream(String resource, ClassLoader classLoader) {
    return getResourceAsStream(resource, getClassLoaders(classLoader));
  }
           
InputStream getResourceAsStream(String resource, ClassLoader[] classLoader) {
    for (ClassLoader cl : classLoader) {
      if (null != cl) {

        // try to find the resource as passed
        InputStream returnValue = cl.getResourceAsStream(resource);

        // now, some class loaders want this leading "/", so we'll add it and try again if we didn't find the resource
        if (null == returnValue) {
          returnValue = cl.getResourceAsStream("/" + resource);
        }

        if (null != returnValue) {
          return returnValue;
        }
      }
    }
    return null;
  }
           
  • classForName()方法、getResourceAsURL()方法

      这两个方法和getResourceAsStream()方法一样,也有三个重载方法,只是底层的实现稍有差别,不再贴出代码了。

7、ResolverUtil

  ResolverUtil可以根据指定的条件查找指定包下的类,其中使用的条件由Test接口表示。

ResolverUtil中使用c lassLoader 字段( ClassLoader 类型)记录了当前使用的类加载器,默认情况下,使用的是当前线程上下文绑定的ClassLoader ,我们可以通过setC!assLoader ()方法修改使用类加载器。My Batis 提供了两个常用的Test 接口实现,分别是IsA 和AnnotatedWith。IsA 用于检测类是否继承了指定的类或接口, AnnotatedWith 用于检测类是否添加了指定的注解。

Mybatis源碼學習(8)-資源加載
  • 字段

      matches字段表示查找到的所有符合条件的Class集合;classloader表示查找类时,使用的类加载器,当为null时,默认使用当前线程的加载器,即Thread.currentThread().getContextClassLoader()。

/** The set of matches being accumulated. */
  private Set<Class<? extends T>> matches = new HashSet<Class<? extends T>>();

  /**
   * The ClassLoader to use when looking for classes. If null then the ClassLoader returned
   * by Thread.currentThread().getContextClassLoader() will be used.
   */
  private ClassLoader classloader;

 /**
   * Provides access to the classes discovered so far. If no calls have been made to
   * any of the {@code find()} methods, this set will be empty.
   *
   * @return the set of classes that have been discovered.
   */
  public Set<Class<? extends T>> getClasses() {
    return matches;
  }
/**
   * Returns the classloader that will be used for scanning for classes. If no explicit
   * ClassLoader has been set by the calling, the context class loader will be used.
   *
   * @return the ClassLoader that will be used to scan for classes
   */
  public ClassLoader getClassLoader() {
    return classloader == null ? Thread.currentThread().getContextClassLoader() : classloader;
  }

  /**
   * Sets an explicit ClassLoader that should be used when scanning for classes. If none
   * is set then the context classloader will be used.
   *
   * @param classloader a ClassLoader to use when scanning for classes
   */
  public void setClassLoader(ClassLoader classloader) {
    this.classloader = classloader;
  }
           
  • 内部类 条件判断接口和类

      Test是接口,IsA和AnnotatedWith是两个实现类,主要是用于判断查询出来的类是否符合要求,符合要求的类会通过addIfMatching()方法添加到字段matches中。

/**
   * A simple interface that specifies how to test classes to determine if they
   * are to be included in the results produced by the ResolverUtil.
   */
  public interface Test {
    /**
     * Will be called repeatedly with candidate classes. Must return True if a class
     * is to be included in the results, false otherwise.
     */
    boolean matches(Class<?> type);
  }
           
/**
   * 判断 是否是以parentType为基类的类
   * A Test that checks to see if each class is assignable to the provided class. Note
   * that this test will match the parent type itself if it is presented for matching.
   */
  public static class IsA implements Test {
    private Class<?> parent;

    /** Constructs an IsA test using the supplied Class as the parent class/interface. */
    public IsA(Class<?> parentType) {
      this.parent = parentType;
    }

    /** Returns true if type is assignable to the parent type supplied in the constructor. */
    @Override
    public boolean matches(Class<?> type) {
      return type != null && parent.isAssignableFrom(type);
    }

    @Override
    public String toString() {
      return "is assignable to " + parent.getSimpleName();
    }
  }
           
/**
   * 判断是否包含有annotation注解
   * A Test that checks to see if each class is annotated with a specific annotation. If it
   * is, then the test returns true, otherwise false.
   */
  public static class AnnotatedWith implements Test {
    private Class<? extends Annotation> annotation;

    /** Constructs an AnnotatedWith test for the specified annotation type. */
    public AnnotatedWith(Class<? extends Annotation> annotation) {
      this.annotation = annotation;
    }

    /** Returns true if the type is annotated with the class provided to the constructor. */
    @Override
    public boolean matches(Class<?> type) {
      return type != null && type.isAnnotationPresent(annotation);
    }

    @Override
    public String toString() {
      return "annotated with @" + annotation.getSimpleName();
    }
  }
           
  • 根据指定的条件查找指定包下的类

      findAnnotated()方法、findImplementations()方法分别用来在指定路径下加载有指定注解、是指定类子类的方法。他们分别创建了自己的Test实现类,即判断是否符合条件的Test子类,然后底层最终是由find()方法实现,查询和判断;最后符合条件的类通过addIfMatching()方法,赋值到matches集合中。其中,在find()方法中,真正实现加载类的代码是VFS.getInstance().list(path);即是由VFS实现了类加载,然后在find()方法中,通过Test实现类,进行是否符合条件的判断。

public ResolverUtil<T> findAnnotated(Class<? extends Annotation> annotation, String... packageNames) {
    if (packageNames == null) {
      return this;
    }

    Test test = new AnnotatedWith(annotation);
    for (String pkg : packageNames) {
      find(test, pkg);
    }

    return this;
  }
           
public ResolverUtil<T> findImplementations(Class<?> parent, String... packageNames) {
    if (packageNames == null) {
      return this;
    }

    Test test = new IsA(parent);
    for (String pkg : packageNames) {
      find(test, pkg);
    }

    return this;
  }
           
public ResolverUtil<T> find(Test test, String packageName) {
    String path = getPackagePath(packageName);

    try {
      List<String> children = VFS.getInstance().list(path);
      for (String child : children) {
        if (child.endsWith(".class")) {
          addIfMatching(test, child);
        }
      }
    } catch (IOException ioe) {
      log.error("Could not read package: " + packageName, ioe);
    }

    return this;
  }
           
@SuppressWarnings("unchecked")
  protected void addIfMatching(Test test, String fqn) {
    try {
      String externalName = fqn.substring(0, fqn.indexOf('.')).replace('/', '.');
      ClassLoader loader = getClassLoader();
      if (log.isDebugEnabled()) {
        log.debug("Checking to see if class " + externalName + " matches criteria [" + test + "]");
      }

      Class<?> type = loader.loadClass(externalName);
      if (test.matches(type)) {
        matches.add((Class<T>) type);
      }
    } catch (Throwable t) {
      log.warn("Could not examine class '" + fqn + "'" + " due to a " +
          t.getClass().getName() + " with message: " + t.getMessage());
    }
  }
           
8、Resources

  Resources是一个提供了多个静态方法的工具类, 其中封装了一个ClassLoaderWrapper 类型的静态字段, Resources提供的这些静态方法都是通过调用ClassLoaderWrapper 对象的相应方法实现的。

  其中,getDefaultClassLoader()和setDefaultClassLoader()方法用于获取或者设置classLoaderWrapper.defaultClassLoader字段的值.在ClassLoaderWrapper类分析的时候,提到过,即在setDefaultClassLoader()方法中,设置了ClassLoaderWrapper类的默认类加载器。

  主要提供了以下方法:

  • getResourceURL()方法
  • getResourceAsStream()方法
  • getResourceAsProperties()方法
  • getResourceAsReader()方法
  • getResourceAsFile()方法
  • getUrlAsStream()方法
  • getUrlAsReader()方法
  • getUrlAsProperties()方法
  • classForName()方法
    Mybatis源碼學習(8)-資源加載
public class Resources {
	/**
	 * 类加载器包装类实例
	 */
  private static ClassLoaderWrapper classLoaderWrapper = new ClassLoaderWrapper();

  /**
   * Charset to use when calling getResourceAsReader.
   * null means use the system default.
   */
  private static Charset charset;

  Resources() {
  }

  /**
   * Returns the default classloader (may be null).
   *	获取默认类加载器,通过类加载器的包装类实例获取
   * @return The default classloader
   */
  public static ClassLoader getDefaultClassLoader() {
    return classLoaderWrapper.defaultClassLoader;
  }

  /**
   * Sets the default classloader
   *	设置默认类加载器,通过类加载器的包装类实例设置
   * @param defaultClassLoader - the new default ClassLoader
   */
  public static void setDefaultClassLoader(ClassLoader defaultClassLoader) {
    classLoaderWrapper.defaultClassLoader = defaultClassLoader;
  }

  /**
   * Returns the URL of the resource on the classpath
   *	根据resource获取URL,底层通过类加载器的包装类实例(classLoaderWrapper)实现:
   *		URL url = classLoader.getResource(resource);
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static URL getResourceURL(String resource) throws IOException {
      // issue #625
      return getResourceURL(null, resource);
  }

  /**
   * Returns the URL of the resource on the classpath
   *	根据resource获取URL,底层通过类加载器的包装类实例(classLoaderWrapper)实现:
   *		URL url = classLoader.getResource(resource);
   * @param loader   The classloader used to fetch the resource
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static URL getResourceURL(ClassLoader loader, String resource) throws IOException {
    URL url = classLoaderWrapper.getResourceAsURL(resource, loader);
    if (url == null) {
      throw new IOException("Could not find resource " + resource);
    }
    return url;
  }

  /**
   * Returns a resource on the classpath as a Stream object
   *	根据resource获取InputStream,底层通过类加载器的包装类实例(classLoaderWrapper)实现:
   *		InputStream returnValue = classLoader.getResourceAsStream(resource);
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static InputStream getResourceAsStream(String resource) throws IOException {
    return getResourceAsStream(null, resource);
  }

  /**
   * Returns a resource on the classpath as a Stream object
   *	根据resource获取InputStream,底层通过类加载器的包装类实例(classLoaderWrapper)实现:
   *		InputStream returnValue = classLoader.getResourceAsStream(resource);
   * @param loader   The classloader used to fetch the resource
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static InputStream getResourceAsStream(ClassLoader loader, String resource) throws IOException {
    InputStream in = classLoaderWrapper.getResourceAsStream(resource, loader);
    if (in == null) {
      throw new IOException("Could not find resource " + resource);
    }
    return in;
  }

  /**
   * Returns a resource on the classpath as a Properties object
   *	根据resource获取Properties,底层通过类加载器的包装类实例(classLoaderWrapper)
   *		获取InputStream对象,然后再封装成Properties对象:
   *		InputStream returnValue = classLoader.getResourceAsStream(resource);
   *		Properties props = new Properties();
   *		props.load(in);
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static Properties getResourceAsProperties(String resource) throws IOException {
    Properties props = new Properties();
    InputStream in = getResourceAsStream(resource);
    props.load(in);
    in.close();
    return props;
  }

  /**
   * Returns a resource on the classpath as a Properties object
   *	根据resource获取Properties,底层通过类加载器的包装类实例(classLoaderWrapper)
   *		获取InputStream对象,然后再封装成Properties对象:
   *		InputStream returnValue = classLoader.getResourceAsStream(resource);
   *		Properties props = new Properties();
   *		props.load(in);
   * @param loader   The classloader used to fetch the resource
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static Properties getResourceAsProperties(ClassLoader loader, String resource) throws IOException {
    Properties props = new Properties();
    InputStream in = getResourceAsStream(loader, resource);
    props.load(in);
    in.close();
    return props;
  }

  /**
   * Returns a resource on the classpath as a Reader object
   *	根据resource获取Properties,底层通过类加载器的包装类实例(classLoaderWrapper)
   *		获取InputStream对象,然后再封装成Reader对象:
   *		InputStream returnValue = classLoader.getResourceAsStream(resource);
   *		new InputStreamReader(inputStream);
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static Reader getResourceAsReader(String resource) throws IOException {
    Reader reader;
    if (charset == null) {
      reader = new InputStreamReader(getResourceAsStream(resource));
    } else {
      reader = new InputStreamReader(getResourceAsStream(resource), charset);
    }
    return reader;
  }

  /**
   * Returns a resource on the classpath as a Reader object
   *	根据resource获取Properties,底层通过类加载器的包装类实例(classLoaderWrapper)
   *		获取InputStream对象,然后再封装成Reader对象:
   *		InputStream returnValue = classLoader.getResourceAsStream(resource);
   *		new InputStreamReader(inputStream);
   * @param loader   The classloader used to fetch the resource
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static Reader getResourceAsReader(ClassLoader loader, String resource) throws IOException {
    Reader reader;
    if (charset == null) {
      reader = new InputStreamReader(getResourceAsStream(loader, resource));
    } else {
      reader = new InputStreamReader(getResourceAsStream(loader, resource), charset);
    }
    return reader;
  }

  /**
   * Returns a resource on the classpath as a File object
   *	根据resource获取File,底层通过类加载器的包装类实例(classLoaderWrapper)
   *		获取URL对象,然后再封装成File对象:
   *		new File(getResourceURL(resource).getFile())
   * @param resource The resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static File getResourceAsFile(String resource) throws IOException {
    return new File(getResourceURL(resource).getFile());
  }

  /**
   * Returns a resource on the classpath as a File object
   *	根据resource获取File,底层通过类加载器的包装类实例(classLoaderWrapper)
   *		获取URL对象,然后再封装成File对象:
   *		new File(getResourceURL(resource).getFile())
   * @param loader   - the classloader used to fetch the resource
   * @param resource - the resource to find
   * @return The resource
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static File getResourceAsFile(ClassLoader loader, String resource) throws IOException {
    return new File(getResourceURL(loader, resource).getFile());
  }

  /**
   * Gets a URL as an input stream
   *
   * @param urlString - the URL to get
   * @return An input stream with the data from the URL
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static InputStream getUrlAsStream(String urlString) throws IOException {
    URL url = new URL(urlString);
    URLConnection conn = url.openConnection();
    return conn.getInputStream();
  }

  /**
   * Gets a URL as a Reader
   *
   * @param urlString - the URL to get
   * @return A Reader with the data from the URL
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static Reader getUrlAsReader(String urlString) throws IOException {
    Reader reader;
    if (charset == null) {
      reader = new InputStreamReader(getUrlAsStream(urlString));
    } else {
      reader = new InputStreamReader(getUrlAsStream(urlString), charset);
    }
    return reader;
  }

  /**
   * Gets a URL as a Properties object
   *
   * @param urlString - the URL to get
   * @return A Properties object with the data from the URL
   * @throws java.io.IOException If the resource cannot be found or read
   */
  public static Properties getUrlAsProperties(String urlString) throws IOException {
    Properties props = new Properties();
    InputStream in = getUrlAsStream(urlString);
    props.load(in);
    in.close();
    return props;
  }

  /**
   * Loads a class
   *
   * @param className - the class to fetch
   * @return The loaded class
   * @throws ClassNotFoundException If the class cannot be found (duh!)
   */
  public static Class<?> classForName(String className) throws ClassNotFoundException {
    return classLoaderWrapper.classForName(className);
  }

  public static Charset getCharset() {
    return charset;
  }

  public static void setCharset(Charset charset) {
    Resources.charset = charset;
  }

}

           

繼續閱讀