我们正在使用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);
}
由于我们需要使用该方法来解析和从数据库中获取数据,因此缺少这种方法。所以,最后问题是:
是否有可能将截获的方法传递给固定值,或者可能是更好的选择?
答案 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());
}
};
}
}