7

I have a class

class Foo {
    int increment(int x) {
        return x + 1;
    }
}

I want to obtain a copy of this class in runtime, e. g. a class like

class Foo$Copy1 {
    int increment(int x) {
        return x + 1;
    }
}

Which has all the same methods, but a different name.

Proxy seem to help with delegating, but not copying the methods with all their bodies.

leventov
  • 14,760
  • 11
  • 69
  • 98
  • 2
    Why would you want to do that? Methods do not get instantiated. Are you sure you're talking about a class and not an object? – Thomas Weller Nov 23 '16 at 21:54
  • 3
    The obvious question is: why do you want to do this? You can't convert a class loaded through a standard classloader back into the class file it came from, the best you can do is write your own classloader to intercept calls to `defineClass()`. – biziclop Nov 23 '16 at 21:54
  • 1
    @biziclop I want to keep the profile of each method call within the copied methods clean monomorphic. – leventov Nov 23 '16 at 21:58

3 Answers3

12

You can use Byte Buddy for this:

Class<?> type = new ByteBuddy()
  .redefine(Foo.class)
  .name("Foo$Copy1")
  .make()
  .load(Foo.class.getClassLoader())
  .getLoaded();

Method method = type.getDeclaredMethod("increment", int.class);
int result = (Integer) method.invoke(type.newInstance(), 1);

Note that this approach redefines any uses of the class within Foo, e.g. if a method returned Foo, it would now return Foo$Copy1. Same goes for all code-references.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
  • Thank you. Does it use private ClassLoader's `defineClass()` method for this or goes another way? – leventov Nov 23 '16 at 21:57
  • You can choose a `ClassLoadingStrategy` as a second argument. By default, unless the bootstrap class loader is supplied, injection into the class loader of `Foo` is used. With `ClassLoadingStrategy.Default.WRAPPER`, you can also create a throw-away class loader. – Rafael Winterhalter Nov 23 '16 at 22:00
2

@leventov what about this one? https://gist.github.com/serkan-ozal/8737583841b4b12e3a42d39d4af051ae

sozal
  • 41
  • 2
  • Probably this is the right solution for those who already use Javassist. I had no preference between Javassist and Byte Buddy initially – leventov Nov 24 '16 at 17:14
  • 1
    @leventov in addition, you may also use BCEL from JDK (it comes from JDK but not public API, because it is located under "com.sun.org.apache.bcel.internal" package) without being dependent to Javassist. – sozal Nov 24 '16 at 17:45
  • 1
    like this ` String copyClassName = classToBeCopied.getName() + "$Copy"; ClassGen classGen = new ClassGen(Repository.lookupClass(classToBeCopied)); classGen.setClassName(copyClassName); byte[] bytecode = classGen.getJavaClass().getBytes(); return UNSAFE.defineClass( copyClassName, bytecode, 0, bytecode.length, classToBeCopied.getClassLoader(), classToBeCopied.getProtectionDomain()); ` – sozal Nov 24 '16 at 17:48
1

I think using Unsafe could be enough if you have normal access to the bytecode.

Something like Foo.class.getClassLoader().getResourceAsStream() can give you the bytecode for the class.

Then use sun.misc.Unsafe.defineClass(String name, byte[] code, int off, int len, ClassLoader classLoader, ProtectionDomain protectionDomain) to define the class in the same classloader and protection domain as Foo but with a different name.

The details are to figure out, but it might be the simplest approach without any third party libraries.

Oleg Šelajev
  • 3,530
  • 1
  • 17
  • 25