Как кэшировать инструментированный класс с помощью экспедитора экземпляров?
Вариант использования - реализовать грязный полевой трекер. Для этого у меня есть интерфейс:
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
всегда одинаково для попаданий в кэш, поэтому экземпляр делегата также одинаков.
Можно ли привязать экспедитор к поставщику экземпляра, чтобы перехваченные методы передавали его? Как это можно сделать?
1 ответ
Вы можете создать перехватчик без состояния, сделав intercept
метод static
, Чтобы получить доступ к состоянию объекта, определите два поля вашего подкласса, к которым вы обращаетесь, используя @FieldValue
аннотации в вашем теперь статическом перехватчике. Вместо использования FixedValue::reference
инструментов, вам также необходимо использовать FieldAccessor
реализация, чтобы прочитать значение. Вы также должны определить поля, используя defineField
метод строителя.
Вы можете установить эти поля либо:
- Добавление методов установки в ваш
Dirtyable
интерфейс и перехватывать их с помощьюFieldAccessor
реализация. - Определение явного конструктора, которому вы предоставляете значения. Это также позволяет вам определять поля, которые будут
final
, Чтобы реализовать конструктор, сначала нужно вызвать супер-конструктор, а затем вызватьFieldAccessor
несколько раз, чтобы установить поля.
Таким образом, вы создали класс без сохранения состояния, который вы можете использовать повторно, но тот, который вам нужно инициализировать. Byte Buddy уже предлагает встроенный TypeCache
для легкого повторного использования.