Loading jars at runtime - java

I am trying to add jar file to classpath at runtime. I use this code
public static void addURL(URL u) throws IOException {
URLClassLoader sysloader = (URLClassLoader) ClassLoader
.getSystemClassLoader();
Class<URLClassLoader> sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[] { u });
System.out.println(u);
} catch (Throwable t) {
t.printStackTrace();
throw new IOException("Error");
}
}
System out prints this url:
file:/B:/Java/Tools/mysql-connector-java-5.1.18/mysql-connector-java-5.1.18/mysql-connector-java-5.1.18-bin.jar
I was check this path carefully, this jar exist. Even this test show that com.mysql.jdbc.
Driver class exists.
javap -classpath "B:\Java\Tools\mysql-connector-java-5.1.18\
mysql-connector-java-5.1.18\mysql-connector-java-5.1.18-bin.jar" com.mysql.jdbc.
Driver
Compiled from "Driver.java"
public class com.mysql.jdbc.Driver extends com.mysql.jdbc.NonRegisteringDriver i
mplements java.sql.Driver{
public com.mysql.jdbc.Driver() throws java.sql.SQLException;
static {};
}
But I still get java.lang.ClassNotFoundException when I use this Class.forName(driver).
What is wrong with this code?

The URL is ok, nevertheless you try to load a jar from classpath, so it means that yo need to have the file in cp first.
In your case you want to load a jar that is not in classpath so you have to use
URLClassLoader and for JAR you can use also the JARClassLoader
If you want some sample lesson on it:
http://docs.oracle.com/javase/tutorial/deployment/jar/jarclassloader.html
Here a sample I ran by myself see if helps you. It search the Logger class of Log4j that is not in my classpath, of course i got exception on invocation of the constructor since i did not pass the right params to the constructor
package org.stackoverflow;
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
public class URLClassLoaderSample
{
public static void main(String[] args) throws Exception
{
File f = new File("C:\\_programs\\apache\\log4j\\v1.1.16\\log4j-1.2.16.jar");
URLClassLoader urlCl = new URLClassLoader(new URL[] { f.toURL()},System.class.getClassLoader());
Class log4jClass = urlCl.loadClass("org.apache.log4j.Logger");
log4jClass.newInstance();
}
}
Exception in thread "main" java.lang.InstantiationException: org.apache.log4j.Logger
at java.lang.Class.newInstance0(Class.java:357)
at java.lang.Class.newInstance(Class.java:325)
at org.stackoverflow.URLClassLoaderSample.main(URLClassLoaderSample.java:19)
Exception due to the wrong invocation, nevertheless at this stage we already found the class

Ok try the alternative approach with DataSource and not directly the Driver
Below is the code (working with oracle driver, i don't have my sql db, but the properties are the same)
Generally using the DataSource interface is the preferred approach since JDBC 2.0
The DataSource jar was not in the classpath neither for the test below
public static void urlCLSample2() throws Exception
{
File f = new File("C:\\_programs\\jdbc_drivers\\oracle\\v11.2\\ojdbc6.jar");
URLClassLoader urlCl = new URLClassLoader(new URL[] { f.toURL() }, System.class.getClassLoader());
// replace the data source class with MySQL data source class.
Class dsClass = urlCl.loadClass("oracle.jdbc.pool.OracleDataSource");
DataSource ds = (DataSource) dsClass.newInstance();
invokeProperty(dsClass, ds, "setServerName", String.class, "<put your server here>");
invokeProperty(dsClass, ds, "setDatabaseName", String.class, "<put your db instance here>");
invokeProperty(dsClass, ds, "setPortNumber", int.class, <put your port here>);
invokeProperty(dsClass, ds, "setDriverType",String.class, "thin");
ds.getConnection("<put your username here>", "<put your username password here>");
System.out.println("Got Connection");
}
// Helper method to invoke properties
private static void invokeProperty(Class dsClass, DataSource ds, String propertyName, Class paramClass,
Object paramValue) throws Exception
{
try
{
Method method = dsClass.getDeclaredMethod(propertyName, paramClass);
method.setAccessible(true);
method.invoke(ds, paramValue);
}
catch (Exception e)
{
throw new Exception("Failed to invoke method");
}
}

Related

Load SPI class with URLClassLoader rise ClassNotFoundException

I did some research, But due to complexity of this situation, Not working for me.
Child first class loader and Service Provider Interface (SPI)
Like flink or tomcat, My application run as framework with platform and system classloader.
Framework load plugin as module and plugin may depend some lib, so make this define:
plugin/plugin-demo.jar
depend/plugin-demo/depend-1.jar
depend/plugin-demo/depend-2.jar
framework will create two classloader like this:
URLClassLoader dependClassloader = new URLClassLoader({URI-TO-depend-jars}, currentThreadClassLoader);
URLClassLoader pluginClassloader = new URLClassLoader({URI-TO-plugin-jar},dependClassloader);
With an HelloWorld demo this is working file ( and at first I NOT set systemClassloader as parent).
But with JDBC driver com.mysql.cj.jdbc.Driver which using SPI goes into trouble:
Even I manual register driver:
Class<?> clazz = Class.forName("com.mysql.cj.jdbc.Driver", true, pluginClassloader);
com.mysql.cj.jdbc.Driver driver = (com.mysql.cj.jdbc.Driver) clazz.getConstructor().newInstance();
DriverManager.registerDriver(driver);
This working fine, But after that:
DriverManager.getConnection(this.hostName, this.userName, this.password)
will rise
Caused by: java.lang.ClassNotFoundException: com.mysql.cj.jdbc.Driver
at java.base/java.net.URLClassLoader.findClass(URLClassLoader.java:440)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:587)
at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
... 7 more
Or:
Caused by: java.sql.SQLException: No suitable driver found for jdbc:mysql://localhost:3306/furryblack
at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:706)
at java.sql/java.sql.DriverManager.getConnection(DriverManager.java:229)
I try to print all driver:
Enumeration<java.sql.Driver> driverEnumeration = DriverManager.getDrivers();
while (driverEnumeration.hasMoreElements()) {
java.sql.Driver driver = driverEnumeration.nextElement();
System.out.println(driver);
}
And there is no driver registered.
So, Question is: why NoClassDefFoundError ?
I have some guess: DriverManager run in systemclassloader but driver load in my classloader parent won't search in children, So I set currentThreadClassLoader as parent but still rise exception.
Update 1:
URI-TO-depend-jars is Array of File.toURI().toURL().
This design working fine with demo, So I think it should be correct.
And with debug, The ClassLoader parent chain is
ModuleLoader -> DependLoader
And with systemclassloader is
ModuleLoader -> DependLoader -> BuiltinAppClassLoader -> PlatformClassLoader -> JDKInternalLoader
This is the full code:
Interface in jar 1:
public interface AbstractComponent {
void handle();
}
Plugin in jar2 (depend jar3 in pom.xml):
public class Component implements AbstractComponent {
#Override
public void handle() {
System.out.println("This is component handle");
SpecialDepend.tool();
}
}
Depend in jar3:
public class SpecialDepend {
public static void tool() {
System.out.println("This is tool");
}
}
Main in jar1:
#Test
public void test() {
String path = "D:\\Server\\Classloader";
File libFile = Paths.get(path, "lib", "lib.jar").toFile();
File modFile = Paths.get(path, "mod", "mod.jar").toFile();
URLClassLoader libLoader;
try {
URL url;
url = libFile.toURI().toURL();
URL[] urls = {url};
libLoader = new URLClassLoader(urls);
} catch (MalformedURLException exception) {
throw new RuntimeException(exception);
}
URLClassLoader modLoader;
try {
URL url;
url = modFile.toURI().toURL();
URL[] urls = {url};
modLoader = new URLClassLoader(urls, libLoader);
} catch (MalformedURLException exception) {
throw new RuntimeException(exception);
}
try {
Class<?> clazz = Class.forName("demo.Component", true, modLoader);
if (AbstractComponent.class.isAssignableFrom(clazz)) {
AbstractComponent instance = (AbstractComponent) clazz.getConstructor().newInstance();
instance.handle();
}
} catch (ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException exception) {
throw new RuntimeException(exception);
}
}
Output is
This is component handle
This is tool
This is working perfect.
Update 2:
I try to print more debug and some unnecessary code, Then I found, The Driver class can be found and instancelized, But the DriverManager.registerDriver didn't register it.
So the question become: Why DriverManager can't register driver load from sub classloader?
Update3
contextClassLoader is get from Thread.currentThread().getContextClassLoader() But inject by framework with currentThread.setContextClassLoader(exclusiveClassLoader);
As double check I print the hashcode, Its same.
And I debug into DriverManager, Its was registered the driver into internal List but after that, getDrivers will got nothing.
ClassLoader looks for classes in its parent first, and the parent delegates to its parent and so on. With that said, ClassLoaders that are siblings cannot see eachothers classes.
Also the method DriverManager#getDrivers() internally validates if the caller ClassLoader can load the class with DriverManager#isDriverAllowed(Driver, ClassLoader).
this means that even if your Driver is added to the registration list, it is only added as an instance of DriverInfo, this means that it would only be loaded on demand (Lazy), and still might not register when loading is attempted, that's why you get an empty list.

How to load a JDBC driver dynamically during runtime since Java 9?

I'm currently migrating my Java 8 code to Java 11 and stumbled across a problem. I'm looking for jar files in a directory and add them to the classpath in order to use them as JDBC drivers.
After doing so I can easily use DriverManager.getConnection(jdbcString); to get a connection to any database I loaded a driver beforehand.
I used to load drivers using this bit of code which no longer works since the SystemClassLoader is no longer a URLClassLoader.
Method method = URLClassLoader.class.getDeclaredMethod("addURL", new Class[] { URL.class });
method.setAccessible(true);
method.invoke(ClassLoader.getSystemClassLoader(), new Object[] { jdbcDriver.toURI().toURL() });
So after looking around for alternatives I found this answer on SO:
https://stackoverflow.com/a/14479658/10511969
Unfortunately for this approach I'd need the drivers class name, i.e. "org.postgresql.Driver" which I don't know.
Is there just no way to do this anymore, or am I missing something?
Using a Shim is a good way to load the JDBC driver when the driver is, for some reason, not accessibile via the system class loader context. I have ran into this a few times with multi-threaded scripts that have their own separated classpath context.
http://www.kfu.com/~nsayer/Java/dyn-jdbc.html
Not knowing the driver's class seems like an odd constraint.
I would go for a custom class loader that after ever class initialisation (I think you can do that), calls DriverManager.getDrivers and registers any new drivers it finds. (I have no time at the moment to write the code.)
The hacky alternative would be to load all your code (except a bootstrap) in a URLClassLoader and addURL to that.
Edit: So I wrote some code.
It creates a class loader for the drivers that also contains a "scout" class that forwards DriverManager.drivers (which is a naughty caller sensitive method (a newish one!)). A fake driver within the application class loader forwards connect attempts onto any dynamically loaded drivers at the time of request.
I don't have any JDBC 4.0 or later drivers conveniently around to test this on. You'll probably want to change the URL - you'll need the Scout class and the driver jar.
import java.lang.reflect.*;
import java.net.*;
import java.sql.*;
import java.util.*;
import java.util.logging.*;
import java.util.stream.*;
class FakeJDBCDriver {
public static void main(String[] args) throws Exception {
URLClassLoader loader = URLClassLoader.newInstance(
new URL[] { new java.io.File("dynamic").toURI().toURL() },
FakeJDBCDriver.class.getClassLoader()
);
Class<?> scout = loader.loadClass("Scout");
Method driversMethod = scout.getMethod("drivers");
DriverManager.registerDriver(new Driver() {
public int getMajorVersion() {
return 0;
}
public int getMinorVersion() {
return 0;
}
public Logger getParentLogger() throws SQLFeatureNotSupportedException {
throw new SQLFeatureNotSupportedException();
}
public DriverPropertyInfo[] getPropertyInfo(String url, Properties info) {
return new DriverPropertyInfo[] { };
}
public boolean jdbcCompliant() {
return false;
}
public boolean acceptsURL(String url) throws SQLException {
if (url == null) {
throw new SQLException();
}
for (Iterator<Driver> iter=drivers(); iter.hasNext(); ) {
Driver driver = iter.next();
if (
driver.getClass().getClassLoader() == loader &&
driver.acceptsURL(url)
) {
return true;
}
}
return false;
}
public Connection connect(String url, Properties info) throws SQLException {
if (url == null) {
throw new SQLException();
}
for (Iterator<Driver> iter=drivers(); iter.hasNext(); ) {
Driver driver = iter.next();
if (
driver.getClass().getClassLoader() == loader &&
driver.acceptsURL(url)
) {
Connection connection = driver.connect(url, info);
if (connection != null) {
return connection;
}
}
}
return null;
}
private Iterator<Driver> drivers() {
try {
return ((Stream<Driver>)driversMethod.invoke(null)).iterator();
} catch (IllegalAccessException exc) {
throw new Error(exc);
} catch (InvocationTargetException exc) {
Throwable cause = exc.getTargetException();
if (cause instanceof Error) {
throw (Error)cause;
} else if (cause instanceof RuntimeException) {
throw (RuntimeException)cause;
} else {
throw new Error(exc);
}
}
}
});
// This the driver I'm trying to access, but isn't even in a jar.
Class.forName("MyDriver", true, loader);
// Just some nonsense to smoke test.
System.err.println(DriverManager.drivers().collect(Collectors.toList()));
System.err.println(DriverManager.getConnection("jdbc:mydriver"));
}
}
Within a directory dynamic (relative to current working directory):
import java.sql.*;
public interface Scout {
public static java.util.stream.Stream<Driver> drivers() {
return DriverManager.drivers();
}
}
I would always suggest avoiding setting the thread context class loader to anything other than a loader that denies everything, or perhaps null.
Modules may well allow you to load drivers cleanly, but I've not looked.
if you don`t know the driver name, you cannot use reflect to use urlLoader to load jar, which you exactly want.
I have same problem with dynamically load driver, because of jars are conflict.
Even though, I have to know the driver name to jar, which i want to load use my url class loader.
DriverManager use class loader to load jar, so it could find jdbc driver by name. As usual we use: class.forName。
We use self defined class loader to load our driver, so that it can solve the conflict of jars.

How to load class files into a project to create an object

With the following code snippet, I am trying to load some class files into my project to create an object. Unfortunately, it seems that something is missing, because there are no classes loaded. What is it?
private static void myClassloader() throws Exception
{
File file = new File(pathGeneratedClasses);
try
{
#SuppressWarnings("deprecation")
URL url = file.toURL();
URL[] urls = new URL[]{url};
ClassLoader sqlQuery = new URLClassLoader(urls);
Class myClass = sqlQuery.loadClass("de.cimt.jaxb.JaxCodeGen");
}
catch (Exception e)
{
System.out.println(e.toString());
}
}
sqlQuery.loadClass() will be returning the class you requested for, try assigning the returned value to something.
If no class is found then ClassNotFoundException will be thrown.

jar is not uploaded dynamically through class loaders

In my project class path poi 2.5 version is already there and on runtime i want to load poi 3.5 jar as for two java classes in my project require poi 3.5 version , so for that I have written class loader that will call the poi 3.5 jar on runtime please advise what is wrong now as it is not loading poi 3.5 version jar at runtime
below is my classloader
public class MyClassLoader {
private static final Class[] parameters = new Class[] {URL.class};
public static void addFile(String s) throws IOException
{
File f = new File(s);
addFile(f);
}
public static void addFile(File f) throws IOException
{
addURL(f.toURI().toURL());
}
public static void addURL(URL u) throws IOException
{
URLClassLoader sysloader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class sysclass = URLClassLoader.class;
try {
Method method = sysclass.getDeclaredMethod("addURL", parameters);
method.setAccessible(true);
method.invoke(sysloader, new Object[] {u});
} catch (Throwable t) {
t.printStackTrace();
throw new IOException("Error, could not add URL to system classloader");
}
}
}
and here i am calling from inside main method one of the method of poi 3.5 class which is newly added in 3.5 but it is not loaded
MyClassLoader.addFile("C:\\Release14branchupdated\\lib\\thirdparty\\POI-3.5\\poi-3.10-FINAL.jar");
Constructor<?> cs = ClassLoader.getSystemClassLoader().loadClass("org.apache.poi.ss.formula.FormulaCellCacheEntrySet").getConstructor(String.class);
System.out.println("$$$$$$$$$$"+cs.getName());
org.apache.poi.ss.formula.FormulaCellCacheEntrySet instance = (org.apache.poi.ss.formula.FormulaCellCacheEntrySet)cs.newInstance();
instance.iterator();
now upon execution the error that i am getting is not ble to find class FormulaCellCacheEntrySet

ClassNotFoundException when using dynamic classloader

It's a project downloaded from Web and I just changed the path to find the class which I want to make it load dynamically.Here are the codes in which I try to load the class on runtime.But I got a ClassNotFoundException eventually:
private static IExample newInstanceWithThrows() throws InstantiationException, IllegalAccessException, ClassNotFoundException {
URLClassLoader tmp =
new URLClassLoader(new URL[] {getClassPath()}) {
public Class<?> loadClass(String name)
throws ClassNotFoundException {
if ("example.Example".equals(name)
|| "example.Leak".equals(name))
return findClass(name);
return super.loadClass(name);
}
};
return (IExample) tmp.loadClass("example.Example")
.newInstance();
}
private static URL getClassPath() {
String dir = "/Users/longtuan/develop/rjc2011/classes/";
try {
//return new URL(dir);
File path = new File(dir);
return path.toURL();
} catch (MalformedURLException e) {
throw new RuntimeException(e);
}
}
And the path to class example.Example is:
/Users/longtuan/develop/java/rjc2011/classes/example
And the run command I used is:
java -classpath ./bin example.Main
Current directory is:
/Users/longtuan/develop/java/rjc2011
All the things of current directory are like this:
Example is the directory in which I put all the java files.I place the compiled class file in directory bin except the class example.Example which is put in classes lonely.
Above are all the informations I can give, thanks for any help.

Categories

Resources