Menerapkan Spring Framework API dari awal. Panduan untuk pemula. Bagian 1



Kerangka Kerja Musim Semi adalah salah satu kerangka kerja yang paling rumit untuk memahami dan belajar. Kebanyakan pengembang mempelajarinya secara perlahan, melalui tugas-tugas praktis dan google. Pendekatan ini tidak efektif, karena tidak memberikan gambaran yang lengkap dan pada saat yang sama mahal.

Saya ingin menawarkan kepada Anda pendekatan baru yang fundamental untuk studi Spring. Ini terdiri dari kenyataan bahwa seseorang melewati serangkaian tutorial yang disiapkan secara khusus dan secara mandiri mengimplementasikan fungsionalitas pegas. Keunikan dari pendekatan ini adalah bahwa, selain pemahaman 100% dari aspek yang dipelajari dari Spring, itu juga memberikan peningkatan besar pada Java Core (Annotations, Reflection, Files, Generics).

Artikel ini akan memberi Anda pengalaman yang tak terlupakan dan membuat Anda merasa seperti pengembang penting. Langkah demi langkah, Anda akan membuat kacang kelas Anda dan mengatur siklus hidup mereka (sama seperti di musim semi yang nyata). Kelas yang akan Anda terapkan adalah BeanFactory , Komponen , Layanan , BeanPostProcessor , BeanNameAware , BeanFactoryAware , InisialisasiBean , PostConstruct , PreDestroy , DisposableBean , ApplicationContext , ApplicationListener , ContextClosedEvent .

Sedikit tentang dirimu


Nama saya Yaroslav dan saya seorang Pengembang Java dengan pengalaman 4 tahun. Saat ini saya bekerja untuk Sistem EPAM (SPB), dan saya mempelajari secara mendalam teknologi yang kami gunakan. Cukup sering saya harus berurusan dengan musim semi, dan saya melihat di dalamnya beberapa jalan tengah di mana Anda dapat tumbuh (Jawa semua orang tahu dengan baik, dan alat dan teknologi yang terlalu spesifik dapat datang dan pergi).

Beberapa bulan yang lalu, saya lulus sertifikasi Spring Professional v5.0 (tanpa mengambil kursus). Setelah itu, saya berpikir tentang cara mengajar orang lain meloncat. Sayangnya, saat ini tidak ada metodologi pengajaran yang efektif. Sebagian besar pengembang memiliki ide kerangka kerja yang sangat dangkal dan fitur-fiturnya. Debug sumber mata air terlalu sulit dan sama sekali tidak efektif dari sudut pandang pelatihan (entah bagaimana saya suka ini). Apakah 10 proyek? Ya, di suatu tempat Anda dapat memperdalam pengetahuan Anda dan mendapatkan banyak pengalaman praktis, tetapi banyak dari apa yang "tersembunyi" tidak akan pernah terbuka sebelum Anda. Baca Spring in Action? Keren, tetapi usaha yang mahal. Saya sudah bekerja 40% (selama persiapan sertifikasi), tapi itu tidak mudah.

Satu-satunya cara untuk memahami sesuatu sampai akhir adalah mengembangkannya sendiri. Baru-baru ini, saya memiliki gagasan bahwa Anda dapat memimpin seseorang melalui tutorial menarik yang akan mengawasi pengembangan kerangka DI-nya. Fitur utamanya adalah bahwa API akan bertepatan dengan API yang sedang dipelajari. Kedahsyatan pendekatan ini adalah bahwa selain pemahaman mendalam (tanpa spasi) tentang pegas, seseorang akan mendapatkan sejumlah besar pengalaman di Java Core. Terus terang, saya sendiri belajar banyak hal baru selama persiapan artikel, baik di Spring dan di Java Core. Ayo mulai berkembang!

Proyeksikan dari awal


Jadi, hal pertama yang harus dilakukan adalah membuka IDE favorit Anda dan membuat proyek dari awal. Kami tidak akan menghubungkan Maven atau perpustakaan pihak ketiga mana pun. Kami bahkan tidak akan menghubungkan dependensi Spring. Tujuan kami adalah mengembangkan API yang paling mirip dengan Spring API, dan mengimplementasikannya sendiri.

Dalam proyek bersih, buat 2 paket utama. Paket pertama adalah aplikasi Anda ( com.kciray ), dan kelas Main.java di dalamnya. Paket kedua adalah org.springframework. Ya, kami akan menduplikasi struktur paket pegas asli, nama kelasnya dan metode mereka. Ada efek yang menarik - ketika Anda membuat sesuatu milik Anda sendiri, efek Anda sendiri mulai tampak sederhana dan dapat dimengerti. Kemudian, ketika Anda bekerja di proyek-proyek besar, akan tampak bagi Anda bahwa semuanya dibuat di sana berdasarkan benda kerja Anda. Pendekatan ini dapat memiliki efek yang sangat positif pada pemahaman sistem secara keseluruhan, memperbaikinya, memperbaiki bug, menyelesaikan masalah, dan sebagainya.

Jika Anda memiliki masalah, Anda dapat mengambil proyek yang berfungsi di sini .

Buat wadah


Untuk memulai, atur tugas. Misalkan kita memiliki 2 kelas - ProductFacade dan PromotionService . Sekarang bayangkan Anda ingin menghubungkan kelas-kelas ini satu sama lain, tetapi agar kelas-kelas itu sendiri tidak saling mengenal (Pattern DI). Kami membutuhkan kelas terpisah yang akan mengelola semua kelas ini dan menentukan dependensi di antara mereka. Sebut saja wadah. Mari kita buat kelas Container ... Meskipun tidak, tunggu! Musim semi tidak memiliki kelas wadah tunggal. Kami memiliki banyak implementasi kontainer, dan semua implementasi ini dapat dibagi menjadi 2 jenis - pabrik dan konteks bin. Pabrik bin membuat kacang dan menautkannya bersama-sama (injeksi ketergantungan, DI), dan konteksnya hampir sama, ditambah lagi menambahkan beberapa fitur tambahan (misalnya, pesan internasionalisasi). Tetapi kita tidak memerlukan fungsi tambahan ini sekarang, jadi kita akan bekerja dengan pabrik bin.

Buat kelas BeanFactory baru dan masukkan ke dalam paket org.springframework.beans.factory . Biarkan Map<String, Object> singletons disimpan di dalam kelas ini, di mana id nampan dipetakan ke nampan itu sendiri. Tambahkan padanya metode Object getBean(String beanName) , yang menarik biji dengan pengidentifikasi.

 public class BeanFactory { private Map<String, Object> singletons = new HashMap(); public Object getBean(String beanName){ return singletons.get(beanName); } } 

Harap dicatat bahwa BeanFactory dan FactoryBean adalah dua hal yang berbeda. Yang pertama adalah pabrik bin (wadah), dan yang kedua adalah pabrik bin, yang duduk di dalam wadah dan juga menghasilkan sampah. Pabrik di dalam pabrik. Jika Anda bingung antara definisi ini, Anda mungkin ingat bahwa dalam bahasa Inggris kata benda kedua adalah yang pertama, dan yang pertama adalah sesuatu seperti kata sifat. Di Bean Factory, kata utama adalah factory, dan di Factory Bean , bean.

Sekarang, buat kelas ProductService dan PromotionsService . ProductService akan mengembalikan produk dari basis data, tetapi sebelum itu Anda perlu memeriksa apakah ada diskon (Promosi) yang berlaku untuk produk ini. Dalam e-commerce, pekerjaan yang didiskon seringkali dialokasikan ke kelas layanan yang terpisah (dan terkadang ke layanan web pihak ketiga).

 public class PromotionsService { } public class ProductService { private PromotionsService promotionsService; public PromotionsService getPromotionsService() { return promotionsService; } public void setPromotionsService(PromotionsService promotionsService) { this.promotionsService = promotionsService; } } 

Sekarang kita perlu membuat wadah kita ( BeanFactory ) mendeteksi kelas kita, membuatnya untuk kita dan menyuntikkan satu ke yang lain. Operasi seperti new ProductService() harus ditempatkan di dalam wadah dan dilakukan untuk pengembang. Mari kita gunakan pendekatan paling modern (pemindaian kelas dan anotasi). Untuk melakukan ini, kita perlu membuat anotasi @Component dengan @Component ( org.springframework.beans.factory.stereotype ).

 @Retention(RetentionPolicy.RUNTIME) public @interface Component { } 

Secara default, anotasi tidak dimuat ke dalam memori saat program sedang berjalan ( RetentionPolicy.CLASS ). Kami mengubah perilaku ini melalui kebijakan penyimpanan baru ( RetentionPolicy.RUNTIME ).

Sekarang tambahkan @Component sebelum kelas ProductService dan sebelum PromotionService .

 @Component public class ProductService { //... } @Component public class PromotionService { //... } 


Kami membutuhkan BeanFactory memindai paket kami ( com.kciray ) dan menemukan kelas di dalamnya yang dianotasi oleh @Component . Tugas ini jauh dari sepele. Tidak ada solusi siap pakai di Java Core, dan kita harus membuat sendiri tongkat penyangga. Ribuan aplikasi pegas menggunakan pemindaian komponen melalui kruk ini. Anda telah mempelajari kebenaran yang mengerikan. Anda harus mengekstrak nama ClassLoader dari ClassLoader dan memeriksa ClassLoader mereka diakhiri dengan ".class" atau tidak, dan kemudian membangun nama lengkap mereka dan mengeluarkan objek kelas dari sana!

Saya ingin segera memperingatkan Anda bahwa akan ada banyak pengecualian yang diperiksa, jadi bersiaplah untuk membungkusnya. Tapi pertama-tama, mari kita putuskan apa yang kita inginkan. Kami ingin menambahkan metode khusus ke BeanFactory dan menyebutnya di Main :

 //BeanFactory.java public class BeanFactory{ public void instantiate(String basePackage) { } } //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); 

Selanjutnya, kita perlu mendapatkan ClassLoader . Ini bertanggung jawab untuk memuat kelas, dan itu diekstraksi cukup sederhana:

 ClassLoader classLoader = ClassLoader.getSystemClassLoader(); 

Anda mungkin sudah memperhatikan bahwa paket dipisahkan oleh titik, dan file oleh garis miring. Kita perlu mengonversi jalur kumpulan ke jalur folder, dan mendapatkan sesuatu seperti List<URL> (jalur di sistem file Anda tempat Anda dapat mencari file kelas).

 String path = basePackage.replace('.', '/'); //"com.kciray" -> "com/kciray" Enumeration<URL> resources = classLoader.getResources(path); 

Jadi tunggu sebentar! Enumeration<URL> bukan List<URL> . Tentang apa semua ini? Oh, ngeri, ini leluhur leluhur Iterator , tersedia sejak Java 1.0. Ini adalah warisan yang harus kita tangani. Jika dimungkinkan untuk berjalan menggunakan Iterable menggunakan untuk (semua koleksi mengimplementasikannya), maka dalam kasus Enumeration Anda harus melakukan bypass pegangan, through while(resources.hasMoreElements()) dan nextElement() . Namun tidak ada cara untuk menghapus item dari koleksi. Baru tahun 1996, hanya hardcore. Oh ya, di Java 9 mereka menambahkan metode Enumeration.asIterator() , jadi Anda bisa mengatasinya.

Mari kita melangkah lebih jauh. Kita perlu mengekstrak folder dan bekerja melalui konten masing-masing. Konversikan URL menjadi file, lalu dapatkan namanya. Perlu dicatat di sini bahwa kami tidak akan memindai paket bersarang sehingga tidak menyulitkan kode. Anda dapat memperumit tugas Anda dan melakukan rekursi jika Anda mau.

 while (resources.hasMoreElements()) { URL resource = resources.nextElement(); File file = new File(resource.toURI()); for(File classFile : file.listFiles()){ String fileName = classFile.getName();//ProductService.class } } 

Selanjutnya, kita perlu mendapatkan nama file tanpa ekstensi. Di halaman pada tahun 2018, Java telah mengembangkan File I / O (NIO 2) selama bertahun-tahun, tetapi masih tidak dapat memisahkan ekstensi dari nama file. Saya harus membuat sepeda sendiri, karena kami memutuskan untuk tidak menggunakan perpustakaan pihak ketiga seperti Apache Commons. Mari kita gunakan cara kakek lama lastIndexOf(".") :

 if(fileName.endsWith(".class")){ String className = fileName.substring(0, fileName.lastIndexOf(".")); } 

Selanjutnya, kita bisa mendapatkan objek kelas menggunakan nama lengkap kelas (untuk ini kita sebut kelas kelas Class ):

 Class classObject = Class.forName(basePackage + "." + className); 

Oke, sekarang kelas kita ada di tangan kita. Lebih jauh, itu hanya untuk menyoroti di antara mereka yang memiliki anotasi @Component :

 if(classObject.isAnnotationPresent(Component.class)){ System.out.println("Component: " + classObject); } 

Jalankan dan periksa. Konsol harus seperti ini:

 Component: class com.kciray.ProductService Component: class com.kciray.PromotionsService 

Sekarang kita perlu membuat kacang kita. Anda perlu melakukan sesuatu seperti new ProductService() , tetapi untuk setiap kacang kami memiliki kelas kami sendiri. Refleksi di Jawa memberi kami solusi universal (konstruktor default disebut):

 Object instance = classObject.newInstance();//=new CustomClass() 

Selanjutnya, kita perlu meletakkan kacang ini di Map<String, Object> singletons . Untuk melakukan ini, pilih nama kacang (id-nya). Di Jawa, kami memanggil variabel seperti kelas (hanya huruf pertama yang menggunakan huruf kecil). Pendekatan ini juga dapat diterapkan pada kacang, karena Spring adalah kerangka kerja Java! Konversikan nama nampan sehingga huruf pertama kecil, dan tambahkan ke peta:

 String beanName = className.substring(0, 1).toLowerCase() + className.substring(1); singletons.put(beanName, instance); 

Sekarang pastikan semuanya berfungsi. Wadah harus membuat kacang, dan harus diambil dengan nama. Harap perhatikan bahwa nama metode instantiate() dan nama metode classObject.newInstance(); memiliki akar yang sama. Selain itu, instantiate() adalah bagian dari siklus hidup kacang. Di Jawa, semuanya saling berhubungan!

 //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); ProductService productService = (ProductService) beanFactory.getBean("productService"); System.out.println(productService);//ProductService@612 


Coba juga org.springframework.beans.factory.stereotype.Service anotasi org.springframework.beans.factory.stereotype.Service . Ia melakukan fungsi yang persis sama dengan @Component , tetapi disebut berbeda. Intinya adalah dalam nama - Anda menunjukkan bahwa kelas adalah layanan, bukan hanya komponen. Ini adalah sesuatu seperti mengetik konseptual. Dalam sertifikasi musim semi ada pertanyaan "Apa penjelasan yang distereotipkan?" (dari yang terdaftar). " Jadi, penjelasan stereotip adalah yang ada dalam paket stereotype .

Isi propertinya


Lihat diagram di bawah, ini menunjukkan awal dari siklus hidup kacang. Apa yang kami lakukan sebelum ini adalah Instantiate (membuat kacang melalui newInstance() ). Langkah selanjutnya adalah injeksi silang kacang (injeksi ketergantungan, itu juga inversi kontrol (IOC)). Anda harus memeriksa sifat-sifat kacang dan memahami sifat-sifat mana yang perlu disuntikkan. Jika Anda memanggil productService.getPromotionsService() , Anda akan mendapatkan null , karena ketergantungan belum ditambahkan.



Pertama, buat paket org.springframework.beans.factory.annotation dan tambahkan anotasi @Autowired . Idenya adalah untuk menandai bidang yang dependensi dengan anotasi ini.

 @Retention(RetentionPolicy.RUNTIME) public @interface Autowired { } 

Selanjutnya, tambahkan ke properti:

 @Component public class ProductService { @Autowired PromotionsService promotionsService; //... } 

Sekarang kita perlu mengajari BeanFactory kita BeanFactory menemukan anotasi ini dan menyuntikkan dependensi padanya. Tambahkan metode terpisah untuk ini, dan panggil dari Main :

 public class BeanFactory { //... public void populateProperties(){ System.out.println("==populateProperties=="); } } 

Selanjutnya, kita hanya perlu melalui semua kacang kita di peta singletons , dan untuk setiap kacang, melalui semua bidangnya ( object.getClass().getDeclaredFields() mengembalikan semua bidang, termasuk yang pribadi). Dan periksa apakah bidang tersebut memiliki anotasi @Autowired :

 for (Object object : singletons.values()) { for (Field field : object.getClass().getDeclaredFields()) { if (field.isAnnotationPresent(Autowired.class)) { } } } 

Selanjutnya, kita perlu memeriksa semua tempat sampah sekali lagi dan melihat tipenya - tiba-tiba ini adalah tipe yang ingin diambil oleh bin kita sendiri. Ya, kami mendapatkan siklus tiga dimensi!

 for (Object dependency : singletons.values()) { if (dependency.getClass().equals(field.getType())) { } } 

Selanjutnya, ketika kami menemukan kecanduan, kami harus menyuntikkannya. Hal pertama yang Anda mungkin pikirkan adalah menulis promotionsService menggunakan refleksi secara langsung. Tetapi musim semi tidak bekerja seperti itu. Lagi pula, jika bidang tersebut memiliki pengubah private , maka pertama-tama kita harus mengaturnya sebagai public , kemudian menulis nilai kami, kemudian mengaturnya ke private lagi (untuk menjaga integritas). Kedengarannya seperti tongkat penyangga besar. Alih-alih kruk besar, mari kita membuat kruk kecil (kami akan membentuk nama setter dan menyebutnya):

 String setterName = "set" + field.getName().substring(0, 1).toUpperCase() + field.getName().substring(1);//setPromotionsService System.out.println("Setter name = " + setterName); Method setter = object.getClass().getMethod(setterName, dependency.getClass()); setter.invoke(object, dependency); 

Sekarang jalankan proyek Anda dan pastikan bahwa saat memanggil productService.getPromotionsService() alih-alih null , kacang kami dikembalikan.

Apa yang kami terapkan adalah injeksi menurut jenis. Ada juga suntikan dengan nama ( javax.annotation.Resource ). Ini berbeda karena bukan tipe bidang, namanya akan diekstraksi, dan menurutnya - ketergantungan dari peta. Semuanya serupa di sini, bahkan dalam sesuatu yang lebih sederhana. Saya merekomendasikan Anda untuk bereksperimen dan membuat kacang Anda sendiri, lalu menyuntikkannya dengan @Resource dan memperpanjang metode populateProperties() .

Kami mendukung kacang yang tahu tentang nama mereka




Ada kalanya Anda perlu memasukkan namanya ke tempat sampah. Kebutuhan seperti itu tidak sering muncul, karena sampah, pada dasarnya, tidak boleh saling mengenal dan bahwa mereka adalah sampah. Dalam versi pertama musim semi, diasumsikan bahwa bean adalah POJO (Plain Old Java Objec, objek Java lama yang baik), dan seluruh konfigurasi diberikan dalam file XML dan dipisahkan dari implementasinya. Tapi kami menerapkan fungsi ini, karena injeksi nama adalah bagian dari siklus hidup bin.

Bagaimana kita tahu kacang mana yang ingin tahu siapa namanya dan apa yang tidak dia inginkan? Hal pertama yang terlintas dalam pikiran adalah membuat anotasi baru dari tipe @InjectName dan memahatnya menjadi bidang tipe String. Tetapi solusi ini akan terlalu umum dan memungkinkan Anda menembak diri sendiri berkali-kali (letakkan anotasi ini pada bidang yang jenisnya tidak sesuai (bukan String), atau coba menyuntikkan nama ke beberapa bidang dalam kelas yang sama). Ada solusi lain, lebih akurat - untuk membuat antarmuka khusus dengan satu metode setter. Semua tempat sampah yang menerapkannya mendapatkan namanya. Buat kelas BeanNameAware di paket org.springframework.beans.factory :

 public interface BeanNameAware { void setBeanName(String name); } 

Selanjutnya, biarkan Layanan PromotionsService kami menerapkannya:

 @Component public class PromotionsService implements BeanNameAware { private String beanName; @Override public void setBeanName(String name) { beanName = name; } public String getBeanName() { return beanName; } } 

Dan akhirnya, tambahkan metode baru ke pabrik kacang. Semuanya sederhana di sini - kita melihat bin-singleton kita, memeriksa apakah bin mengimplementasikan antarmuka kita, dan memanggil setter:

 public void injectBeanNames(){ for (String name : singletons.keySet()) { Object bean = singletons.get(name); if(bean instanceof BeanNameAware){ ((BeanNameAware) bean).setBeanName(name); } } } 

Jalankan dan pastikan semuanya berfungsi:

 BeanFactory beanFactory = new BeanFactory(); beanFactory.instantiate("com.kciray"); beanFactory.populateProperties(); beanFactory.injectBeanNames(); //... System.out.println("Bean name = " + promotionsService.getBeanName()); 

Perlu dicatat bahwa pada musim semi ada antarmuka serupa lainnya. Saya sarankan Anda menerapkan antarmuka BeanFactoryAware sendiri , yang memungkinkan kacang menerima tautan ke pabrik kacang. Ini diimplementasikan dengan cara yang serupa.

Inisialisasi Kacang




Bayangkan Anda memiliki situasi di mana Anda perlu menjalankan beberapa kode setelah dependensi telah disuntikkan (properti bin diatur). Secara sederhana, kita perlu memberi bin kemampuan untuk menginisialisasi sendiri. Atau, kita bisa membuat antarmuka InitializingBean , dan memasukkan tanda tangan metode void afterPropertiesSet() di dalamnya. Implementasi mekanisme ini persis sama dengan yang disajikan untuk antarmuka BeanNameAware , sehingga solusinya ada di bawah spoiler. Berlatih dan lakukan sendiri dalam satu menit:

Solusi Inisialisasi Bean
 //InitializingBean.java package org.springframework.beans.factory; public interface InitializingBean { void afterPropertiesSet(); } //BeanFactory.java public void initializeBeans(){ for (Object bean : singletons.values()) { if(bean instanceof InitializingBean){ ((InitializingBean) bean).afterPropertiesSet(); } } } //Main.java beanFactory.initializeBeans(); 



Menambahkan Prosesor Posting


Bayangkan diri Anda di tempat pengembang musim semi pertama. Kerangka kerja Anda berkembang dan sangat populer di kalangan pengembang, surat dikirim ke surat setiap hari dengan permintaan untuk menambahkan satu atau fitur bermanfaat lainnya. Jika untuk setiap fitur tersebut Anda menambahkan antarmuka Anda sendiri dan memeriksanya dalam siklus hidup bean, maka itu (siklus hidup) akan menjadi tersumbat dengan informasi yang tidak perlu. Sebagai gantinya, kami dapat membuat satu antarmuka universal yang memungkinkan Anda untuk menambahkan beberapa logika (benar-benar ada, apakah itu memeriksa anotasi, mengganti nampan dengan nampan lain, mengatur beberapa properti khusus, dan sebagainya).

Mari kita pikirkan untuk apa antarmuka ini. Perlu melakukan beberapa pemrosesan pasca kacang, maka itu dapat disebut BeanPostProcessor. Tetapi kita dihadapkan dengan pertanyaan yang sulit - kapan logika harus diikuti? Bagaimanapun, kita dapat menjalankannya sebelum inisialisasi, tetapi kita dapat mengeksekusinya setelah itu. Untuk beberapa tugas, opsi pertama lebih baik, untuk yang lain - yang kedua ... Bagaimana menjadi?

Kami dapat mengaktifkan kedua opsi sekaligus. Biarkan satu post-prosesor membawa dua logika, dua metode. Satu dieksekusi sebelum inisialisasi (sebelum metode afterPropertiesSet() ), dan yang lainnya setelah. Sekarang mari kita berpikir tentang metode itu sendiri - parameter apa yang harus mereka miliki? Jelas, Object bean itu sendiri ( Object bean ) harus ada di sana. Untuk kenyamanan, selain kacang, Anda bisa memberikan nama kacang ini. Anda ingat bahwa nampan itu sendiri tidak tahu tentang namanya. Dan kami tidak ingin memaksa semua kacang untuk mengimplementasikan antarmuka BeanNameAware. Tetapi, pada tingkat pasca-prosesor, nama kacang bisa sangat berguna. Karena itu, kami menambahkannya sebagai parameter kedua.

Dan apa yang harus metode kembali ketika memposting pengolahan kacang? Mari kita membuatnya mengembalikan nampan itu sendiri. Ini memberi kami fleksibilitas super, karena alih-alih tempat sampah, Anda dapat memasukkan objek proxy yang membungkus panggilannya (dan menambah keamanan). Atau Anda dapat sepenuhnya mengembalikan objek lain dengan membuat ulang tempat sampah lagi. Pengembang diberi kebebasan bertindak yang sangat besar. Di bawah ini adalah versi final dari antarmuka yang dirancang:

 package org.springframework.beans.factory.config; public interface BeanPostProcessor { Object postProcessBeforeInitialization(Object bean, String beanName); Object postProcessAfterInitialization(Object bean, String beanName); } 

Selanjutnya, kita perlu menambahkan daftar prosesor sederhana ke pabrik kacang kita dan kemampuan untuk menambahkan yang baru. Ya, ini adalah ArrayList biasa.

 //BeanFactory.java private List<BeanPostProcessor> postProcessors = new ArrayList<>(); public void addPostProcessor(BeanPostProcessor postProcessor){ postProcessors.add(postProcessor); } 

Sekarang ubah metode initializeBeans sehingga memperhitungkan post-prosesor akun:

 public void initializeBeans() { for (String name : singletons.keySet()) { Object bean = singletons.get(name); for (BeanPostProcessor postProcessor : postProcessors) { postProcessor.postProcessBeforeInitialization(bean, name); } if (bean instanceof InitializingBean) { ((InitializingBean) bean).afterPropertiesSet(); } for (BeanPostProcessor postProcessor : postProcessors) { postProcessor.postProcessAfterInitialization(bean, name); } } } 

Mari kita buat prosesor pos kecil yang hanya melacak panggilan ke konsol dan menambahkannya ke pabrik kacang kami:

 public class CustomPostProcessor implements BeanPostProcessor { @Override public Object postProcessBeforeInitialization(Object bean, String beanName) { System.out.println("---CustomPostProcessor Before " + beanName); return bean; } @Override public Object postProcessAfterInitialization(Object bean, String beanName) { System.out.println("---CustomPostProcessor After " + beanName); return bean; } } 

 //Main.java BeanFactory beanFactory = new BeanFactory(); beanFactory.addPostProcessor(new CustomPostProcessor()); 


Sekarang jalankan dan pastikan semuanya berfungsi. Sebagai tugas pelatihan, buat prosesor pos yang akan memberikan anotasi @PostConstruct (javax.annotation.PostConstruct) . Ini memberikan cara alternatif untuk menginisialisasi (berakar di Jawa, bukan di musim semi). Esensinya adalah bahwa Anda menempatkan anotasi pada beberapa metode, dan metode ini akan disebut SEBELUM inisialisasi pegas standar (InitializingBean).

Pastikan untuk membuat semua anotasi dan paket (bahkan javax.annotation) secara manual, jangan hubungkan dependensi! Ini akan membantu Anda untuk melihat perbedaan antara inti pegas dan ekstensi (dukungan javax), dan mengingatnya. Ini akan mempertahankan satu gaya di masa depan.

Anda akan tertarik pada fakta bahwa pada musim semi nyata anotasi @PostConstructdiimplementasikan dengan cara ini, melalui post-processor CommonAnnotationBeanPostProcessor. Tapi jangan mengintip ke sana, tulis implementasi Anda.

Terakhir, saya sarankan Anda menambahkan metode void close()ke kelas BeanFactorydan bekerja dua mekanisme lagi. Yang pertama adalah penjelasan @PreDestroy (javax.annotation.PreDestroy), dimaksudkan untuk metode yang harus dipanggil ketika wadah ditutup. Yang kedua adalah antarmuka org.springframework.beans.factory.DisposableBeanyang berisi metode void destroy(). Semua nampan yang menjalankan antarmuka ini akan memiliki kemampuan untuk menghancurkan diri mereka sendiri (membebaskan sumber daya, misalnya).

@PreDestroy + DisposableBean
 //DisposableBean.java package org.springframework.beans.factory; public interface DisposableBean { void destroy(); } //PreDestroy.java package javax.annotation; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @Retention(RetentionPolicy.RUNTIME) public @interface PreDestroy { } //DisposableBean.java public void close() { for (Object bean : singletons.values()) { for (Method method : bean.getClass().getMethods()) { if (method.isAnnotationPresent(PreDestroy.class)) { try { method.invoke(bean); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InvocationTargetException e) { e.printStackTrace(); } } } if (bean instanceof DisposableBean) { ((DisposableBean) bean).destroy(); } } } 



Siklus Hidup Kacang Penuh


Jadi kami telah menerapkan siklus hidup penuh tempat sampah, dalam bentuk modernnya. Saya harap pendekatan ini membantu Anda mengingatnya.

Konteks favorit kami


Pemrogram sangat sering menggunakan istilah konteks, tetapi tidak semua orang mengerti apa artinya sebenarnya. Sekarang kita akan mengatur semuanya. Seperti yang saya catat di awal artikel, konteks adalah implementasi dari wadah, juga BeanFactory. Tapi, selain fungsi dasar (DI), masih menambah beberapa fitur keren. Salah satu fitur ini adalah mengirim dan memproses acara antar nampan.

Artikel itu ternyata terlalu besar dan isinya mulai terpotong, jadi saya menaruh informasi konteks di bawah spoiler.

Kami menyadari konteksnya
. org.springframework.context , ApplicationContext . BeanFactory . , close() .

 public class ApplicationContext { private BeanFactory beanFactory = new BeanFactory(); public ApplicationContext(String basePackage) throws ReflectiveOperationException{ System.out.println("******Context is under construction******"); beanFactory.instantiate(basePackage); beanFactory.populateProperties(); beanFactory.injectBeanNames(); beanFactory.initializeBeans(); } public void close(){ beanFactory.close(); } } 


Main , , :

 ApplicationContext applicationContext = new ApplicationContext("com.kciray"); applicationContext.close(); 

, . close() , « » - . , :

 package org.springframework.context.event; public class ContextClosedEvent { } 

ApplicationListener , . , ( ApplicationListener<E> ). , Java-, . , , :

 package org.springframework.context; public interface ApplicationListener<E>{ void onApplicationEvent(E event); } 

ApplicationContext . close() , , . ApplicationListener<ContextClosedEvent> , onApplicationEvent(ContextClosedEvent) . , ?

 public void close(){ beanFactory.close(); for(Object bean : beanFactory.getSingletons().values()) { if (bean instanceof ApplicationListener) { } } } 

Tapi tidak. . bean instanceof ApplicationListener<ContextClosedEvent> . Java. (type erasure) , <T> <Object>. , ? , ApplicationListener<ContextClosedEvent> , ?

, , . , , , , :

 for (Type type: bean.getClass().getGenericInterfaces()){ if(type instanceof ParameterizedType){ ParameterizedType parameterizedType = (ParameterizedType) type; } } 

, , , — . , :

 Type firstParameter = parameterizedType.getActualTypeArguments()[0]; if(firstParameter.equals(ContextClosedEvent.class)){ Method method = bean.getClass().getMethod("onApplicationEvent", ContextClosedEvent.class); method.invoke(bean, new ContextClosedEvent()); } 

ApplicationListener:

 @Service public class PromotionsService implements BeanNameAware, ApplicationListener<ContextClosedEvent> { //... @Override public void onApplicationEvent(ContextClosedEvent event) { System.out.println(">> ContextClosed EVENT"); } } 

, Main , , :

 //Main.java void testContext() throws ReflectiveOperationException{ ApplicationContext applicationContext = new ApplicationContext("com.kciray"); applicationContext.close(); } 


Kesimpulan


Awalnya, saya merencanakan artikel ini untuk Baeldung dalam bahasa Inggris, tetapi kemudian saya berpikir bahwa audiens Habré dapat secara positif mengevaluasi pendekatan pelatihan ini. Jika Anda menyukai ide saya, pastikan untuk mendukung artikel tersebut. Jika dia mendapat peringkat lebih dari 30, maka saya berjanji untuk melanjutkan. Saat menulis artikel, saya mencoba untuk menunjukkan pengetahuan Spring Core, yang paling sering digunakan, dan juga berdasarkan Panduan Studi Sertifikasi Core Spring 5.0 . Di masa depan, dengan bantuan tutorial seperti itu, Anda dapat mencakup seluruh sertifikasi dan membuat pegas lebih mudah diakses oleh pengembang Java.

Perbarui 05/10/2018


Surat terus-menerus datang kepada saya dengan pertanyaan "dan ketika kelanjutan, kami sedang menunggunya." Tetapi tidak ada waktu sama sekali, dan proyek pribadi lainnya adalah prioritas. Namun, jika salah satu dari Anda benar-benar menyukai gagasan itu, Anda dapat mempelajari bagian sempit musim semi dan menulis artikel lanjutan. Jika Anda tidak memiliki akun habr, maka saya dapat menerbitkan artikel dari akun saya atau membantu Anda mendapatkan undangan.

Distribusi topik:
Wadah Musim Semi - [nama pengguna]
Musim Semi AOP - [nama pengguna]
Musim Semi - [nama pengguna]
Spring Cloud - [nama pengguna]

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


All Articles