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 a lot of great 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 it 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.

publicclassLazyLoggerimplementsLogger{
    private Logger realLogger;
    private Class<?> clazz;
    privateLazyLogger(Class<?> clazz){
        this.clazz = clazz;
    }
    publicstatic Logger getLogger(Class<?> clazz){
        returnnew LazyLogger(clazz);
    }
    private Logger getRealLogger(){
        if (realLogger == null)
            realLogger = LoggerFactory.getLogger(this.clazz);
        return realLogger;
    }
    @Overridepublicvoidtrace(String msg){
        getRealLogger().trace(msg);
    }
    @Overridepublicvoiddebug(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 with @Delegate .

@AllArgsConstructor(staticName = "getLogger")
publicclassLazyLoggerimplementsLogger{
  privatefinalstatic Function<Class<?>, Logger> $function = LoggerFactory::getLogger;
  private Logger $logger = null;
  privatefinal Class<?> clazz;
  @Delegateprivate Logger getLogger(){
	if ($logger == null)
		$logger = $function.apply(clazz);
	return $logger;
  }
}
Использование:
privatestaticfinal Logger logger = LazyLogger.getLogger(MyClass.class);

@Delegate is responsible for creating the org.slf4j.Logger interface methods at the time of compilation , inside it calls 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 we get a double check in getLogger (), and even with an AtomicReference! But wait, Lombok already has @Getter (lazy = true) !.

@RequiredArgsConstructor(staticName = "getLogger")
publicclassLazyLoggerThreadSafeimplementsLogger{
  privatestaticfinal Function<Class<?>, Logger> $function = LoggerFactory::getLogger;
  privatefinal Class<?> clazz;
  @Getter(lazy = true, onMethod_ = { @Delegate }, value = AccessLevel.PRIVATE)
  privatefinal Logger logger = createLogger();
  private Logger createLogger(){
	return $function.apply(clazz);
  }
}
Использование:
privatestaticfinal 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


But 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 doesn't matter, we use Dynamic Proxy :

@AllArgsConstructor(access = AccessLevel.PRIVATE)
publicclassLazyFactory<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());
	}
	publicstatic <T> T getLazy(Class<T> interfaceClass, Supplier<T> supplier){
		returnnew LazyFactory<T>(interfaceClass, supplier).getLazyObject();
	}
	privateclassDynamicInvocationHandlerimplementsInvocationHandler{
		@Getter(lazy = true)
		privatefinal I internalObject = supplier.get();
		@Overridepublic Object invoke(Object proxy, Method method, Object[] args)throws Throwable {
			return method.invoke(getInternalObject(), args);
		}
	}
}
Использование:
publicinterfaceHello{
    voidsayHello();
}
publicclassLazyHelloimplementsHello{
    publicLazyHello(){
        System.out.println("LazyHello under constuction");
    }
    @OverridepublicvoidsayHello(){
        System.out.println("I'm very lazy for saying Hello..");
    }
}
privatestaticfinal Hello lazyObj = LazyFactory.getLazy(Hello.class, LazyHello::new);
lazyObj.sayHello();

As you can see, the code is also not very much, in part 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 be created only when invoke () is called DynamicInvocationHandler, that is, one of the methods of interface I.
GetInternalObject () which is generated by @Getter (lazy = true ) .

The topic can be developed further, but 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!

Links:

JEP draft: Lazy Static Final Fields
Lombok

Also popular now: