|
From: Colin S. <col...@ex...> - 2003-11-23 20:37:06
|
I just figured out that we have an issue with the existing code in
ClassloaderUtils.getResourceAsStream, which is used in a number of places.
Whoever wrote the original method:
public static InputStream getResourceAsStream(Class clazz, String
name) {
ClassLoader ccl = Thread.currentThread().getContextClassLoader();
InputStream in = null;
if (ccl != null) {
in = ccl.getResourceAsStream(name);
}
if (in == null) {
in = clazz.getResourceAsStream(name);
}
return in;
}
did not realize that ClassLoader.getResourceAsStream and
Class.getResourceAsStream do _not_ behave the same way w/regards to the
path this is supplied to them.
Class.getResource, expects to be given a path which is either relative
or absolute to that class, as indicated by a leading /. If there is a
leading slash, it strips off that slash, then passes on the request to
its classloader (ie it calls ClassLoader.getResource). If there is no
slash, it prepends the class package (without a leading slash) to the
specified path, then calls getResource on its classloader.
ClassLoader.getResource, on the other hand, expects that it is given
only absolute paths, _without_ a leading slash. And it will fail if
there is a leading slash.
As such, if the code above is called with a path that is correct for the
call to the context classloader (ie no leading slash), it will be
incorrect for the call to class.getResource (as it will be considered
relative). If it is called with code that is fully qualified/absolute
and correct for the class.getResurce (ie it has a leading slash), it
will always fail for the context classloader getResource call.
Not good... Now this code is used in a number of places, usually with
'/' prepended on the actual path. That is, the code assumes the resource
lookup behaves in a similar fashion to Class.getResource, not
ClassLoader.getResource. In that respect, the solution of 'least
surpirse' is to treat the context classloader case the same. Even though
it doesn't actually use the class package, if the path doesn't start
with '/', it should be assumed to be relative and added to the specified
resource path. Then this should be documented more explicitly (ie that
it behaves like Class.getResource, not ClassLoader.getResource).
Then, this could be deprecated, and we could make a prefered variation
of getResource which doesn't take a class argument, just a resource
path, and it would simply behave as the existing
ClassLoader.getResource. From what I know, except in Java 1.1, the
context classloader is always safe to use. (do we need Java 1.1
compatibility here?)
Then there is the question of classPackageAsResourcePath, which I
originally wrote, but actually is only really correct when then used to
call the current ClassLoaderUtils.getResourceAsStream or
Class.getResourceAsStream. At a minimum, it should be documented, but I
think it would be cleaner to not have it prepend a leading slash at all,
so it could be fed to the regular ClassLoader.getResource.
Finally, I think it possibly breaks the principal of least surprise that
in most places (like ClassPathXmlApplicationContext or the web xml
application context), we take resource paths and assume that
a/b/c/d/123.abc
is the same thing as
/a/b/c/d/123.abc
That is not how ClassLoader.getResource treats things, and that's not
how Class.getResource treats things, so why are we doing it? At a
minimum, it should be strongly documented, but I think it's simpler to
just accept the first form only, to be like classloader resource
specifications in other java apps/libs. This would obviously break some
existing code. In fact, we even usually add the leading / right now
automatically if it's not there.
What do you guys think?
|