Reputation: 1864
We're using bytebuddy to replace different annotated methods, e.g. like these:
public class Example{
@Setting
public String foo(){
return "hello";
}
@Setting
public String bar(){
return "world";
}
}
At the moment, we use a MethodDelegation:
new ByteBuddy().subclass(Example.class)
.method(ElementMatchers.isAnnotatedWith(Setting.class)
.intercept(MethodDelegation.to(interceptors)).make().load(...)
and the interceptors
has following:
public String interceptString(@Origin Method method) {
// fetch current value from DB
return db.fetchString(method);
}
As you can see, we need some information from the original method to fetch the right data from the database. This is working, but:
We only need the value from the database once (when the application starts). Afterwards, the value is not really dynamic. Due to performance reasons, we want to change the MethodDelegation into a FixedValue, so that there is only one call to the DB for each method/settting and all subsequent calls will use the "cached" fixed value.
Normally, we would use something like
//...
.method(ElementMatchers.isAnnotatedWith(Setting.class)
.intercept(FixedValue.value(getValue()))
and
private Object getValue(){
Method method = ???
return db.fetchString(method);
}
Since we need the method to resolve and fetch the data from the DB, this is missing. So, finally the question is:
Is there a possibility to pass the intercepted method the fixed-value or what could be a better option here?
Upvotes: 1
Views: 722
Reputation: 5558
One way to solve your problem would be to add a cache to your class. Basically add a field that stores the values and retrieves them from database on the first access.
Example code that should come close to what you want.
// 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";
Constructor implementation:
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());
}
};
}
}
Upvotes: 1