I'm trying to create an instance of a class from jar file loaded on a byte array.
I'm receiving two args:
1. byte[] which represents jar file with required class
2. Qualified class name
When I'm testing it locally it works as expected, but when I upload exactly the same jar file with the same qualified class name remotely (using web application implemented with Spring MVC for back and AngularJS for front end deployed in Tomcat server) It can't find the required class:
java.lang.ClassNotFoundException
When I was debugging, it turned out, that classloader is properly invoked but no one class is loaded from jar.
I would be grateful if anyone can tell what can be the reason of that difference or how can I implement this functionality in other ways.
A method which loads class and returns an instance of it:
public static <T> T getInstanceOfLoadedClass(byte[] jarFileBytes, String qualifiedClassName) throws ClassFromJarInstantiationException {
LOGGER.info("Getting instance of class from loaded jar file. Class name: " + qualifiedClassName);
try {
return (T) Class.forName(qualifiedClassName, true, new ByteClassLoader(jarFileBytes)).newInstance();
} catch (InstantiationException | IllegalAccessException | IOException | ClassNotFoundException | NoSuchFieldException e) {
LOGGER.error("Exception was thrown while reading jar file for " + qualifiedClassName + "class.", e);
throw new ClassFromJarInstantiationException(e);
}
}
Custom ByteClassLoader:
public class ByteClassLoader extends ClassLoader {
private static final Logger LOGGER = Logger.getLogger(ByteClassLoader.class);
private final byte[] jarBytes;
private final Set<String> names;
public ByteClassLoader(byte[] jarBytes) throws IOException {
this.jarBytes = jarBytes;
this.names = loadNames(jarBytes);
}
private Set<String> loadNames(byte[] jarBytes) throws IOException {
Set<String> set = new HashSet<>();
try (ZipInputStream jis = new ZipInputStream(new ByteArrayInputStream(jarBytes))) {
ZipEntry entry;
while ((entry = jis.getNextEntry()) != null) {
set.add(entry.getName());
}
}
return Collections.unmodifiableSet(set);
}
#Override
public InputStream getResourceAsStream(String resourceName) {
if (!names.contains(resourceName)) {
return null;
}
boolean found = false;
ZipInputStream zipInputStream = null;
try {
zipInputStream = new ZipInputStream(new ByteArrayInputStream(jarBytes));
ZipEntry entry;
while ((entry = zipInputStream.getNextEntry()) != null) {
if (entry.getName().equals(resourceName)) {
found = true;
return zipInputStream;
}
}
} catch (IOException e) {
LOGGER.error("ByteClassLoader threw exception while reading jar byte stream for resource: "+resourceName, e);
e.printStackTrace();
} finally {
if (zipInputStream != null && !found) {
try {
zipInputStream.close();
} catch (IOException e) {
LOGGER.error("ByteClassLoader threw exception while closing jar byte stream for resource: "+resourceName, e);
e.printStackTrace();
}
}
}
return null;
} }
The problem was that the class required to be loaded was in a range of classloader while it was tested.
Hope it helps someone in solving this problem because it is really easy to miss.
Related
Ok, basically, I try to use the method described here JarFileLoader to load a jar containing a class that will be used the same as if it was on the classpath (the class name will be dynamic so that we can just add any jar with any class and the program will load it through parsing a text file, in the main line).
Problem is that when I debug and check the URLClassLoader object
protected Class<?> findClass(final String name)
Line :
Resource res = ucp.getResource(path, false);
the getResource() does not find the class name in parameter.
Does someone already try loading a jar file this way ?
Thanks.
Loader :
public class JarFileLoader extends URLClassLoader {
public JarFileLoader() {
super(new URL[] {});
}
public JarFileLoader withFile(String jarFile) {
return withFile(new File(jarFile));
}
public JarFileLoader withFile(File jarFile) {
try {
if (jarFile.exists())
addURL(new URL("file://" + jarFile.getAbsolutePath() + "!/"));
} catch (MalformedURLException e) {
throw new IllegalArgumentException(e);
}
return this;
}
public JarFileLoader withLibDir(String path) {
Stream.of(new File(path).listFiles(f -> f.getName().endsWith(".jar"))).forEach(this::withFile);
return this;
}
}
Main :
public static void main(String[] args) {
new Initializer();
JarFileLoader cl = new JarFileLoader();
cl = cl.withFile(new File("libs/dpr-common.jar"));
try {
cl.loadClass("com.*****.atm.dpr.common.util.DPRConfigurationLoader");
System.out.println("Success!");
} catch (ClassNotFoundException e) {
System.out.println("Failed.");
e.printStackTrace();
} finally {
try {
cl.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Here the test class I used. When I debug URLClassLoader I can see in the third loop the path of the jar file(loop on the classpath and the URL you add here), but still does not find ressource (and cannot debug the class URLClassPath so do not know what getRessource does exactly).
Ok I take the answer from this question : How to load all the jars from a directory dynamically?
And changing the URL part at the beginning with the way it is done in the long part it works.
So an example could be :
String path = "libs/dpr-common.jar";
if (new File(path).exists()) {
URL myJarFile = new File(path).toURI().toURL();
URL[] urls = { myJarFile };
URLClassLoader child = new URLClassLoader(urls);
Class DPRConfLoad = Class.forName("com.thales.atm.dpr.common.util.DPRConfigurationLoader", true, child);
Method method = DPRConfLoad.getDeclaredMethod("getInstance");
final Object dprConf = method.invoke(DPRConfLoad);
}
All my time wasted in search while it was the example which was wrong... Still does not understand why they use a stupid URL like "jar:file..." etc.
Thanks everyone.
I don't want to give java.library.path via properties. How can load the when .jar files start to run where it located. Library already located in the dist/lib/something.dll.
This is how I do this in production. As you see, I exploit reflection to modify an otherwise private data member usr_paths in the class loader.
/*
* Adds the supplied path into java.library.path.
* This is benign if the path is already present.
*/
public static synchronized void addLibraryPath(java.nio.file.Path path) throws myexception
{
if (path == null){
return;
}
String newPath = path.toString();
try {
/*We are using reflection here to circumvent encapsulation; usr_paths is not public*/
final java.lang.reflect.Field field = ClassLoader.class.getDeclaredField("usr_paths");
field.setAccessible(true);
/*Benign if the path is already present*/
final String[] oldPaths = (String[])field.get(null);
for (String it : oldPaths){
if (it.equals(newPath)){
return;
}
}
/*Add the new path*/
final String[] newPaths = java.util.Arrays.copyOf(oldPaths, oldPaths.length + 1);
newPaths[newPaths.length - 1] = newPath;
field.set(null, newPaths);
} catch (final java.lang.IllegalAccessException | NoSuchFieldException e){
throw new myexception(e.getMessage());
}
}
I used an alternative method for loading library for JVM. First step add your dll file to a package . Add following codes your main class's main method.
public static void main(String args[]) {
/* Set the Nimbus look and feel */
/* Create and display the form */
String startupPath=System.getProperty("java.library.path");
System.out.println![enter image description here][1]("LibraryPath -1:"+startupPath);
String[] trim=startupPath.split(";");
if(trim.length > 0){
String firstPath=trim[0];
System.out.println("firstPath :"+firstPath);
String destPath=firstPath+"\\rxtxSerial.dll";
File destinationFile=new File(destPath);
if(!destinationFile.exists()){
try {
InputStream dllStream=Base.class.getClassLoader().getResourceAsStream("dllPack/rxtxSerial.dll");
FileOutputStream fos = null;
try{
fos = new FileOutputStream(destPath);
byte[] buf = new byte[2048];
int r = dllStream.read(buf);
while(r != -1) {
fos.write(buf, 0, r);
r = dllStream.read(buf);
}
}finally{
if(fos!=null){
fos.close();
}
}
System.out.println("Complated...");
} catch (IOException ex) {
Logger.getLogger(Base.class.getName()).log(Level.SEVERE, null, ex);
System.out.println("Problem occured closing...");
System.exit(0);
}
}
}
I'm trying to load a jar file directly into memory without dropping it to the HDD. I have tried using the ClassLoader, but i get an error.
This is my code:
Custom Classloader
public class CLS_ClassLoader extends ClassLoader {
private byte[] bArrData;
public CLS_ClassLoader(ClassLoader parent, byte[] bArrData) {
super(parent);
this.bArrData = bArrData;
}
public Class<?> loadClass(String name) throws ClassNotFoundException {
return defineClass(name, bArrData, 0,
bArrData.length);
}
}
Main
ClassLoader tParentClsLoader = CLS_ClassLoader.class.getClassLoader();
CLS_ClassLoader tClsLoader = new CLS_ClassLoader(tParentClsLoader, fileToByteArray("D:/App.jar"));
Class<?> tClass = null;
try {
tClass = tClsLoader.loadClass("pkg_main.CLS_Main");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Output
Exception in thread "main" java.lang.ClassFormatError: Incompatible magic value 1347093252 in class file pkg_main/CLS_Main
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.lang.ClassLoader.defineClass(Unknown Source)
at pkg_main.CLS_ClassLoader.loadClass(CLS_ClassLoader.java:20)
at pkg_main.CSL_Main.main(CSL_Main.java:27)
My idea is to take a encrypted jar file , decrypted it on runtime and load directly into memory.
Sorry for the typos, I did not speak English well. Thanks in advance!
Your main mistake is that defineClass(...) is expecting class bytes and you're feeding it with the whole jar file. The actual exception is thrown if class bytes do not start with 0xCAFEBABE, typical Java class file header. So, you need an additional step to sort the classes out of the jar file. The following implementation demonstrates the idea:
class CCLoader extends ClassLoader {
private Map<String, byte[]> classes = new HashMap<String, byte[]>();
public CCLoader(InputStream in) {
super(CCLoader.class.getClassLoader());
try {
JarInputStream jis = new JarInputStream(in);
JarEntry je = null;
String entryName = null;
while ((je = jis.getNextJarEntry()) != null) {
entryName = je.getName();
if (je.getName().endsWith(".class")) {
byte[] classBytes = readClass(jis);
String canonicalName = entryName.replaceAll("/", ".").replaceAll(".class", "");
classes.put(canonicalName, classBytes);
}
}
jis.close();
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
private byte[] readClass(InputStream stream) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
while(true){
int qwe = stream.read();
if(qwe == -1) break;
baos.write(qwe);
}
return baos.toByteArray();
}
public Class loadClass(String name) throws ClassNotFoundException {
try {
return this.getParent().loadClass(name);
} catch (ClassNotFoundException e) {
return findClass(name);
}
}
public Class findClass(String name) throws ClassNotFoundException {
byte[] classBytes = classes.get(name);
return defineClass(name, classBytes, 0, classBytes.length);
}
}
Following your example you can try it like that:
ClassLoader tClsLoader = new CCLoader(new FileInputStream("C:/commons-io-2.0.1.jar"));
Class<?> tClass = tClsLoader.loadClass("org.apache.commons.io.FileExistsException");
I'm looking to write a custom class loader that will load a JAR file from across a custom network. In the end, all I have to work with is a byte array of the JAR file.
I cannot dump the byte array onto the file system and use a URLClassLoader.
My first plan was to create a JarFile object from a stream or byte array, but it only supports a File object.
I've already written up something that uses a JarInputStream:
public class RemoteClassLoader extends ClassLoader {
private final byte[] jarBytes;
public RemoteClassLoader(byte[] jarBytes) {
this.jarBytes = jarBytes;
}
#Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> clazz = findLoadedClass(name);
if (clazz == null) {
try {
InputStream in = getResourceAsStream(name.replace('.', '/') + ".class");
ByteArrayOutputStream out = new ByteArrayOutputStream();
StreamUtils.writeTo(in, out);
byte[] bytes = out.toByteArray();
clazz = defineClass(name, bytes, 0, bytes.length);
if (resolve) {
resolveClass(clazz);
}
} catch (Exception e) {
clazz = super.loadClass(name, resolve);
}
}
return clazz;
}
#Override
public URL getResource(String name) {
return null;
}
#Override
public InputStream getResourceAsStream(String name) {
try (JarInputStream jis = new JarInputStream(new ByteArrayInputStream(jarBytes))) {
JarEntry entry;
while ((entry = jis.getNextJarEntry()) != null) {
if (entry.getName().equals(name)) {
return jis;
}
}
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
This may work fine for small JAR files, but I tried loading up a 2.7MB jar file with almost 2000 classes and it was taking around 160 ms just to iterate through all the entries let alone load the class it found.
If anyone knows a solution that's faster than iterating through a JarInputStream's entries each time a class is loaded, please share!
The best you can do
First you have no need to use JarInputStream as it only adds the support of the manifest to the class ZipInputStream which we don't really care here. You cannot put your entries into a cache (unless you store directly the content of each entries which would be terrible in term of memory consumption) because a ZipInputStream is not meant to be shared so it cannot be read concurrently. The best you can do is to store the name of the entries into a cache to only iterate over the entries when we know that the entry exists.
The code could be something like this:
public class RemoteClassLoader extends ClassLoader {
private final byte[] jarBytes;
private final Set<String> names;
public RemoteClassLoader(byte[] jarBytes) throws IOException {
this.jarBytes = jarBytes;
this.names = RemoteClassLoader.loadNames(jarBytes);
}
/**
* This will put all the entries into a thread-safe Set
*/
private static Set<String> loadNames(byte[] jarBytes) throws IOException {
Set<String> set = new HashSet<>();
try (ZipInputStream jis =
new ZipInputStream(new ByteArrayInputStream(jarBytes))) {
ZipEntry entry;
while ((entry = jis.getNextEntry()) != null) {
set.add(entry.getName());
}
}
return Collections.unmodifiableSet(set);
}
...
#Override
public InputStream getResourceAsStream(String name) {
// Check first if the entry name is known
if (!names.contains(name)) {
return null;
}
// I moved the JarInputStream declaration outside the
// try-with-resources statement as it must not be closed otherwise
// the returned InputStream won't be readable as already closed
boolean found = false;
ZipInputStream jis = null;
try {
jis = new ZipInputStream(new ByteArrayInputStream(jarBytes));
ZipEntry entry;
while ((entry = jis.getNextEntry()) != null) {
if (entry.getName().equals(name)) {
found = true;
return jis;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// Only close the stream if the entry could not be found
if (jis != null && !found) {
try {
jis.close();
} catch (IOException e) {
// ignore me
}
}
}
return null;
}
}
The ideal solution
Accessing to a zip entry using JarInputStream is clearly not the way to do it as you will need to iterate over the entries to find it which is not a scalable approach because the performance will depend on total amount of entries in your jar file.
To get the best possible performances, you need to use a ZipFile in order to access directly to an entry thanks to the method getEntry(name) whatever the size of your archive. Unfortunately the class ZipFile doesn't provide any constructors that accept the content of your archive as a byte array (it is not a good practice anyway as you could face OOME if the file is too big) but only as a File, so you will need to change the logic of your class in order to store the content of your zip into a temporary file, then provide this temporary file to your ZipFile to be able to access to the entry directly.
The code could be something like this:
public class RemoteClassLoader extends ClassLoader {
private final ZipFile zipFile;
public RemoteClassLoader(byte[] jarBytes) throws IOException {
this.zipFile = RemoteClassLoader.load(jarBytes);
}
private static ZipFile load(byte[] jarBytes) throws IOException {
// Create my temporary file
Path path = Files.createTempFile("RemoteClassLoader", "jar");
// Delete the file on exit
path.toFile().deleteOnExit();
// Copy the content of my jar into the temporary file
try (InputStream is = new ByteArrayInputStream(jarBytes)) {
Files.copy(is, path, StandardCopyOption.REPLACE_EXISTING);
}
return new ZipFile(path.toFile());
}
...
#Override
public InputStream getResourceAsStream(String name) {
// Get the entry by its name
ZipEntry entry = zipFile.getEntry(name);
if (entry != null) {
// The entry could be found
try {
// Gives the content of the entry as InputStream
return zipFile.getInputStream(entry);
} catch (IOException e) {
// Could not get the content of the entry
// you could log the error if needed
return null;
}
}
// The entry could not be found
return null;
}
}
I would iterate through the class once and cache the entries. I would also look at the source code for URLClassLoader to see how it does it. If that fails, write the data to a temporary file and load it via the normal class loader.
I've been messing around with ClassLoaders in java recently, trying to test code which uses dynamic loading of classes (using Class.forName(String name)) with a custom ClassLoader.
I've got my own custom ClassLoader set up, which is supposed to be configurable to throw a ClassNotFoundException when trying to load a given class.
public class CustomTestClassLoader extends ClassLoader {
private static String[] notAllowed = new String[]{};
public static void setNotAllowed(String... nonAllowedClassNames) {
notAllowed = nonAllowedClassNames;
}
public static String[] getNotAllowed() {
return notAllowed;
}
public CustomTestClassLoader(ClassLoader parent){super(parent);}
#Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
for (String s : notAllowed) {
if (name.equals(s)) {
throw new ClassNotFoundException("Loading this class is not allowed for testing purposes.");
}
}
if(name.startsWith("java") || name.startsWith("sun") || getClass().getName().equals(name)) {
return getParent().loadClass(name);
}
Class<?> gotOne = super.findLoadedClass(name);
if (gotOne != null) {
return gotOne;
}
Class<?> c;
InputStream in = getParent().getResourceAsStream(name.replace('.', '/')+".class");
if (in == null) {
throw new ClassNotFoundException("Couldn't locate the classfile: "+name);
}
try {
byte[] classData = readBytes(in);
c = defineClass(name, classData, 0, classData.length);
} catch(IOException e) {
throw new ClassNotFoundException("Couldn't read the class data.", e);
} finally {
try {
in.close();
} catch (IOException e) {/* not much we can do at this point */}
}
if (resolve) {
resolveClass(c);
}
return c;
}
private byte[] readBytes(InputStream in) throws IOException {
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[4194304];
int read = in.read(buffer);
while (read != -1) {
out.write(buffer, 0, read);
read = in.read(buffer);
}
out.close();
return out.toByteArray();
}
}
I'm using -Djava.system.class.loader=com.classloadertest.test.CustomTestClassLoader to set this classloader as default ClassLoader.
I was hoping to be able to force a ClassNotFoundException by disallowing certain class names using CustomTestClassLoader.setNotAllowed(String...).
However, it only works for ClassLoader.loadClass, and not for Class.forName:
public void test() {
ClassLoader loader = this.getClass().getClassLoader();
CustomTestClassLoader custom = (CustomTestClassLoader)loader;
CustomTestClassLoader.setNotAllowed(NAME);
for (String s : custom.getNotAllowed())
System.out.println("notAllowed: "+s);
try {
System.out.println(Class.forName(NAME));
} catch (ClassNotFoundException e) {
System.out.println("forName(String) failed");
}
try {
System.out.println(Class.forName(NAME,false,custom));
} catch (ClassNotFoundException e) {
System.out.println("forName(String,boolean,ClassLoader) failed");
}
try {
System.out.println(custom.loadClass(NAME));
} catch (ClassNotFoundException e) {
System.out.println("ClassLoader.loadClass failed");
}
}
Now I expected all three try blocks to fail, since the documentation of Class.forName says it uses the ClassLoader of the caller (which should be custom/loader in this test).
However, only the final try block fails. Here is the output I get:
notAllowed: com.classloadertest.test.Test
class com.classloadertest.test.Test
class com.classloadertest.test.Test
ClassLoader.loadClass failed
Does Class.forName really use the classloader? And if so, which methods?
It seems to be using a native call, so I have no idea what it does under the covers.
Of course if anyone knows any alternative ways of testing a Class.forName() call, it would be much appreciated as well.
Class.forName() uses the classloader of the class where it is called from (e.g in your case the class that contains the test() method). So, if you are running it in a different environment this will cause the problem.
UPDATE That ClassLoader will be used in Class.forName() which loaded your Test class. And that may be the solution: It may be an Eclipse-defined classloader, that has access to your class, so it will load it. Despite that its parent (or root) classloaders have explicit rule to forbid the loading of that class.
I still recommend to make a wrapper class for this instantiation. You should load that class with your CustomTestClassLoader, then you can use Class.forName() in that class.