Bytebuddy:在截获的fixedvalue中获取方法

时间:2017-07-21 10:09:11

标签: java byte-buddy

我们正在使用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,这样每个方法/设置只有一次DB调用,所有后续调用都将使用" cached"固定价值。

通常情况下,我们会使用类似

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

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

由于我们需要使用该方法来解析和从数据库中获取数据,因此缺少这种方法。所以,最后问题是:

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

1 个答案:

答案 0 :(得分: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());
            }
        };
    }
}