ユースケースは、ダーティフィールドトラッカーを実装することです。包装方法であるインターセプタクラスの計測器クラスをインスタンス転送者でキャッシュする方法は?
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);
}
}
:
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を定義し、このため、私は、インターフェイスを持っています。ただし、呼び出しごとに計測するのは少し高価ですので、計測されたサブクラスを指定されたインスタンスのクラスからキャッシュします。このため私はresilience4j
ライブラリ(a.k.a. javaslang-circuitbreaker
)を使用します。キャッシュミスがキャッシュヒットのために0-2msするためにログから
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-100msから落下します。
@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
は常にキャッシュヒットのために同じであることを除いて、うまく機能し、そのデリゲートのインスタンスも同じである。ここでは完全を期すために
は、インターセプタ・メソッドです。
フォワーダをインスタンスのサプライヤにバインドして、インターセプトされたメソッドがそれに転送することは可能ですか?これはどうすればできますか?
フィールドを設定しましたか?おそらくあなたはセッターにインターセプターを適用するでしょうか? –
多くの失敗した試みの後、私は解決策1を見ました。それはもっと冗長であり、フィールドは最終的なものではありませんが、機能します!ここでは2試みに私のソリューションです: '.defineConstructor() .withParameters(delegateClass、Function.class) .intercept(MethodCall.invoke(delegateClass.getConstructor()) .andThen(ofField(DELEGATE_FIELD).setsArgumentAt(0) (1))) –
ソリューション1では、代理人とリゾルバの追加されたセッターを傍受することを避けるために、マッチャー条件 'isDeclaredBy(delegateClass)'を追加しました。また、 'Class :: newInstance'の後に、私は' Dirtyable'インターフェースにそれらを追加することを避けるためにリフレクションによってセッターを呼び出します。私はそれをきれいに保ちたい:) –