0

I want to load a library in separated classloader because don't want to add directly as dependency to do not conflict with other versions in the project. I created a loader:

public LibLoader(String resourcePath) {
    //resourcePath="/lib/Log4JHack-1.0.jar"
    URL url = getClass().getResource(resourcePath);
    loader = new URLClassLoader(new URL[] {url}, getClass().getClassLoader());
}

url = [file:/D:/..../lib/Log4JHack-1.0.jar]

if url is a file, then it works well.

url = [jar:file:/C:/..../Log4JHackLoader-1.0.jar!/lib/Log4JHack-1.0.jar]

if url is a jar:file (jar inside jar), then it don't work:

ERROR StatusLogger Unable to open jar:jar:file:/C:/Users/Dani/.m2/repository/hu/daniel/hari/log4jhack/Log4JHackLoader/1.0/Log4JHackLoader-1.0.jar!/lib/Log4JHack-1.0.jar!/META-INF/log4j-provider.properties java.net.MalformedURLException: no !/ in spec
    at java.net.URL.<init>(URL.java:620)
    at java.net.URL.<init>(URL.java:483)
    at java.net.URL.<init>(URL.java:432)
    at java.net.JarURLConnection.parseSpecs(JarURLConnection.java:175)
    at java.net.JarURLConnection.<init>(JarURLConnection.java:158)
    at sun.net.www.protocol.jar.JarURLConnection.<init>(JarURLConnection.java:81)
    at sun.net.www.protocol.jar.Handler.openConnection(Handler.java:41)
    at java.net.URL.openConnection(URL.java:972)
    at java.net.URL.openStream(URL.java:1038)
    at org.apache.logging.log4j.util.ProviderUtil.loadProvider(ProviderUtil.java:79)
    at org.apache.logging.log4j.util.ProviderUtil.<init>(ProviderUtil.java:66)
    at org.apache.logging.log4j.util.ProviderUtil.lazyInit(ProviderUtil.java:122)
    at org.apache.logging.log4j.util.ProviderUtil.hasProviders(ProviderUtil.java:106)
    at org.apache.logging.log4j.LogManager.<clinit>(LogManager.java:91)
    at hu.daniel.hari.log4jpattern.logrenderer.log4j.log4j.capture.log4j2.StringLoggerCapture.<clinit>(StringLoggerCapture.java:34)
    at hu.daniel.hari.log4jpattern.logrenderer.log4j.Log4j2Hack.doRender(Log4j2Hack.java:30)
    at hu.daniel.hari.log4jpattern.logrenderer.log4j.Log4j2Hack.render(Log4j2Hack.java:23)
    at hu.daniel.hari.log4jpattern.logrenderer.log4j.renderer.AbstractLog4jRendererAdapter.render(AbstractLog4jRendererAdapter.java:25)
    at hu.daniel.hari.log4jpattern.logrenderer.service.LogRendererServiceImpl.getOutput(LogRendererServiceImpl.java:44)
    at hu.daniel.hari.log4jpattern.logrenderer.service.LogRendererServiceImpl.render(LogRendererServiceImpl.java:37)
    at hu.daniel.hari.log4jpattern.logrenderer.TestMain.main(TestMain.java:14)
Caused by: java.lang.NullPointerException: no !/ in spec
    at sun.net.www.protocol.jar.Handler.parseAbsoluteSpec(Handler.java:171)
    at sun.net.www.protocol.jar.Handler.parseURL(Handler.java:151)
    at java.net.URL.<init>(URL.java:615)
    ... 20 more

Since I want to pack the loadable Log4JHack-1.0.jar to Log4JHackLoader-1.0.jar, I need a solution for loading from inside jar.

Daniel Hári
  • 7,254
  • 5
  • 39
  • 54

2 Answers2

1

It's not 100% clear to me what you're trying to do here. Why are you trying to include conflicting dependencies in your classpath?

In any case, this is a known limitation of UrlClassLoader. Have you considered extracting the nested jar as a temporary file on the file system, and then pointing your class loader to it?

ck1
  • 5,243
  • 1
  • 21
  • 25
  • Exactly this is the actual resource path. I am creating a tester for log4j, so I hacked to lib to access something, and I want to separate this from normal usage of log4j in the project. It is working, if the jar is not nested to another jar, otherwise not. – Daniel Hári Jun 04 '16 at 20:27
  • It looks like you're using Maven. Have you considered using the Shade plugin to create a patched version of Log4J with your changes, so that you don't need to jump through classloader hoops? https://maven.apache.org/plugins/maven-shade-plugin – ck1 Jun 04 '16 at 20:35
  • Yes its made Log4JHack-1.0.jar with maven shade plugin. Resource path is same as your example. But I wanted to pack the whole story in the loader module, to do not pollute the user module with hardcoded string class references. So after loading the loader module normally as a dependency, this happens. – Daniel Hári Jun 04 '16 at 20:37
  • It's funny that you go on exactly the same steps as me. :) Yes, currently I writing out as a temporary file, but I am not very pleased about this solution however. – Daniel Hári Jun 04 '16 at 20:49
  • Here's a classloader implementation that internally attempts to extract the nested jar as a temporary file. Note: I haven't tested this code before, so I don't know how well it works. http://qdolan.blogspot.com/2008/10/embedded-jar-classloader-in-under-100.html – ck1 Jun 04 '16 at 20:55
  • The problem with temp file is that it is not deleted on exit, even you set file.deleteOnExit() switch because the file is still used so not deleted. – Daniel Hári Jun 04 '16 at 21:13
0

The github.com/squark-io/nested-jar-classloader project is able to load classes in nested jar files, but has a lot of external dependencies. I added this same mechanism in my project (sourceforge.net/projects/mdiutilities/) but in this case without external dependencies.

These two projects do not work by making a temporary copy of the nested jar files, but directly load the classes bytes.

Hervé Girod
  • 465
  • 3
  • 12