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;
}

从日志中,检测时间从缓存未命中的 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缓存命中总是相同的,所以委托实例也是相同的。

是否可以将转发器绑定到实例的供应商,以便拦截的方法转发给它?怎么可能做到这一点?

4

1 回答 1

2

您可以通过创建intercept方法来创建无状态拦截器static。要访问对象的状态,请在子类上定义两个字段,您可以使用@FieldValue现在静态拦截器中的注释访问它们。FixedValue::reference您还需要使用FieldAccessor实现来读取值,而不是使用检测。您还需要使用defineFieldbuilder 方法定义字段。

您可以通过以下方式设置这些字段:

  1. 在您的界面中添加 setter 方法Dirtyable并使用实现拦截它们FieldAccessor
  2. 定义提供值的显式构造函数。这也允许您将字段定义为final. 要实现构造函数,首先需要调用一个超级构造函数,然后FieldAccessor多次调用来设置字段。

这样做,您创建了一个完全无状态的类,您可以重用但需要初始化一个。TypeCacheByte Buddy 已经提供了一个易于重用的内置功能。

于 2017-05-05T09:03:41.853 回答