如何使用实例转发器缓存已检测的 class?
How to cache an instrumented class with an instance forwarder?
用例是实现脏场跟踪器。为此,我有一个接口:
public interface Dirtyable {
String ID = "dirty";
Set<String> getDirty();
static <T> T wrap(final T delegate) {
return DirtyableInterceptor.wrap(delegate, ReflectionUtils::getPropertyName);
}
static <T> T wrap(final T delegate, final Function<Method, String> resolver) {
return DirtyableInterceptor.wrap(delegate, resolver);
}
}
拦截器class中的包裹方式为:
static <T> T wrap(final T delegate, final Function<Method, String> resolver) {
requireNonNull(delegate, "Delegate must be non-null");
requireNonNull(resolver, "Resolver must be non-null");
final Try<Class<T>> delegateClassTry = Try.of(() -> getClassForType(delegate.getClass()));
return delegateClassTry.flatMapTry(delegateClass ->
dirtyableFor(delegate, delegateClass, resolver))
.mapTry(Class::newInstance)
.getOrElseThrow(t -> new IllegalStateException(
"Could not wrap dirtyable for " + delegate.getClass(), t));
}
方法 dirtyableFor
定义了一个 ByteBuddy,它在每次调用时转发到特定实例。但是,在每次调用时进行检测有点昂贵,因此它会缓存来自给定实例 class 的检测子 class。为此,我使用 resilience4j
库 (a.k.a。javaslang-circuitbreaker
).
private static <T> Try<Class<? extends T>> dirtyableFor(final T delegate,
final Class<T> clazz,
final Function<Method, String> resolver) {
long start = System.currentTimeMillis();
Try<Class<? extends T>> r = Try.of(() -> ofCheckedSupplier(() ->
new ByteBuddy().subclass(clazz)
.defineField(Dirtyable.ID, Set.class, Visibility.PRIVATE)
.method(nameMatches("getDirty"))
.intercept(reference(new HashSet<>()))
.implement(Dirtyable.class)
.method(not(isDeclaredBy(Object.class))
.and(not(isAbstract()))
.and(isPublic()))
.intercept(withDefaultConfiguration()
.withBinders(Pipe.Binder.install(Function.class))
.to(new DirtyableInterceptor(delegate, resolver)))
.make().load(clazz.getClassLoader())
.getLoaded())
.withCache(getCache())
.decorate()
.apply(clazz));
System.out.println("Instrumentation time: " + (System.currentTimeMillis() - start));
return r;
}
private static <T> Cache<Class<? super T>, Class<T>> getCache() {
final CachingProvider provider = Caching.getCachingProvider();
final CacheManager manager = provider.getCacheManager();
final javax.cache.Cache<Class<? super T>, Class<T>> cache =
manager.getCache(Dirtyable.ID);
final Cache<Class<? super T>, Class<T>> dirtyCache = Cache.of(cache);
dirtyCache.getEventStream().map(Object::toString).subscribe(logger::debug);
return dirtyCache;
}
根据日志,检测时间从缓存未命中的 70-100 毫秒下降到缓存命中的 0-2 毫秒。
为了完整起见,这里是拦截器方法:
@RuntimeType
@SuppressWarnings("unused")
public Object intercept(final @Origin Method method, final @This Dirtyable dirtyable,
final @Pipe Function<Object, Object> pipe) throws Throwable {
if (ReflectionUtils.isSetter(method)) {
final String property = resolver.apply(method);
dirtyable.getDirty().add(property);
logger.debug("Intercepted setter [{}], resolved property " +
"[{}] flagged as dirty.", method, property);
}
return pipe.apply(this.delegate);
}
这个解决方案效果很好,除了 DirtyableInterceptor
对于缓存命中总是相同的,所以委托实例也是相同的。
是否可以将转发器绑定到实例的供应商,以便将拦截的方法转发给它?这怎么可能?
您可以通过 intercept
方法 static
创建无状态拦截器。要访问对象的状态,请在您的 subclass 上定义两个字段,您可以使用现在的静态拦截器中的 @FieldValue
注释访问它们。您还需要使用 FieldAccessor
实现来读取值,而不是使用 FixedValue::reference
检测。您还需要使用 defineField
构建器方法定义字段。
您可以通过以下方式设置这些字段:
- 在您的
Dirtyable
接口中添加 setter 方法并使用 FieldAccessor
实现拦截它们。
- 正在定义您为其提供值的显式构造函数。这也允许您将字段定义为
final
。要实现构造函数,首先需要调用超级构造函数,然后多次调用 FieldAccessor
来设置字段。
这样做,您就创建了一个完全无状态的 class,您可以重复使用它,但需要对其进行初始化。 Byte Buddy 已经提供了一个内置的 TypeCache
以便于重复使用。
用例是实现脏场跟踪器。为此,我有一个接口:
public interface Dirtyable {
String ID = "dirty";
Set<String> getDirty();
static <T> T wrap(final T delegate) {
return DirtyableInterceptor.wrap(delegate, ReflectionUtils::getPropertyName);
}
static <T> T wrap(final T delegate, final Function<Method, String> resolver) {
return DirtyableInterceptor.wrap(delegate, resolver);
}
}
拦截器class中的包裹方式为:
static <T> T wrap(final T delegate, final Function<Method, String> resolver) {
requireNonNull(delegate, "Delegate must be non-null");
requireNonNull(resolver, "Resolver must be non-null");
final Try<Class<T>> delegateClassTry = Try.of(() -> getClassForType(delegate.getClass()));
return delegateClassTry.flatMapTry(delegateClass ->
dirtyableFor(delegate, delegateClass, resolver))
.mapTry(Class::newInstance)
.getOrElseThrow(t -> new IllegalStateException(
"Could not wrap dirtyable for " + delegate.getClass(), t));
}
方法 dirtyableFor
定义了一个 ByteBuddy,它在每次调用时转发到特定实例。但是,在每次调用时进行检测有点昂贵,因此它会缓存来自给定实例 class 的检测子 class。为此,我使用 resilience4j
库 (a.k.a。javaslang-circuitbreaker
).
private static <T> Try<Class<? extends T>> dirtyableFor(final T delegate,
final Class<T> clazz,
final Function<Method, String> resolver) {
long start = System.currentTimeMillis();
Try<Class<? extends T>> r = Try.of(() -> ofCheckedSupplier(() ->
new ByteBuddy().subclass(clazz)
.defineField(Dirtyable.ID, Set.class, Visibility.PRIVATE)
.method(nameMatches("getDirty"))
.intercept(reference(new HashSet<>()))
.implement(Dirtyable.class)
.method(not(isDeclaredBy(Object.class))
.and(not(isAbstract()))
.and(isPublic()))
.intercept(withDefaultConfiguration()
.withBinders(Pipe.Binder.install(Function.class))
.to(new DirtyableInterceptor(delegate, resolver)))
.make().load(clazz.getClassLoader())
.getLoaded())
.withCache(getCache())
.decorate()
.apply(clazz));
System.out.println("Instrumentation time: " + (System.currentTimeMillis() - start));
return r;
}
private static <T> Cache<Class<? super T>, Class<T>> getCache() {
final CachingProvider provider = Caching.getCachingProvider();
final CacheManager manager = provider.getCacheManager();
final javax.cache.Cache<Class<? super T>, Class<T>> cache =
manager.getCache(Dirtyable.ID);
final Cache<Class<? super T>, Class<T>> dirtyCache = Cache.of(cache);
dirtyCache.getEventStream().map(Object::toString).subscribe(logger::debug);
return dirtyCache;
}
根据日志,检测时间从缓存未命中的 70-100 毫秒下降到缓存命中的 0-2 毫秒。
为了完整起见,这里是拦截器方法:
@RuntimeType
@SuppressWarnings("unused")
public Object intercept(final @Origin Method method, final @This Dirtyable dirtyable,
final @Pipe Function<Object, Object> pipe) throws Throwable {
if (ReflectionUtils.isSetter(method)) {
final String property = resolver.apply(method);
dirtyable.getDirty().add(property);
logger.debug("Intercepted setter [{}], resolved property " +
"[{}] flagged as dirty.", method, property);
}
return pipe.apply(this.delegate);
}
这个解决方案效果很好,除了 DirtyableInterceptor
对于缓存命中总是相同的,所以委托实例也是相同的。
是否可以将转发器绑定到实例的供应商,以便将拦截的方法转发给它?这怎么可能?
您可以通过 intercept
方法 static
创建无状态拦截器。要访问对象的状态,请在您的 subclass 上定义两个字段,您可以使用现在的静态拦截器中的 @FieldValue
注释访问它们。您还需要使用 FieldAccessor
实现来读取值,而不是使用 FixedValue::reference
检测。您还需要使用 defineField
构建器方法定义字段。
您可以通过以下方式设置这些字段:
- 在您的
Dirtyable
接口中添加 setter 方法并使用FieldAccessor
实现拦截它们。 - 正在定义您为其提供值的显式构造函数。这也允许您将字段定义为
final
。要实现构造函数,首先需要调用超级构造函数,然后多次调用FieldAccessor
来设置字段。
这样做,您就创建了一个完全无状态的 class,您可以重复使用它,但需要对其进行初始化。 Byte Buddy 已经提供了一个内置的 TypeCache
以便于重复使用。