مساء الخير
كما تعلمون ، "الكسل هو محرك التقدم" ، الجودة الأكثر فائدة للمبرمج ، وبفضل ذلك ظهرت الكثير من الأطر الرائعة وما إلى ذلك. لكن اليوم لا أريد أن أكتب عن الكسل البشري.
قبل أسبوعين ، صادفت
مقالًا حول مسودة أولية لميزة ، المعدل البطيء الجديد للحقول النهائية. وبالطبع ، يتم إعطاء تهيئة التسجيلات كمثال أوضح ، عندما كانت هذه الميزة مفيدة. لا ، لا أحد يجادل ، بالطبع ، أن الحطابين هم من يتحملون التكاليف ، وأنشئوها في البداية ، ثم ضعها في الاعتبار. Brr ولكن هل من المستحيل حقًا كتابة حل
عكاز أنيق في Java القديمة الجيدة؟
وابدأ الترميز على الفور
القرار الأول هو "على الجبهة". نقوم بتطبيق واجهة
org.slf4j.Logger (اخترت slf4j ، ولكن هذا صحيح أيضًا لأي إطار عمل تسجيل آخر) ، ويغلف المسجل الحقيقي ، ويهيئه عندما يتم استدعاء بعض طرق الواجهة. نمط الوكيل بالإضافة إلى طريقة المصنع. كل شيء بسيط ، كل شيء يعمل.
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 () مؤشر الترابط الآمن. هذا ليس جيدًا ، دعنا نفكر أكثر.
تطوير الموضوع
بدلاً من ذلك ، يوجد
لومبوك مع تعليق توضيحي
@ .
@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 () + <الطريقة المطلوبة>. في وقت الاستدعاء الأول للطريقة ، تقوم الدالة $ بإنشاء مسجل حقيقي. وأضاف $ إلى الحقول لإخفائها من لومبوك. لا يقوم بإنشاء رسائل / رسائل ، ولا يُنشئ مُنشئين لهذه الحقول.
لذا ، يبدو جيدًا ، ولكن هناك شيء مفقود. أوه بالتأكيد! خيط آمن الآن في getLogger () سنكتب فحصًا مزدوجًا ، وحتى مع AtomicReference! لكن انتظر ، لومبوك لديه بالفعل
Getter (كسول = صحيح) !
@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);
ما الذي يحدث هنا؟ والحقيقة هي أن معالج التعليقات التوضيحية الذي يستخدمه لومبوك لمعالجة التعليقات التوضيحية يمكن أن يمر عبر شفرة المصدر عدة مرات لمعالجة التعليقات التوضيحية التي تم إنشاؤها في الخطوة السابقة. يمكنك أن تقرأ عنها
هنا . أثناء التمرير الأول لـ
Getter (lazy = true) ، يتم إنشاء getLogger ()
بتهيئة كسولة ويضيف تعليقات إلى
Delegate . وأثناء التمرير الثاني ،
يتم إنشاء الأساليب نفسها من
Delegate .
وللحلوى
ولكن ماذا لو أردت من Lazy تهيئة كائن آخر ، وليس أداة تسجيل؟ إذا كنت بحاجة إلى نوع من مصنع كسول عالمي ، فأين يمكنني نقل المورد فقط إنشاء الكائن الحقيقي؟
Delegate لن ينقذنا بعد الآن ، فهو يحتاج إلى فئة معينة ، مع مجموعة محددة من الأساليب. ولكن لا يهم ، نحن نستخدم
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();
كما ترون ، لا يوجد الكثير من التعليمات البرمجية على الإطلاق ، جزئياً بفضل لومبوك. بضع كلمات حول كيفية عمل ذلك. LazyFactory في طريقة ثابتة بإرجاع وكيل ديناميكي. داخل DynamicInvocationHandler يوجد "كائن حقيقي" ، ولكن سيتم إنشاؤه فقط عندما يتم استدعاء () DynamicInvocationHandler ، أي إحدى طرق الواجهة الأولى.
GetInternalObject () الذي يتم إنشاؤه بواسطة
Getter (lazy = true) هو المسؤول عن إنشاء "الكائن الحقيقي".
يمكن تطوير الموضوع بشكل أكبر ، ولكن من الواضح بالفعل أن التهيئة البطيئة لأي شيء بسيطة وموجزة ويسهل دمجها في التعليمات البرمجية الحالية.
شكرا لك ، كل التوفيق!
المراجع:
مشروع JEP: الحقول النهائية الساكنة كسوللومبوك