For new comers, one most frustrating issue is 'The file is obviously there, but the code just can't find it...Damn it. :) '.
Based on the daily work and study, I have a feeling that, to have a good understanding of how things works can help a lot when you want to use those things.
High buildings are always built on firm foundations. Just take some time to read the doc or source code when you have doubts. All the daily accumulations will contribute to better understanding. OK, stop all the craps.
To get resources based on classpath, there are basically three ways:
URL url = this.getClass().getResource("resource_name"); URL url = this.getClass().getClassLoader().getResource("resource_name"); URL url = Thread.currentThread().getContextClassLoader().getResource("resource_name");
The 1st one,
URL url = this.getClass().getResource("resource_name");
This is to get resource based on current class instance. But, if we check the source code of this method, we'll see that actually we still get the resource using classloader.
/** * Finds a resource with a given name. The rules for searching resources * associated with a given class are implemented by the defining * {@linkplain ClassLoader class loader} of the class. This method * delegates to this object's class loader. If this object was loaded by * the bootstrap class loader, the method delegates to {@link * ClassLoader#getSystemResource}. * * <p> Before delegation, an absolute resource name is constructed from the * given resource name using this algorithm: * * <ul> * * <li> If the <tt>name</tt> begins with a <tt>'/'</tt> * (<tt>'\u002f'</tt>), then the absolute name of the resource is the * portion of the <tt>name</tt> following the <tt>'/'</tt>. * * <li> Otherwise, the absolute name is of the following form: * * <blockquote><pre> * <tt>modified_package_name</tt>/<tt>name</tt> * </pre></blockquote> * * <p> Where the <tt>modified_package_name</tt> is the package name of this * object with <tt>'/'</tt> substituted for <tt>'.'</tt> * (<tt>'\u002e'</tt>). * * </ul> * * @param name name of the desired resource * @return A {@link java.net.URL} object or <tt>null</tt> if no * resource with this name is found * @since JDK1.1 */ public java.net.URL getResource(String name) { name = resolveName(name); ClassLoader cl = getClassLoader0(); if (cl==null) { // A system class. return ClassLoader.getSystemResource(name); } return cl.getResource(name); }
And, in the resolveName(name) method:
/** * Add a package name prefix if the name is not absolute Remove leading "/" * if name is absolute */ private String resolveName(String name) { if (name == null) { return name; } if (!name.startsWith("/")) { Class c = this; while (c.isArray()) { c = c.getComponentType(); } String baseName = c.getName(); int index = baseName.lastIndexOf('.'); if (index != -1) { name = baseName.substring(0, index).replace('.', '/') +"/"+name; } } else { name = name.substring(1); } return name; }
Based on these, we can have the conclusion that:
*****
1. If the passed in resource name is starting with slash '/', the input path is relative to the classpath.
Attention: If you are running a test case, and if the test classpath have exactly the same resource with the main classpath, then the one on the test classpath will be used. If the test classpath does not have the resource, the main classpath will be used.
2. If the passed in resource name does not start with slash '/'. Then the method resolveName will add the package to the path. So in this case, the path is relative to current package path.
*****
Example:
package com.test; import java.io.IOException; import java.io.InputStream; import java.util.Properties; import org.junit.Test; public class PathTest { @Test public void test() { System.out.println("For=> this.getClass().getResource"); String path = "test.properties"; System.out.println("'"+path+"' => "+this.getClass().getResource(path)); InputStream in = this.getClass().getResourceAsStream(path); System.out.println("Property Version => " + loadFile(in)); path = "/com/test/test.properties"; System.out.println("'"+path+"' => "+this.getClass().getResource(path)); in = this.getClass().getResourceAsStream(path); System.out.println("Property Version => " + loadFile(in)); path = "/test/test.properties"; System.out.println("'"+path+"' => "+this.getClass().getResource(path)); in = this.getClass().getResourceAsStream(path); System.out.println("Property Version => " + loadFile(in)); path = ""; System.out.println("'"+path+"' => "+this.getClass().getResource(path)); path = "/"; System.out.println("'"+path+"' => "+this.getClass().getResource(path)); path = "/test"; System.out.println("'"+path+"' => "+this.getClass().getResource(path)); path = "/non-test"; System.out.println("'"+path+"' => "+this.getClass().getResource(path)); path = "/non-test-emptyfolder"; System.out.println("'"+path+"' => "+this.getClass().getResource(path)); path = "test-emptyfolder"; System.out.println("'"+path+"' => "+this.getClass().getResource(path)); path = "/test-emptyfolder"; System.out.println("'"+path+"' => "+this.getClass().getResource(path)); } public String loadFile(InputStream in){ Properties prop = new Properties(); try { prop.load(in); return (String) prop.get("test.version"); } catch (IOException e) { e.printStackTrace(); } return null; } }Output:
For=> this.getClass().getResource 'test.properties' => file:/C:/stsworkspace/test-code/target/classes/com/test/test.properties Property Version => 5.0(src/main/java/come.test/test.properties) '/com/test/test.properties' => file:/C:/stsworkspace/test-code/target/classes/com/test/test.properties Property Version => 5.0(src/main/java/come.test/test.properties) '/test/test.properties' => file:/C:/stsworkspace/test-code/target/classes/test/test.properties Property Version => 1.0(src/main/resources/test.properties) '' => file:/C:/stsworkspace/test-code/target/test-classes/com/test/ '/' => file:/C:/stsworkspace/test-code/target/test-classes/ '/test' => file:/C:/stsworkspace/test-code/target/test-classes/test '/non-test' => file:/C:/stsworkspace/test-code/target/classes/non-test '/non-test-emptyfolder' => null 'test-emptyfolder' => null '/test-emptyfolder' => null
Above is the result when we run in IDE, but if we run the test case using maven, there will be problem. Because, when maven do the packaging, by default it'll remove all the non-class files in the package because it expects you to put all the resources under src/main/resources. Also, it'll ignore all the empty folders under resources.
After we package the project:
In the target folder we can see that:
Inside classes folder:
Inside test-classes folder:
==========================================Beautiful Line Break======================================
The 2nd one,
URL url = this.getClass().getClassLoader().getResource("resource_name");
This is to get the resource using classloader. By looking into the source code, we can see that it'll go all the way up to get the parent class loader. Regarding the classloader part, I'll not include details now, I still more research on the basic JVM knowledge.
/** * Finds the resource with the given name. A resource is some data * (images, audio, text, etc) that can be accessed by class code in a way * that is independent of the location of the code. * * <p> The name of a resource is a '<tt>/</tt>'-separated path name that * identifies the resource. * * <p> This method will first search the parent class loader for the * resource; if the parent is <tt>null</tt> the path of the class loader * built-in to the virtual machine is searched. That failing, this method * will invoke {@link #findResource(String)} to find the resource. </p> * * @param name * The resource name * * @return A <tt>URL</tt> object for reading the resource, or * <tt>null</tt> if the resource could not be found or the invoker * doesn't have adequate privileges to get the resource. * * @since 1.1 */ public URL getResource(String name) { URL url; if (parent != null) { url = parent.getResource(name); } else { url = getBootstrapResource(name); } if (url == null) { url = findResource(name); } return url; }
Attention:
If one class is loaded by bootstrap loader, then sometimes it'll return null when we get the classloader. For example, if we use 'new Object().getClass().getClassLoader()', it may return null. If we use this classloader to load resource, it'll throw null pointer exception. So to make it safe, we better use 'this.getClass().getClassLoader()'.
Normally if we don't specify special classloader in our project. The class that we write ourselves will be loaded using 'ClassLoader.getSystemClassLoader()'.
*****
So, in the second way, the resource path that passed in should be relative to the classpath.
And it does not support absolute path.
Attention: If you are running a test case, and if the test classpath have exactly the same resource with the main classpath, then the one on the test classpath will be used. If the test classpath does not have the resource, the main classpath will be used.
*****
Example:
System.out.println(); System.out.println("For=> this.getClass().getClassLoader().getResource"); path = "test/test.properties"; System.out.println("'"+path+"' => "+this.getClass().getClassLoader().getResource(path)); in = this.getClass().getClassLoader().getResourceAsStream(path); System.out.println("Property Version => " + loadFile(in)); path = "com/test/test.properties"; System.out.println("'"+path+"' => "+this.getClass().getClassLoader().getResource(path)); in = this.getClass().getClassLoader().getResourceAsStream(path); System.out.println("Property Version => " + loadFile(in)); path = "com/test/test.properties"; System.out.println("'"+path+"' => "+this.getClass().getClassLoader().getResource(path)); in = this.getClass().getClassLoader().getResourceAsStream(path); System.out.println("Property Version => " + loadFile(in)); path = ""; System.out.println("'"+path+"' => "+this.getClass().getClassLoader().getResource(path)); path = "/"; System.out.println("'"+path+"' => "+this.getClass().getClassLoader().getResource(path)); path = "test"; System.out.println("'"+path+"' => "+this.getClass().getClassLoader().getResource(path)); path = "/test"; System.out.println("'"+path+"' => "+this.getClass().getClassLoader().getResource(path)); path = "non-test"; System.out.println("'"+path+"' => "+this.getClass().getClassLoader().getResource(path)); path = "/non-test"; System.out.println("'"+path+"' => "+this.getClass().getClassLoader().getResource(path)); path = "non-test-emptyfolder"; System.out.println("'"+path+"' => "+this.getClass().getClassLoader().getResource(path)); path = "/non-test-emptyfolder"; System.out.println("'"+path+"' => "+this.getClass().getClassLoader().getResource(path)); path = "test-emptyfolder"; System.out.println("'"+path+"' => "+this.getClass().getClassLoader().getResource(path)); path = "/test-emptyfolder"; System.out.println("'"+path+"' => "+this.getClass().getClassLoader().getResource(path));
Output:
For=> this.getClass().getClassLoader().getResource 'test/test.properties' => file:/C:/stsworkspace/test-code/target/classes/test/test.properties Property Version => 1.0(src/main/resources/test.properties) 'com/test/test.properties' => file:/C:/stsworkspace/test-code/target/classes/com/test/test.properties Property Version => 5.0(src/main/java/come.test/test.properties) 'com/test/test.properties' => file:/C:/stsworkspace/test-code/target/classes/com/test/test.properties Property Version => 5.0(src/main/java/come.test/test.properties) '' => file:/C:/stsworkspace/test-code/target/test-classes/ '/' => null 'test' => file:/C:/stsworkspace/test-code/target/test-classes/test '/test' => null 'non-test' => file:/C:/stsworkspace/test-code/target/classes/non-test '/non-test' => null 'non-test-emptyfolder' => null '/non-test-emptyfolder' => null 'test-emptyfolder' => null '/test-emptyfolder' => null
Again, Above is the result when we run in IDE, if we run the test case using maven, there will be problem.
No comments:
Post a Comment