I have some classes with this organisation
--> : Inherit
TwittEntititesNetwork --> TwitterGephiStreamer
TwittGrapher --> TwitterGephiStreamer
TwitterGephiStreamer is Abstract
TwitterGephiStreamer have a method : myMethod()
Directory
./myApp.jar
./NetworkLogicDirectory/TwittGrapher.jar
./NetworkLogicDirectory/TwittEntititesNetwork.jar
I use this code to load dynamically the daughters classes (which are in another .jar file)
public static TwitterGephiStreamer LoadNetworkLogicJar() throws Exception
{
File dir = new File(NetworkLogicDirectory);
URL[] urls = new URL[dir.listFiles().length];
for(int i = 0;i < dir.listFiles().length;i++)
{
File s = dir.listFiles()[i];
String url = "file:///"+s.getAbsolutePath();
urls[i] = new URL(url);
}
ClassLoader = new URLClassLoader(urls);
if(defaultProps.containsKey("NetworkLogic") && !defaultProps.getProperty("NetworkLogic").isEmpty())
{
Class<?> networkLogicClassLoader = ClassLoader.loadClass("org.naoyun.gephistream.networklogic."+defaultProps.getProperty("NetworkLogic"));
Object object = networkLogicClassLoader.newInstance();
return (TwitterGephiStreamer) object;
}
else
{
throw new Exception("blabalbalbal ");
}
}
So it's have to return a TwitterGephiStreamer which I can use as a normal class and I can use myMethod() normally .
When I run on eclispe it's works well I don't have any error.
When I export my app as a runnable .jar (myApp.jar) it's throw me this error :
java.lang.ClassCastException: org.naoyun.gephistream.networklogic.TwittEntitiesNetwork cannot be cast to org.naoyun.gephistream.TwitterGephiStreamer
at org.naoyun.utils.ConfigurationTools.LoadNetworkLogicJar(ConfigurationTools.java:62)
at org.naoyun.TwitterStreamer.<init>(TwitterStreamer.java:34)
at org.naoyun.TwitterStreamer.main(TwitterStreamer.java:26)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
at org.eclipse.jdt.internal.jarinjarloader.JarRsrcLoader.main(JarRsrcLoader.java:58)
So, I don't know how to resolve this problem. Is my code creepy but Eclispe can handle it on the fly, or is there other stuff that I'm not up do date?
Thanks for your time !
You probably have org.naoyun.gephistream.TwitterGephiStreamer included more than once on your dynamic classpath. When the subclass is loaded it loads a copy of TwitterGephiStreamer that is conflicting with a previous loaded copy.
Since, TwittGrapher.jar and TwittEntititesNetwork.jar depend on the same core classes/interfaces (i.e. TwitterGephiStreamer), I would suggesting putting those types in a separate utility jar. This should help eliminate any duplicated types on the classpath and provide for a clean jar dependency tree:
myApp
/ \
TwittGrapher TwittEntitiesNetwork
\ /
TwittUtil
You should not use Eclipse to build your executable jar but rather make one manually, e.g. using an Ant build script.
When Eclipse builds an executable jar it packages everything together in one tidy package, loading dependent jars through its own classloader, found in the "jarinjarloader" package you see referenced in your stack trace. This classloader can't find your external jar when it's not part of the "omni-jar" that Eclipse builds.
I've used the URLClassLoader in the past to successfully do what you're trying to do: load external jars at runtime. Here is a question on SO that explains how to use it:
Is it possible to “add” to classpath dynamically in java?
This tutorial should help with using Ant to build an executable jar:
Build an executable jar file by referencing your dependencies
This Ant tutorial is similar and shows how to do it with Eclipse, though it doesn't set the dependencies in the manifest. You'll need to do that for your core application classes, not any external jars to be loaded at runtime.
Related
I am trying to run runner jar of the quarkus application which would be listening over port 9411 on http.
Programmatically using UrlClassLoader, when I try to load the jar it throws
(also with java -jar)
1.java.lang.ClassNotFoundException: io.quarkus.runtime.Quarkus
2.java.lang.reflect.InvocationTargetException
here is the snippet of code ,
URLClassLoader loader = new URLClassLoader(
new URL[]{ new File(<location of runner jar>).toURI().toURL()});
Thread.currentThread().setContextClassLoader(loader);
Class<?> mainClass = loader.loadClass("io.quarkus.runner.GeneratedMain");
Method mainMethod = mainClass.getMethod("main", String[].class);
mainMethod.invoke(null, (Object) new String[]{});
another observation is when I place /lib folder at the runner jar location it loads successfully meaning it requires the lib folder altogether.
How can I make my code work only with runner jar?
To produce a fat jar that includes all the libraries necessary to run the app, use the property quarkus.package.uber-jar=true (you can add that into src/main/resources/application.properties or pass it as a system property when running the build).
With mvn clean package I got the following error starting:
Unrecognized configuration key "quarkus.package.uber-jar" was provided
I've found
quarkus.package.type=uber-jar
as a property.
What I prefer is setting
<quarkus.package.type>uber-jar</quarkus.package.type>
in the pom properties.
https://github.com/fluentcodes/sandbox/tree/java-quarkus-empty
I have some maven projects with dependencies back to:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.1</version>
</dependency>
resulting in download of a single gson-2.8.1.jar file. I retrieve a copy of this file using the "clean package" maven build so I can reference a single directory for shared libraries used by classes dynamically loaded by my web application.
Due to the war file build policies (using "clean install deploy" maven build) the war file is also getting a copy of the gson-2.8.1.jar file in its WEB-INF/lib directory. As far as I know, these are all from the same jar file.
However, when running the web application and using a call like:
JsonObject retVal = null;
try {
paramData.add(0, userID);
paramData.add(0, session);
retVal = (JsonObject) method.invoke(null,
paramData.toArray(new Object[0]));
} catch (Exception e) {
I get an odd exception like:
"Caused by: java.lang.ClassCastException: com.google.gson.JsonObject cannot be cast to com.google.gson.JsonObject"
I don't understand why the same JsonObject class loaded from the gson-2.8.1.jar file in the shared library directory is not compatible with the JsonObject class that may be being loaded from the gson-2.8.1.jar in the war file. They have the same size, etc. and are the same class... because they all originated from the same gson-2.8.1.jar file...
I've tried to isolate the classes so only the one jar file in the shared library directory is called (e.g., removing it from the war file). However, in some cases I'll create a class not found exception when attempting to dynamically load the class.
I realize my description is hard to follow. Is there some reason the JsonObject loaded from this jar would result in Java thinking it has two incompatible classes solely because they may have been loaded by different class loaders when what is loaded is the same object?
What you're observing is expected classloading behavior. If you load the same Foo class from two different sources, from the JVM perspective these are two different classes and as such cannot be casted to one another (i.e. Foo cannot be cast to Foo). Likewise, if you have the same class Foo loaded from two different classloaders, these also cannot be cast to one another.
To resolve this issue, simply eliminate one of the copies. Since you have gone through the effort of setting up a shared library (which is the preferred approach), simply remove the gson jar from your apps WEB-INF/lib dir by changing the scope of your maven dependency to `provided.
For example:
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.8.1</version>
<scope>provided</scope>
</dependency>
This tells maven to include this dependency at compile time, but not at runtime (because we know it will be provided by the shared library).
I am trying to create Java plugins for an existing application. The plugins would like to re-use a lot of the already existing code-base in the main application (e.g. logging, error handling, etc).
I am trying to load plugins as .jar files like this:
String localPath = "...";
String pluginName = "...";
File jarFile = new File(localPath);
ClassLoader pluginLoader = URLClassLoader.newInstance(new URL[]{jarFile.toURL()});
pluginLoader.loadClass(pluginName).newInstance();
The problem I am having is that the classes I would like to import inside the plugin can not be found, even though they exist in the main app, I am getting errors like this:
NoClassDefFoundError: com/foo/exception/FooException
at com.foo.plugins.PluginManager.loadPlugin(PluginManager.java:187)
at com.foo.plugins.PluginManager.loadPlugins(PluginManager.java:86)
...
com/foo/exception/FooException is used everywhere in the code, but I didn't want to have to include this class (and many many others) in the plugin jar file. Instead I would like the ClassLoader to somehow be aware of the locally existing classes. Is this possible? If so, how can I do it?
You need to use your main application's classloader as a parent:
ClassLoader mainLoader = ThisClass.class.getClassLoader(); // some class in the main application
ClassLoader pluginLoader = URLClassLoader.newInstance(new URL[]{jarFile.toURL()}, mainLoader);
Then classes loaded by the plugin classloader will have access to classes loaded by the main classloader (as well as that classloader's parent, if it has one, and so on).
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.
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?
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.
I have a Java EE application where you can define variables of a certain type. To validate that the value expression is valid for it's type, I create a string containing a small class:
public class CompilableExpression {
private <type> expression = <expression>;
}
.. and try to compile it using JavaCompiler:
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<>();
Iterable<? extends JavaFileObject> compilationUnits =
Arrays.asList(stringContainingCompilableExpression);
CompilationTask task = compiler.getTask(
null, null, diagnostics, options, null, compilationUnits
);
task.call();
This works fine if you are using type: String and expression: "my string", or type: Integer and expression: 10.
I also want to validate types using the #Entity annotation.
When I try to do so I get an error:
Cannot find annotation method name() in type javax.persistence.Table: class file for javax.persistence.Table not found
So, I need to add a JAR containing javax.persistence classes to the class path somehow. Is there a generic way to find this JAR? I'm using GlassFish, and don't want to build a GlassFish only solution.
Or is adding it to my project as a normal (non provided) dependency the way to go?
Update
I'm trying to at least find the location in GlassFish (at ~/glassfish-4.1/glassfish):
find ./ -name '*ee*.jar'
./lib/javaee.jar
./modules/security-ee.jar
./modules/amx-javaee.jar
./modules/javaee-kernel.jar
./modules/autostart/osgi-javaee-base.jar
./modules/autostart/osgi-ee-resources.jar
./modules/deployment-javaee-full.jar
./modules/deployment-javaee-core.jar
./modules/glassfish-ee-api.jar
./modules/javax.management.j2ee-api.jar
My best guess is to use ./lib/javaee.jar, but when I check the contents it's almost empty:
$JAVA_HOME/bin/jar tf ./lib/javaee.jar
META-INF/
META-INF/MANIFEST.MF
META-INF/maven/
META-INF/maven/org.glassfish.main.extras/
META-INF/maven/org.glassfish.main.extras/javaee/
META-INF/maven/org.glassfish.main.extras/javaee/pom.xml
META-INF/maven/org.glassfish.main.extras/javaee/pom.properties
Does anyone know where (in the GlassFish installation) to get the JAR including the javax.persistence classes?
The JAR you are looking for is in $GLASSFISH_HOME/glassfish/modules/javax.persistence.jar
If you are deploying to a JavaEE App Server, the JAR with the #Entity annotation will already be in your application's runtime classpath. You shouldn't have to load any JAR files in code (as you described in your comment).
During development you typically configure your App Server in your IDE and that process should include the JAR with the annotation into your compilation classpath.
You might need to manually include it in the project compile classpath / application server libraries classpath depending on how your IDE handles this. For Glassfish all the API JARs are where you were looking in the modules directory.
Even though this ties your project to finding the JAR for compilation in a specific location relative to the app server install I find it's still a better approach then copying JARs into you project for compilation. This ensures you are compiling against the correct JARs that are deployed to the app server and so long as these are JavaEE APIs your application will deploy fine into any app server.
You could also set up your project to use Maven, include the required deps for the persistence APIs and it will find the compile time deps in your maven cache.
Also you might want to check out Jar Explorer which lets you search for classes etc inside JARs, folders of JARs etc. Its pretty convenient for finding these things.