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());
}
};
}
}
我们正在使用 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());
}
};
}
}