下午好
如您所知,“懒惰是进步的引擎”,这是程序员最有用的素质,这要归功于它有许多出色的框架等等。 但是今天我不想写关于人的懒惰的文章。
几周前,我遇到
了一篇有关功能草案的初稿
的文章 ,该草案是用于最终领域的新的惰性修改器。 当然,当此功能有用时,记录器的初始化是最明显的示例。 不,没有人争辩,当然,记录器开销很大,请一开始就创建它们,然后紧记。 rr 但是,用旧的Java编写优雅的
拐杖解决方案真的是不可能的吗?
并立即开始编码
第一个决定是“额头上”。 我们实现了
org.slf4j.Logger接口(我选择了slf4j,但对于其他任何日志记录框架也是如此),封装了真正的logger,在调用某些接口方法时对其进行初始化。 代理模式加工厂方法。 一切都很简单,一切正常。
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); } : :
但是,等等,
org.slf4j.Logger接口
有大约40种方法,我
需要用手实现所有这些方法吗? 而且getRealLogger()看起来不是线程安全的。 这不好,让我们进一步考虑。
发展主题
另外,也可以使用带有
@Delegate批注的
Lombok 。
@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负责在编译时创建
org.slf4j.Logger接口方法;在内部,它调用getLogger()+ <所需的方法>。 在第一次调用该方法时,$函数将创建一个真实的记录器。 将$添加到字段中以将其隐藏在龙目岛。 它不会生成Getters / Setters,也不会为此类字段创建构造函数。
因此,它看起来不错,但是缺少一些东西。 哦,确定! 线程安全 现在在getLogger()中,我们将进行仔细检查,甚至使用AtomicReference! 但是,等等,龙目岛已经有了
@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);
这是怎么回事 事实是,Lombok用于处理注释的注释处理器可以多次遍历源代码,以处理在上一步中生成的注释。 你可以
在这里阅读。 在
@Getter的第一遍(lazy = true)期间,使用
lazy初始化生成getLogger()并注释其
@Delegate 。 在第二遍过程中,从
@Delegate生成方法本身。
还有甜点
但是,如果我想让Lazy初始化另一个对象而不是记录器怎么办? 如果我需要一种通用的惰性工厂,我将仅将创建真实对象的供应商转移到哪里?
@Delegate将不再拯救我们,他需要一个特定的类,具有特定的方法集。 但这没关系,我们使用
动态代理 :
@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();
如您所见,完全没有太多代码,这部分归功于Lombok。 几句话,这是如何工作的。 LazyFactory以静态方法返回动态代理。 在DynamicInvocationHandler内部有一个“真实对象”,但是只有在调用invoke()DynamicInvocationHandler时,即接口I的方法之一,它才会创建。
@Getter(lazy = true)生成的
GetInternalObject()负责创建“真实对象”。
可以进一步开发该主题,但是很明显,任何事物的惰性初始化都是简单,简洁且易于集成到现有代码中。
谢谢你,万事如意!
参考文献:
JEP草案:惰性静态最终字段龙目岛