Indivon
Indivon

Reputation: 1864

Bytebuddy: get method in intercepted fixedvalue

We're using bytebuddy to replace different annotated methods, e.g. like these:

public class Example{

    @Setting
    public String foo(){
        return "hello";
    }
    @Setting
    public String bar(){
        return "world";
    }
}

At the moment, we use a MethodDelegation:

new ByteBuddy().subclass(Example.class)
.method(ElementMatchers.isAnnotatedWith(Setting.class)
.intercept(MethodDelegation.to(interceptors)).make().load(...)

and the interceptors has following:

public String interceptString(@Origin Method method) {
    // fetch current value from DB
    return db.fetchString(method);    
}

As you can see, we need some information from the original method to fetch the right data from the database. This is working, but:

We only need the value from the database once (when the application starts). Afterwards, the value is not really dynamic. Due to performance reasons, we want to change the MethodDelegation into a FixedValue, so that there is only one call to the DB for each method/settting and all subsequent calls will use the "cached" fixed value.

Normally, we would use something like

//...
.method(ElementMatchers.isAnnotatedWith(Setting.class)
.intercept(FixedValue.value(getValue()))

and

  private Object getValue(){
    Method method = ???
    return db.fetchString(method);
  }

Since we need the method to resolve and fetch the data from the DB, this is missing. So, finally the question is:

Is there a possibility to pass the intercepted method the fixed-value or what could be a better option here?

Upvotes: 1

Views: 722

Answers (1)

k5_
k5_

Reputation: 5558

One way to solve your problem would be to add a cache to your class. Basically add a field that stores the values and retrieves them from database on the first access.

Example code that should come close to what you want.

// Wrapper arround ConcurrentHashMap for the cached values
// An instance of that class will be a field in the subclass of Example
public static class ConfigCache {
    private Map<String, Object> values = new ConcurrentHashMap<>();

    public Object computeIfAbsent(String key, Function<String, Object> mappingFunction){
        return values.computeIfAbsent(key, mappingFunction);
    }
}

public static void main(String[] args) throws Exception {
    Class<? extends Example> clazz = new ByteBuddy().subclass(Example.class)
             // Add a field to the class
            .defineField(CONFIG_CACHE, ConfigCache.class, Visibility.PUBLIC, FieldManifestation.FINAL)
            // Add a constructor that initializes the field
            .defineConstructor(Modifier.PUBLIC).withParameter(ConfigCache.class).intercept(new FieldAssignConstructor())
            .method(ElementMatchers.nameStartsWith("get").or(ElementMatchers.nameStartsWith("is")))
            .intercept(MethodDelegation.to(Stack.class)) //
            .make()
            .load(ByteBuddyEnhancer.class.getClassLoader(), ClassLoadingStrategy.Default.INJECTION).getLoaded();

    Example example = clazz.getConstructor(ConfigCache.class).newInstance(new ConfigCache());
    example.getX();
}

// Use @FieldValue to access fields of a class  
@RuntimeType
public static Object intercept(@Origin Method method, @FieldValue(CONFIG_CACHE) ConfigCache cache){
    return cache.computeIfAbsent(method.getName(), key -> {
        // Do whatever you want here
        System.out.println("Computing for " + key);
        return null;
    });
}

private static final String CONFIG_CACHE = "configCache";

Constructor implementation:

private static final class FieldAssignConstructor implements Implementation {
    @Override
    public InstrumentedType prepare(InstrumentedType instrumentedType) {
        return instrumentedType;
    }

    @Override
    public ByteCodeAppender appender(Target implementationTarget) {
        return new ByteCodeAppender() {

            @Override
            public Size apply(MethodVisitor methodVisitor, Context instrumentationContext,
                    MethodDescription instrumentedMethod) {

                StackManipulation.Size size = new StackManipulation.Compound(
                        MethodVariableAccess.REFERENCE
                                .loadFrom(0),
                        MethodInvocation.invoke(new TypeDescription.ForLoadedType(Example.class)
                                .getDeclaredMethods().filter(ElementMatchers.isConstructor()
                                        .and(ElementMatchers.takesArguments(0)))
                                .getOnly()),
                        MethodVariableAccess.REFERENCE.loadFrom(0), //
                        MethodVariableAccess.REFERENCE.loadFrom(1), //
                        FieldAccess
                                .forField(implementationTarget.getInstrumentedType().getDeclaredFields()
                                        .filter(ElementMatchers.named(CONFIG_CACHE)).getOnly())
                                .write(),
                        MethodReturn.VOID).apply(methodVisitor, instrumentationContext);
                return new Size(size.getMaximalSize(), instrumentedMethod.getStackSize());
            }
        };
    }
}

Upvotes: 1

Related Questions