1.java访问资源的方式
比如现在有一个资源文件application.properties,现在要得到该配置文件的流,该文件放在resouces目录下,文件内容如下
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.用类加载器来实现,不过这个是直接加载编译好的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包,或者网络资源。通过不同前缀名来代表每一种资源的协议,具体请求而的时候由不同的处理协议的handler进行处理
举个例子:
/**
* java 访问资源可以通过URL这个对象来访问各种资源,实际上URL为了得到inpustream需要以下的过程
* URL -> URLConnection -> URLStreamHandler -> InputStream
* 这里用了委派模式,获取inputStream会先要获取URLConnection,而URLConnection是由URLStreamHandler创建而来
* 下面秒速了java支持的集中协议,可以rt.jar下在sun.net.www.protocol找到支持的协议模式
* URL url = new URL("https://www.baidu.com"); // https 协议
* URL ftpURL = new URL("ftp://ftp.baidu.com"); // ftp 协议
* URL jar = new URL("jar://jar.baidu.com"); // jar 协议
* 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 核心包的工具类,把流的内容转成字符串
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
}
}
}
基本上可以了解到是根据 "sun.net.www.protocol" + 协议名称+ ".handler" 得到一个类的全限定名,然后通过Class.forName(clsName)来得到类,如果出错,则用引导类加载器来加载类,判断cls不为空,cls.newInstance()用反射创建一个类
在java的
sun.misc.Launcher有用到URLStreamHandlerFactory
的工厂,ExtClassLoader和AppClassLoader的有用到这个URLStreamHandlerFactory工厂,有兴趣的源码可以研究下
2.拓展java的classpath路劲加载协议
要模仿创建一个类名为 sun.net.www.protocol.classpath.hanlder的类,该类如下:
/**
* Classpath 协议 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 实现
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文件协议加载资源文件
Resource fileResource = recourceLoder.getResource("file:D:/Eclipse2018Data/personProject/spring/spring-resources/src/main/resources/application.properties");
System.out.println(fileResource.getInputStream());
//通过https协议加载资源文件
Resource httpResource = recourceLoder.getResource("https://start.spring.io/");
System.out.println(fileResource.getInputStream());
}
}
4.基于spring的拓展协议
/**
* spring 拓展协议举例
*
*/
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()的协议一个个遍历先解析
,解析到了就返回,cp拓展的协议的实现是委派给了ClassPathResource去解析了