Bytebuddy:截获固定值中的get方法

Bytebuddy: get method in intercepted fixedvalue

我们正在使用 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/settting 仅调用一次 DB,并且所有后续调用都将使用 "cached" 固定值。

通常,我们会使用

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

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

由于我们需要从数据库解析和获取数据的方法,因此缺少此方法。所以,最后的问题是:

是否有可能将固定值传递给拦截方法,或者什么是更好的选择?

解决问题的一种方法是向 class 添加缓存。基本上添加一个存储值的字段,并在第一次访问时从数据库中检索它们。

应该接近您想要的示例代码。

// 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());
            }
        };
    }
}