在前一篇讲Spring Boot启动流程的文章中,在获取Initializers和Listeners的时候都会通过ClassLoader的getResources方法获取相关文件的全限定名。
今天着重分析一下ClassLoader.getResources,ClsasLoader.getResource,Class.getResource
看下面一段代码
System.out.println(TestResources.class.getResource("Test.xml")); System.out.println(TestResources.class.getResource("/Test.xml"));返回结果:
null file:/Users/xujingzhou/IdeaProjects/test-resources/target/classes/Test.xml我们先看现象,同样都是调用调用class.getResource方法,为什么会有不同的结果呢。
首先看一下class.getResource()
public java.net.URL getResource(String name) { name = resolveName(name); //获取当前的类加载器 ClassLoader cl = getClassLoader0(); if (cl==null) { // A system class. //调用系统类加载器 return ClassLoader.getSystemResource(name); } //调用classLoader的getResource方法 return cl.getResource(name); }首先调用的是resolveName(),根据我们传入的参数一个加‘/’一个没加,刚好在resolveName中存在判断。
private String resolveName(String name) { if (name == null) { return name; } //如果name的开头不包含'/' if (!name.startsWith("/")) { //获取当前类 Class<?> c = this; while (c.isArray()) { c = c.getComponentType(); } //获取当前类名 String baseName = c.getName(); //获取最后一个'.'出现的坐标 int index = baseName.lastIndexOf('.'); if (index != -1) { //将当前传入的name拼接到当前类路径下 name = baseName.substring(0, index).replace('.', '/') +"/"+name; } } else { //返回去除‘/’之后的名字。 name = name.substring(1); } return name; }看到这里,我们应该去看看ClassLoader中的getResource是如何实现的了,先做个小实验吧。
System.out.println(TestResources.class.getClassLoader().getResource("Test.xml")); System.out.println(TestResources.class.getClassLoader().getResource("/Test.xml"));输出结果:
file:/Users/xujingzhou/IdeaProjects/test-resources/target/classes/Test.xml null先看结论,当传入的name为文件名的时候,是可以找到当前文件的全限定名的。但是为路径的时候就找不到了,接下来就看看是如何实现的。
public URL getResource(String name) { URL url; //如果父加载器不为null,则调用父加载器,符合双亲委派机制 if (parent != null) { url = parent.getResource(name); //这其实是一个递归调用,最终都会走到这一步调用系统启动类加载器 } else { url = getBootstrapResource(name); } //一个递归回退的过程,都会走一遍findResource方法 if (url == null) { url = findResource(name); } return url;//如果最后都没加载到,双亲委派机制失败,加载应用自身的类加载器 }可是这样做的目的是为了什么呢
现在主要分为两种情况
name前加‘/’ TestClassLoader.class.getResource("/") Class类中的getResource方法返回的是""ClassLoader类中的getResource方法返回的是 路径 TestClassLoader.class.getClassLoader().getResource("/") ClassLoader类中的getResource方法返回的是 null name前不加‘/’ TestClassLoader.class.getResource("") Class类中的getResource方法返回的是 路径ClassLoader类中的getResource方法返回的是 路径 TestClassLoader.class.getClassLoader().getResource("") ClassLoader类中的getResource方法返回的是 路径class的getResource
当path不以‘/’开头我们就可以获得和当前类在路径相同的文件,也就是我们说的同级当path以‘/’开头时,这样就可以获取到classpath下任意路径下的资源classLoader的getResource
当path不以‘/’开头的时候,首先通过双亲委派机制,逐级向上委托的形式加载,最后发现双亲没有加载,然后通过当前类加载classpath下资源文件当path以‘/’开头时,‘/’表示boot classLoader中的加载范围,这个类是CPP写的,所以加载范围是null。