字节码对象和类加载器getResourceAsStream区别

再学习MyBatis的时候学习到的一个小知识点。使用MyBatis需要先加载全局配置文件。然后在程序中将该文件的资源路径传递过去。由于习惯的原因,刚开始的时候写的路径如下:

String resource = "/SqlMapConfig.xml";
InputStream inStream = Resources.getResourceAsStream(resource);

运行程序发现报错:资源没找到。

点进去源码是这样定义的:

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

可以看到MyBatis使用的是类加载器获取流对象。不过之前在编程中我一直使用的是字节码对象的getResourceAsStream方法。

现在验证一下两者的区别(src目录下创建一个xml文件):

现在通过两种方式获取该资源流:

public static void main(String[] args) {
    InputStream inStream = Test02.class.getResourceAsStream("/User.xml");
    System.out.println(inStream);	//获取到流对象
    inStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("/User.xml");
    System.out.println(inStream);	//输出null
}

对比两个方法的源码:

类加载器:

public InputStream getResourceAsStream(String name) {
    URL url = getResource(name);
    try {
        return url != null ? url.openStream() : null;
    } catch (IOException e) {
        return null;
    }
}

它是直接通过传递的路径信息获取资源的URL,如果URL不为null,则打开流。再对比字节码对象中的源码:

public InputStream getResourceAsStream(String name) {
    name = resolveName(name);
    ClassLoader cl = getClassLoader0();
        if (cl==null) {
            // A system class.
            return ClassLoader.getSystemResourceAsStream(name);
        }
    return cl.getResourceAsStream(name);
}

发现它底层也是调用的类加载器的getResourceAsStream方法,只不过在这之前有一个resolveName方法,对我们传递的路径做了前置处理。再看一下该方法的源码:

//name为路径信息
private String resolveName(String name) {
    if (name == null) {
        return name;
    }
    //如果name不以/开头的情况
    if (!name.startsWith("/")) {
        Class<?> c = this;
        while (c.isArray()) {		//不是Class<?>[]不进循环
            c = c.getComponentType();
        }
        String baseName = c.getName();	//得到baseName == com.mec.mybatis.test.Test
        int index = baseName.lastIndexOf('.');
        if (index != -1) {		//条件成立
            name = baseName.substring(0, index).replace('.', '/')	//进入这里
                    +"/"+name;          //得到com/mec/mybatis/test/Test
        }
    } else {
        //name如果以/开头   --------->他就会把/ 给你去掉
            name = name.substring(1);
    }

    return name;
}

上面就可以看出来,两种情况处理:

  1. 路径信息不以 / 开头  他会获取该类的class文件所在的路径,然后返回给类加载器,然后类加载器在该资源路径下加载资源
  2. 以 / 开头。进入else他会直接把 / 去掉。交给类加载器。注意:类加载器加载资源是从的根路径开始加载。加/会报错的。

我们可以在测试一下,类加载器认为的根路径是什么:

public static void main(String[] args) {
    try {
        //不传路径信息看它从什么地方加载资源
	Enumeration<URL> urls = Thread.currentThread().getContextClassLoader().getResources("");
	while(urls.hasMoreElements()) {
	    System.out.println(urls.nextElement());
	}
    } catch (IOException e) {
	e.printStackTrace();
    }
}

 输出结果如下:

所以将我们最开始的路径信息连起来,路径应该是.....bin/User.xml

所以如果带上/  那就变成了  .....bin//User.xml   -----  报错bin目录下没有“/User.xml”这个配置文件

通过输出可以看到类加载器自己认为的工程根是bin/  这个和classpath有关,其实它会去classpath中寻找自己应该从哪里开始加载资源,也即classpath告诉他根在哪里。

 

打开classpath显示如下:

<?xml version="1.0" encoding="UTF-8"?>
<classpath>
	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
	<classpathentry kind="src" path="src"/>
	<classpathentry kind="lib" path="src/lib/mybatis-3.3.0.jar" sourcepath="F:/文件夹/mybatis-3-mybatis-3.3.0/mybatis-3-mybatis-3.3.0.zip"/>
	<classpathentry kind="lib" path="src/lib/asm-4.2.jar"/>
	<classpathentry kind="lib" path="src/lib/cglib-3.1.jar"/>
	<classpathentry kind="lib" path="src/lib/commons-logging-1.2.jar"/>
	<classpathentry kind="lib" path="src/lib/javassist-3.17.1-GA.jar"/>
	<classpathentry kind="lib" path="src/lib/log4j-1.2.17.jar"/>
	<classpathentry kind="lib" path="src/lib/log4j-api-2.2.jar"/>
	<classpathentry kind="lib" path="src/lib/log4j-core-2.2.jar" sourcepath="F:/文件夹/mybatis-3-mybatis-3.3.0/mybatis-3-mybatis-3.3.0.zip"/>
	<classpathentry kind="lib" path="src/lib/slf4j-api-1.7.12.jar"/>
	<classpathentry kind="lib" path="src/lib/slf4j-log4j12-1.7.12.jar"/>
	<classpathentry kind="lib" path="src/lib/mysql-connector-java-5.1.46-bin.jar"/>
	<classpathentry kind="lib" path="src/lib/MecUtils.jar"/>
	<classpathentry kind="output" path="bin"/>
</classpath>

其中都是指定的资源路径,不过最后一个  output 的 path=“bin”。这个就是指定将上面所有标签中指定的路径中的资源全部输出到bin下,所以我们在src目录下的内容才会在bin目录下产生一个副本。这里也就相当于告诉类加载器,他应该去bin目录下加载资源。

我们可以将classpath的输出路径改一下,如下:

<classpathentry kind="output" path="yhj"/>

在创建一个空的目录(yhj)如下:

运行原来的程序看结果:

它直接连主类都加载不到了。因为他是去yhj目录下去找我们的资源,但是现在该目录为空,所以他没找到就加载不到。

综上所述:

使用字节码对象获取资源:

  1. 不带 /  会从本字节码文件的路径下加载资源
  2.  带 / 他会从classpath中指定的根中去加载资源

使用类加载器:  直接从classpath中指定的根去加载资源

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值