Can a Java Class Add a Method to Itself at Runtime

Can a Java class add a method to itself at runtime?

It's not simple. Once a class is loaded by a classloader, there is no way to change the methods of loaded classes. When a class is requested, a classloader will load it and link it. And there is no way (with Java) to change the linked code or to add/remove methods.

The only trick that comes to my mind is playing with classloaders. If we delete a custom classloader, then the classes loaded by that classloader should be deleted or inaccessible too. The idea that comes to my mind is to

  1. implement one custom classloader
  2. load the dynamic class with that custom classloader
  3. if we have an updated version of this class,
  4. remove the custom classloader and
  5. load the new version of this class with a new instance of the custom classloader

I leave that as food for thought, can't prove, if this leads to a solution or if we have pitfalls.

As a simple answer to the question: No, we can't change a loaded class like we can change the content of fields with reflection. (we can't add or remove fields too).

Is there a way to add a method to a class definition at runtime?

What you want is possible!

But not like this. The answer to your actual question is a simple, flat out 'No'. But you don't want what you describe in your question.

Let me elaborate.

Let's first say that you could add methods at runtime. You can't*, but let's say you could.

That would accomplish nothing whatsoever; given:

public class Example implements Singleton<Example> {
@StaticContract Example getInstanceStatic() { return new Example(); }
}

We can already see issues here (this method is.. public. It has to be, that's the rule of interfaces. But given that you want this to be a singleton, that'd be very bad news).

But let's carry on for a moment. The idea is that you want to be able to write, in other code:

    Example.instance();

but - how? The compiler won't LET YOU do that, because the method isn't there, and if we go with your plan (of adding the method at runtime), then at compile time it'll never be there, and javac will refuse to compile this. If somehow it DID compile this, then at runtime, where you pull your magic trick and somehow add this method, all would be well, but that's a moot point - short of hacking together a class file with a bytecode editor, there's no way to obtain a class file with the compiled version of Example.instance().

You don't want to add this at runtime.

But maybe you want to add it at compile time.

And THAT? That you can do!

Strategy #1: Lombok

Project Lombok lets you write @UtilityClass which makes it act singleton-esque. Lombok intentionally does not have @Singleton because as a concept, singletons are so universally deriled as bad code style. I guess you could fork lombok and add it if you must have this.

Strategy #2: Annotation Processors

Other than lombok, annotation processors cannot add things to existing source files. But they can make new ones! Given as actual real bytes on disk source file:

@SingletonizeMe
public class Example {
Example() {} // without lombok you're going to have to write this yourself to ensure nobody outside of the package can instantiate this...
}

then you can write an annotation processor which means that javac will automatically produce this file:

// generated code
package same.pkg.as.your.example;

public class ExampleUtil {
public static final Example EXAMPLE_INSTANCE = new Example();
}

and compile it as part of the build, and any code that contains ExampleUtil.EXAMPLE_INSTANCE will just be compiled right along, without any complaints. Annotation Processors solve the problem of 'okay, maybe at runtime this would work but how do I explain to javac to just do what I want without it refusing to compile code that it thinks stands no chance of working at runtime?'.

Strategy #3: Dependency injection systems

From dagger to spring to guice, there are tons of libraries out there that do 'dependency injection', and pretty much all of them have an option to inject things singleton style. Give those 3 libraries a quick look, it should be fairly obvious how that works once you follow their get-started-quick tutorials.

*) You'd think the answer is yes, what with instrumention and the ability to use agent technology to reload a class file. But is that 'adding a method to a class'? No, it is not - it is reloading a class, which does not normally work if you try to add any new members; the hot code replace tech built into VMs doesn't let you change (or add, or remove) any signatures.

Changing a method for all instances of class at runtime

No you cannot alter the method at run time with reflection. Reflection can be used to call methods, access fields, etc. There is no possible technique to alter a Java class at run time which would basically mean recompiling the source to byte code.

Synthesizing new methods at runtime in Java?

Beyond bytecode engineering libraries, if you have an interface for your class, you could use Java's Proxy class.

With an interface:

public interface Foo {
void bar();
}

The concrete class:

class FooImpl {
public void bar() {
System.out.println("foo bar");
}
}

To process the methods called, you use InvocationHandler:

class FooInvocationHandler implements InvocationHandler {

private Foo foo;

FooInvocationHandler(Foo foo) {
this.foo = foo;
}

public Object invoke(Object proxy, Method method, Object[] args) throws Exception {
if (method.getName().equals("bar"))
System.out.println("foo me");
// return null if you don't want to invoke the method below
}
return method.invoke(foo, args); // Calls the original method
}
}

Then create a FooFactory to generate and wrap FooImpl instances using Proxy:

public class FooFactory {
public static Foo createFoo(...) {
Foo foo = new FooImpl(...);
foo = Proxy.newProxyInstance(Foo.class.getClassLoader(),
new Class[] { Foo.class },
new FooInvocationHandler(foo));
return foo;
}
}

This would wrap the FooImpl object so that this:

Foo foo = FooFactory.createFoo(...);
foo.bar();

Prints:

foo me
foo bar

This is an alternative to the BCEL libraries, which can do this and a lot more, including generating classes from runtime information, but the BCEL libraries aren't native. (Proxy is in java.lang.reflect on everything since 1.3.)



Related Topics



Leave a reply



Submit