Selamat siang
Seperti yang Anda tahu, "kemalasan adalah mesin kemajuan", kualitas yang paling berguna dari seorang programmer, berkat itu ada banyak kerangka kerja yang indah dan seterusnya dan seterusnya. Tetapi hari ini saya ingin menulis bukan tentang kemalasan manusia.
Beberapa minggu yang lalu saya menemukan
sebuah artikel tentang konsep kasar fitur, pengubah malas baru untuk bidang terakhir. Dan tentu saja, inisialisasi penebang diberikan sebagai contoh paling jelas, ketika fitur ini bermanfaat. Tidak, tidak ada yang berdebat, tentu saja, penebang kayu di atas kepala, buat mereka di awal, lalu ingat. Sdr Tetapi apakah benar-benar mustahil untuk menulis solusi
kruk yang elegan di Jawa kuno yang baik?
Dan segera mulai coding
Keputusan pertama adalah "di dahi." Kami menerapkan antarmuka
org.slf4j.Logger (saya memilih slf4j, tetapi ini juga berlaku untuk kerangka logging lainnya), merangkum logger nyata, menginisialisasi ketika beberapa metode antarmuka dipanggil. Pola proxy ditambah Metode Pabrik. Semuanya sederhana, semuanya berfungsi.
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); } : :
Tapi tunggu, antarmuka
org.slf4j.Logger memiliki sekitar 40 metode, apakah saya
perlu menerapkan semuanya dengan tangan saya? Dan getRealLogger () tidak terlihat Thread Safe. Ini tidak baik, mari kita berpikir lebih jauh.
Mengembangkan tema
Atau, ada
Lombok dengan penjelasan
@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 bertanggung jawab untuk membuat metode antarmuka
org.slf4j.Logger pada saat kompilasi; di dalamnya, ia memanggil getLogger () + <metode yang diperlukan>. Pada saat panggilan pertama ke metode, $ function menciptakan logger nyata. $ ditambahkan ke bidang untuk menyembunyikannya dari Lombok. Itu tidak menghasilkan Getters / Setters, tidak membuat konstruktor untuk bidang tersebut.
Jadi, ini terlihat bagus, tetapi ada sesuatu yang hilang. Oh tentu! Aman utas Sekarang di getLogger () kita akan menulis cek ganda, dan bahkan dengan AtomicReference! Tapi tunggu, Lombok sudah punya
@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);
Apa yang sedang terjadi di sini? Faktanya adalah bahwa Prosesor Anotasi yang digunakan Lombok untuk memproses anotasi dapat melalui kode sumber beberapa kali untuk memproses anotasi yang dihasilkan pada langkah sebelumnya. Anda dapat membacanya di
sini . Selama pass pertama dari
@Getter (lazy = true), getLogger () dihasilkan dengan
inisialisasi Lazy dan memberi catatan pada
@Delegate -
nya . Dan selama pass kedua, metode itu sendiri dari
@Delegate dihasilkan .
Dan untuk pencuci mulut
Tetapi bagaimana jika saya ingin Malas menginisialisasi objek lain, bukan logger? Jika saya memerlukan semacam Pabrik Malas universal, di mana saya akan mentransfer hanya Pemasok yang menciptakan objek nyata?
@ Delegate tidak akan menyelamatkan kita lagi, dia membutuhkan kelas tertentu, dengan serangkaian metode tertentu. Tapi itu tidak masalah, kami menggunakan
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();
Seperti yang Anda lihat, tidak ada banyak kode sama sekali, sebagian berkat Lombok. Beberapa kata tentang cara kerjanya. LazyFactory dalam metode statis mengembalikan proxy dinamis. Di dalam DynamicInvocationHandler ada "objek nyata", tetapi itu akan dibuat hanya ketika memanggil () DynamicInvocationHandler disebut, yaitu, salah satu metode antarmuka I.
GetInternalObject () yang dihasilkan oleh
@Getter (lazy = true) bertanggung jawab untuk membuat "objek nyata".
Topiknya dapat dikembangkan lebih lanjut, tetapi sudah jelas bahwa inisialisasi malas dari sesuatu itu sederhana, ringkas dan mudah diintegrasikan ke dalam kode yang ada.
Terima kasih semuanya!
Referensi:
Draf JEP: Bidang Final Statis MalasLombok