sixlegs.com / blog / java / definalizer.html

Root Beer Logo Root Beer

Chris Nokleberg's Fizzy Weblog

May 2006
Su M Tu W Th F Sa
  1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 26 27
28 29 30 31
Previous  |  Next  |  More...
#  Making more objects mockable with Definalizer

After reading this diatribe on the use of the final modifier, I decided to implement one of the commenters' suggestions: write a tool to strip them out. Of course, by the time I was almost finished I stumbled across Xzajo's BCEL-based implementation, which was only posted a couple of weeks ago.

My implementation has a couple of advantages:

  • It has no dependencies (ASM is Jar Jar'd).
  • It removes final modifiers from methods, too.

Here is all the code:

package com.sixlegs.definalizer;

import java.lang.instrument.*;
import java.security.ProtectionDomain;
import org.objectweb.asm.*;
import static org.objectweb.asm.Opcodes.ACC_FINAL;

public class Main implements ClassFileTransformer
{
  public static void premain(String args, Instrumentation inst) {
    inst.addTransformer(new Main());
  }

  public byte[] transform(ClassLoader loader, String className, 
    Class<?> clazz, ProtectionDomain protectionDomain, byte[] b) {
    ClassReader reader = new ClassReader(b);
    ClassWriter writer = new ClassWriter(true);
    ClassAdapter adapter = new Definalizer(writer, className);
    reader.accept(adapter, true);
    return writer.toByteArray();
  }

  static class Definalizer extends ClassAdapter 
  {
    public Definalizer(ClassVisitor cv, String className) {
      super(cv);
    }

    @Override public void visit(int version, int access, /* ... */) {
      access &= ~ACC_FINAL;
      cv.visit(version, access, name, signature, superName, interfaces);
    }

    @Override public MethodVisitor visitMethod(int access, String name, /* ... */) {
      access &= ~ACC_FINAL;
      return cv.visitMethod(access, name, desc, signature, exceptions);
    }
  }
}

Compile this code into a jar and specify a "Premain-Class" manifest entry:

Premain-Class: com.sixlegs.definalizer.Main

Now the jar can be specified as an argument to -javaagent. Here is an example which demonstrates using CGLIB to create a subclass at runtime:

import net.sf.cglib.proxy.*;
import java.lang.reflect.Method;

public class Test
{
  public static class FinalExample {
    @Override final public String toString()  {
      return "final method";
    }
  }
  
  public static void main(String[] args) throws Exception {
    Enhancer e = new Enhancer();
    e.setSuperclass(FinalExample.class);
    e.setCallback(new MethodInterceptor(){
      public Object intercept(Object obj, Method method,
        Object[] args, MethodProxy proxy) throws Throwable {
        System.err.println("calling " + method);
        return proxy.invokeSuper(obj, args);
      }
    });
    System.err.println("RESULT: " + (FinalExample)e.create());
  }
}

The command line I used to run it was:

CLASSPATH=.:cglib.jar java -javaagent:definalizer.jar Test

Normally this would fail because the class and method are final, but because definalizer has stripped all of the final modifiers, the method is successfully intercepted:

calling public java.lang.String Test$FinalExample.toString()

The entire source distribution can be downloaded here, or just the binary jar (use at your own risk). If someone is interested in integrating this into their own project, let me know.

[Powered By FreeMarker]  [Valid Atom 1.0]  [Weblog Commenting by HaloScan.com]