I am trying to dynamically extract configuration from a given WAR file.
My goal is to find all class that implement some interface (Parameter
).
The war file is not on the classpath, so I create a temporary classloader to inspect its classes.
URL webClasses = new URL("jar","","file:" + new File(path).getAbsolutePath() + "!/WEB-INF/classes/");
URLClassLoader cl = URLClassLoader.newInstance(new URL[] { webClasses });
Reflections reflections = new Reflections(ClasspathHelper.forClassLoader(cl), new SubTypesScanner(), cl);
Set<Class<? extends Parameter>> params = reflections.getSubTypesOf(Parameter.class);
This works fine. I find all the implementation of Parameter
in my webapp.
Now, I would like to do the same for each jar present in WEB-INF/lib
.
Unfortunatelly I have not found a way to build the classloader in that case. The classloader seems to ignore jar URLs (jar:<url>!/WEB-INF/lib/{entry}.jar
).
When I run the code below, there is no class found by Reflections:
Set<URL> libs = findLibJars(warUrl));
// The URLs are in the following format : {myWar}.war!/WEB-INF/lib/{myLib}.jar
URLClassLoader cl = URLClassLoader.newInstance(urls.toArray(new URL[urls.size()]));
cl.loadClass("my.company.config.AppParameter");
If possible, I would like to avoid having to extract WEB-INF/lib/*.jar
from the WAR file and analyze them separately.
I am missing something here ? (other way to do it, other way to create the classloader, ... any lead would help). Maybe this can be done without using the Reflections library ?
It is certainly possible. Here is an ugly example of doing it:
String warName = "wlaj.war";
ClassLoader loader = new ClassLoader() {
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
// This probably needs fixing:
String fileName = name.replace('.', '/') + ".class";
try {
try (ZipFile zf = new ZipFile(warName)) {
ZipEntry jar = zf.getEntry("WEB-INF/lib/jlaj.jar");
if (jar == null)
throw new ClassNotFoundException("No jlaj.jar");
try (ZipInputStream jarInput = new ZipInputStream(zf.getInputStream(jar))) {
for (ZipEntry cl; (cl = jarInput.getNextEntry()) != null; ) {
if (fileName.equals(cl.getName())) {
ByteArrayOutputStream data = new ByteArrayOutputStream();
byte[] buffer = new byte[4096];
for (int len; (len = jarInput.read(buffer)) != -1; ) {
data.write(buffer, 0, len);
}
buffer = data.toByteArray();
return defineClass(name, buffer, 0, buffer.length);
}
}
}
}
throw new ClassNotFoundException();
} catch (IOException ex) {
throw new ClassNotFoundException("Error opening class file", ex);
}
}
};
loader.loadClass("jlaj.NewJFrame");
The problems:
"jlaj.NewJFrame" -> "jlaj/NewJFrame.class"
, but it gets very ugly when trying to load inner classes and maybe in some other situations I can't even think about.readAll
method because it looks very messy.ZipFile
and ZipEntry
, if you want to iterate over those externally.On the positive side, it works.