Bytebuddy: получить метод в перехваченном фиксированном значении

Мы используем bytebuddy для замены различных аннотированных методов, например. как это:

public class Example{

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

На данный момент мы используем MethodDelegation:

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

и interceptors имеет следующее:

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

Как видите, нам нужна некоторая информация из исходного метода, чтобы получить правильные данные из базы данных. Это работает, но:

Нам нужно значение из базы данных только один раз (при запуске приложения). После этого значение на самом деле не является динамическим. Из-за соображений производительности мы хотим изменить MethodDelegation на FixedValue, чтобы был только один вызов БД для каждого метода/настройки, и все последующие вызовы будут использовать «кэшированное» фиксированное значение.

Обычно мы использовали бы что-то вроде

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

а также

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

Поскольку нам нужен метод для разрешения и извлечения данных из БД, он отсутствует. Итак, наконец, вопрос:

Есть ли возможность передать перехваченному методу фиксированное значение или что здесь может быть лучше?


person Indivon    schedule 21.07.2017    source источник


Ответы (1)


Одним из способов решения вашей проблемы было бы добавить кеш в ваш класс. В основном добавьте поле, которое хранит значения и извлекает их из базы данных при первом доступе.

Пример кода, который должен быть близок к тому, что вы хотите.

// 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";

Реализация конструктора:

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());
            }
        };
    }
}
person k5_    schedule 25.07.2017