E novamente sobre preguiça

Boa tarde

Como você sabe, “a preguiça é o motor do progresso”, a qualidade mais útil de um programador, graças a ele existem muitas estruturas maravilhosas e assim por diante. Hoje, porém, não quero escrever sobre preguiça humana.

Há algumas semanas, deparei com um artigo sobre um rascunho de um recurso, o novo modificador preguiçoso dos campos finais. E, claro, a inicialização dos loggers é dada como o exemplo mais óbvio, quando esse recurso foi útil. Não, ninguém argumenta, é claro, os madeireiros estão sobrecarregados, crie-os no início e lembre-se. Brr Mas é realmente impossível escrever uma solução elegante de muleta no bom e velho Java?

E comece a codificar imediatamente


A primeira decisão é "na testa". Implementamos a interface org.slf4j.Logger (eu escolhi o slf4j, mas isso também é válido para qualquer outra estrutura de log), encapsulamos o real logger e inicializamos quando algum método de interface é chamado. Padrão de proxy mais Método de fábrica. Tudo é simples, tudo 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); } : :  

Mas espere, a interface org.slf4j.Logger possui cerca de 40 métodos. Preciso implementar todos eles com as mãos? E getRealLogger () não parece seguro para threads. Isso não é bom, vamos pensar mais.

Desenvolvendo o tema


Como alternativa, há Lombok com anotação @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); 

O @Delegate é responsável por criar os métodos de interface org.slf4j.Logger no momento da compilação; por dentro, chama getLogger () + <o método necessário>. No momento da primeira chamada ao método, a função $ cria um criador de logs real. $ adicionado aos campos para ocultá-los do Lombok. Não gera Getters / Setters, não cria construtores para esses campos.

Então, parece bom, mas algo está faltando. Oh claro! Segmento seguro Agora, em getLogger (), escreveremos uma verificação dupla e até com AtomicReference! Mas espere, o Lombok já possui o @Getter (preguiçoso = verdadeiro) !

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

O que está acontecendo aqui? O fato é que o processador de anotações usado pelo Lombok para processar anotações pode passar pelo código-fonte várias vezes para processar as anotações geradas na etapa anterior. Você pode ler sobre isso aqui . Durante a primeira passagem do @Getter (preguiçoso = true), getLogger () é gerado com a inicialização do Lazy e faz anotações no seu @Delegate . E durante a segunda passagem, os próprios métodos do @Delegate são gerados .

E para sobremesa


Mas e se eu quiser que o Lazy inicialize outro objeto, não um logger? Se eu precisar de um tipo de Lazy Factory universal, para onde transferirei apenas o Fornecedor que cria o objeto real? O @Delegate não nos salvará mais, ele precisa de uma classe específica, com um conjunto específico de métodos. Mas isso não importa, usamos o 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 você pode ver, não há muito código, em parte graças ao Lombok. Algumas palavras de como isso funciona. LazyFactory em um método estático retorna um proxy dinâmico. Dentro de DynamicInvocationHandler, há um "objeto real", mas ele será criado apenas quando invocar () DynamicInvocationHandler for chamado, ou seja, um dos métodos da interface I.
GetInternalObject (), gerado por @Getter (preguiçoso = true), é responsável por criar o "objeto real".

O tópico pode ser desenvolvido ainda mais, mas já está claro que a inicialização lenta de qualquer coisa é simples, concisa e fácil de integrar ao código existente.

Obrigado, tudo de bom!

Referências:

Esboço do JEP: campos finais estáticos preguiçosos
Lombok

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


All Articles