Guten Tag!
Wie Sie wissen, ist „Faulheit der Motor des Fortschritts“, die nützlichste Eigenschaft eines Programmierers, dank dessen gibt es viele wunderbare Frameworks und so weiter und so fort. Aber heute möchte ich nicht über menschliche Faulheit schreiben.
Vor ein paar Wochen bin ich auf
einen Artikel über einen Entwurf eines groben Entwurfs eines Features gestoßen, den neuen faulen Modifikator für endgültige Felder. Und natürlich wird die Initialisierung von Loggern als das offensichtlichste Beispiel angegeben, als diese Funktion nützlich war. Nein, niemand argumentiert natürlich, Logger sind Overhead, erstellen sie zu Beginn und denken dann daran. Brr Aber ist es wirklich unmöglich, eine elegante
Krückenlösung im guten alten Java zu schreiben?
Und fangen Sie sofort an zu programmieren
Die erste Entscheidung ist "auf der Stirn". Wir implementieren die Schnittstelle
org.slf4j.Logger (ich habe slf4j gewählt, dies gilt jedoch auch für jedes andere Protokollierungsframework), kapseln den realen Protokollierer und initialisieren ihn, wenn eine Schnittstellenmethode aufgerufen wird. Proxy-Muster plus Factory-Methode. Alles ist einfach, alles funktioniert.
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); } : :
Aber warten Sie, die
org.slf4j.Logger- Schnittstelle
verfügt über ungefähr 40 Methoden.
Muss ich alle mit meinen Händen implementieren? Und getRealLogger () sieht nicht threadsicher aus. Das ist nicht gut, denken wir weiter.
Das Thema entwickeln
Alternativ gibt es
Lombok mit
@ Delegate- Annotation.
@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 ist für die Erstellung der Schnittstellenmethoden
org.slf4j.Logger zum Zeitpunkt der Kompilierung verantwortlich. Im Inneren ruft es getLogger () + <die erforderliche Methode> auf. Zum Zeitpunkt des ersten Aufrufs der Methode erstellt die Funktion $ einen echten Logger. $ wurde zu Feldern hinzugefügt, um sie vor Lombok zu verbergen. Es werden keine Getter / Setter generiert, keine Konstruktoren für solche Felder.
Es sieht also gut aus, aber es fehlt etwas. Oh sicher! Gewindesicher Jetzt werden wir in getLogger () eine doppelte Prüfung schreiben, und sogar mit AtomicReference! Aber warte, Lombok hat bereits
@Getter (faul = wahr) !
@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);
Was ist hier los? Tatsache ist, dass der Anmerkungsprozessor, mit dem Lombok Anmerkungen verarbeitet, den Quellcode mehrmals durchlaufen kann, um die im vorherigen Schritt generierten Anmerkungen zu verarbeiten. Sie können hier darüber lesen. Während des ersten Durchlaufs von
@Getter (faul = wahr) wird getLogger () mit
Lazy-Initialisierung generiert und kommentiert sein
@Delegate . Und während des zweiten Durchgangs werden die Methoden selbst aus
@Delegate generiert .
Und zum Nachtisch
Aber was ist, wenn Lazy ein anderes Objekt initialisieren soll, keinen Logger? Wenn ich eine Art universelle Lazy Factory benötige, wohin übertrage ich nur den Lieferanten, der das reale Objekt erstellt?
@Delegate wird uns nicht mehr retten, er benötigt eine bestimmte Klasse mit einem bestimmten Satz von Methoden. Es spielt jedoch keine Rolle, wir verwenden
Dynamic Proxy :
@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();
Wie Sie sehen können, gibt es überhaupt nicht viel Code, auch dank Lombok. Ein paar Worte, wie das funktioniert. LazyFactory in einer statischen Methode gibt einen dynamischen Proxy zurück. In DynamicInvocationHandler gibt es ein „reales Objekt“, das jedoch nur erstellt wird, wenn invoke () aufgerufen wird. DynamicInvocationHandler wird aufgerufen, dh eine der Methoden der Schnittstelle I.
GetInternalObject (), das von
@Getter (lazy = true) generiert wird, ist für die Erstellung des "realen Objekts" verantwortlich.
Das Thema kann weiterentwickelt werden, aber es ist bereits klar, dass die verzögerte Initialisierung von allem einfach, präzise und leicht in vorhandenen Code zu integrieren ist.
Vielen Dank, alles Gute!
Referenzen:
JEP-Entwurf: Lazy Static Final FieldsLombok