在java中有一个非常重要的类URL(Uniform Resource Locator)中文名为统一资源定位符,它可以标识本地的文件资源或类路径资源(xml文件、properties文件),也可以标识网络资源(http、ftp)。
首先我们在项目中经常可以看到下面这样的代码,对,非常普通!
@Test
public void go_loadPrjFile() {
URL url = Resource_test.class.getResource("/com/example/a.txt");
System.out.println(url);//file:/C:/Users/fei.chen/Desktop/demo/target/test-classes/com/example/a.txt
System.out.println(Resource_test.class.getResource("a.txt"));//file:/C:/Users/fei.chen/Desktop/demo/target/test-classes/com/example/jvm/net/a.txt
System.out.println(Resource_test.class.getClassLoader().getResource("application.properties"));//file:/C:/Users/fei.chen/Desktop/demo/target/test-classes/application.properties
System.out.println(Resource_test.class.getClassLoader().getResource("/com/example/a.txt"));//null
System.out.println("输入1:url.handler.getClass().getName()=?");
}
@Test
public void go_httpRequest() {
String url = "https://start.spring.io/";
RestTemplate restTemplate = new RestTemplate();
try (InputStream inputStream = restTemplate.execute(url, HttpMethod.GET, request -> {
}, response -> {
return response.getBody();
})) {
System.out.println("输入2:"+inputStream);
} catch (Exception ex) {
ex.printStackTrace();
}
}
这里思考几个问题?
1. ClassLoader.getResourceAsStream为何可以读取到本地文件资源?
通过指定命令行参数实现,运行时java.exe -classpath 项目的类根路径(C:\Users\fei.chen\Desktop\demo\target\test-classes),它包含了你项目类路径,可以理解到这一层基本上到位了。
2. Resource_test.getResource和Thread.currentThread().getContextClassLoader().getResource读取路径有何不同?
这个其实是考用法,本身没有啥技术含量,看源码都会知道。but,你看过了么,从输出结果中已经知道,.class是从类的相对路径中读取,可以识别/(代表项目的类根路径),ClassLoader默认是从项目的类根路径读取,不能识别/。
3. 代码输入1和输入2会输出什么呢?
没错重点来了,输入1的结果为 sun.net.www.protocol.file.Handler,也就是说go_loadPrjFile中所有获得的URL都是通过它完成的(协议都是file),而输入2的结果为sun.net.www.protocol.http.HttpURLConnection$HttpInputStream@77f80c04,go_httpRequest的协议是http。那HttpURLConnection又是什么鬼?
URLConnection | 不赘述 |
URLStreamHandler | 不赘述 |
URLStreamHandlerFactory | 不赘述 |
这样做有什么好处呢?当然是通过url可以构建统一的编程模型。
协议 | 实现 | 演示 |
file | sun.net.www.protocol.file.Handler | URL url = new URL("file:/META-INF/license.txt"); |
ftp | sun.net.www.protocol.ftp.Handler | URL url = new URL("ftp:/META-INF/license.txt"); |
jar | sun.net.www.protocol.jar.Handler | ... |
mailto | sun.net.www.protocol.mailto.Handler | ... |
http | sun.net.www.protocol.http.Handler | ... |
https | sun.net.www.protocol.https.Handler | .. |
//源码引自sun.misc;
public class Launcher {
private static class Factory implements URLStreamHandlerFactory {
private static String PREFIX = "sun.net.www.protocol";
private Factory() {
}
//通过模式:sun.net.www.protocol.${protocol}.Handler
public URLStreamHandler createURLStreamHandler(String var1) {
String var2 = PREFIX + "." + var1 + ".Handler";
try {
Class var3 = Class.forName(var2);
return (URLStreamHandler)var3.newInstance();
} catch (ReflectiveOperationException var4) {
throw new InternalError("could not load " + var1 + "system protocol handler", var4);
}
}
}
}
//使用也比较简单
@Test
public void go_buildURL() throws IOException {
File file = new File("src/test/resources/application.properties");
URL fileURL = file.toURI().toURL();
URLConnection urlConnection = fileURL.openConnection();
InputStream inputStream = urlConnection.getInputStream();
String content = StreamUtils.copyToString(inputStream, Charset.forName("UTF-8"));
System.out.println(content);
}
到这里基本上已经豁然开朗,这里提问一个问题:spring中经常用到这样的new URL("classpath:/META-INF/license.txt"),它是怎么实现的,sun底层好像是不支持这种协议的?
没错,它是不支持,会报错:java.net.MalformedURLException: unknown protocol: classpath....,那我们还是向下看吧。
spring的资源定义都是通过核心接口Resource的,它有几个比较重要的类
Resource | 核心接口是: InputStream getInputStream() |
ResourceLoader | 核心接口是: Resource getResource(String location); |
ProtocolResolver | 核心接口是: Resource resolve(String location, ResourceLoader resourceLoader) |
//用法
@Test
public void go_loadClasspathFile() throws IOException {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource("classpath:/META-INF/license.txt");
System.out.println(resource.getURL());
//拓展协议
((DefaultResourceLoader) resourceLoader).addProtocolResolver(new ProtocolResolver() {
private static final String PROTOCOL_PREFIX = "cp:/";
@Override
public Resource resolve(String location, ResourceLoader resourceLoader) {
if (location.startsWith(PROTOCOL_PREFIX)) {
String classpath = ResourceLoader.CLASSPATH_URL_PREFIX + location.substring(PROTOCOL_PREFIX.length());
return resourceLoader.getResource(classpath);
}
return null;
}
});
Resource resource1 = resourceLoader.getResource("cp:/application.properties");
System.out.println(resource1.getURL());
}
//源码引自org.springframework.core.io
public class DefaultResourceLoader implements ResourceLoader {
private ClassLoader classLoader;
private final Set protocolResolvers = new LinkedHashSet(4);
public DefaultResourceLoader() {
this.classLoader = ClassUtils.getDefaultClassLoader();
}
public DefaultResourceLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
public void setClassLoader(ClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public ClassLoader getClassLoader() {
return (this.classLoader != null ? this.classLoader : ClassUtils.getDefaultClassLoader());
}
//核心方法1:可以自定义实现拓展协议
public void addProtocolResolver(ProtocolResolver resolver) {
Assert.notNull(resolver, "ProtocolResolver must not be null");
this.protocolResolvers.add(resolver);
}
public Collection getProtocolResolvers() {
return this.protocolResolvers;
}
//核心方法2:封装了sun的URL拓展协议,还支持/与CLASSPATH_URL
@Override
public Resource getResource(String location) {
Assert.notNull(location, "Location must not be null");
for (ProtocolResolver protocolResolver : this.protocolResolvers) {
Resource resource = protocolResolver.resolve(location, this);
if (resource != null) {
return resource;
}
}
if (location.startsWith("/")) {
return getResourceByPath(location);
}
else if (location.startsWith(CLASSPATH_URL_PREFIX)) {
return new ClassPathResource(location.substring(CLASSPATH_URL_PREFIX.length()), getClassLoader());
}
else {
try {
// 封装sun的
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
protected Resource getResourceByPath(String path) {
return new ClassPathContextResource(path, getClassLoader());
}
}
总结,URL拓展协议非常重要,可以将spring在他的基础上给与拓展,再遇到这样的new URL("dubbo://192.168.1.2:20080")也就不陌生了。