user1921819
user1921819

Reputation: 3630

Debugging techniques for Byte Buddy?

I am trying to generate a very simple code with Byte Buddy.

I have a POJO class where some fields are annotated with @SecureAttribute, For such fields I would like to override getter implementation and redirect the call to a SecurityService.getSecureValue() implementation.

Original class:

public class Properties {
    @SecureAttribute
    protected String password;

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

Desired Proxy:

public class PropertiesProxy {
    private SecurityService securityService;

    public void setSecurityService(SecurityService var1) {
        this.securityService = var1;
    }

    public SecurityService getSecurityService() {
        return this.securityService;
    }

    @Override
    public String getPassword() {
        return securityService.getSecureValue(password);
    }
}

Emitting a field was easy but overriding a method becomes complicated. I have found a number of samples relative to my task which I try to apply but do not seem to get the required result.

So my major question is: how do I trace and debug the code generator? First thing I've learned was to print the class to file:

    DynamicType.Unloaded<?> unloadedType = byteBuddy.make();
    unloadedType.saveIn(new File("d:/temp/bytebuddy"));

This gives me an output where the extra field was added but not a glance of the getter override (disassembled from .class file):

public class PropertiesImpl$ByteBuddy$OLlyZYNY extends PropertiesImpl {
    private SecurityService securityService;

    public void setSecurityService(SecurityService var1) {
        this.securityService = var1;
    }

    public SecurityService getSecurityService() {
        return this.securityService;
    }

    public PropertiesImpl$ByteBuddy$OLlyZYNY() {
    }
}

Here I do not exactly understand how to look for the error. Does it mean that I used totally wrong method implementation and Byte Buddy simply skipped it? Or am I wrong with ElementMatchers? Is there some trace or whatever that will give me a clue how to fix my code?

Current implementation:

private Class<?> wrapProperties() throws IOException {

    DynamicType.Builder<?> byteBuddy = new ByteBuddy()
        .subclass(PropertiesImpl.class)
        .defineProperty("securityService", SecurityService.class);

    Arrays.stream(PropertiesImpl.class.getDeclaredFields())
        .filter(item -> item.getAnnotation(SecureAttribute.class) != null)
        .forEach(item -> byteBuddy
            .method(ElementMatchers.named(getGetterBeanName(item)))
            .intercept(new GetterWrapperImplementation(item)));

    DynamicType.Unloaded<?> unloadedType = byteBuddy.make();
    unloadedType.saveIn(new File("d:/temp/bytebuddy"));
    Class<?> wrapperClass = unloadedType.load(PropertiesImpl.class.getClassLoader(), ClassLoadingStrategy.Default.WRAPPER)
        .getLoaded();

    return wrapperClass;
}

public static class GetterWrapperImplementation implements Implementation {
    public static final TypeDescription SS_TYPE;
    public static final MethodDescription SS_GET_SECURE_VALUE;

    private final Field filed;

    static {
        try {
            SS_TYPE = new TypeDescription.ForLoadedType(SecurityService.class);
            SS_GET_SECURE_VALUE = new MethodDescription.ForLoadedMethod(SecurityService.class.getDeclaredMethod("getSecureValue", String.class));
        }
        catch (final NoSuchMethodException | SecurityException e) {
            throw new RuntimeException(e);
        }
    }


    public GetterWrapperImplementation(Field filed) {
        this.filed = filed;
    }

    @Override
    public InstrumentedType prepare(final InstrumentedType instrumentedType) {
        return instrumentedType;
    }

    @Override
    public ByteCodeAppender appender(final Target implementationTarget) {
        final TypeDescription thisType = implementationTarget.getInstrumentedType();

        return new ByteCodeAppender.Simple(Arrays.asList(
            TypeCreation.of(SS_TYPE),

            // get securityService field
            MethodVariableAccess.loadThis(),
            FieldAccess.forField(thisType.getDeclaredFields()
                .filter(ElementMatchers.named("securityService"))
                .getOnly()
            ).read(),

            // get secure field
            MethodVariableAccess.loadThis(),
            FieldAccess.forField(thisType.getDeclaredFields()
                .filter(ElementMatchers.named(filed.getName()))
                .getOnly()
            ).read(),

            MethodInvocation.invoke(SS_GET_SECURE_VALUE),
            MethodReturn.of(TypeDescription.STRING)
        ));
    }
}

What I know for the fact is that breakpoints inside ByteCodeAppender appender(final Target implementationTarget) do not get hit, but again not sure how to interpret this.

Thanks.

Upvotes: 2

Views: 1070

Answers (1)

Rafael Winterhalter
Rafael Winterhalter

Reputation: 43997

The Byte Buddy DSL is immutable. This means that you always have to call:

builder = builder.method(...).intercept(...);

Your forEach does not do what you expect for this reason.

As for your implementation, you can just use MethodCall on a field and define the other field as an argument.

Upvotes: 2

Related Questions