spring IOC资源加载,资源的查找定位,类的继承结构如下:
资源的加载是通过类XmlBeanDefinitionReader类来实现的。经过之前的一系列调用 ,到达loadBeanDefinitions方法。如下,非关键代码已经去掉。
public int loadBeanDefinitions(EncodedResource encodedResource) throws BeanDefinitionStoreException {
try {
InputStream inputStream = encodedResource.getResource().getInputStream();
try {
InputSource inputSource = new InputSource(inputStream);
if (encodedResource.getEncoding() != null) {
inputSource.setEncoding(encodedResource.getEncoding());
}
return doLoadBeanDefinitions(inputSource, encodedResource.getResource());
}
finally {
inputStream.close();
}
}
catch (IOException ex) {
throw new BeanDefinitionStoreException(
"IOException parsing XML document from " + encodedResource.getResource(), ex);
}
finally {
currentResources.remove(encodedResource);
if (currentResources.isEmpty()) {
this.resourcesCurrentlyBeingLoaded.remove();
}
}
}
执行 InputStream inputStream =encodedResource.getResource().getInputStream() 获得资源的I/O。由上图中类的继承关系,接口InputStreamStream的方法getInputStream()。接口方法的实现类为ClassPathResource。方法的实现如下:
public InputStream getInputStream() throws IOException {
InputStream is;
if (this.clazz != null) {
is = this.clazz.getResourceAsStream(this.path);
}
else if (this.classLoader != null) {
is = this.classLoader.getResourceAsStream(this.path);
}
else {
is = ClassLoader.getSystemResourceAsStream(this.path);
}
if (is == null) {
throw new FileNotFoundException(getDescription() + " cannot be opened because it does not exist");
}
return is;
}
spring IOC中使用的类加载器是“应用类加载器”,在初始化时已经确定,因此classLoader不为null。进入第二个判断入口。调用抽象类ClassLoader的getResourceAsStream()方法,方法的实现类是java.net.URLClassLoader,返回资源的I/O,如下:
public InputStream getResourceAsStream(String name) {
URL url = getResource(name);
try {
if (url == null) {
return null;
}
URLConnection urlc = url.openConnection();
InputStream is = urlc.getInputStream();
if (urlc instanceof JarURLConnection) {
JarURLConnection juc = (JarURLConnection)urlc;
JarFile jar = juc.getJarFile();
synchronized (closeables) {
if (!closeables.containsKey(jar)) {
closeables.put(jar, null);
}
}
} else if (urlc instanceof sun.net.www.protocol.file.FileURLConnection) {
synchronized (closeables) {
closeables.put(is, null);
}
}
return is;
} catch (IOException e) {
return null;
}
}
ClassLoader类的getResource、getResources等方法可以加载classpath中的资源,ClassLoader获取资源传入的参数是相当于classpath的相对路径,如果某个资源想要被ClassLoader加载。该资源需要放到当前的classpath中,或者把资源的目录、jar包文件作为classpath。ClassLoader在加载一个资源时默认使用双亲委派模型原则。如果可以通过父加载器找到资源,则自己不必继续查找,交由父加载器去查找,并返回父加载器查找到的资源。
调用java.lang.ClassLoader的getResource返回资源的i/o,可以看到双亲委派模型在资源查找中的运用。
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;
}
因资源文件放在classpath路径下,默认由应用类加载器加载。即调用url= findResource(name)返回资源信息。
接下来,了解getBootstrapResource(name)和findResource(name)。先看getBootstrapResource(name)的执行代码。
/**
* Find resources from the VM's built-in classloader.(通过虚拟机内置的类加载器查找资源。)
*/
private static URL getBootstrapResource(String name) {
//取得启动类加载器的路径实例
URLClassPath ucp = getBootstrapClassPath();
//通过启动类加载器获取资源
Resource res = ucp.getResource(name);
//返回资源的URL实例对象。
return res != null ? res.getURL() : null;
}
// Returns the URLClassPath that is used for finding system resources.
static URLClassPath getBootstrapClassPath() {
return sun.misc.Launcher.getBootstrapClassPath();
}
调试代码,getBootstrapResource返回值为null,父类加载器查找的路径中没有指定资源,通知子类去加载器类。
执行url= findResource(name);
/**
* Finds the resource with the specified name on the URL search path.
*
* @param name the name of the resource
* @return a {@code URL} for the resource, or {@code null}
* if the resource could not be found, or if the loader is closed.
*/
public URL findResource(final String name) {
/*
* The same restriction to finding classes applies to resources
*/
URL url = AccessController.doPrivileged(
new PrivilegedAction<URL>() {
public URL run() {
return ucp.findResource(name, true);
}
}, acc);
return url != null ? ucp.checkURL(url) : null;
}
其中,AccessController是后续要研究的内容,这里暂不提及。调试,返回的url不为null。通过对上述调用过程及结果分析,资源的查找过程符合双亲委派模型原则。
补充一
虚拟机类加载过程的“加载”阶段。在加载阶段,虚拟机需要完成以下三件事情:
1、 通过一个类的全限定名获取定义此类的二进制字节流。
2、 将这个字节流代表的静态存储结构转化为方法区运行时数据结构。
3、 在内存中生成一个代表这个类的java.lang.Class对象,作为方法区这个类的各种数据访问入口。
虚拟机规范的这3点要求,其实并不算具体。如第一点,通过一个类的全限定名获取定义此类的二进制字节流。它没有指明二进制字节流要从一个class文件获取,也没有说怎样获取,灵活度相当大。例如:
1、 从zip包中获取。
2、 从网络获取。
3、 运行时计算生成,使用最多的场景就是动态代理技术。
4、 由其它文件生成。
5、 从数据库中读取。
补充二
URL类的用法。java URL处理,URL(UniformResource Loacator)统一资源定位符,表示为互联网上的资源。java网络类可以通过网络或者远程连接来实现应用。而且,可以对国际互联网以及URL资源进行访问。java的URL类可以让访问网络资源就像是访问本地文件夹一样方便快捷。可以通过使用java的URL类经由URL完成读取和修改数据操作。看个例子:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;
public class UrlTest {
public static void main(String[] args) throws IOException {
//创建url连接实例
URL url = new URL("https://www.baidu.com");
System.out.println(url);
//在访问这个连接的资源和内容之前,需要打开这些资源和内容上的连接,使用openConnection来完成这一操作
URLConnection urlcon = (URLConnection)url.openConnection();
InputStream is = urlcon.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
StringBuffer sb = new StringBuffer();
String s = null;
while((s=br.readLine())!=null){
sb.append(s).append("\n");
}
System.out.println(sb.toString());
}
}
通过上述例子,对URL的使用有了直观的了解。spring中获取到资源的URL,执行 URLConnection urlc = url.openConnection(),打开资源的连接,为接下来资源的访问作好准备。