Loading Inner Classes using a ClassLoader - java

I am writing a program that lets users type java code into a text area, then compile it and load it into the program as a sort of "plugin." I am currently able to compile the .java files and load the outer classes, but I am not able to load/instantiate inner classes written by users without errors. Currently this is what I use to load the outer classes, this code works and I am able to easily use the outer classes without complications whatsoever. (I did some editing for better readability, if you notice a typo tell me)
private ArrayList<String> execute(ArrayList<String> fileNames) {
ArrayList<String> successStories = new ArrayList();
ArrayList<Class<?>> eventHandlers = new ArrayList();
// Load all classes first...
for (int i = 0; i < fileNames.size(); i++) {
Class<?> clazz = loadClassByName2(fileNames.get(i));
if (EventHandler.class.isAssignableFrom(clazz)) {
eventHandlers.add(clazz);
successStories.add(fileNames.get(i));
} else if (InterfaceInnerClass.class.isAssignableFrom(clazz)) {
successStories.add(fileNames.get(i));
} else {
System.out.println(clazz.getName() + " couldn't be loaded");
}
}
// Then instantiate the handlers.
for (int i = 0; i < eventHandlers.size(); i++) {
try {
Object obj = eventHandlers.get(i).newInstance();
if (obj instanceof EventHandler) {
EventHandler EH = (EventHandler)obj;
EH.name = EH.getClass().getSimpleName();
CmdEvents.addEvent(EH);
}
} catch (Exception e) {
e.printStackTrace();
}
}
return successStories;
}
public static Class<?> loadClassByName2(String name) {
try {
// My program sets up classpath environment variables so "./" is all that is needed as the URL
URLClassLoader classLoader = new URLClassLoader(
new URL[] { new File("./").toURI().toURL() });
// Load the class from the classloader by name....
Class<?> c = classLoader.loadClass("plugins.event_handlers." + name);
classLoader.close();
return c;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
The original list of file names is sent from a GUI with every .class file in the plugin directory listed. They user selects the classes they want to load, clicks a button and it sends those filenames to these methods. Within this code EventHandler is a class which you will see below, InterfaceInnerClass is simply an interface used as a tag to ensure there aren't any serious problems, and CmdEvents is a console command within my program used to manage these "plugin" classes. Like I said above, this code works fine for outer classes, the problem is when I try to load inner classes. The code for my EventHandler abstract class is as follows.
public abstract class EventHandler {
public String name; // Don't mind this being public, I have my reasons for this.
public abstract void execute(String input);
public abstract boolean condition(String input);
}
The way my program works, it recieves a String from a user, then calls condition(String) and if it returns true, it calls execute(String). I wrote some test code to try out my loader as follows.
package plugins.event_handlers;
public class Test_Handler extends events.EventHandler {
public void execute(String input) {
System.out.println("Testing...");
TestInner inner = new TestInner();
inner.test();
System.out.println("Did it work?");
}
public boolean condition(String input) {
return input.contains("testinput");
}
public class TestInner implements events.InterfaceInnerClass {
public TestInner() {
System.out.println("The inner works!");
}
public void test() {
System.out.println("Inner class has been tested");
}
}
}
I run my program, select both the Test_Handler.class and Test_Handler$TestInner.class, then click the button. When the method returns an ArrayList or successfully loaded classes, it returns BOTH the outer and inner class. However, when I run the program and pass "testinput" to the condition and execute methods, this is my output.
Testing... Exception in thread "Execute_Thread_Test_Handler"
java.lang.NoClassDefFoundError:
plugins/event_handlers/Test_Handler$TestInner at
plugins.event_handlers.Test_Handler.execute(Test_Handler.java:11) at
events.ThreadEventExecutor.run(ThreadEventExecutor.java:20) Caused by:
java.lang.ClassNotFoundException:
plugins.event_handlers.Test_Handler$TestInner at
java.net.URLClassLoader$1.run(Unknown Source) at
java.net.URLClassLoader$1.run(Unknown Source) at
java.security.AccessController.doPrivileged(Native Method) at
java.net.URLClassLoader.findClass(Unknown Source) at
java.lang.ClassLoader.loadClass(Unknown Source) at
java.lang.ClassLoader.loadClass(Unknown Source) ... 2 more
What I am wanting it to print is
Testing...
The inner works!
Inner class has been tested
Did it work?
So finally my question, how do I make the above code work? I do not want to have to make my users write their own classloaders and such to load an inner/seperate class (because not all my users will necessarily be amazing at coding) so I need to be able to reference the inner class type without the code blowing up.

Well its been a very long time since I asked but I was looking back and forth between my code and the classloader javadocs and I'm simply being dumb.
In my loadClassByName2 I call classLoader.close(); which should NOT be done if the freshly loaded class is going to load any more classes.
According to the javadocs, each class type keeps track of the classloader that loaded it. If that class type ever needs to reference an unloaded class, it calls upon its classloader to find and load it. When I closed the classloader immediately after loading the single class I made it so the classloader was not able to find/load any other classes (including local/internal classes).

Related

Java Adding field and method to compiled class and reload using class loader

I would like to add field along with its getter/setter in compiled Java classes which is loaded in a Spring boot application. I was able to modify the class using JavaAssist and ASM. But the problem is it is not letting me reload the class after modification, since this is already been loaded. I tried to write a class extending java.lang.ClassLoader but custom classloader is not getting called. Also, I checked java's Instrumentation API which clearly states
The retransformation may change method bodies, the constant pool and
attributes. The retransformation must not add, remove or rename fields
or methods, change the signatures of methods, or change inheritance.
These restrictions maybe be lifted in future versions. The class file
bytes are not checked, verified and installed until after the
transformations have been applied, if the resultant bytes are in error
this method will throw an exception.
Could you please let me know how to achieve this? I am open to runtime vs compile time modification. If you can share some examples that will be great. Subclassing may not be an option because this class will be used by third-party jars on which we don't have any control and this jar will us the class from the class pool. Also, could you please let me know how to use custom class loader?
Technology
Java - JDK 8
Spring Boot - 2.x
Spring 5
Bytecode manipulation - ASM or JavaAssist
I would like to achieve below
From
class A {
Integer num;
}
To
class A {
Integer num;
//Newly added field
private String numModified;
//Newly added method
public String getNumModified(){}
public String setNumModified(String numModified){}
}
When trying to load the class using below methods
private static Class loadClass(byte[] b,String className) {
// Override defineClass (as it is protected) and define the class.
Class clazz = null;
try {
ClassLoader loader = ClassLoader.getSystemClassLoader();
Class cls = Class.forName("java.lang.ClassLoader");
java.lang.reflect.Method method =
cls.getDeclaredMethod(
"defineClass",
new Class[] { String.class, byte[].class, int.class, int.class });
// Protected method invocation.
method.setAccessible(true);
try {
Object[] args =
new Object[] { className, b, new Integer(0), new Integer(b.length)};
clazz = (Class) method.invoke(loader, args);
} finally {
method.setAccessible(false);
}
} catch (Exception e) {
e.printStackTrace();
//System.exit(1);
}
return clazz;
}
Exception
java.lang.reflect.InvocationTargetException
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 com.test.pii.mask.util.ClassModifier.loadClass(ClassModifier.java:110)
at com.test.pii.mask.util.ClassModifier.modifyClass(ClassModifier.java:85)
at com.test.pii.mask.util.ClassModifier.main(ClassModifier.java:200)
Caused by: java.lang.LinkageError: loader (instance of sun/misc/Launcher$AppClassLoader): attempted duplicate class definition for name: "com/test/pii/web/dto/SomeOther"
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(Unknown Source)
at java.lang.ClassLoader.defineClass(Unknown Source)
... 7 more
Custom Class Loader which is not getting called
public class PIIClassLoader extends ClassLoader {
static {
ClassLoader.registerAsParallelCapable();
}
/**
*
*/
public PIIClassLoader() {
super();
}
/**
* #param parent
*/
public PIIClassLoader(ClassLoader parent) {
super(parent);
}
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
#Override
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException {
// respect the java.* packages.
if( name.startsWith("java.")) {
return super.loadClass(name, resolve);
}
else {
// see if we have already loaded the class.
if(Foo.class.getName().equals(name)) {
return null;
}
Class<?> c = findLoadedClass(name);
if( c != null ) return c;
}
return null;
}
}

ByteBuddy fails when trying to redefine sun.reflect.GeneratedMethodAccessor1

Driven by curiosity, I tried to export the bytecode of GeneratedMethodAccessor1 (generated by the JVM when using reflection).
I try to get the bytecode of the class the following way:
public class MethodExtractor {
public static void main(String[] args) throws Exception {
ExampleClass example = new ExampleClass();
Method exampleMethod = ExampleClass.class
.getDeclaredMethod("exampleMethod");
exampleMethod.setAccessible(true);
int rndSum = 0;
for (int i = 0; i < 20; i++) {
rndSum += (Integer) exampleMethod.invoke(example);
}
Field field = Method.class.getDeclaredField("methodAccessor");
field.setAccessible(true);
Object methodAccessor = field.get(exampleMethod);
Field delegate = methodAccessor.getClass().getDeclaredField("delegate");
delegate.setAccessible(true);
Object gma = delegate.get(methodAccessor);
ByteBuddyAgent.installOnOpenJDK();
try {
ClassFileLocator classFileLocator = ClassFileLocator.AgentBased
.fromInstalledAgent(gma.getClass().getClassLoader());
Unloaded<? extends Object> unloaded = new ByteBuddy().redefine(
gma.getClass(), classFileLocator).make();
Map<TypeDescription, File> saved = unloaded.saveIn(Files
.createTempDirectory("javaproxy").toFile());
saved.forEach((t, u) -> System.out.println(u.getAbsolutePath()));
} catch (IOException e) {
throw new RuntimeException("Failed to save class to file");
}
}
}
I however get the following error when executing this class:
Exception in thread "main" java.lang.NullPointerException
at net.bytebuddy.dynamic.scaffold.TypeWriter$Engine$ForRedefinition.create(TypeWriter.java:172)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1182)
at net.bytebuddy.dynamic.scaffold.inline.InlineDynamicTypeBuilder.make(InlineDynamicTypeBuilder.java:244)
at reegnz.dyna.proxy.extractor.MethodExtractor.main(MethodExtractor.java:48)
Basically I first iterate on the method call enough times for the JVM to inflate the method (generate the GeneratedMethodAccessor) and then try to redefine the class to get the bytecode.
I tried the same method to export a generated Proxy class, and it worked flawlessly. That's what drove me to try this.
It seems that the DelegatingClassLoader of the GeneratedMethodAccessor1 class can't even reload the class when I try to load the class with the loadClass method.
Any ideas how I could retrieve the bytecode for GeneratedMethodAccessor classes?
First of all, the NullPointerException is a bug, I just fixed that. The loader should have thrown an IllegalArgumentException instead but it never got that far. Thanks for bringing this to my attention.
Boiled down, the problem Byte Buddy is facing is that
gma.getClass().getClassLoader().findClass(gma.getClass().getName());
throws a ClassNotFoundException. This is a consequence of using a DelegatingClassLoader for the accessor classes. As an educated guess, I think that this class loader intends to shield its classes from the outside in order to make them easily garbage collectable. However, not allowing the lookup of a class what somewhat breaks the contract for a ClassLoader. Apart from that, I assume that this loading routine will be refactored to use the JDK's anonymous class loaders at some point in the future (similar to classes representing lambda expressions). Strangely enough, it seems like the source code for the DelegatingClassLoader is not available in the JDK even though I can find it in the distribution. Probably, the VM treats these loader specially at some place.
For now, you can use the following ClassFileTransformer which uses some reflection magic on the class loader to locate the loaded class and to then extract the byte array. (The ClassFileLocator interface only takes a name instead of a loaded class in order to allow working with unloaded types which is normally always the case. No idea why this does not work in this case.)
class DelegateExtractor extends ClassFileLocator.AgentBased {
private final ClassLoader classLoader;
private final Instrumentation instrumentation;
public DelegateExtractor(ClassLoader classLoader, Instrumentation instrumentation) {
super(classLoader, instrumentation);
this.classLoader = classLoader;
this.instrumentation = instrumentation;
}
#Override
public Resolution locate(String typeName) {
try {
ExtractionClassFileTransformer classFileTransformer =
new ExtractionClassFileTransformer(classLoader, typeName);
try {
instrumentation.addTransformer(classFileTransformer, true);
// Start nasty hack
Field field = ClassLoader.class.getDeclaredField("classes");
field.setAccessible(true);
instrumentation.retransformClasses(
(Class<?>) ((Vector<?>) field.get(classLoader)).get(0));
// End nasty hack
byte[] binaryRepresentation = classFileTransformer.getBinaryRepresentation();
return binaryRepresentation == null
? Resolution.Illegal.INSTANCE
: new Resolution.Explicit(binaryRepresentation);
} finally {
instrumentation.removeTransformer(classFileTransformer);
}
} catch (Exception ignored) {
return Resolution.Illegal.INSTANCE;
}
}
}
To further simplify your code, you can use the ClassFileLocators directly instead of applying a rewrite which as a matter of fact might slightly modify the class file even if you do not apply any changes to a class.

Accessing public methods of a Private Inner Class, from outside the Enclosing class

I have the following code class Agent.java :
public class Agent {
Helper helper ;
private class SpecificBehaviour extends Behaviour{
private Apple a;
public SpecificBehaviour(Apple a){
setApple(a);
}
public void setApple(Apple a){
this.a=a;
}
public Apple getApple(){
return a;
}
}
public void someMethod(){
helper = new Helper(this);
}
}
In the Helper.java ( another class within the same package) I would like to access the getApple() method. did some search and found this link
I am wondering if there is a better/ easier way of doing this ?
There are at least two issues here:
Helper doesn't know of the existence of SpecificBehaviour, because it's a private class. It could potentially know about the Behaviour class, which you haven't given any details of. If getApple() is declared in Behaviour, and if Behaviour is visible to Helper, then the visibility part needn't be a problem.
Helper will need a reference to an instance of SpecificBehaviour, which means you'll need to instantiate SpecificBehaviour. For that, you'll also need an instance of Agent, because SpecificBehaviour is an inner class. It's not clear whether you have such an instance.
Basically I think the presence of a private inner class is adding confusion here. If you're reasonably new to Java, I'd strongly recommend sticking to top-level classes for the moment. They have a few subtleties around them, and it's best to try to learn one thing at a time.
If this doesn't help, please give more context - your question is quite vague at the moment. Where do you want to use getApple within Helper? Should part of the state of Helper be a reference to an instance of SpecificBehaviour, or should it be a method parameter? Have you created an instance of Agent? What does Behaviour look like? You may find that in the course of answering these questions one at a time, you're better able to figure out the problem for yourself.
- Use Composition principle to get the access to the getApple() method.
Eg:
public class Agent {
Apple a = new Apple(); // Agent class has a reference of type Apple.
.....
.....
}
- Second way would be to make the getApple() method static in Apple class, and then access it from Agent class using the Class name with . (dot) operator.
Eg:
public class Agent {
public void go(){
Apple.getApple();
}
.....
.....
}
You need to ask the Agent object you are passing to the Helper for the instance of the private class SpecificBehaviour. This is the way it works. Encapsulation remember.
Jon Skeet stated that and I completely agree on it:
Helper will need a reference to an instance of SpecificBehaviour,
which means you'll need to instantiate SpecificBehaviour. For that,
you'll also need an instance of Agent, because SpecificBehaviour is an
inner class. It's not clear whether you have such an instance.
Actually, you can understand how weird your try is by testing the sample code below:
Agent.java
public class Agent
{
private class SpecificBehaviour
{
public String toString()
{
return "specific behaviour";
}
}
public Class getInner()
{
return SpecificBehaviour.class;
}
}
Helper.java
public class Helper
{
public static void main(String[] args)
{
try
{
Agent agent = new Agent();
System.out.println(agent.getInner().newInstance().toString());
}
catch (InstantiationException e) { e.printStackTrace(); }
catch (IllegalAccessException e) { e.printStackTrace(); }
}
}
The code above just compiles fine. And let's see what the output is:
java.lang.InstantiationException: Agent$SpecificBehaviour
at java.lang.Class.newInstance0(Class.java:340)
at java.lang.Class.newInstance(Class.java:308)
at Helper.main(Helper.java:5)

Java Reflection, Class Objects

My objective is to read in to the command line the name of a Class I wish to observe info on. When I know the class name before runtime, I have no issue. What I can't seem to manage is how to create a class object based on a string input.
public class Tester {
static void methodInfo2(Object obj) throws ClassNotFoundException {
//some stuff
System.out.print("Test!");
}
public static void main (String args[]) throws ClassNotFoundException{
String className = args[0];
System.out.println("Class: "+className);
//myclass2 mc = new myclass2();
//Class c = mc.getClass();
Class argClass = Class.forName(className);
try {
methodInfo2(argClass);
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
The 2 commented out lines in the main method show what I have done in the past when I know the class name before I compile. The following uncommented line shows what I thought should work, but I receive a ClassNotFoundException. The class certainly exists so I'm not sure what problem I'm having.
Two suggestions:
Make sure you're giving it the fully-qualified name (e.g. "java.lang.Thread" and not just "Thread").
Make sure the compiled class file is actually on the classpath.
Class.forName is the right way to load a class by name at runtime.
Either your argument is wrong or your class isn't in the classpath.

Trouble creating custom class loader

I am trying to create a custom class loader to accomplish the following:
I have a class in package com.company.MyClass
When the class loader is asked to load anything in the following format:
com.company.[additionalPart].MyClass
I'd like the class loader to load com.company.MyClass effectively ignoring the [additionalPart] of the package name.
Here is the code I have:
public class MyClassLoader extends ClassLoader {
private String packageName = "com.company.";
final private String myClass = "MyClass";
public MyClassLoader(ClassLoader parent) {
super(parent);
}
#Override
public Class<?> loadClass(String name) throws ClassNotFoundException {
Class<?> clazz = null;
String className = name;
// Check if the class name to load is of the format "com.company.[something].MyClass
if (name.startsWith(packageName)) {
String restOfClass = className.substring(packageName.length());
// Check if there is some additional part to the package name
int index = restOfClass.indexOf('.');
if (index != -1) {
restOfClass = restOfClass.substring(index + 1);
//finally, check if the class name equals MyClass
if (restOfClass.equals(myClass)) {
// load com.company.MyClass instead of com.company.[something].MyClass
className = packageName + myClass;
clazz = super.loadClass(className, true);
}
}
}
if (clazz == null) {
// Normal clase: just let the parent class loader load the class as usual
clazz = super.loadClass(name);
}
return clazz;
}
}
And here is my test code:
public class TestClassLoader {
public void testClassLoader () throws Exception {
ClassLoader loader = new MyClassLoader(this.getClass().getClassLoader());
clazz = Class.forName("com.company.something.MyClass", true, loader );
}
public static void main (String[] args) throws Exception {
TestClassLoader tcl = new TestClassLoader();
tcl.testClassLoader();
}
}
MyClassLoader picks up the correct class (i.e. com.company.MyClass) and returns it just correctly from loadClass (I have stepped through the code), however, it throws an exception at some later point (i.e. after loadClass is called from the JVM) as follows:
Exception in thread "main"
java.lang.ClassNotFoundException:
com/company/something/MyClass at
java.lang.Class.forName0(Native
Method)
at
java.lang.Class.forName(Class.java:247)
at [my code]
Now, I realize some of you may be thinking "Why would anyone need to do this? There has to be a better way". I am sure that there is, but this is something of education for me, as I'd like to understand how class loaders work, and gain a deeper understanding into the jvm class loading process. So, if you can overlook the inanity of the procedure, and humor me, I'd be very grateful.
Your Question
This is pure speculation. The class name is stored in the java byte code. Thus the classes you manage to load by this technique will be defective. This is probably confusing the system.
The ClassLoader probably keeps a reference to com.company.something.MyClass, but the class itself probably keeps a reference to com.company.MyClass. (I use probably a lot because I don't really know for sure.) Probably everything works OK until you use the MyClass class for something. Then the inconsistency creates trouble. So when is this exception thrown?
If you are interested in learning how class loaders work, then you can use javap to get at the byte code. This would also allow you to check my hypothesis.
If my hypothesis is correct, then the solution would be to fix the byte code. There are several packages that allow you to engineer byte code. Copy a class, change the name of the copied class, and then load it.
Aside
While not relevant to your question: I find the below to be unnecessarily complicated (and it doesn't work on com.company.something.somethingelse.MyClass).
// Check if the class name to load is of the format "com.company.[something].MyClass
if (name.startsWith(packageName)) {
String restOfClass = className.substring(packageName.length());
// Check if there is some additional part to the package name
int index = restOfClass.indexOf('.');
if (index != -1) {
restOfClass = restOfClass.substring(index + 1);
//finally, check if the class name equals MyClass
if (restOfClass.equals(myClass)) {
// load com.company.MyClass instead of com.company.[something].MyClass
className = packageName + myClass;
clazz = super.loadClass(className, true);
}
}
Why not?
//Check if the class name to load is of the format "com.com // Check if the class name to load is of the format "com.company.[something].MyClass"
if ( ( name . startsWith ( packageName ) ) && ( name . endsWith ( myClass ) ) )
I don't think you can really do that via a classloader. Theoretically if some other class is attempting to load a class it assumes is called 'com.mycompany.foo.MyClass' then it's too late, someone already has a class with bytecode referencing 'com.mycompany.foo' and that class is already loaded.
Repackaging is a lot easier at the static level, by using something like ASM to repackage all the code at build time. You of course have to modify both he classes package itself and all the classes that my refer to that package.
If you use Maven, check out the shade plugin. If not I seem to recall a tool called JarJar.
You can of course do that kind of byte code manipulation at runtime via a javaagent and a ClassTransformer. The code for the maven-shade-plugin is actually pretty small -- if you grabbed it and ripped out the maven parts you'd probably have something working in 2-3 days.

Categories

Resources