java冷知识:URL扩展协议与spring Resource的关联

在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又是什么鬼?

URL拓展协议

URLConnection不赘述
URLStreamHandler不赘述
URLStreamHandlerFactory不赘述

这样做有什么好处呢?当然是通过url可以构建统一的编程模型。

协议实现演示
filesun.net.www.protocol.file.HandlerURL url = new URL("file:/META-INF/license.txt");
ftpsun.net.www.protocol.ftp.HandlerURL url = new URL("ftp:/META-INF/license.txt");
jarsun.net.www.protocol.jar.Handler...
mailtosun.net.www.protocol.mailto.Handler...
httpsun.net.www.protocol.http.Handler...
httpssun.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

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<ProtocolResolver> protocolResolvers = new LinkedHashSet<ProtocolResolver>(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<ProtocolResolver> 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")也就不陌生了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值