getResource是Class类中的一个方法 作用呢就是配置文件的读取
首先我们得知道,在new一个对象的时候会在java堆区中生成一个代表这个类的java.lang.Class对象,(不能主动创建,只能获取)作为这个类的各种数据的访问入口 我们怎样使用对应的Class对象呢? 三种方法 ,先看看我的目录结构
Resource.class //直接使用类名.class Class.forName("zhang.Resource") //包名.类名 需要捕获异常 new Resource().getClass() //先获取一个对象 再使用getCLass()方法现在我们写五个语句,前两个没/ 后三个有/
System.out.println(Resource.class.getResource("")); System.out.println(Resource.class.getResource("Resource.class")); System.out.println(Resource.class.getResource("/")); System.out.println(Resource.class.getResource("/Resource.class")); System.out.println(Resource.class.getResource("/zhang/Resource.class"));返回的地址都是绝对地址
file:/C:/Users/java/target//target/classes/zhang/ file:/C:/Users/java/target//target/classes/zhang/Resource. file:/C:/Users/java/target//target/classes/ null file:/C:/Users/java/target//target/classes/zhang/Resource.class现在我们来分析 按ctrl + 鼠标左键点进去看源码
public java.net.URL getResource(String name) { name = resolveName(name); ClassLoader cl = getClassLoader0(); if (cl==null) { // A system class. return ClassLoader.getSystemResource(name); } return cl.getResource(name); }我们可以看到传进去的name(想要加载的文件的相对地址)被resolveName()方法调用并再次赋值给了name 所以我们要分析下resolveName这个方法
private String resolveName(String name) { if (name == null) { return name; } if (!name.startsWith("/")) { //不以”/”开头 Class<?> c = this; //得到 class zhang.Resource while (c.isArray()) { c = c.getComponentType(); } String baseName = c.getName(); //得到 zhang.Resource 也就是去掉了class /* 来看下getName()的源码 public String getName() { String name = this.name; //这里的this.name 就是 zhang.Resource if (name == null) this.name = name = getName0(); return name; } */ int index = baseName.lastIndexOf('.'); //返回最后一次出现.位置的索引 if (index != -1) { name = baseName.substring(0, index).replace('.', '/') //拼接 zhang/想要加载的文件的相对地址 +"/"+name; } } else { name = name.substring(1); //以“/”开头 去掉“/” 所以得到的是 /classes/想要加载的文件的相对地址 } return name; //返回的name都是去掉"/"的 }通过这个方法,我们可以看出加/比不加/多加了个zhang/的目录(通过Class<?> c = this; 然后c.getName() ),也就是包名的目录 所以 加了/ :classpath根目录下 /classes/ 没加/ :字节码对象所在目录下 /classes/zhang/ (多了个zhang/) classpath: 指的是编译后的class文件、xml、properties等配置文件所在的目录 如下图 加了/就是classes下面的 ,没加/就是classes/zhang下面的
接下来再来看看后面的代码
public java.net.URL getResource(String name) { name = resolveName(name); ClassLoader cl = getClassLoader0(); //获取加载该Class的ClassLoader if (cl==null) { //如果加载该Class的ClassLoader为null,则表示这是一个系统class // A system class. 系统类加载器 return ClassLoader.getSystemResource(name);//调用ClassLoader的getSystemResource方法 } return cl.getResource(name);//调用ClassLoader的getResource方法 }所以getResource最终调用的是ClassLoader的getSystemResource或者getResource方法 现在我们来分析ClassLoader类里面的getResource方法
public URL getResource(String name) { URL url; if (parent != null) { url = parent.getResource(name); //递归调用 } else { url = getBootstrapResource(name); //启动类加载器 } if (url == null) { //系统启动类加载器没有加载到,递归回退到第一次调用然后是扩展类加载器 url = findResource(name); } return url; }这里涉及到一个知识点 :双亲委派机制 就是如果一个类加载器(用来加载 class 文件)收到了类加载的请求,首先不会自己尝试去加载这个类,二十把这个请求委派给父类加载器去完成。只有当父类加载器无返完成这个加载请求,子加载起才会尝试自己去完成加载 Java中的类加载器主要分为以下四类:
(1)根类加载器(BootStrapClassLoader), 主要负责加载jre/lib/rt.jar相关的字节码文件的。
(2)扩展类加载器(ExtensionClassLoader), 主要负载加载 jre/lib/ext/*.jar 这些jar包的。 该类加载器在JDK1。9的时候更名为: Platform Class Loader, 其父类加载器为: null。
(3)应用程序类加载器(ApplicationClassLoader), 主要负责加载用户自定义的类以及classpath环境变量所配置的jar包的。 该类加载器在JDK1.9的时候更名为: System ClassLoader, 其父类加载器为: ExtensionClassLoader。
(4)自定义类加载器(UserClassLoader), 负责加载程序员指定的特殊目录下的字节码文件的。大多数情况下,自定义类加载器只需要继承ClassLoader这个抽象类,重写findClass()和loadClass()两个方法即可。
另外Class.getResource和ClassLoader.getResource的区别,就是在加载资源文件的时候,加载方式的不同Class.getResource多了个resolveName方法 Class.getResource最后返回的name都是去掉/的 也就是说你使用ClassLoader.getResource方法时候加了/最后只会返回null
参考 彻底搞懂Class.getResource和ClassLoader.getResource的区别和底层原理 java有几种类加载器?工作原理是什么?