Assigning field values on class and instance initialization

984 views
Skip to first unread message

odbu...@gmail.com

unread,
Nov 17, 2015, 5:30:50 AM11/17/15
to Byte Buddy
Is it possible to intercept the class fields when a class is initialized and instance fields when an instance is initialized other than using .constructor and .invokable?

For example, is there a way to match fields that have an annotation and then intercept them on class/instance creation so that their values can be set?  For example, I want to inject the fields annotated with @InjectMe before Foo() executes.

package test.Foo;
public
class Foo {
   
@InjectMe
   
private static String classField;
    @InjectMe
   
private String instanceField;

    private String ignoreMe;

    public Foo() {
        System.out.println(classField);
        System.out.println(instanceField);
        System.out.println(ignoreMe);
    }
}

I've already tried intercepting the contructor to do this but it doesn't work b/c you can't access @This before super is called in order to access the instance fields:

@Test
public void interceptConstructorFailsWithThis() {
    new ByteBuddy()
                .rebase(TypePool.Default.ofClassPath().describe("test.Foo").resolve(), ClassFileLocator.ForClassLoader.ofClassPath())
                .constructor(ElementMatchers.any())
                .intercept(MethodDelegation.to(
                        new Object() {
                            //Can't use @This here...
                            public void m(@This Object o) {
                                System.out.println(o);
                            }
                        }
                    ).andThen(SuperMethodCall.INSTANCE))
                .make()
                .load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION);
    //fails with "Type uninitializedThis (current frame,stack[1]) is not assignable to 'java/lang/Object'"
    new test.Foo();
}

Also there's no way (that I can find) to intercept static initializers before they run using invokable.  The interception runs after the static fields are assigned and after any static block.


Rafael Winterhalter

unread,
Nov 17, 2015, 6:28:54 AM11/17/15
to odbu...@gmail.com, Byte Buddy
Hi,
constructor interception is special in the way that a super constructor or auxiliary constructor must be called BEFORE any operation on the instance. This is enforced by the Java virtual machine. What does therefore work is calling the super constructor before the interceptor:

.intercept(SuperMethodCall.INSTANCE
   .andThen(
MethodDelegation.to(
      new Object() {

        public void m(@This Object o) {
          System.out.println(o);
        }
      }))

This way it is not only possible to set fields but these fields will also override any value that was set in the constructor. Is this sufficient for your purposes?

As for the static initializers: The original initializer is always executed first. Then the interception code is called. But again, you can set static fields from your interceptor where you can replace any values that were set previously.

Cheers, Rafael

--
You received this message because you are subscribed to the Google Groups "Byte Buddy" group.
To unsubscribe from this group and stop receiving emails from it, send an email to byte-buddy+...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

odbu...@gmail.com

unread,
Nov 18, 2015, 5:24:15 AM11/18/15
to Byte Buddy, odbu...@gmail.com
Yes, this is what I have been doing.  I was hoping there was a way to intercept field assignment independently of the initializers (instance, static, or constructor).  I think this illustrates my point.  Surely there's a way to override/intercept field assignment...

public class ConstructorTest {
    public class A {
        //I wanted intercept this assignment
        protected String name = "A";
        public A() {
            System.out.format("Doing which depends on my name being %s%n", name);
        }
    }
   
    @Test
    public void testIntercept() {
        new ByteBuddy()
                .rebase(TypePool.Default.ofClassPath()
                        .describe("ConstructorTest$A").resolve(),
                    ClassFileLocator.ForClassLoader.ofClassPath())
                .constructor(ElementMatchers.any())
                .intercept(SuperMethodCall.INSTANCE.andThen(
                    MethodDelegation.to(
                            new Object() {
                                public void m(@This Object o) throws Exception {
                                    Field nameField = o.getClass().getDeclaredField("name");
                                    nameField.setAccessible(true);
                                    nameField.set(o, "C");
                                    System.out.println("Changed name to C... oops, that work I did was unecessary... bummer.");
                                }
                            }
                        )))
                .make()
                .load(ClassLoader.getSystemClassLoader(), ClassLoadingStrategy.Default.INJECTION);
        new A();
    }
}


prints:
Doing which depends on my name being A
Changed name to C... oops, that work I did was unecessary... bummer.
Reply all
Reply to author
Forward
0 new messages