krisy
krisy

Reputation: 1558

How to avoid running static initializers?

I have a legacy (not modifiable :-() code, that have a static initializer block. I would like to create a subclass - but without running the static block.

Is there any way doing this? For example: class.ForName method has a second argument which is responsibe for initializing the class - but sadly, it doesn't work for me (probably means something else ..): http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#forName(java.lang.String, boolean, java.lang.ClassLoader)

UPDATE: the above mentioned class.forName does not trigget initialization - but asking for a newInstance does :-(

Thanks, krisy

Upvotes: 3

Views: 1373

Answers (3)

jdb
jdb

Reputation: 4509

You can always patch the old class with ASM. Generating a new class from the old bytecode by ignoring the clinit block should be easy.

import org.objectweb.asm.*;
import org.objectweb.asm.commons.EmptyVisitor;
import java.io.*;

public class ClinitKiller {
    public static void main (String[] args) {
        final InputStream input = ClinitKiller.class.getResourceAsStream(Test.class.getName() + ".class");
        try {
            final byte[] bytes = instrument(input);
            FileOutputStream out = new FileOutputStream("/tmp/Test.class");
            out.write(bytes);
            out.close();
        }
        catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static byte[] instrument(InputStream is) throws IOException {
        ClassReader reader = new ClassReader(is);
        ClassWriter writer = new ClassWriter(0);
        ClinitKillerClassAdapter classAdapter = new ClinitKillerClassAdapter(writer);
        reader.accept(classAdapter, 0);
        return writer.toByteArray();
    }
}

class ClinitKillerClassAdapter extends ClassAdapter {
    public ClinitKillerClassAdapter(final ClassVisitor cv) {
        super(cv);
    }

    public MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) {
        if (name.equals("<clinit>")) {
            return new EmptyVisitor();
        }
        return cv.visitMethod(access, name, desc, signature, exceptions);
    }
}

Here is the before and after for the following class:

public class Test {
    private static final String value;
    static {
        System.out.println("Test static");
        value = "test value";
    }
    public static void main(String[] args) {
        System.out.println(value);
    }
}

Before:

javap -c Test
Compiled from "Test.java"
public class Test extends java.lang.Object{
    public Test();
    Code:
    0:  aload_0
    1:  invokespecial   #1; //Method java/lang/Object."<init>":()V
    4:  return

    public static void main(java.lang.String[]);
    Code:
    0:  getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
    3:  getstatic   #3; //Field value:Ljava/lang/String;
    6:  invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    9:  return

    static {};
    Code:
    0:  getstatic   #2; //Field java/lang/System.out:Ljava/io/PrintStream;
    3:  ldc #5; //String Test static
    5:  invokevirtual   #4; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    8:  ldc #6; //String test value
    10: putstatic   #3; //Field value:Ljava/lang/String;
    13: return

}

Output:
Test static
test value

After:

javap -c Test
Compiled from "Test.java"
public class Test extends java.lang.Object{
    public Test();
    Code:
    0:  aload_0
    1:  invokespecial   #11; //Method java/lang/Object."<init>":()V
    4:  return

    public static void main(java.lang.String[]);
    Code:
    0:  getstatic   #21; //Field java/lang/System.out:Ljava/io/PrintStream;
    3:  getstatic   #23; //Field value:Ljava/lang/String;
    6:  invokevirtual   #29; //Method java/io/PrintStream.println:(Ljava/lang/String;)V
    9:  return

}

Output:
null

Upvotes: 2

gaborsch
gaborsch

Reputation: 15758

You can do the following:

  • Decompile the class (e.g. with JAD)
  • Remove the static initializer code
  • Compile the new class
  • Copy the new class into the jar of the old code (remove the old one)
  • Make your subclass

I know it's not beautiful, but you have hardly another choice. The static initializers will run right after the class is loaded, and there is no possibility to skip it.

Upvotes: 0

Jon Skeet
Jon Skeet

Reputation: 1500735

I have a legacy (not modifiable :-() code, that have a static initializer block. I would like to create a subclass - but without running the static block.

You can create a subclass - but you can never initialize that subclass, which means it's probably useless.

From section 12.4.2 of the JLS (class initialization):

Next, if C is a class rather than an interface, and its superclass SC has not yet been initialized, then recursively perform this entire procedure for SC.

Basically I think you'll need to rethink the design. Ideally so that you can cope with the static initializer running.

Upvotes: 2

Related Questions