2017-05-04 44 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庫(又名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; 
} 

從日誌中,intrumentation時間從70-100ms下降爲高速緩存未命中,以0-2ms的緩存命中。

爲了完整這裏是攔截器方法:

@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批註定義您的子類中的兩個字段。除了使用FixedValue::reference工具外,還需要使用FieldAccessor實現來讀取值。您還需要使用defineField構建器方法來定義字段。

  1. 添加setter方法在Dirtyable界面和使用FieldAccessor實施攔截他們:

    您可以通過設置這些領域無論是。

  2. 定義您提供值的顯式構造函數。這也可以讓你定義字段爲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) (ofField(RESOLVER_FIELD).setsArgumentAt(1))))' –

+1

對於解決方案1,我添加了匹配器條件'isDeclaredBy(delegateClass)'以避免攔截添加的委託和解析器的setter。在'Class :: newInstance'之後,我通過反射調用setters,以避免將它們添加到'Dirtyable'接口。我想保持它乾淨:) –