Intercept methods with logic based on annotations

269 views
Skip to first unread message

fuudtor...@gmail.com

unread,
Dec 7, 2017, 12:13:56 AM12/7/17
to Byte Buddy
Hello.

I want to extract some aop-like things that are spread all around my code into Instrumentation(s).

For example:

public class MyController{
public void authUser(@MdcValue("account") String account, ...){
...
}
}

I want to intercept all methods that have at least one parameter annotated with @MdcValue and put such parameters into logging context.

There are many such methods thus it should work as fast as current non-extracted implementation:

public class MyController{
public void authUser(String account, ...){
try (MdcCloseable ignored = Mdc.put("account", account)){
...
}
}
}

Because of performance reasons I do not want to parse annotations or construct Method instance on each call.

I implemented first solution. I use
intercept(
Advice
.withCustomMapping()
.bind(InjectRandomId.class, new RandomIdInjector())
.to(MdcValueProcessor.class)
)

RandomIdInjector used to add unique id to each method and be able to store actions it map:

public class MdcValueProcessor {
private static final ConcurrentMap<String, MdcProcessor> idToProcessor = new ConcurrentHashMap<>();

@Advice.OnMethodEnter
public static AutoCloseable onMethodEnter(
@Advice.Origin("#t") String instrumentedClass, // #t means class name. @e can inject Method but it will require Class.getDeclaredMethod call on each request. Let's optimize
@Advice.Origin() String methodAsString, // no params means Method.toString - the simplest way - use method.toString to distinguish methods with different signatures
@InjectRandomId String id, // this id will be uniq for each instrumented method
@Advice.AllArguments Object[] allArguments) throws Exception {

return onMethodEnterImpl(instrumentedClass, methodAsString, id, allArguments);
}

@Advice.OnMethodExit(onThrowable = Throwable.class) // on regular exit and on any exception
public static void onMethodExit(@Advice.Enter AutoCloseable closeMdc) throws Exception {
closeMdc.close();
}

// this method is intended to help with debug because @Advice.OnMethodEnter will be inlined and line info will be loss.
// It should be public because it will be invoked from inline code
public static AutoCloseable onMethodEnterImpl(String clazz, String method, String id, @Advice.AllArguments Object[] values) {
...
}
}


Now I want to optimize and clear this code.

I want to inject static final Processors[] field to intstrumented class and initialize it on clinit.

I can add field with:
.defineField("processors", Object[].class, FieldManifestation.FINAL, Visibility.PUBLIC, Ownership.STATIC)


But I do not know how to move further.

Q1: How to add <clinit> with static field initialization?
Q2: How to pass this field to Advice?

Thankyou.

Feodor Bobin
fuudtor...@gmail.com


Rafael Winterhalter

unread,
Dec 9, 2017, 6:05:04 AM12/9/17
to fuudtor...@gmail.com, Byte Buddy
You can define a field using Byte Buddy's DSL and you can access it by using @FieldValue in your Advice method.

Does that not work for you?

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+unsubscribe@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

fuudtor...@gmail.com

unread,
Dec 10, 2017, 11:52:51 PM12/10/17
to Byte Buddy
Not really.

1. I want use final field (to maintain concurrency guarantees). Thus I need to initialize field in clinit block. How can I add it with bytebuddy?

2. I want use random field name. I need FieldDescription to inject field to Advice, don't I? How can get FieldDescription from AgentBuilder.defineField?

Thank you,
Feodor
> To unsubscribe from this group and stop receiving emails from it, send an email to byte-buddy+...@googlegroups.com.

Rafael Winterhalter

unread,
Dec 12, 2017, 5:53:11 PM12/12/17
to Fedor Bobin, Byte Buddy
1. You can intercept .executable(isTypeInitializer()) if you want to write fields from within such a block.
2. If the field does not exist while accessing it, use a FieldDescription.Latent.

Best regards, Rafael

To unsubscribe from this group and stop receiving emails from it, send an email to byte-buddy+unsubscribe@googlegroups.com.

Fedor Bobin

unread,
Dec 14, 2017, 4:01:56 AM12/14/17
to byte-...@googlegroups.com
Hello,

>> 1. You can intercept .executable(isTypeInitializer()) if you want to write fields from within such a block.

Do you mean DynamicType.Builder.executable()? I can not find this method.

>> 2. If the field does not exist while accessing it, use a FieldDescription.Latent.
Thank you. It simplifies things.

Thank you,
Feodor

Rafael Winterhalter

unread,
Dec 14, 2017, 2:37:19 PM12/14/17
to Fedor Bobin, Byte Buddy

Fedor Bobin

unread,
Dec 18, 2017, 2:20:33 AM12/18/17
to Rafael Winterhalter, Byte Buddy
Hello.

I am getting

[Byte Buddy] DISCOVERY AopTest$MyTestClass [sun.misc.Launcher$AppClassLoader@18b4aac2, null, loaded=false]
[Byte Buddy] ERROR AopTest$MyTestClass [sun.misc.Launcher$AppClassLoader@18b4aac2, null, loaded=false]
java.lang.IllegalStateException: Cannot call super (or default) method for static AopTest$MyTestClass()
at net.bytebuddy.implementation.SuperMethodCall$Appender.apply(SuperMethodCall.java:97)
at net.bytebuddy.asm.Advice$Appender$EmulatingMethodVisitor.resolve(Advice.java:7812)
at net.bytebuddy.asm.Advice$Appender.apply(Advice.java:7765)
at net.bytebuddy.implementation.bytecode.ByteCodeAppender$Compound.apply(ByteCodeAppender.java:134)
at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyCode(TypeWriter.java:620)
at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod$WithBody.applyBody(TypeWriter.java:609)
at net.bytebuddy.dynamic.scaffold.TypeWriter$MethodPool$Record$ForDefinedMethod.apply(TypeWriter.java:526)
at net.bytebuddy.dynamic.scaffold.TypeInitializer$Drain$Default.apply(TypeInitializer.java:93)
at net.bytebuddy.implementation.Implementation$Context$Default.drain(Implementation.java:817)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$InitializationHandler$Creating.complete(TypeWriter.java:3006)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining$RedefinitionClassVisitor.visitEnd(TypeWriter.java:3856)
at net.bytebuddy.jar.asm.ClassReader.accept(Unknown Source)
at net.bytebuddy.jar.asm.ClassReader.accept(Unknown Source)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default$ForInlining.create(TypeWriter.java:2941)
at net.bytebuddy.dynamic.scaffold.TypeWriter$Default.make(TypeWriter.java:1633)
at net.bytebuddy.dynamic.scaffold.inline.RebaseDynamicTypeBuilder.make(RebaseDynamicTypeBuilder.java:200)
at net.bytebuddy.agent.builder.AgentBuilder$Default$Transformation$Simple$Resolution.apply(AgentBuilder.java:8905)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.doTransform(AgentBuilder.java:9306)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:9269)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.access$1300(AgentBuilder.java:9047)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:9625)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer$LegacyVmDispatcher.run(AgentBuilder.java:9575)
at java.security.AccessController.doPrivileged(Native Method)
at net.bytebuddy.agent.builder.AgentBuilder$Default$ExecutingTransformer.transform(AgentBuilder.java:9194)
at sun.instrument.TransformerManager.transform(TransformerManager.java:188)
at sun.instrument.InstrumentationImpl.transform(InstrumentationImpl.java:428)
at java.lang.ClassLoader.defineClass1(Native Method)
at java.lang.ClassLoader.defineClass(ClassLoader.java:763)
at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:142)
at java.net.URLClassLoader.defineClass(URLClassLoader.java:467)
at java.net.URLClassLoader.access$100(URLClassLoader.java:73)
at java.net.URLClassLoader$1.run(URLClassLoader.java:368)
at java.net.URLClassLoader$1.run(URLClassLoader.java:362)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(URLClassLoader.java:361)
at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:331)
at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
at AopTest.main(AopTest.java:29)
[Byte Buddy] COMPLETE AopTest$MyTestClass [sun.misc.Launcher$AppClassLoader@18b4aac2, null, loaded=false]

-----------------------------------------------------------------

I have following class:

-----------------------------------------------------------------

import net.bytebuddy.agent.ByteBuddyAgent;
import net.bytebuddy.agent.builder.AgentBuilder;
import net.bytebuddy.asm.Advice;
import net.bytebuddy.description.modifier.FieldManifestation;
import net.bytebuddy.description.modifier.Ownership;
import net.bytebuddy.description.modifier.Visibility;

import static net.bytebuddy.matcher.ElementMatchers.isTypeInitializer;

public class AopTest {
    public static void main(String[] args) {
        ByteBuddyAgent.install();

        new AgentBuilder.Default()
                .type(
                        (typeDescription, classLoader, javaModule, aClass, protectionDomain) ->
                                typeDescription.getSimpleName().equals("MyTestClass")
                )
                .transform(
                        (builder, typeDescription, classLoader, javaModule) ->
                                builder
                                        .defineField("aaaa", Object[].class, Ownership.STATIC, Visibility.PRIVATE, FieldManifestation.FINAL)
                                        .invokable(isTypeInitializer())
                                        .intercept(Advice.to(AssginFieldAdvice.class))
                )
                .with(AgentBuilder.Listener.StreamWriting.toSystemError())
                .installOnByteBuddyAgent();

        new MyTestClass();
    }

    private static class AssginFieldAdvice {
        @Advice.OnMethodExit
        public static void enter(){
            enterImpl();
        }

        public static void enterImpl(){
            System.out.println("AAAAAAA");
        }
    }

    public static class MyTestClass {

    }
}


What am I doing wrong?

Thank you.
Feodor Bobin

Rafael Winterhalter

unread,
Dec 19, 2017, 5:30:44 PM12/19/17
to Fedor Bobin, Byte Buddy
By default, Advice attempts to invoke the original method if it exists. In your case, no type initializer is defined so you have to provide its original implementation:

Advice.to(...).wrap(StubMethod.INSTANCE);

should work for you.

Best regards, Rafael
Reply all
Reply to author
Forward
0 new messages