Для начала я просто хочу упомянуть, что это, вероятно, не лучшее решение, и я уверен, что есть способы оптимизировать это.Тем не менее, я хотел попробовать свои силы в CGLIB и ObjenesisHelper снова.
Используя CGLIB и ObjenesisHelper, мы можем обернуть объект данных в прокси, который будет перехватывать методы get
.Используя этот перехватчик, мы можем добавить логику, которую вы описали в своем посте.Давайте начнем с предположения, что это наши типы данных (для краткости используем lombok).
@Data class W { private X x; }
@Data class X { private Y y; }
@Data class Y { private Z z; }
@Data class Z { private int alpha; }
Наше окончательное решение можно использовать следующим образом:
public static void main(String[] args) {
final W w = ProxyUtil.withLazyDefaults(new W());
System.out.println(w.getX().getY().getZ().getAlpha());
}
Реализация
В настоящее время, если мы попытаемся вызвать new W().getX().getY().getZ().getAlpha()
, мы получим NullPointerException
при вызове getY()
, поскольку getX()
вернул ноль.Даже если нам удастся создать значение X
по умолчанию, нам все равно потребуется значение Y
по умолчанию, чтобы не получить нулевой указатель на getZ()
и getAlpha()
и так далее.Прокси, который мы создаем, должен быть универсальным и иметь возможность рекурсивно оборачивать его подкомпоненты.
Хорошо, так что давайте начнем.Первое, что нам нужно сделать, это создать MethodInterceptor
.Всякий раз, когда какой-либо вызов попадает в наш экземпляр прокси, он выполняет логику нашего MethodInterceptor
.Сначала нам нужно определить, является ли вызываемый метод геттером.Если нет, мы проигнорируем это.Во время этого вызова геттера, если значение не присутствует в наших данных, мы создадим его и обновим объект.Если значение, содержащееся в получателе, является исходным развернутым классом, мы заменим его упакованной версией.Наконец мы вернем завернутый экземпляр. Редактировать Я обновил это, чтобы не вставлять упакованные экземпляры в реальные объекты данных.Это будет менее производительным, если доступ к объекту будет несколько раз таким образом
public class ProxyUtil {
public static <T> T withLazyDefaults(final T data) {
final MethodInterceptor interceptor = (object, method, args, proxy) -> {
if (method.getName().startsWith("get")) {
final Class<?> returnType = method.getReturnType();
Object response = method.invoke(data, args);
if (response == null) {
response = returnType.newInstance();
data.getClass()
.getDeclaredMethod(
method.getName().replaceFirst("get", "set"),
returnType)
.invoke(data, response);
}
if (!returnType.isPrimitive()) {
response = withLazyDefaults(response);
}
return response;
}
return method.invoke(data, args);
};
...
Остальная часть этого метода включает использование CGLIB и Objenisis Helper для создания экземпляра оболочки.CGLib позволит вам проксировать и классы, и интерфейсы, а ObjenesisHelper позволит вам создать экземпляр класса без необходимости вызова конструктора.См. здесь для примера CGLib и здесь для примера ObjenesisHelper .
...
final Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(data.getClass());
final Set<Class<?>> interfaces = new LinkedHashSet<>();
if (data.getClass().isInterface()) {
interfaces.add(data.getClass());
}
interfaces.addAll(Arrays.asList(data.getClass().getInterfaces()));
enhancer.setInterfaces(interfaces.toArray(new Class[interfaces.size()]));
enhancer.setCallbackType(interceptor.getClass());
final Class<?> proxyClass = enhancer.createClass();
Enhancer.registerStaticCallbacks(proxyClass, new Callback[]{interceptor});
return (T) ObjenesisHelper.newInstance(proxyClass);
}
}
Предостережения
- Это не потокобезопаснооперация.
- Отражение замедлит ваш код.
- Для вызовов отражения необходимо добавить лучшую обработку ошибок.
- Если класс не имеет конструктора без аргументов, это не сработает.
- Не учитывает наследование классов данных
- Это может быть наилучшим способом проверкисначала нет аргументов ctor / setter.