Buenas tardes
Como saben, "la pereza es el motor del progreso", la cualidad más útil de un programador, gracias a la cual aparecieron muchos marcos maravillosos y así sucesivamente. Pero hoy no quiero escribir sobre la pereza humana.
Hace un par de semanas me encontré con
un artículo sobre un borrador de una función, el nuevo modificador diferido para los campos finales. Y, por supuesto, la inicialización de los registradores se da como el ejemplo más obvio, cuando esta característica fue útil. No, nadie discute, por supuesto, los registradores están en lo alto, créalos al principio y luego tenlo en cuenta. Brr Pero, ¿es realmente imposible escribir una solución de
muleta elegante en el buen viejo Java?
Y comienza a codificar de inmediato
La primera decisión es "en la frente". Implementamos la interfaz
org.slf4j.Logger (elegí slf4j, pero esto también es cierto para cualquier otro marco de registro), encapsulamos el registrador real, lo inicializamos cuando se llama a algún método de interfaz. Patrón de proxy más Método de fábrica. Todo es simple, todo funciona.
public class LazyLogger implements Logger { private Logger realLogger; private Class<?> clazz; private LazyLogger(Class<?> clazz) { this.clazz = clazz; } public static Logger getLogger(Class<?> clazz) { return new LazyLogger(clazz); } private Logger getRealLogger() { if (realLogger == null) realLogger = LoggerFactory.getLogger(this.clazz); return realLogger; } @Override public void trace(String msg) { getRealLogger().trace(msg); } @Override public void debug(String msg) { getRealLogger().debug(msg); } : :
Pero espere, la interfaz
org.slf4j.Logger tiene alrededor de 40 métodos, ¿
necesito implementarlos todos con mis manos? Y getRealLogger () no parece Thread Safe. Esto no es bueno, pensemos más.
Desarrollando el tema
Alternativamente, hay
Lombok con la anotación
@Delegate .
@AllArgsConstructor(staticName = "getLogger") public class LazyLogger implements Logger { private final static Function<Class<?>, Logger> $function = LoggerFactory::getLogger; private Logger $logger = null; private final Class<?> clazz; @Delegate private Logger getLogger() { if ($logger == null) $logger = $function.apply(clazz); return $logger; } } : private static final Logger logger = LazyLogger.getLogger(MyClass.class);
@Delegate es responsable de crear los métodos de interfaz
org.slf4j.Logger en el momento de la compilación; en el interior, llama a getLogger () + <el método requerido>. En el momento de la primera llamada al método, $ function crea un registrador real. $ agregado a los campos para ocultarlos de Lombok. No genera Getters / Setters, no crea constructores para tales campos.
Entonces, se ve bien, pero falta algo. Oh claro! Hilo seguro Ahora en getLogger () escribiremos una doble verificación, ¡e incluso con AtomicReference! ¡Pero espera, Lombok ya tiene
@Getter (perezoso = verdadero) !
@RequiredArgsConstructor(staticName = "getLogger") public class LazyLoggerThreadSafe implements Logger { private static final Function<Class<?>, Logger> $function = LoggerFactory::getLogger; private final Class<?> clazz; @Getter(lazy = true, onMethod_ = { @Delegate }, value = AccessLevel.PRIVATE) private final Logger logger = createLogger(); private Logger createLogger() { return $function.apply(clazz); } } : private static final Logger logger = LazyLoggerThreadSafe.getLogger(MyClass.class);
¿Qué está pasando aquí? El hecho es que el procesador de anotaciones que Lombok usa para procesar anotaciones puede pasar por el código fuente varias veces para procesar las anotaciones que se generaron en el paso anterior. Puedes leer sobre esto
aquí . Durante el primer paso de
@Getter (lazy = true), getLogger () se genera con la
inicialización de Lazy y anota su
@Delegate . Y durante la segunda pasada,
se generan los propios métodos de
@Delegate .
Y de postre
Pero, ¿qué sucede si quiero que Lazy inicialice otro objeto, no un registrador? Si necesito un tipo de Lazy Factory universal, ¿dónde transferiré solo al Proveedor que crea el objeto real?
@Delegate ya no nos salvará, necesita una clase específica, con un conjunto específico de métodos. Pero no importa, usamos
Proxy dinámico :
@AllArgsConstructor(access = AccessLevel.PRIVATE) public class LazyFactory<I> { private Class<I> interfaceClass; private Supplier<I> supplier; @SuppressWarnings("unchecked") private I getLazyObject() { return (I) Proxy.newProxyInstance( LazyFactory.class.getClassLoader(), new Class[] { interfaceClass }, new LazyFactory.DynamicInvocationHandler()); } public static <T> T getLazy(Class<T> interfaceClass, Supplier<T> supplier) { return new LazyFactory<T>(interfaceClass, supplier).getLazyObject(); } private class DynamicInvocationHandler implements InvocationHandler { @Getter(lazy = true) private final I internalObject = supplier.get(); @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return method.invoke(getInternalObject(), args); } } } : public interface Hello { void sayHello(); } public class LazyHello implements Hello { public LazyHello() { System.out.println("LazyHello under constuction"); } @Override public void sayHello() { System.out.println("I'm very lazy for saying Hello.."); } } private static final Hello lazyObj = LazyFactory.getLazy(Hello.class, LazyHello::new); lazyObj.sayHello();
Como puede ver, no hay mucho código, en parte gracias a Lombok. Algunas palabras de cómo funciona esto. LazyFactory en un método estático devuelve un proxy dinámico. Dentro de DynamicInvocationHandler hay un "objeto real", pero se creará solo cuando se invoque invoke () DynamicInvocationHandler, es decir, uno de los métodos de la interfaz I.
GetInternalObject () que es generado por
@Getter (lazy = true) es responsable de crear el "objeto real".
El tema puede desarrollarse aún más, pero ya está claro que la inicialización diferida de cualquier cosa es simple, concisa y fácil de integrar en el código existente.
Gracias, todo lo mejor!
Referencias
Borrador JEP: Campos finales estáticos perezososLombok