Et encore une fois sur la paresse

Bon après-midi

Comme vous le savez, «la paresse est le moteur du progrès», la qualité la plus utile d'un programmeur, grâce à elle, il existe de nombreux cadres merveilleux et ainsi de suite. Mais aujourd'hui, je ne veux pas écrire sur la paresse humaine.

Il y a quelques semaines, je suis tombé sur un article sur un brouillon brouillon d'une fonctionnalité, le nouveau modificateur paresseux pour les champs finaux. Et bien sûr, l'initialisation des enregistreurs est donnée comme l'exemple le plus évident, lorsque cette fonctionnalité était utile. Non, personne ne fait valoir, bien sûr, les enregistreurs sont des frais généraux, créez-les au début, puis gardez à l'esprit. Brr Mais est-il vraiment impossible d'écrire une solution élégante de béquille dans le bon vieux Java?

Et commencez Ă  coder tout de suite


La première décision est «sur le front». Nous implémentons l'interface org.slf4j.Logger (j'ai choisi slf4j, mais cela est également vrai pour tout autre cadre de journalisation), encapsulons le véritable enregistreur, initialisez-le lorsqu'une méthode d'interface est appelée. Modèle de proxy plus méthode d'usine. Tout est simple, tout fonctionne.

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

Mais attendez, l'interface org.slf4j.Logger a environ 40 méthodes, dois-je les implémenter toutes avec mes mains? Et getRealLogger () ne semble pas Thread Safe. Ce n'est pas bon, réfléchissons plus loin.

Développer le thème


Alternativement, il y a Lombok avec l'annotation @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 est responsable de la création des méthodes d'interface org.slf4j.Logger au moment de la compilation; à l'intérieur, il appelle getLogger () + <la méthode requise>. Au moment du premier appel à la méthode, $ function crée un vrai logger. $ ajouté aux champs pour les cacher à Lombok. Il ne génère pas de Getters / Setters, ne crée pas de constructeurs pour ces champs.

Donc, ça a l'air bien, mais il manque quelque chose. Oh bien sûr! Thread safe Maintenant dans getLogger () nous allons écrire une double vérification, et même avec AtomicReference! Mais attendez, Lombok a déjà @Getter (paresseux = vrai) !.

 @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); 

Que se passe-t-il ici? Le fait est que le processeur d'annotations que Lombok utilise pour traiter les annotations peut parcourir le code source plusieurs fois pour traiter les annotations générées à l'étape précédente. Vous pouvez en lire plus ici . Lors de la première passe de @Getter (lazy = true), getLogger () est généré avec l' initialisation Lazy et annote son @Delegate . Et lors de la deuxième passe, les méthodes elles-mêmes de @Delegate sont générées .

Et pour le dessert


Mais que faire si je veux que Lazy initialise un autre objet, pas un enregistreur? Si j'ai besoin d'une sorte de Lazy Factory universelle, où vais-je transférer uniquement le fournisseur créant l'objet réel? @Delegate ne nous sauvera plus, il a besoin d'une classe spécifique, avec un ensemble spécifique de méthodes. Mais peu importe, nous utilisons 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(); 

Comme vous pouvez le voir, il n'y a pas beaucoup de code du tout, en partie grâce à Lombok. Quelques mots sur la façon dont cela fonctionne. LazyFactory dans une méthode statique renvoie un proxy dynamique. À l'intérieur de DynamicInvocationHandler, il y a un «objet réel», mais il ne sera créé que lorsque invoke () DynamicInvocationHandler est appelé, c'est-à-dire l'une des méthodes de l'interface I.
GetInternalObject () qui est généré par @Getter (lazy = true) est responsable de la création de "l'objet réel".

Le sujet peut être développé davantage, mais il est déjà clair que l'initialisation paresseuse de tout est simple, concise et facile à intégrer dans le code existant.

Merci beaucoup!

Références:

Projet JEP: Champs finaux statiques paresseux
Lombok

Source: https://habr.com/ru/post/fr424029/


All Articles