📜 ⬆️ ⬇️

And again about laziness

Good day!

As you know, “laziness is the engine of progress”, the most useful quality of a programmer, thanks to her there are many wonderful frameworks, and so on and so forth. But today I want to write not about human laziness.

A couple of weeks ago I came across an article about a draft feature outline, a new lazy modifier for final fields. And of course, the initialization of the loggers is given as the most obvious example when this feature would be useful. No, no one argues, of course, loggers are overhead, create them during the start, then keep them in memory. Brr But is it really impossible to write an elegant crutch solution in good old Java?

And we start immediately coding


The first decision "in the forehead" We implement the org.slf4j.Logger interface (I chose slf4j, but this is also true for any other logging framework), encapsulate the real logger, initialize it when some interface method is called. Pattern Proxy plus Factory Method. It's simple, everything works.
')
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); } : :  

But wait, the org.slf4j.Logger interface has about 40 methods, do I need to implement them all by hand? And getRealLogger () does not look like Thread Safe. So no good, let's think further.

Developing the theme


Alternatively, there is a Lombok annotated @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 is responsible for creating the org.slf4j.Logger interface methods at the time of compilation, internally calling getLogger () + <the required method>. At the time of the first call of the method $ function creates a real logger. $ added to fields to hide them from Lombok. It does not generate Getters / Setters, it does not create constructors for such fields.

So, it looks good, but something is missing. Oh, right! Thread Safe. Now in getLogger () we will write double check, moreover with AtomicReference! But wait, Lombok already has @Getter (lazy = true) !.

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

What is going on here? The fact is that the Annotation Processor, which is used by Lombok, for processing annotations, can be passed through the source code several times to process the annotations that were generated in the previous step. You can read about it here . During the first pass, @Getter (lazy = true) generates getLogger () with Lazy initialization and annotates it with @Delegate . And during the second pass, the methods themselves are generated from @Delegate .

And for dessert


And what if I want Lazy to initialize another object, not a logger? If I need such a versatile Lazy Factory, where will I transfer only the Supplier which creates the real object? @Delegate will not save us, it needs a specific class, with a specific set of methods. But it does not matter, we use 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(); 

As you can see, the code is also not much at all, partly thanks to Lombok. A few words how it works. LazyFactory in a static method returns a dynamic proxy. Inside DynamicInvocationHandler is a “real object”, but it will only be created when invoke () is called DynamicInvocationHandler, that is, one of the methods of interface I.
GetInternalObject () which is generated by @Getter (lazy = true) is responsible for creating the “real object”.

The topic can be developed further, but already now it is clear that lazy initialization of anything is simple, concisely and easily integrated into existing code.

Thank you for your attention, all the best!

References:

JEP draft: Lazy Static Final Fields
Lombok

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


All Articles