2017-05-04 3 views
2

ユースケースは、ダーティフィールドトラッカーを実装することです。包装方法であるインターセプタクラスの計測器クラスをインスタンス転送者でキャッシュする方法は?

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は常にキャッシュヒットのために同じであることを除いて、うまく機能し、そのデリゲートのインスタンスも同じである。ここでは完全を期すために

は、インターセプタ・メソッドです。

フォワーダをインスタンスのサプライヤにバインドして、インターセプトされたメソッドがそれに転送することは可能ですか?これはどうすればできますか?

答えて

2

interceptメソッドをstaticにすることで、ステートレスインターセプタを作成できます。オブジェクトの状態にアクセスするには、今静的インターセプタの@FieldValue注釈を使用してアクセスするサブクラスに2つのフィールドを定義します。 FixedValue::reference計測器を使用する代わりに、FieldAccessor実装を使用して値を読み取る必要があります。また、defineFieldビルダーメソッドを使用してフィールドを定義する必要があります。

あなたはどちらかによって、これらのフィールドを設定することができますあなたのDirtyableインタフェースのsetterメソッドを追加し、FieldAccessor実装を使用して、それらを傍受

  1. 値を指定する明示的なコンストラクタを定義します。これにより、フィールドをfinalに定義することもできます。コンストラクタを実装するには、まずスーパーコンストラクタを呼び出してから、FieldAccessorを何度か呼び出してフィールドを設定する必要があります。

これを行うと、再利用できる完全にステートレスなクラスが作成されますが、初期化する必要があります。 Byte Buddyには既にTypeCacheが組み込まれており、簡単に再利用できます。

+1

フィールドを設定しましたか?おそらくあなたはセッターにインターセプターを適用するでしょうか? –

+0

多くの失敗した試みの後、私は解決策1を見ました。それはもっと冗長であり、フィールドは最終的なものではありませんが、機能します!ここでは2試みに私のソリューションです: '.defineConstructor() .withParameters(delegateClass、Function.class) .intercept(MethodCall.invoke(delegateClass.getConstructor()) .andThen(ofField(DELEGATE_FIELD).setsArgumentAt(0) (1))) –

+1

ソリューション1では、代理人とリゾルバの追加されたセッターを傍受することを避けるために、マッチャー条件 'isDeclaredBy(delegateClass)'を追加しました。また、 'Class :: newInstance'の後に、私は' Dirtyable'インターフェースにそれらを追加することを避けるためにリフレクションによってセッターを呼び出します。私はそれをきれいに保ちたい:) –

関連する問題