I've the following scenario:
I've a byte[] that contains the .class data of a class (loaded from the file system)
And I have another byte[] of this some object of this class that was previously Serialized to some other stream.
First do load the byte[] of the .class file with my custom class loader which is:
public class MainSearchClassLoader extends ClassLoader
{
public MainSearchClassLoader()
{
super(MainSearchClassLoader.class.getClassLoader());
}
public Class<?> findClass(String name) throws ClassNotFoundException
{
try
{
byte[] bytecode = FileUtil.readClassByteCode();
return super.defineClass(ReflectionUtil.getStubBinaryClassName() , bytecode, 0, bytecode.length);
} catch (IOException e)
{
e.printStackTrace();
}
return null;
}
}
Then I am trying to de-serialize this instance using the following code:
public static Object getObjectFromBytes(final byte[] bytes)
{
Object object = null;
try
{
object = new ObjectInputStream(new ByteArrayInputStream(bytes)).readObject();
} catch (final Exception ioe)
{
ioe.printStackTrace();
}
return object;
}
That takes the serialized bytes and supposed to return instance (of the pre-loaded class using my custom class loader) .. I got the following exception:
11/03/06 14:23:27 oracle.classloader.util.AnnotatedClassNotFoundException:
Missing class: mainSearchObjects.dc_index
Dependent class: java.io.ObjectInputStream
Loader: jre.bootstrap:1.5.0_06
Code-Source: unknown
Configuration: jre bootstrap
This load was initiated at MainSearch.web.MainSearch:0.0.0 using the Class.forName() method.
The missing class is not available from any code-source or loader in the system.
11/03/06 14:23:27 at oracle.classloader.PolicyClassLoader.handleClassNotFound (PolicyClassLoader.java:2068) [/D:/jdevstudio10134/j2ee/home/lib/pcl.jar (from system property java.class.path), by sun.misc.Launcher$AppClassLoader#14916158]
at oracle.classloader.PolicyClassLoader.internalLoadClass (PolicyClassLoader.java:1679) [/D:/jdevstudio10134/j2ee/home/lib/pcl.jar (from system property java.class.path), by sun.misc.Launcher$AppClassLoader#14916158]
at oracle.classloader.PolicyClassLoader.loadClass (PolicyClassLoader.java:1635) [/D:/jdevstudio10134/j2ee/home/lib/pcl.jar (from system property java.class.path), by sun.misc.Launcher$AppClassLoader#14916158]
at oracle.classloader.PolicyClassLoader.loadClass (PolicyClassLoader.java:1620) [/D:/jdevstudio10134/j2ee/home/lib/pcl.jar (from system property java.class.path), by sun.misc.Launcher$AppClassLoader#14916158]
at java.lang.ClassLoader.loadClassInternal (ClassLoader.java:319) [jre bootstrap, by jre.bootstrap:1.5.0_06]
at java.lang.Class.forName0 (Native method) [unknown, by unknown]
at java.lang.Class.forName (Class.java:242) [jre bootstrap, by jre.bootstrap:1.5.0_06]
at java.io.ObjectInputStream.resolveClass (ObjectInputStream.java:574) [jre bootstrap, by jre.bootstrap:1.5.0_06]
at java.io.ObjectInputStream.readNonProxyDesc (ObjectInputStream.java:1538) [jre bootstrap, by jre.bootstrap:1.5.0_06]
at java.io.ObjectInputStream.readClassDesc (ObjectInputStream.java:1460) [jre bootstrap, by jre.bootstrap:1.5.0_06]
at java.io.ObjectInputStream.readOrdinaryObject (ObjectInputStream.java:1693) [jre bootstrap, by jre.bootstrap:1.5.0_06]
at java.io.ObjectInputStream.readObject0 (ObjectInputStream.java:1299) [jre bootstrap, by jre.bootstrap:1.5.0_06]
at java.io.ObjectInputStream.readObject (ObjectInputStream.java:339) [jre bootstrap, by jre.bootstrap:1.5.0_06]
...........
I understood that.. The bootstrap class loader that is used by the de-serization code cannot see the class loaded by one of its children (my class loader) and I think this is a correct behaiour, isn't it?
So, Isn't a solution to this problem??
http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4340158
You need your own ObjectInputStream.
Related
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.
So I have a Spring Boot application that loads external jars from the paths below:
java -cp "main-0.0.1-SNAPSHOT.jar" -Dloader.path="%USERPROFILE%\Addons\" -Dloader.main=moe.ofs.backend.BackendApplication org.springframework.boot.loader.PropertiesLauncher
The main jar doesn't know external jars at compile time. External jars are loaded like "plugins" or "addons" by specifying
-Dloader.path=...
All of external jars depend on an interface from "main-0.0.1-SNAPSHOT.jar", and they are supposed to do object serializations more or less.
The interface is called Configurable, and it provides two default methods like these:
default <T extends Serializable> void writeFile(T object, String fileName) throws IOException {
Path configFilePath = configPath.resolve(fileName + ".data");
FileOutputStream fileOutputStream = new FileOutputStream(configFilePath.toFile());
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(object);
objectOutputStream.close();
}
default <T extends Serializable> T readFile(String fileName) throws IOException, ClassNotFoundException {
Path configFilePath = configPath.resolve(fileName + ".data");
FileInputStream fileInputStream = new FileInputStream(configFilePath.toFile());
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
return (T) objectInputStream.readObject();
}
Classes in external jars implement this interface, and they call readFile() and writeFile().
writeFile() works perfectly fine and doesn't seem to cause any problem; readFile(), however, throws a ClassNotFoundException, and that's what I'm trying to figure out.
java.lang.ClassNotFoundException: moe.ofs.addon.navdata.domain.Navaid
at java.net.URLClassLoader.findClass(URLClassLoader.java:382)
at java.lang.ClassLoader.loadClass(ClassLoader.java:418)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:355)
at java.lang.ClassLoader.loadClass(ClassLoader.java:351)
at java.lang.Class.forName0(Native Method)
at java.lang.Class.forName(Class.java:348)
at java.io.ObjectInputStream.resolveClass(ObjectInputStream.java:719)
at java.io.ObjectInputStream.readNonProxyDesc(ObjectInputStream.java:1922)
at java.io.ObjectInputStream.readClassDesc(ObjectInputStream.java:1805)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2096)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1624)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:464)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
at java.util.ArrayList.readObject(ArrayList.java:797)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at java.io.ObjectStreamClass.invokeReadObject(ObjectStreamClass.java:1170)
at java.io.ObjectInputStream.readSerialData(ObjectInputStream.java:2232)
at java.io.ObjectInputStream.readOrdinaryObject(ObjectInputStream.java:2123)
at java.io.ObjectInputStream.readObject0(ObjectInputStream.java:1624)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:464)
at java.io.ObjectInputStream.readObject(ObjectInputStream.java:422)
at moe.ofs.backend.Configurable.lambda$readFile$0(Configurable.java:186)
at java.lang.Thread.run(Thread.java:748)
After some testing it seems to me that ClassNotFoundException is thrown by Class.forName() because the default ClassLoader has a hard time looking for moe.ofs.addon.navdata.domain.Navaid, which is the class I'm trying to deserialize.
Navaid implements Serializable, and it also has a static final long serialVersionUID.
I had hoped that I could solve this by setting a context class loader for current thread, so that ObjectInputStream will use Spring Boot class loader to resolve Navaid class:
Thread.currentThread().setContextClassLoader(getClass().getClassLoader());
This, when printed out, gives something like
Thread.currentThread().getContextClassLoader() = org.springframework.boot.loader.LaunchedURLClassLoader#7a0b4753
Except that ObjectInputStream#readObject still throws ClassNotFoundException.
If I explicitly make a call to load Navaid class from Spring Boot loader such as:
getClass().getClassLoader().loadClass("moe.ofs.addon.navdata.domain.Navaid");
It returns a Navaid instance without any issue.
And as expected, when directly calling
Class.forName("moe.ofs.addon.navdata.domain.Navaid")
a ClassNotFoundException is thrown, even if the thread context loader has been explicitly set to LaunchedURLClassLoader; ObjectInputStream#readObject always tries to resolve the class by making a call to system default classloader to load the class.
Then I tried to load an ObjectInputStream using LaunchedURLClassLoader, but the instance still used Class.forName() from system default class loader.
ClassLoader cl = getClass().getClassLoader();
Thread.currentThread().setContextClassLoader(cl);
System.out.println("Thread.currentThread().getContextClassLoader() = " + Thread.currentThread().getContextClassLoader());
Class<?> tClass = getClass().getClassLoader().loadClass("java.io.ObjectInputStream");
System.out.println("tClass = " + tClass);
Path configFilePath = configPath.resolve(fileName + ".data");
FileInputStream fileInputStream = new FileInputStream(configFilePath.toFile());
Constructor<?> constructor = tClass.getConstructor(InputStream.class);
ObjectInputStream objectInputStream = (ObjectInputStream) constructor.newInstance(fileInputStream);
objectInputStream.readObject(); // throws ClassNotFoundException
Any input is appreciated. Thanks in advance.
As far as i know, you should override the method resolveClass on ObjectInputStream
Something like that:
default <T extends Serializable> T readFile(String fileName, ClassLoader loader) throws IOException, ClassNotFoundException {
Path configFilePath = configPath.resolve(fileName + ".data");
FileInputStream fileInputStream = new FileInputStream(configFilePath.toFile());
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream){
protected Class<?> resolveClass(ObjectStreamClass desc)
throws IOException, ClassNotFoundException {
try {
return Class.forName(desc.getName(), false, loader);
} catch(ClassNotFoundException cnfe) {
return super.resolveClass(desc);
}
}
};
return (T) objectInputStream.readObject();
}
Never tried it myself, but it is worth a shot.
There is also http://commons.apache.org/proper/commons-io/javadocs/api-2.4/org/apache/commons/io/input/ClassLoaderObjectInputStream.html if you aready have commons-io in your project.
https://github.com/apache/commons-io/blob/master/src/main/java/org/apache/commons/io/input/ClassLoaderObjectInputStream.java
i have one problem in which i need some help.
Problem statement:
I'm using one jar to generate reports in excel sheet format. This jar is required only if the user wants to generate report in excel format. Other formats of report available are html and txt which don't require this jar.
The current user generates the reports in html format so he says, why should I download this jar and export it in the classpath when I don't need report in the excel format.
Now the problem is if this jar is removed, this build will fail/as all the imports to the classes which are being used will give error. Class.forName can load the class at run-time and doesn't give me error but with this I will not be able to use the method of that class since I cannot have the reference of the class.
Is there any way out or this is not possible?
Did you try to compile it with the jar as a dependency for the compile.
Then at runtime, you will have a part where you check if the jar is needed and if so you can dynamically get the jar and load it like so (Code does not work like this of course ;) ):
import java.lang.reflect.Method;
import java.net.URLClassLoader;
Method addURL = null;
try {
addURL = URLClassLoader.class.getDeclaredMethod("addURL",
new Class[]{URL.class});
} catch (Exception e1) {
//Log error
}
addURL.setAccessible(true);
//Maybe download the file or check if file exist else give out error and end processing
File yourJar = new File(filePath+"/"+fileName+".jar");
//Replace Your.Main.Class with your main class
addURL.invoke(Your.Main.Class.class
.getClassLoader(), yourJar.toURI().toURL());
// Your class should now be loaded and no more ClassNotFound exception should occur when it is accessed, but not if it is accessed before!
The problem is that you are hard wiring your dependencies. So your code needs to do some imports for the third party libs. What you need is to loosely couple the third party libs so that the core of you application does not need to import anything related to 3rd party libs. Use an interface which defines a method or the set of methods needed to generate reports in any format. Make this interface part of your core application. Format specific implementation goes then in separate modules which are dependent on your core application and on the 3rd party libs. Use a factory in the core application to load the specific implementation at runtime using refelction. If a format is requested from which the relevant module jars are not present in the classpath, a ClassNotFoundException will be thrown, catch it and handle accordingly.
Here a sample structure for your application
Core application
class ReportData {
}
interface ReportGenerator {
byte[] generate(ReportData data);
}
class ReportGeneratorFactory {
public ReportGenerator getInstance(String format)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
ReportGenerator reportGenerator = null;
if("txt".equals(format)) {
reportGenerator = (ReportGenerator)
Class.forName("com.foo.TxtReportGenerator").newInstance();
} else if("html".equals(format)) {
reportGenerator = (ReportGenerator)
Class.forName("com.foo.HtmlReportGenerator").newInstance();
} else if("xl".equals(format)) {
reportGenerator = (ReportGenerator)
Class.forName("com.foo.XlReportGenerator").newInstance();
} else {
throw new UnsupportedOperationException(
String.format("Unsupport format %s", format));
}
return reportGenerator;
}
}
Txt / Html Export (Could be part of the core application if no 3rd party lib are needed)
class TxtReportGenerator implements ReportGenerator {
public byte[] generate(ReportData data) {
// TODO Auto-generated method stub
return null;
}
}
class HtmlReportGenerator implements ReportGenerator {
public byte[] generate(ReportData data) {
// TODO Auto-generated method stub
return null;
}
}
Module (own jar) for XL report (depends on your core application and on the 3rd party lib)
class XlReportGenerator implements ReportGenerator {
public byte[] generate(ReportData data) {
// TODO Auto-generated method stub
return null;
}
}
Usage:
public static void main(String[] args)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
byte[] report = new ReportGeneratorFactory()
.getInstance("xl")
.generate(new ReportData());
}
I am writing an application that will load Java scripts. I currently have a GUI which utilizes a JFileChooser to allow the user to select a script from their machine. The script file can be anywhere. It is not on the classpath. Having only a File object to represent that script file, how can I obtain a Class representation of it?
I know that to load a class you need its binary name, so in.this.format. However, the problem with that is I don't know how the script writer may have packaged it. For example, he/she may have, while developing it, put the script file in the package foo.bar. After I download this script and place it in my documents (i.e., not in foo/bar), I can't load the script without knowing that it was packaged in foo.bar. If the class name is Test and I try to create a URLClassLoader pointing to the script file by doing new URLClassLoader(new URL[] { new URL(scriptFile.toURI().toURL()) }) and I do classLoader.loadClass("Test") I will get an exception saying that the class had the wrong name, and the correct name is foo.bar.Test. But how am I supposed to know that ahead of time?
This is what I have right now:
public class ScriptClassLoader extends URLClassLoader {
private final File script;
public ScriptClassLoader(File script) throws MalformedURLException {
super(new URL[] { script.toURI().toURL() });
this.script = script;
}
public Class<?> load() throws ClassNotFoundException {
String fileName = script.getName();
String className = fileName.substring(0, fileName.indexOf(".class"));
return loadClass(className);
}
}
How do people load scripts at runtime that are not part of the program's classpath, and the binary name of the class is not known?
If you just need to load a class from a given .class file, no matter how this classes is named, you can load the data yourself and then call ClassLoader's defineClass() method:
RandomAccessFile raf = new RandomAccessFile(script, "r");
try {
byte[] classData = new byte[(int) raf.length()];
raf.readFully(classData);
return super.defineClass(null, classData, 0, classData.length);
} finally {
raf.close();
}
I try to extend ClassLoader. My ClassLoader.loadClass is:
protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {// i put "throw"s here to clean code below
Class<?> result = null;
byte[] bytes = null;
try {
bytes = getClassFromFS(pathToClass); //get .class file from file system in bytes[]
} catch (FileNotFoundException ex) {
Logger.getLogger(MyLoader.class.getName()).log(Level.SEVERE, null, ex);
} catch (IOException ex) {
Logger.getLogger(MyLoader.class.getName()).log(Level.SEVERE, null, ex);
}
System.out.println("MyLoader:мой loadClass загружает класс");
return defineClass(name, bytes, 0, bytes.length); // the proplem is here !!!
return super.loadClass(name, resolve);
}
lines in the "main" thread
/*first argument - path to file. File exist, I checked it*/
myClassLoader = new MyLoader("D:\\\\customclassloader\\ClassX.class", ClassLoader.getSystemClassLoader());
classX = (SimpleInterface) myClassLoader.loadClass("customclassloader.ClassX",true).newInstance();
</pre>
then I have exception
<pre>Exception in thread "main" java.lang.SecurityException: Prohibited package name: java.lang
at java.lang.ClassLoader.preDefineClass(ClassLoader.java:650)
at java.lang.ClassLoader.defineClass(ClassLoader.java:786)
at java.lang.ClassLoader.defineClass(ClassLoader.java:635)
at customclassloader.MyLoader.loadClass(MyLoader.java:61)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:792)
at java.lang.ClassLoader.defineClass(ClassLoader.java:635)// the proplem is here !!!
at customclassloader.MyLoader.loadClass(MyLoader.java:61)
at customclassloader.CustomClassLoader.main(CustomClassLoader.java:32)
ClassX:
package customclassloader;
/**
*
* #author roman
*/
public class ClassX {
static {
System.out.println("класс ClassX инициируеться");
}
public ClassX() {
System.out.println("класс ClassX конструируеться");
}
public void f(){
System.out.println("класс ClassX выполняет f();");
}
}
I donn't understend.I name class "customclassloader.ClassX". Why do it show me the name "java.lang"?
The ClassX.class compiled in the same project and the same package.
It shows java.lang because java.lang.Object is the superclass of all classes which the loader is trying to load.
you can check this link for sample
http://www.javaworld.com/jw-10-1996/jw-10-indepth.html?page=2
" the next step is to check if the primordial class loader can resolve this class name. This check is essential to both the sanity and security of the system. For example, if you return your own instance of java.lang.Object to the caller, then this object will share no common superclass with any other object! The security of the system can be compromised if your class loader returned its own value of java.lang.SecurityManager, which did not have the same checks as the real one did. "
It seems that Java forbid you to put your own classes in the package java.lang.
And because you're extending ClassLoader, which is in java.lang, your class is considered as a part of the java.lang package.
Try to put it in another package, using the package statement on top of your import instructions
PS : answer as seen here : java.lang.SecurityException: Prohibited package name: java.lang