-
Notifications
You must be signed in to change notification settings - Fork 5
Java资源管理以及在Spring中的应用
- XML文件
- Properties文件
File file1 = new File(""); //默认为用户路径
System.out.println(file1.getAbsolutePath());
System.out.println(System.getProperty("user.dir"));//用户当前工作路径
File file = new File("D:\\projects\\idea_workspace\\spring-boot-lesson\\spring-java-resources\\src\\main\\resources\\application.properties");
URL fileURL = file.toURI().toURL();
System.out.println(fileURL);
以上代码得出结果:
file:/D:/projects/idea_workspace/spring-boot-lesson/spring-java-resources/src/main/resources/application.properties
它是file协议(URL)
读取Properties文件事例
public class PropertiesDemo {
public static void main(String[] args) throws Exception {
Properties properties = new Properties();
//1. 绝对路径方式,不妥
// File file = new File("D:\\projects\\idea_workspace\\spring-boot-lesson\\spring-java-resources\\src\\main\\resources\\application.properties");
// properties.load(new FileReader(file));
//2. 可以读取class path路径下的,太多,不可取
// System.out.println(System.getProperty("java.class.path"));
//3. 利用ClassLoader,读取class path路径下的资源(相对路径)
//Maven默认的Resource目录就会放到class path下
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
//读取class path路径下的文件
InputStream is = classLoader.getResourceAsStream("application.properties");
properties.load(is);
System.out.println(properties.getProperty("spring.application.name"));
}
}
- HTTP
- FTP
利用org.springframework.web.client.RestTemplate
访问HTTP资源:
//https://start.spring.io/
// Spring RestTemplate
RestTemplate restTemplate = new RestTemplate();
InputStream inputStreamFromRestTemplate =
restTemplate.execute("https://start.spring.io/",
HttpMethod.GET,
request -> {
},
response -> {
return response.getBody();
}
);
System.out.println(inputStreamFromRestTemplate);
利用java.net.URL
访问HTTP资源:
URL url = new URL("https://start.spring.io/");
InputStream inputStreamFromURL = url.openStream();
System.out.println(inputStreamFromURL); //inputStreamFromRestTemplate对应的实现类与inputStreamFromURL对应的实现类相同
Java URL协议具有以下层次关系:
java.net.URL#setURLStreamHandlerFactory
-> java.net.URLStreamHandlerFactory#createURLStreamHandler
-> java.net.URLStreamHandler#openConnection(java.net.URL)
由上面的代码InputStream inputStreamFromURL = url.openStream();
可得知,需要通过java.net.URL#openStream
得到InputStream
对象。
public final InputStream openStream() throws java.io.IOException {
return openConnection().getInputStream();
}
由java.net.URL#openConnection()
方法可得知,
public URLConnection openConnection() throws java.io.IOException {
return handler.openConnection(this);
}
需要得到 java.net.URLConnection
,需要使用java.net.URLStreamHandler#openConnection(java.net.URL)
方法,而java.net.URLStreamHandler
是一个抽象类,比如有以下的具体实现:
//策略模式的实现
//REMIND: decide whether to allow the "null" class prefix or not.
//packagePrefixList += "sun.net.www.protocol";
//模式 URLStreamHandler = sun.net.www.protocol.${protocol}.Handler
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
通过 java.net.URLConnection#getInputStream
得知,需要得到具体的InputStream,则需要扩展URLConnection,比如 sun.net.www.protocol.http.HttpURLConnection
,它继承自 java.net.HttpURLConnection
。
public synchronized InputStream getInputStream() throws IOException {
this.connecting = true;
SocketPermission var1 = this.URLtoSocketPermission(this.url);
if(var1 != null) {
try {
return (InputStream)AccessController.doPrivileged(new PrivilegedExceptionAction<InputStream>() {
public InputStream run() throws IOException {
return HttpURLConnection.this.getInputStream0();
}
}, (AccessControlContext)null, new Permission[]{var1});
} catch (PrivilegedActionException var3) {
throw (IOException)var3.getException();
}
} else {
return this.getInputStream0();
}
}
URL -> URLConnection -> URLStreamHandler -> InputStream
针对每个协议的不同,JDK利用策略模式进行相应的实现,每个协议都有相应的Handler。
比如URL其中一个构造方法:
public URL(String protocol, String host, int port, String file,
URLStreamHandler handler) throws MalformedURLException {
if (handler != null) {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
// check for permission to specify a handler
checkSpecifyHandler(sm);
}
}
....
// Note: we don't do validation of the URL here. Too risky to change
// right now, but worth considering for future reference. -br
if (handler == null &&
(handler = getURLStreamHandler(protocol)) == null) {
throw new MalformedURLException("unknown protocol: " + protocol);
}
this.handler = handler;
}
上面构造方法得知,每个URL都可以跟进不同的Handler构造。
需要扩展URL协议的话,需要按照约定的Handler包命名规则:
sun.net.www.protocol(前缀) + 协议(${protocol}) + Handler
思考一个问题:Spring Boot打成Jar包,是如何访问到Jar包中的资源?
-
Spring boot其实对
java.net.URLStreamHandlerFactory
做了扩展org.springframework.boot.context.embedded.tomcat.SslStoreProviderUrlStreamHandlerFactory
org.springframework.boot.context.embedded.jetty.JasperInitializer.WarUrlStreamHandlerFactory
-
JDK对
java.net.URLStreamHandlerFactory
的默认实现-
sun.misc.Launcher.Factory
它的实现如下:
private static class Factory implements URLStreamHandlerFactory { private static String PREFIX = "sun.net.www.protocol"; private Factory() { } 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); } } }
通过查看factory调用的源码可知,Spring Boot读取Jar文件中的资源是跟
java.net.URLClassLoader#URLClassLoader(java.net.URL[], java.lang.ClassLoader, java.net.URLStreamHandlerFactory)
有关的。static class AppClassLoader extends URLClassLoader { ..... AppClassLoader(URL[] var1, ClassLoader var2) { super(var1, var2, Launcher.factory); } ..... }
在读取Jar包中资源的时候,他已经设置了相应的URLStreamHandlerFactory(即:
java.net.URL#setURLStreamHandlerFactory
),进而创建相应的URLStreamHandler,即:sun.net.www.protocol.jar.Handler
。 -
Spring中资源管理是Resource接口,org.springframework.core.io.Resource
,他有以下一些实现:
org.springframework.core.io.FileSystemResource
org.springframework.core.io.PathResource
org.springframework.core.io.ClassPathResource
通过 org.springframework.core.io.ResourceLoader#getResource
得到相应Resource对象,而 org.springframework.core.io.ResourceLoade
有很多种实现,其默认实现为:org.springframework.core.io.DefaultResourceLoader
。
如下案例说明ResourceLoader的用法:
public class ResourceDemo {
public static void main(String[] args) throws Exception {
// Resource
// FileSystemResource
// ClasspathResource
DefaultResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource("classpath:/application.properties");
InputStream inputStream = resource.getInputStream();
String content = StreamUtils.copyToString(inputStream, Charset.forName("UTF-8"));
System.out.println(content);
}
}
如上代码发现,需要获取InputStream,则需要通过ResourceLoader获取Resource,然后调用getInputStream得到。具体如下:
org.springframework.core.io.DefaultResourceLoader#getResource
方法:
@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 {
// Try to parse the location as a URL...
URL url = new URL(location);
return new UrlResource(url);
}
catch (MalformedURLException ex) {
// No URL -> resolve as resource path.
return getResourceByPath(location);
}
}
}
上述代码可知,如果url前缀为“classpath:”的时候,则通过 org.springframework.core.io.ClassPathResource#ClassPathResource(java.lang.String, java.lang.ClassLoader)
实现,其getInputStream
方法如下:
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;
}
如果不是“classpath:”开头的,则由 org.springframework.core.io.UrlResource
实现,此时底层getInputStream
实现就是委派给了java.net.URLConnection
实现。如下代码所示:
/**
* This implementation opens an InputStream for the given URL.
* <p>It sets the {@code useCaches} flag to {@code false},
* mainly to avoid jar file locking on Windows.
* @see java.net.URL#openConnection()
* @see java.net.URLConnection#setUseCaches(boolean)
* @see java.net.URLConnection#getInputStream()
*/
@Override
public InputStream getInputStream() throws IOException {
URLConnection con = this.url.openConnection();
ResourceUtils.useCachesIfNecessary(con);
try {
return con.getInputStream();
}
catch (IOException ex) {
// Close the HTTP connection (if applicable).
if (con instanceof HttpURLConnection) {
((HttpURLConnection) con).disconnect();
}
throw ex;
}
}
综上,说明Spring底层实现也是采用Java URL扩展协议的实现,同理,org.springframework.core.io.ClassPathResource#getURL
方法实现也是委派给Class或者ClassLoader去实现。
/**
* Resolves a URL for the underlying class path resource.
* @return the resolved URL, or {@code null} if not resolvable
*/
protected URL resolveURL() {
if (this.clazz != null) {
return this.clazz.getResource(this.path);
}
else if (this.classLoader != null) {
return this.classLoader.getResource(this.path);
}
else {
return ClassLoader.getSystemResource(this.path);
}
}
排除某个配置文件在maven中的配置(pom.xml)
<resources>
<resource>
<directory>${basedir}/src/main/resources</directory>
<!--包含resource的路径-->
<includes>
<!-- basedir:当前module所在的根目录 -->
<include>${basedir}</include>
</includes>
<!--排除某个resource的路径-->
<excludes>
<exclude>
*.properties
</exclude>
</excludes>
</resource>
</resources>
SegmentFault: https://segmentfault.com/u/landy8530
简书:https://www.jianshu.com/u/36a7d3a994ac
CSDN:https://blog.csdn.net/landy8530
开源中国:https://my.oschina.net/landy8530
微信公众号:蚂蚁与咖啡的故事