1.java訪問資源的方式
比如現(xiàn)在有一個資源文件application.properties,現(xiàn)在要得到該配置文件的流,該文件放在resouces目錄下,文件內(nèi)容如下
spring.application.name = spring-resources
通過java方式的加載資源,打印輸出流
/**
* @Project: spring
* @description: java 加載資源的方式
* @author: sunkang
* @create: 2018-09-23 22:16
* @ModificationHistory who when What
**/
public class FileLoadDemo {
public static void main(String[] args) throws IOException {
//1.用類加載器來實現(xiàn),不過這個是直接加載編譯好的classpath路勁的
InputStream inputStream= FileLoadDemo.class.getClassLoader().getResourceAsStream("application.properties");
System.out.println(inputStream);
//2.通過絕對路勁的方式來加載
File file = new File("");
//file.getAbsolutePath() 得到的是user.dir,工作路徑,跟下面表示方法一樣
System.out.println(System.getProperty("user.dir"));
//spring-resources為一個模塊,所以這里需要加上模塊的路徑
File resouceFile = new File("spring-resources/src/main/resources/application.properties");
InputStream ins= new BufferedInputStream(new FileInputStream(resouceFile));
System.out.println(ins);
//3通過nio加載
InputStream nos= Files.newInputStream(Paths.get("spring-resources/src/main/resources/application.properties"));
System.out.println(nos);
//4 通過URL的方式
URL fileURL = file.toURI().toURL();
URLConnection urlConnection = fileURL.openConnection();
InputStream inputStreamFromURL = urlConnection.getInputStream();
System.out.println(inputStreamFromURL);
}
}
這里面著重講解第四種方式
通過URL來定義資源的位置,該資源可以是文件,jar包,或者網(wǎng)絡資源。通過不同前綴名來代表每一種資源的協(xié)議,具體請求而的時候由不同的處理協(xié)議的handler進行處理
舉個例子:
/**
* java 訪問資源可以通過URL這個對象來訪問各種資源,實際上URL為了得到inpustream需要以下的過程
* URL -> URLConnection -> URLStreamHandler -> InputStream
* 這里用了委派模式,獲取inputStream會先要獲取URLConnection,而URLConnection是由URLStreamHandler創(chuàng)建而來
* 下面秒速了java支持的集中協(xié)議,可以rt.jar下在sun.net.www.protocol找到支持的協(xié)議模式
* URL url = new URL("https://www.baidu.com"); // https 協(xié)議
* URL ftpURL = new URL("ftp://ftp.baidu.com"); // ftp 協(xié)議
* URL jar = new URL("jar://jar.baidu.com"); // jar 協(xié)議
* file URLStreamHandler = sun.net.www.protocol.file.Handler
* http URLStreamHandler = sun.net.www.protocol.http.Handler
* https URLStreamHandler = sun.net.www.protocol.https.Handler
* jar URLStreamHandler = sun.net.www.protocol.jar.Handler
* ftp URLStreamHandler = sun.net.www.protocol.ftp.Handler
* 模式 URLStreamHandler = sun.net.www.protocol.${protocol}.Handler
*
*/
public class FileDemo {
public static void main(String[] args) throws Exception {
File file = new File("spring-resources/src/main/resources/application.properties");
URL fileURL = file.toURI().toURL();
URLConnection urlConnection = fileURL.openConnection();
InputStream inputStreamFromURL = urlConnection.getInputStream();
//spring-core 核心包的工具類,把流的內(nèi)容轉成字符串
String content = StreamUtils.copyToString(inputStreamFromURL, Charset.forName("UTF-8"));
System.out.println(inputStreamFromURL);
}
}
從下面的這個圖可以看出URL和其他接口的關系

在URL源碼部分中的getURLStreamHandler的方法中,存在如下的代碼段
if (handler == null) {
String packagePrefixList = null;
packagePrefixList
= java.security.AccessController.doPrivileged(
new sun.security.action.GetPropertyAction(
protocolPathProp,""));
if (packagePrefixList != "") {
packagePrefixList += "|";
}
// REMIND: decide whether to allow the "null" class prefix
// or not.
packagePrefixList += "sun.net.www.protocol";
StringTokenizer packagePrefixIter =
new StringTokenizer(packagePrefixList, "|");
while (handler == null &&
packagePrefixIter.hasMoreTokens()) {
String packagePrefix =
packagePrefixIter.nextToken().trim();
try {
String clsName = packagePrefix + "." + protocol +
".Handler";
Class<?> cls = null;
try {
cls = Class.forName(clsName);
} catch (ClassNotFoundException e) {
ClassLoader cl = ClassLoader.getSystemClassLoader();
if (cl != null) {
cls = cl.loadClass(clsName);
}
}
if (cls != null) {
handler =
(URLStreamHandler)cls.newInstance();
}
} catch (Exception e) {
// any number of exceptions can get thrown here
}
}
}
基本上可以了解到是根據(jù) "sun.net.www.protocol" + 協(xié)議名稱+ ".handler" 得到一個類的全限定名,然后通過Class.forName(clsName)來得到類,如果出錯,則用引導類加載器來加載類,判斷cls不為空,cls.newInstance()用反射創(chuàng)建一個類
在java的
sun.misc.Launcher有用到URLStreamHandlerFactory的工廠,ExtClassLoader和AppClassLoader的有用到這個URLStreamHandlerFactory工廠,有興趣的源碼可以研究下
2.拓展java的classpath路勁加載協(xié)議
要模仿創(chuàng)建一個類名為 sun.net.www.protocol.classpath.hanlder的類,該類如下:
/**
* Classpath 協(xié)議 Handler
*
*/
public class Handler extends URLStreamHandler {
private final String PROTOCOL_PREFIX = "classpath:/";
@Override
protected URLConnection openConnection(URL url) throws IOException {
// 比如 url = "classpath:/META-INF/license.txt"
// classpath = META-INF/license.txt
// 移除前綴 classpath:/
// classpath:/META-INF/license.txt
String urlString = url.toString();
// META-INF/license.txt
String classpath = urlString.substring(PROTOCOL_PREFIX.length());
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL classpathURL = classLoader.getResource(classpath);
// 委派給 ClassLoader 實現(xiàn)
return classpathURL.openConnection();
}
}
測試類:
public class ClassPathUrlTest {
public static void main(String[] args) throws IOException {
URL url = new URL("classpath:/application.properties");
URLConnection urlConnection = url.openConnection();
InputStream inputStreamFromURL = urlConnection.getInputStream();
String content = StreamUtils.copyToString(inputStreamFromURL, Charset.forName("UTF-8"));
System.out.println(content);
}
}
測試結果如下:
spring.application.name = spring-resources
3.spring的加載方式 (DefaultResourceLoader)
/**
* @Project: spring
* @description: spring 默認的加載資源的方式
* @author: sunkang
* @create: 2018-09-23 16:51
* @ModificationHistory who when What
**/
public class SprignResouceLoadTest {
public static void main(String[] args) throws IOException {
// ApplicationContext context = new ClassPathXmlApplicationContext();
// context.getResource("application.properties")
//ClassPathXmlApplicationContext繼承了DefaultResourceLoader,所以實際上DefaultResourceLoader在處理
//用classPath
//默認的加載器
ResourceLoader recourceLoder = new DefaultResourceLoader();
Resource resource = recourceLoder.getResource("application.properties");
InputStream ins= resource.getInputStream();
System.out.println(ins);
//加載 classpath路徑下的文件
Resource classpathResource = recourceLoder.getResource("classpath:application.properties");
System.out.println(classpathResource.getInputStream());
//通過file文件協(xié)議加載資源文件
Resource fileResource = recourceLoder.getResource("file:D:/Eclipse2018Data/personProject/spring/spring-resources/src/main/resources/application.properties");
System.out.println(fileResource.getInputStream());
//通過https協(xié)議加載資源文件
Resource httpResource = recourceLoder.getResource("https://start.spring.io/");
System.out.println(fileResource.getInputStream());
}
}
4.基于spring的拓展協(xié)議
/**
* spring 拓展協(xié)議舉例
*
*/
public class ResourceDemo {
public static void main(String[] args) throws IOException {
// Resource
// FileSystemResource
// ClasspathResource
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
// 添加一個protocol = "cp" 處理
resourceLoader.addProtocolResolver(new ProtocolResolver() {
private static final String PROTOCOL_PREFIX = "cp:/";
@Override
public Resource resolve(String location, ResourceLoader resourceLoader) {
if (location.startsWith(PROTOCOL_PREFIX)) {
// application.properties
String classpath = ResourceLoader.CLASSPATH_URL_PREFIX +
location.substring(PROTOCOL_PREFIX.length());
// cp:/application.properties -> classpath:application.properties
return resourceLoader.getResource(classpath);
}
return null;
}
});
Resource resource =
resourceLoader.getResource("cp:/application.properties");
InputStream inputStream = resource.getInputStream();
String content = StreamUtils.copyToString(inputStream, Charset.forName("UTF-8"));
System.out.println(content);
}
}
看DefaultResourceLoader的源碼可以了解到,先由添加的
resourceLoader.addProtocolResolver()的協(xié)議一個個遍歷先解析,解析到了就返回,cp拓展的協(xié)議的實現(xiàn)是委派給了ClassPathResource去解析了