Boot dirimu, Spring akan datang (Bagian 1)

Evgeny EvgenyBorisov Borisov (NAYA Technologies) dan Kirill tolkkv Tolkachev (Cyan.Finance, Twitter ) berbicara tentang momen paling penting dan menarik dari Spring Boot pada contoh pemula untuk Iron Bank imajiner.



Artikel ini didasarkan pada laporan Eugene dan Cyril dari konferensi Joker kami 2017. Di bawah potongan adalah video dan transkrip teks dari laporan tersebut.


Konferensi Joker disponsori oleh banyak bank, jadi mari kita bayangkan aplikasi tempat kita akan mempelajari pekerjaan boot Spring dan starter yang kita buat terhubung dengan bank.



Jadi, misalkan pesanan diterima untuk aplikasi dari Iron Bank of Braavos. Bank biasa hanya mengirim uang bolak-balik. Misalnya, seperti ini (kami memiliki API untuk ini):

http://localhost:8080/credit\?name\=Targarian\&amount\=100

Dan di Iron Bank, sebelum mentransfer uang, API bank perlu menghitung apakah seseorang dapat mengembalikannya. Mungkin dia tidak akan selamat musim dingin dan tidak akan ada yang kembali. Oleh karena itu, disediakan layanan yang memeriksa keandalan.

Misalnya, jika kami mencoba mentransfer uang ke Targaryen, operasi akan disetujui:



Tetapi jika Stark, maka tidak ada:



Tidak heran: Starks mati terlalu sering. Mengapa mentransfer uang jika seseorang tidak selamat musim dingin?

Mari kita lihat tampilannya di dalam.

 @RestController @RequiredArgsConstructor public class IronBankController { private final TransferMoneyService transferMoney; private final MoneyDao moneyDao; @GetMapping("/credit") public String credit(@RequestParam String name, @RequestParam long amount) {   long resultedDeposit = transferMoney.transfer(name, amount);   if (resultedDeposit == -1) {     return "Rejected<br/>" + name + " <b>will`t</b> survive this winter";   }   return format(       "<i>Credit approved for %s</i> <br/>Current  bank balance: <b>%s</b>",       name,       resultedDeposit   ); } @GetMapping("/state") public long currentState() {   return moneyDao.findAll().get(0).getTotalAmount(); } } 

Ini adalah pengontrol string biasa.

Siapa yang bertanggung jawab atas logika pilihan, kepada siapa memberikan pinjaman, dan kepada siapa - tidak? Baris sederhana: jika nama Anda Stark, kami tentu tidak mengkhianati. Dalam kasus lain - betapa beruntungnya. Bank biasa.

 @Service public class NameBasedProphetService implements ProphetService { @Override public boolean willSurvive(String name) {   return !name.contains("Stark") && ThreadLocalRandom.current().nextBoolean(); } } 

Segala sesuatu yang lain tidak begitu menarik. Ini adalah beberapa penjelasan yang melakukan semua pekerjaan untuk kita. Semuanya sangat cepat.

Di mana semua konfigurasi utama? Hanya ada satu pengontrol. Di Dao, umumnya antarmuka kosong.

 public interface MoneyDao extends JpaRepository<Bank, String> { } 

Dalam layanan - hanya layanan terjemahan dan prediksi kepada siapa Anda dapat mengeluarkan. Tidak ada direktori Conf. Bahkan, kami hanya memiliki application.yml (daftar orang-orang yang membayar utang). Dan utama adalah yang paling umum:

 @SpringBootApplication @EnableConfigurationProperties(ProphetProperties.class) public class MoneyRavenApplication { public static void main(String[] args) {   SpringApplication.run(MoneyRavenApplication.class, args); } } 

Jadi di mana semua sihir itu tersembunyi?

Faktanya adalah bahwa pengembang tidak suka memikirkan dependensi, mengkonfigurasi konfigurasi, terutama jika ini adalah konfigurasi XML, dan memikirkan bagaimana aplikasi mereka mulai. Karena itu, Spring Boot memecahkan masalah ini untuk kami. Kami hanya perlu menulis aplikasi.

Ketergantungan


Masalah pertama yang selalu kita miliki adalah konflik versi. Setiap kali kami menghubungkan pustaka yang berbeda yang merujuk pustaka lain, konflik ketergantungan muncul. Setiap kali saya membaca di Internet bahwa saya perlu menambahkan beberapa entitas-manajer, muncul pertanyaan, dan versi mana yang harus saya tambahkan sehingga tidak merusak apa pun?

Spring Boot memecahkan masalah konflik versi.

Bagaimana kita biasanya mendapatkan proyek Boot Spring (jika kita belum datang ke suatu tempat di mana sudah ada)?

  • atau pergi ke start.spring.io , letakkan kotak centang yang Josh Long ajarkan untuk kita atur, klik pada Proyek Unduh dan buka proyek di mana semuanya sudah ada;
  • atau gunakan IntelliJ, di mana, berkat opsi yang muncul, kotak centang di Spring Initializer dapat diatur langsung dari sana.

Jika kita bekerja dengan Maven, maka proyek tersebut akan memiliki pom.xml, di mana ada induk Spring Boot yang disebut spring-boot-dependencies . Akan ada blok besar manajemen ketergantungan.

Saya tidak akan masuk ke detail Maven sekarang. Hanya dua kata.

Blok dependensi-manajemen tidak mendaftarkan dependensi. Ini adalah blok yang dengannya Anda dapat menentukan versi jika dependensi ini diperlukan. Dan ketika Anda menunjukkan semacam dependensi di blok manajemen dependensi tanpa menentukan versi, Maven mulai mencari apakah ada blok manajemen dependensi di mana induk versi ini ditulis dalam pom induk atau di tempat lain. Yaitu dalam proyek saya, menambahkan ketergantungan baru, saya tidak akan lagi menunjukkan versi dengan harapan bahwa itu ditunjukkan di suatu tempat di induknya. Dan jika itu tidak ditentukan pada induknya, maka dipastikan tidak akan menimbulkan konflik dengan siapa pun. Dalam manajemen ketergantungan kita, lima ratus ketergantungan yang baik ditunjukkan, dan semuanya konsisten satu sama lain.

Tapi apa masalahnya? Masalahnya adalah bahwa di perusahaan saya, misalnya, saya memiliki pom orang tua saya sendiri. Jika saya ingin menggunakan Spring, apa yang harus saya lakukan dengan pom orang tua saya?



Kami tidak memiliki banyak warisan. Kami ingin menggunakan pom kami, dan mendapatkan blok manajemen ketergantungan dari luar.



Ini bisa dilakukan. Cukup untuk mendaftarkan impor BOM dari blok manajemen ketergantungan.

 <dependencyManagement> <dependencies>    <dependency>       <groupId>io.spring.platform</groupId>       <artifactId>platform-bom</artifactId>       <version>Brussels-SR2</version>       <type>pom</type>       <scope>import</scope>    </dependency> </dependencies> </dependencyManagement> 

Siapa yang ingin tahu lebih banyak tentang bom - lihat laporan " Maven vs. Gradle ". Di sana semua ini dijelaskan secara rinci.

Saat ini, menjadi sangat populer di kalangan perusahaan besar untuk menulis blok manajemen ketergantungan yang sedemikian besar, di mana mereka menunjukkan semua versi produk mereka dan semua versi produk yang menggunakan produk mereka dan yang tidak saling bertentangan. Dan ini disebut bom. Benda ini dapat diimpor ke blok manajemen ketergantungan Anda tanpa warisan.

Dan ini adalah bagaimana hal itu dilakukan di Gradle (seperti biasa, hal yang sama, hanya lebih mudah):

 dependencyManagement { imports {   mavenBom 'org.springframework.cloud:spring-cloud-dependencies:Dalston.RELEASE' } } 

Sekarang mari kita bicara tentang ketergantungan itu sendiri.

Apa yang akan kita tulis di aplikasi? Manajemen ketergantungan baik, tetapi kami ingin aplikasi memiliki kemampuan tertentu, misalnya, merespons melalui HTTP, memiliki database atau dukungan untuk JPA. Karena itu, yang kita butuhkan sekarang adalah mendapatkan tiga dependensi.
Dulu terlihat seperti ini. Saya ingin bekerja dengan database dan itu dimulai: beberapa jenis manajer transaksi diperlukan, sehingga modul spring-tx diperlukan. Saya perlu hibernasi, jadi EntityManager, hibernate-core atau yang lainnya diperlukan. Saya mengkonfigurasi semuanya melalui Spring, jadi saya perlu spring core. Yaitu, untuk satu hal sederhana, Anda harus memikirkan selusin dependensi.

Hari ini kami memiliki permulaan. Gagasan pemula adalah bahwa kita menaruh ketergantungan padanya. Untuk mulai dengan, dia mengumpulkan ketergantungan yang dibutuhkan untuk dunia dari mana dia berasal. Misalnya, jika ini adalah starter keamanan, maka Anda tidak berpikir tentang dependensi apa yang dibutuhkan, mereka segera tiba dalam bentuk dependensi transitif ke starter. Atau jika Anda bekerja dengan Spring Data Jpa, maka pasang dependensi pada starter dan itu akan membawa semua modul yang diperlukan untuk bekerja dengan Spring Data Jpa.

Yaitu Pom kami terlihat seperti ini: hanya berisi 3-5 dependensi yang kita butuhkan:

 'org.springframework.boot:spring-boot-starter-web' 'org.springframework.boot:spring-boot-starter-data-jpa' 'com.h2database:h2' 

Dengan dependensi yang beres, semuanya menjadi lebih mudah. Kita perlu berpikir lebih sedikit sekarang. Tidak ada konflik, dan jumlah ketergantungan telah menurun.

Pengaturan konteks


Mari kita bicara tentang rasa sakit berikutnya yang selalu kita alami - mengatur konteksnya. Setiap kali kita mulai menulis aplikasi dari awal, dibutuhkan banyak waktu untuk mengonfigurasi seluruh infrastruktur. Kami mendaftar di xml atau java config banyak yang disebut kacang infrastruktur. Jika kami bekerja dengan hibernate, kami membutuhkan kacang EntityManagerFactory. Banyak kacang infrastruktur - manajer transaksi, sumber data, dll. - Itu perlu untuk menyesuaikan dengan tangan Anda. Secara alami, mereka semua jatuh ke dalam konteks.

Selama laporan Spring Ripper , kami membuat konteks di utama, dan jika itu konteks xml, itu awalnya kosong. Jika kami membangun konteks melalui AnnotationConfigApplicationContext , ada beberapa prosesor beanpost yang dapat mengkonfigurasi kacang sesuai dengan penjelasan, tetapi konteksnya juga hampir kosong.
Dan sekarang di utama ada SpringApplication.run dan tidak ada konteks yang terlihat:

 @SpringBootApplilcation class App { public static void main(String[] args) {   SpringApplication.run(App.class,args); } } 

Tetapi sebenarnya kita memiliki konteks. SpringApplication.run mengembalikan kita beberapa konteks.


Ini adalah kasus yang sepenuhnya tidak biasa. Dulu ada dua opsi:

  • jika ini adalah aplikasi desktop, langsung di utama, Anda harus menulis baru dengan tangan Anda, pilih ClassPathXmlApplicationContext , dll.
  • jika kami bekerja dengan Tomcat, maka ada manajer servlet, yang, oleh beberapa konvensi, mencari XML dan, secara default, membangun konteks darinya.

Dengan kata lain, konteksnya entah bagaimana. Dan kami masih melewati beberapa kelas konfigurasi ke input. Pada umumnya, kami memilih jenis konteks. Sekarang kita hanya memiliki SpringApplication.run , dibutuhkan konfigurasi sebagai argumen dan membangun konteks

Riddle: apa yang bisa kita lewati di sana?


Diberikan:

RipperApplication.class
 publicโ€ฆ main(String[] args) {  SpringApplication.run(?,args); } 

Pertanyaan: apa lagi yang bisa ditransfer ke sana?

Opsi:
  1. RipperApplication.class
  2. String.class
  3. "context.xml"
  4. new ClassPathResource("context.xml")
  5. Package.getPackage("conference.spring.boot.ripper")

Jawabannya
Jawabannya adalah:
Dokumentasi mengatakan bahwa apa pun dapat ditransfer ke sana. Minimal, ini akan dikompilasi dan entah bagaimana akan bekerja.



Yaitu sebenarnya, semua jawaban itu benar. Semua dari mereka dapat dibuat berfungsi, bahkan String.class , dan dalam beberapa kondisi Anda bahkan tidak perlu melakukan apa pun untuk membuatnya berfungsi. Tapi ini cerita yang berbeda.

Satu-satunya hal yang tidak disebutkan dalam dokumentasi adalah dalam bentuk apa mengirim kami ke sana. Tapi ini sudah dari ranah pengetahuan rahasia.

 SpringApplication.run(Object[] sources, String[] args) # APPLICATION SETTINGS (SpringApplication) spring.main.sources= # class name, package name, xml location spring.main.web-environment= # true/false spring.main.banner-mode=console # log/off 

SpringApplication sangat penting di sini - lebih jauh ke slide kita akan memilikinya dengan Carlson.

Carlson kami menciptakan semacam konteks berdasarkan masukan yang kami sampaikan kepadanya. Saya ingatkan Anda, kami berikan kepadanya, misalnya, lima opsi hebat yang bisa Anda buat semuanya berfungsi menggunakan SpringApplication.run :

  • RipperApplication.class
  • String.class
  • "context.xml"
  • new ClassPathResource("context.xml")
  • Package.getPackage("conference.spring.boot.ripper")

Apa yang dilakukan SpringApplication untuk kita?



Ketika kami membuat konteks melalui new pada main melalui new , kami memiliki banyak kelas berbeda yang mengimplementasikan antarmuka ApplicationContext :



Dan opsi apa yang ada ketika Carlson membangun konteks?

Itu hanya membuat dua jenis konteks: konteks web ( WebApplicationContext ) atau konteks umum ( AnnotationConfigApplicationContext ).



Pilihan konteks didasarkan pada kehadiran dua kelas di classpath:



Artinya, jumlah konfigurasi tidak berkurang. Untuk membangun konteks, kita dapat menentukan semua opsi konfigurasi. Untuk membangun konteks, saya dapat mengirimkan skrip groovy atau xml; Saya dapat menunjukkan paket mana yang akan dipindai atau lulus kelas yang ditandai dengan beberapa anotasi. Artinya, saya memiliki semua kemungkinan.

Namun, ini adalah Boot Musim Semi. Kami belum membuat bin tunggal, bukan kelas tunggal, kami hanya punya main, dan di dalamnya adalah Carlson - SpringApplication.run . Di pintu masuk, ia menerima kelas yang ditandai dengan semacam anotasi Spring Boot.

Jika Anda melihat konteks ini, apa yang akan terjadi di sana?

Dalam aplikasi kami, setelah menghubungkan sepasang starter, ada 436 nampan.



Hampir 500 kacang hanya untuk mulai menulis.



Selanjutnya, kita akan mengerti dari mana kacang ini berasal.

Tetapi pertama-tama, kami ingin melakukan hal yang sama.

Keajaiban permulaan, selain untuk menyelesaikan semua masalah dengan kecanduan, adalah bahwa kami hanya menghubungkan 3-4 pemula dan kami memiliki 436 sampah. Kami akan menghubungkan 10 starter, akan ada lebih dari 1000 nampan, karena setiap starter, kecuali untuk dependensi, sudah membawa konfigurasi di mana beberapa nampan yang diperlukan terdaftar. Yaitu Anda mengatakan bahwa Anda menginginkan starter untuk web, jadi Anda memerlukan pengirim servlet dan InternalResourceViewResolver . Kami menghubungkan starter jpa - kami membutuhkan kacang EntityManagerFactory . Semua kacang ini sudah berada di suatu tempat dalam konfigurasi starter, dan mereka secara ajaib datang ke aplikasi tanpa tindakan apa pun dari kita.

Untuk memahami cara kerjanya, hari ini kami akan menulis starter, yang juga akan membawa tempat sampah infrastruktur ke semua aplikasi yang menggunakan starter ini.

Hukum Besi 1.1. Selalu kirim gagak




Mari kita lihat persyaratan dari pelanggan. Iron Bank memiliki banyak aplikasi berbeda yang berjalan di cabang yang berbeda. Pelanggan ingin gagak dikirim setiap kali aplikasi naik - informasi bahwa aplikasi telah naik.

Mari kita mulai menulis kode dalam aplikasi bank besi tertentu (bank besi). Kami akan menulis starter sehingga semua aplikasi Iron Bank yang akan bergantung pada starter ini dapat secara otomatis mengirim gagak. Kami ingat bahwa permulaan memungkinkan kami untuk mengencangkan dependensi secara otomatis. Dan yang paling penting, kita tidak menulis hampir semua konfigurasi.

Kami membuat pendengar yang mendengarkan agar konteks diperbarui (acara terakhir), setelah itu mengirim gagak. Kami akan mendengarkan ContextRefreshEvent .

 public class IronListener implements ApplicationListener<ContextRefreshedEvent> { @Override public void onApplicationEvent(ContextRefreshedEvent event) { System.out.println(" ..."); } } 


Kami menulis pendengar dalam konfigurasi pemula. Sejauh ini, hanya akan ada pendengar, tetapi besok pelanggan akan meminta beberapa infrastruktur lainnya, dan kami juga akan menuliskannya dalam konfigurasi ini.

 @Configuration public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } } 


Muncul pertanyaan: bagaimana cara membuat konfigurasi starter kita secara otomatis masuk ke semua aplikasi yang menggunakan starter ini?

Untuk semua kesempatan ada "memungkinkan sesuatu".



Sungguh, jika saya bergantung pada 20 permulaan, saya harus meletakkan @Enable ? Dan apakah starter memiliki beberapa konfigurasi? Kelas konfigurasi utama akan digantung dengan @Enable* , bagaimana pohon Tahun Baru?



Sebenarnya, saya ingin mendapatkan semacam inversi kontrol di tingkat ketergantungan. Saya ingin menghubungkan starter (sehingga semuanya berfungsi), dan tidak tahu apa-apa tentang bagian dalamnya. Karena itu, kami akan menggunakan spring.factories.



Jadi apa itu spring.factories


Dokumentasi mengatakan bahwa ada spring.factories di mana Anda perlu menunjukkan korespondensi antarmuka dan apa yang Anda perlu memuatnya - konfigurasi kami. Dan semua ini secara ajaib akan muncul dalam konteks, sementara berbagai kondisi akan bekerja pada mereka.



Jadi kita mendapatkan inversi kontrol, yang kami butuhkan.



Mari kita coba implementasikan. Alih-alih mengakses nyali starter yang saya hubungkan (ambil konfigurasi ini, dan ini ...), semuanya akan persis sebaliknya. Starter akan memiliki file bernama spring.factories . Dalam file ini kami meresepkan konfigurasi mana dari starter ini yang harus diaktifkan untuk semua orang yang telah mengunduhnya. Beberapa saat kemudian saya akan menjelaskan bagaimana sebenarnya ini bekerja di Spring Boot - pada titik tertentu mulai memindai semua guci dan mencari file spring.factories.

 org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.ironbank.IronConfiguration 


 @Configuration public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } } 


Sekarang yang tersisa untuk kita lakukan adalah menghubungkan starter di proyek.

 compile project(':iron-starter') 


Di pakar, sama - Anda perlu mendaftarkan ketergantungan.

Kami meluncurkan aplikasi kami. Burung gagak harus lepas landas pada saat naik, meskipun kami tidak melakukan apa pun dalam aplikasi itu sendiri. Dalam hal infrastruktur, kami, tentu saja, menulis dan mengkonfigurasi starter. Tapi dari sudut pandang pengembang, kami baru saja menghubungkan ketergantungan dan konfigurasi muncul - gagak terbang. Semua seperti yang kita inginkan.

Ini bukan sihir. Pembalikan kontrol seharusnya bukan sihir. Sama seperti penggunaan Spring seharusnya tidak menjadi sihir. Kita tahu bahwa ini adalah kerangka kerja terutama untuk inversi kontrol. Karena ada inversi kontrol untuk kode Anda, maka ada inversi kontrol untuk modul.

@SpringBootApplication di sekitar kepala


Ingatlah saat ketika kita membangun konteks dengan tangan kita. Kami menulis new AnnotationConfigApplicationContext dan melewati beberapa konfigurasi ke input, yang merupakan kelas java. Sekarang kita juga menulis SpringApplication.run dan lulus kelas di sana, yang merupakan konfigurasi, hanya itu ditandai dengan penjelasan lain yang agak kuat @SpringBootApplication , yang membawa seluruh dunia.

 @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @SpringBootConfiguration @EnableAutoConfiguration @ComponentScan(excludeFilters = { @Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class), @Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) }) public @interface SpringBootApplication { โ€ฆ } 

Pertama, di dalam ada @Configuration , yaitu konfigurasi. Anda dapat menulis @Bean dan, seperti biasa, daftarkan kacang.

Kedua, @ComponentScan berdiri di atasnya. Secara default, ini benar-benar memindai semua paket dan sub paket. Oleh karena itu, jika Anda mulai membuat layanan dalam paket yang sama atau dalam @Service - @Service - @Service , @RestController - mereka dipindai secara otomatis, karena konfigurasi utama memulai proses pemindaian Anda.

Sebenarnya, @SpringBootApplication tidak melakukan hal baru. Dia hanya mengkompilasi semua praktik terbaik yang ada di aplikasi Spring, jadi ini sekarang semacam komposisi anotasi, termasuk @ComponentScan .

Selain itu, masih ada hal-hal yang tidak ada sebelumnya - @EnableAutoConfiguration . Ini adalah kelas yang saya tentukan di spring.factories.
@EnableAutoConfiguration , jika Anda melihat, membawa @Import dengan itu:

 @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import({EnableAutoConfigurationImportSelector.class}) public @interface EnableAutoConfiguration {  String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";  Class<?>[] exclude() default {};  String[] excludeName() default {}; } 

Tugas utama @EnableAutoConfiguration adalah membuat impor yang ingin kita singkirkan dalam aplikasi kita, karena implementasinya seharusnya memaksa kita untuk menulis nama beberapa kelas dari starter. Dan kita hanya bisa mencari tahu dari dokumentasi. Tapi semuanya harus dengan sendirinya.

Anda harus memperhatikan kelas ini. Itu berakhir dengan ImportSelector . Di musim semi reguler, kami menulis Import(Some Configuration.class) beberapa konfigurasi dan memuat, seperti semua tanggungannya. Ini ImportSelector , ini bukan konfigurasi. ImportSelector semua pemula kami ke dalam konteks. Itu memproses penjelasan @EnableAutoConfiguration dari spring.factories, yang memilih konfigurasi mana yang akan dimuat, dan menambahkan kacang yang kami tentukan di IronConfiguration ke konteks.



Bagaimana dia melakukannya?

Pertama-tama, ia menggunakan kelas utilitas langsung, SpringFactoriesLoader, yang melihat spring.factories dan memuat semuanya dari apa yang diminta. Dia memiliki dua metode, tetapi mereka tidak jauh berbeda.



Spring Factories Loader ada di Spring 3.2, tidak ada yang menggunakannya. Itu tampaknya ditulis sebagai pengembangan potensial dari kerangka kerja. Dan itu tumbuh menjadi Spring Boot, di mana ada begitu banyak mekanisme menggunakan konvensi spring.factories. Kami akan menunjukkan lebih jauh bahwa, selain konfigurasi, Anda juga dapat menulis di spring.factories - pendengar, prosesor yang tidak biasa, dll.

 static <T> List<T> loadFactories( Class<T> factoryClass, ClassLoader cl ) static List<String> loadFactoryNames( Class<?> factoryClass, ClassLoader cl ) 

Inilah cara kerja inversi kontrol.Kita tampaknya mematuhi prinsip tertutup terbuka, yang dengannya kita tidak perlu mengubah sesuatu di suatu tempat setiap saat. Setiap starter membawa banyak hal berguna untuk proyek (sejauh ini kita hanya berbicara tentang konfigurasi yang dibawanya). Dan setiap starter dapat memiliki file sendiri bernama spring.factories. Dengan bantuannya, dia memberi tahu apa yang sebenarnya dia bawa. Dan di Spring Boot ada banyak mekanisme berbeda yang mampu dari semua permulaan untuk membawa apa yang dikatakan pegas.

Namun ada nuansa dalam keseluruhan skema ini. Jika kita mempelajari cara kerjanya di Spring itu sendiri, ketika orang-orang yang telah membuat seluruh skema permulaan ini menulis, kita akan melihat bahwa mereka memiliki satu ketergantungan org.springframework.boot:spring-boot-autoconfigure, ada garis dengan META-INF / spring. Pabrik-pabrik denganEnableAutoConfiguration , dan memiliki banyak konfigurasi (terakhir kali saya melihat, ada sekitar 80 konfigurasi otomatis yang tidak terhubung di sana).

 spring-boot-autoconfigure.jar/spring.factories</b> org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ org.springframework.boot.autoconfigure.aop.AopAutoConfiguration,\ org.springframework.boot.autoconfigure.amqp.RabbitAutoConfiguration,\ org.springframework.boot.autoconfigure.batch.BatchAutoConfiguration,\ org.springframework.boot.autoconfigure.cache.CacheAutoConfiguration.\ ... 

Yaitu, terlepas dari apakah saya menghubungkan starter atau tidak terhubung, ketika saya bekerja dengan Spring Boot, akan selalu ada salah satu jar-s (jar Spring Boot itu sendiri), di mana ada spring.factories pribadi, di mana 90 konfigurasi ditulis. Masing-masing konfigurasi ini dapat berisi banyak konfigurasi lain, misalnya CacheAutoConfiguration, berisi hal semacam itu - sesuatu yang ingin kami hindari:

 for (int i = 0; i < types.length; i++) { Imports[i] = CacheConfigurations.getConfigurationClass(types[i]); } return imports; 

Selain itu, kemudian beberapa peta secara statis dikeluarkan dari kelas di sana, dan konfigurasi yang dimuat (yang tidak ada di musim semi ini. Pabrik) di-hardcode dalam peta ini. Mereka tidak akan begitu mudah ditemukan.

 private static final Map<CacheType, Class<?>> MAPPINGS; static { Map<CacheType, Class<?>> mappings = new HashMap<CacheType, Class<?>>(); mappings.put(CacheType.GENERIC,    GenericCacheConfiguration.class); mappings.put(CacheType.EHCACHE,    EhCacheCacheConfiguration.class); mappings.put(CacheType.HAZELCAST,  HazelcastCacheConfiguration.class); mappings.put(CacheType.INFINISPAN, InfinispanCacheConfiguration.class); mappings.put(CacheType.JCACHE,     JCacheCacheConfiguration.class); mappings.put(CacheType.COUCHBASE,  CouchbaseCacheConfiguration.class); mappings.put(CacheType.REDIS,      RedisCacheConfiguration.class); mappings.put(CacheType.CAFFEINE,   CaffeineCacheConfiguration.class); addGuavaMapping(mappings); mappings.put(CacheType.SIMPLE,     SimpleCacheConfiguration.class); mappings.put(CacheType.NONE,       NoOpCacheConfiguration.class); MAPPINGS = Collections.unmodifiableMap(mappings); } 

Yang paling menarik adalah bahwa pada tahap boot mereka semua akan benar-benar mencoba untuk boot.



Mereka akan berusaha. Tetapi:



Untuk merangkum hasil sementara. Bagian dari konfigurasi - baik, baik, permulaan yang tepat, yang mengamati inversi kontrol dan prinsip tertutup terbuka - membawa pegas mereka. Pabrik-pabrik di mana nyali mereka ditulis. Kami akan melakukan hal itu, pada prinsipnya, kami tidak bisa melakukan sebaliknya.

Selain itu, ada bagian lain dari konfigurasi yang ditentukan dalam Boot Spring itu sendiri, yang selalu dimuat - ada lebih dari 90 konfigurasi. Ada juga 30 konfigurasi lebih yang hanya di-hardcode di Spring Boot.

Semua ini naik, dan kemudian konfigurasi mulai disaring. Pada akhir 2013 ada laporantentang apa yang baru di Spring 4, yang mengatakan bahwa anotasi muncul @Conditionalyang memungkinkan Anda untuk menulis kondisi di anotasi Anda yang merujuk ke kelas yang mengembalikan trueatau false. Tergantung pada ini, kacang dibuat atau tidak. Karena konfigurasi java di Spring juga kacang, Anda juga dapat mengatur kondisional yang berbeda di sana. Dengan demikian, konfigurasi dipertimbangkan, tetapi jika kembali bersyarat false, mereka akan dibuang.

Namun ada nuansa. Pertama-tama, ini mengarah pada situasi di mana nampan mungkin atau tidak, tergantung pada beberapa pengaturan lingkungan.



Anggap ini sebagai contoh.

Hukum Besi 1.2. Raven hanya dalam produksi


Pelanggan memiliki persyaratan baru. Gagak adalah hal yang mahal, jumlahnya tidak terlalu banyak. Karena itu, mereka perlu diluncurkan hanya jika kita tahu bahwa produksi telah meningkat.



Dengan demikian, pendengar yang meluncurkan gagak hanya boleh dibuat jika itu adalah produksi. Mari kita coba melakukannya.

Kami masuk ke konfigurasi dan menulis:

 @Configuration <b>@ConditionalOnProduction</b> public class IronConfiguration { @Bean public RavenListener ravenListener() { return new RavenListener(); } } 

Bagaimana kita memutuskan apakah ini produksi atau tidak? Saya punya satu perusahaan aneh yang mengatakan: "Jika Windows ada di mesin, itu berarti bukan produksi, tetapi jika bukan Windows, maka produksi." Setiap orang memiliki persyaratan mereka sendiri.

Secara khusus, Iron Bank mengatakan bahwa mereka ingin mengelola ini secara manual: ketika layanan naik, pop-up akan muncul: "produksi atau tidak." Kondisi seperti itu tidak disediakan di Spring Boot.

 @Retention(RUNTIME) @Conditional(OnProductionCondition.class) public @interface ConditionalOnProduction { } 

Kami membuat popap lama yang bagus:

 public class OnProductionCondition implements Condition { @Override public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {   return JOptionPane.showConfirmDialog(parentComponent: null, " ?") == 0; } } 

Ayo kita coba.



Kami meningkatkan layanan, klik ya di jendela, dan gagak terbang (pendengar dibuat).
Kami mulai lagi, menjawab Tidak, gagak tidak terbang.

Jadi, penjelasannya @Conditional(OnProductionCondition.class)mengacu pada kelas yang baru saja ditulis, di mana ada metode yang harus kembali trueatau false. Penyejuk udara seperti itu dapat diciptakan secara independen, yang membuat aplikasi sangat dinamis, memungkinkannya bekerja secara berbeda dalam kondisi yang berbeda.

Pazzler


Jadi @ConditionalOnProductionkami menulis. Kita dapat membuat beberapa konfigurasi, membuatnya dalam kondisi baik. Misalkan kita memiliki kondisi kita sendiri dan itu populer - suka @ConditionalOnProduction. Dan ada, misalnya, 15 biji yang dibutuhkan hanya dalam produksi. Saya menandai mereka dengan anotasi ini.

Pertanyaan: logika yang mengetahui apakah itu produksi atau tidak, berapa kali harus berhasil?
Apa bedanya? Yah, mungkin logika ini mahal, butuh waktu, dan waktu adalah uang.

Sebagai ilustrasi, kami memberikan sebuah contoh:

 @Configuration @ConditionalOn public class UndeadArmyConfiguration { ... } @Configuration public class DragonIslandConfiguration { @Bean @ConditionalOn public DragonGlassFactory dragonGlassFactory() {  return new DragonGlassFactory(); } ... } 

Di sini kita memiliki dua nampan: satu reguler, satu konfigurasi. Keduanya ditandai dengan anotasi bersyarat - mereka diperlukan hanya jika musim dingin telah tiba.

Anotasi harganya dua kali. Setiap panggilan ke pusat cuaca di dunia Game of Thrones itu mahal - Anda harus membayar uang setiap kali untuk mengetahui cuaca apa itu.



Jika ini berhasil dengan caching, logikanya akan dipanggil hanya sekali (yaitu, OnProductionCondition.classitu akan dipanggil sekali, jendela dengan pilihan akan muncul sekali - produksi atau tidak). Pekerjaan yang konsisten terlihat logis. Di sisi lain, konfigurasi dibuat pada satu titik waktu, dan kacang lain dapat dibuat dalam beberapa detik ketika ada perubahan. Bagaimana jika musim dingin tiba dalam 5 detik ini?

Jawaban yang benartidak begitu jelas - 300 atau 400. Sebenarnya ada semacam permainan lengkap. Kami mencari-cari untuk waktu yang sangat lama untuk memahami apa yang terjadi. Bagaimana itu terjadi adalah masalah yang terpisah.

Situasinya seperti ini. Jika kondisinya di atas kelas di atas (kelas @Component, @Configurationatau @Servicekondisinya juga dengan itu), maka memenuhi tiga kali untuk setiap bin tersebut. Apalagi, jika konfigurasi ini terdaftar di starter, maka dua kali.

 @Configuration @ConditionalOn public class UndeadArmyConfiguration { ... } 

Jika tempat sampah terdaftar di dalam konfigurasi, maka selalu sekali.

 @Configuration public class DragonIslandConfiguration { @Bean @ConditionalOn public DragonGlassFactory dragonGlassFactory() {  return new DragonGlassFactory(); } ... } 

Oleh karena itu, teka-teki ini tidak memiliki jawaban yang pasti, karena Anda perlu mencari tahu di mana konfigurasi ditulis. Jika terdaftar di starter, maka kondisinya untuk beberapa alasan akan berfungsi dua kali, dan kondisi untuk bin dalam hal apa pun akan bekerja sekali, kita mendapatkan 300. Tetapi jika konfigurasi tidak di starter, hanya kondisinya akan mulai tiga kali, ditambah satu kali lagi untuk bin . Kami mendapat 400. Muncul

pertanyaan: bagaimana ini bekerja dan mengapa bisa seperti itu? Dan jawaban saya hanya ini:



Tidak masalah bagaimana cara kerjanya. Penting untuk memahami yang berikut: ketika Anda menulis penjelasan kondisi Anda, perlu melakukan caching di dalamnya sendiri, dan melalui bidang statis sehingga logika tidak dipanggil berkali-kali. Karena walaupun Anda menggunakan anotasi ini satu kali, logikanya akan berhasil lebih dari sekali.

Hukum Besi 1.3. Raven di


Kami terus mengembangkan starter kami. Kita harus entah bagaimana menentukan penerbangan gagak.



Dalam file apa kita meresepkan hal-hal untuk pemula? Permulaan membawa konfigurasi di mana ada kacang. Bagaimana kacang ini dikonfigurasi? Di mana mereka mendapatkan sumber data, pengguna, dll. Secara alami, mereka memiliki default untuk semua kesempatan, tetapi bagaimana mereka membiarkan ini didefinisikan ulang? Ada dua opsi: application.propertiesdan application.yml. Di sana Anda dapat memasukkan beberapa informasi yang akan tetap lengkap dengan indah di IDEA.

Apa yang membuat starter kita lebih buruk? Siapa pun yang menggunakannya juga harus dapat mengetahui di mana alamat gagak terbang - kita perlu membuat daftar penerima. Ini yang pertama.

Kedua, kami ingin pendengar tidak dibuat dan gagak tidak dikirim jika orang tersebut tidak mendaftarkan penerima. Kami membutuhkan kondisi tambahan untuk membuat pendengar yang dikirim gagak. Yaitustarter itu sendiri diperlukan karena dapat memiliki banyak hal berbeda selain gagak. Tetapi jika tidak tertulis di mana burung gagak harus terbang, maka burung itu tidak diciptakan.

Dan yang ketiga - kami juga ingin pelengkapan otomatis, sehingga orang yang menarik starter kami ke dirinya sendiri mendapatkan pujian pada semua properti yang dibaca starter.

Untuk masing-masing tugas ini, kami memiliki alat kami sendiri. Tapi pertama-tama, Anda perlu melihat penjelasan yang ada. Mungkin sesuatu akan cocok untuk kita?

 @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication ... 

Memang, ada beberapa hal di sini yang akan membantu kita. Di tempat pertama @ConditionalOnProperty. Ini adalah kondisi yang berfungsi jika ada properti atau properti tertentu dengan beberapa nilai yang ditentukan dalam application.yml. Demikian pula, kita harus @ConfigurationalPropertymembuat pelengkapan otomatis.

Pelengkapan otomatis


Kita harus memastikan bahwa semua properti mulai terisi otomatis. Alangkah baiknya jika ini akan melengkapi otomatis tidak hanya di antara orang-orang yang akan mendaftarkan mereka di application.yml mereka, tetapi juga di starter kami.

Sebut saja properti kami "gagak". Dia harus tahu ke mana harus terbang.

 @ConfigurationProperties("") public class RavenProperties { List<String> ; } 

IDEA memberi tahu kami bahwa ada sesuatu yang salah di sini:



Dokumentasi mengatakan bahwa kami belum menambahkan dependensi (di Maven tidak akan ada referensi ke dokumentasi, tetapi tombol โ€œtambah ketergantunganโ€). Cukup tambahkan ke proyek Anda.

 subproject { dependencies { compileOnly 'org.springframework.boot:spring-boot-configuration-processor' compile 'org.springframework.boot: spring-boot-starter' } } 

Sekarang, menurut IDEA, kami memiliki segalanya.

Saya akan menjelaskan jenis kecanduan apa yang kami tambahkan. Semua orang tahu apa itu prosesor anotasi. Dalam bentuk yang disederhanakan, ini adalah hal yang dapat melakukan sesuatu pada tahap kompilasi. Sebagai contoh, Lombok memiliki prosesor anotasi sendiri, yang menghasilkan banyak kode berguna pada tahap kompilasi - setter, getter.

Di mana autocomplet pada properti berasal dari yang ada di properti aplikasi? Ada file JSON yang bisa digunakan IDEA. File ini menjelaskan semua properti yang harus dapat dikompilasi secara otomatis oleh IDEA. Jika Anda menginginkan properti yang Anda buat sebagai starter, IDEA juga dapat melakukan kompilasi otomatis, Anda memiliki dua cara:

  • Anda dapat secara manual masuk ke JSON ini sendiri dan menambahkannya dalam format tertentu;
  • Anda dapat menarik prosesor anotasi dari Spring Boot, yang dapat menghasilkan bagian JSON ini sendiri pada tahap kompilasi. Properti apa yang harus ditambahkan di sana ditentukan oleh penjelasan Spring Boot ajaib, yang dengannya kita dapat menandai kelas yang merupakan pemilik properti. Pada tahap kompilasi, prosesor anotasi Spring Boot menemukan semua kelas yang ditandai @ConfigurationalProperties, membaca properti nama dari mereka, dan menghasilkan JSON. Akibatnya, semua orang yang akan bergantung pada starter akan menerima JSON ini sebagai hadiah.

Anda juga perlu mengingat @EnableConfigurationPropertiesbahwa kelas ini muncul di dalam konteks Anda sebagai kacang.

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction public RavenListener ravenListener() { return new RavenListener(); } } 

Semuanya tidak terlihat sangat bagus, tetapi Anda perlu melakukan ini agar itu muncul sedikit lebih awal dari sisa kacang (karena sisa kacang menggunakan properti untuk mengkonfigurasi sendiri).

Akibatnya, perlu untuk menempatkan dua anotasi:

  • @EnableConfigurationProperties dengan menunjukkan properti siapa;
  • @ConfigurationalProperties mengatakan awalan apa.

Dan seseorang tidak boleh melupakan getter dan setter. Mereka juga penting, jika tidak ada yang berhasil - tindakan tidak naik.

Akibatnya, kami memiliki file yang pada prinsipnya dapat ditulis secara manual. Tetapi tidak ada yang suka menulis secara manual.

 { "hints": [], "groups": [ { "sourceType": "com.ironbank.RavenProperties", "name": "", "type": "com.ironbankRavenProperties" } ], "properties": [ { "sourceType": "com.ironbank.RavenProperties", "name": ".", "type": "java.util.List<java.lang.String>" } ] } 

Alamat gagak


Kami melakukan bagian pertama dari tugas - kami mendapat beberapa properti. Tapi belum ada yang terkait dengan properti ini. Sekarang mereka harus ditetapkan sebagai syarat untuk membuat pendengar kita.

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") public RavenListener ravenListener() { return new RavenListener(); } } 


Kami menambahkan satu syarat lagi - gagak harus dibuat hanya dengan syarat bahwa di suatu tempat seseorang mengatakan ke mana harus terbang.
Sekarang kita akan menulis di mana harus terbang di application.yml.

 spring: application.name: money-raven jpa.hibernate.ddl-auto: validate ironbank: ---:   -  : -: ,   : true 

Tetap memberi resep dalam logika bahwa dia terbang ke tempat dia diberitahu.

Untuk melakukan ini, kita dapat menghasilkan konstruktor. Musim semi baru memiliki injeksi konstruktor - ini adalah cara yang disarankan. Eugene suka @Autowiredmembuat segala sesuatu muncul di aplikasi melalui refleksi. Saya suka mengikuti konvensi yang ditawarkan Spring:

 @RequiredArgsConstructor public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; @Override public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" โ€ฆ " + s); }); } } 

Tapi itu tidak gratis. Di satu sisi Anda mendapatkan perilaku yang dapat diverifikasi, di sisi lain Anda mendapatkan beberapa wasir.

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

Tidak ada tempat @Aurowired, dengan Spring 4.3 Anda tidak dapat menginstalnya. Jika ada satu konstruktor tunggal, itu @Aurowired. Dalam hal ini, anotasi digunakan @RequiredArgsConstructor, yang menghasilkan konstruktor tunggal. Ini sama dengan perilaku ini:

 public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; public RavenListener(RavenProperties ravenProperties) { this.ravenProperties = ravenProperties; } public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" โ€ฆ " + s); }); } } 

Spring menyarankan untuk menulis dengan cara ini atau menggunakan Lombok. Jurgen Holler, yang telah menulis 80% kode Spring sejak 2002, menyarankan Anda untuk mengaturnya @Aurowiredagar terlihat (jika tidak, kebanyakan orang melihat dan tidak melihat injeksi).

 public class RavenListener implements ApplicationListener<ContextRefreshedEvent>{ private final RavenProperties ravenProperties; @Aurowired public RavenListener(RavenProperties ravenProperties) { this.ravenProperties = ravenProperties; } public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println(" โ€ฆ " + s); }); } } 

Bagaimana kami membayar untuk pendekatan ini? Kami harus menambahkan RavenPropertieske konfigurasi Java. Dan jika saya letakkan di @Aurowiredatas lapangan, tidak ada yang harus diubah.

Jadi, burung gagak dikirim. Kami menyelesaikan tugas, yang memungkinkan pengguna starter kami memiliki pujian dalam konfigurasi mereka, sementara kami mendapat nampan yang hidup dan mati tergantung pada konfigurasi ini.

Hukum Besi 1.4. Gagak kustom




Kebetulan Anda perlu menyesuaikan perilaku starter. Sebagai contoh, kita memiliki gagak hitam kita sendiri. Dan kami membutuhkan yang putih yang merokok, dan kami ingin mengirimkannya agar orang melihat asap di cakrawala.

Mari kita beralih dari alegori ke kehidupan nyata. Pemula membawakan saya banyak kacang infrastruktur, dan itu hebat. Tapi saya tidak suka bagaimana mereka dikonfigurasi. Saya masuk ke properti aplikasi, dan mengubah sesuatu di sana, dan sekarang saya suka semuanya. Tetapi ada situasi ketika pengaturannya sangat rumit sehingga lebih mudah untuk mendaftarkan sumber data sendiri daripada mencoba mencari tahu properti aplikasi. Artinya, kami ingin mendaftarkan sumber data kami sendiri di nampan yang diterima dari starter. Lalu apa yang akan terjadi?

Saya mendaftarkan sesuatu sendiri, dan starter membawakan saya sumber data saya. Apakah saya punya dua sekarang? Atau akankah seseorang menghancurkan satu (dan yang mana?)

Kami ingin menunjukkan kepada Anda kondisi lain yang memungkinkan starter untuk membawa semacam nampan hanya jika orang yang menggunakan starter tidak memiliki nampan seperti itu. Ternyata, ini benar-benar nontrivial.

Ada sejumlah besar kondisi yang telah dibuat kepada kami:

 @ConditionalOnBean @ConditionalOnClass @ConditionalOnCloudPlatform @ConditionalOnExpression @ConditionalOnJava @ConditionalOnJndi @ConditionalOnMissingBean @ConditionalOnMissingClass @ConditionalOnNotWebApplication @ConditionalOnProperty @ConditionalOnResource @ConditionalOnSingleCandidate @ConditionalOnWebApplication ... 

Pada prinsipnya, @ConditionalOnMissingBeanada juga, jadi gunakan saja yang sudah jadi. Mari kita masuk ke konfigurasi, di mana kami mengindikasikan bahwa itu harus dibuat hanya jika belum ada yang membuat tempat sampah seperti itu sebelumnya.

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnMissingBean</b> public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

Jika Anda membuka sebagian besar permulaan, Anda akan melihat bahwa setiap nampan, setiap konfigurasi digantung dengan sekumpulan anotasi. Kami hanya mencoba membuat analog.

Ketika mencoba untuk meluncurkan gagak tidak pergi, tetapi Acara muncul, yang kami tulis di pendengar baru kami - MyRavenListener.



Ada dua poin penting di sini.
Poin pertama adalah bahwa kita terhubung dari pendengar yang ada, dan tidak menulis pendengar di sana:

 @Component public class MyRavenListener implements ApplicationListener { public MyRavenListener(RavenProperties ravenProperties) { super(ravenProperties); } @Override public void onApplicationEvent(ContextRefreshedEvent event) { ravenProperties.get().forEach(s -> { System.out.println("event = " + event); }); } } 

Kedua - kami membuatnya dengan bantuan komponen. Jika kami membuatnya dalam konfigurasi Java, mis. akan mendaftarkan kelas yang sama dengan kacang konfigurasi, tidak ada yang berhasil bagi kami.

Jika saya membersihkan extendsdan hanya membuat semacam pendengar aplikasi, itu @ConditionalOnMissingBeantidak akan berhasil. Tapi sejak itu kelas ini juga dipanggil, ketika kita mencoba membuatnya, kita dapat menulis ravenListener- sama seperti yang kita miliki di konfigurasi kita. Di atas, kami fokus pada fakta bahwa nama kacang di konfigurasi Java akan dengan nama metode. Dan dalam hal ini, kita membuat sebuah bin bernama ravenListener.

Mengapa Anda perlu tahu semua ini? Dengan Spring Boot, semuanya biasanya super, tetapi hanya di awal. Ketika proyek bergerak maju, satu starter muncul, yang kedua, yang ketiga. Anda mulai menulis beberapa hal dengan tangan Anda, karena bahkan starter terbaik tidak akan memberikan apa yang Anda butuhkan. Dan konflik bin dimulai. Oleh karena itu, ada baiknya jika Anda memiliki setidaknya ide umum tentang cara memastikan bahwa satu kacang tidak dibuat, dan bagaimana mendaftarkan kacang di rumah Anda sehingga starter tidak membawa konflik (atau Anda memiliki dua starter yang membawa satu dan yang sama bin yang sama sehingga mereka tidak saling bertentangan). Untuk menyelesaikan konflik, saya menulis kacang saya, yang akan memastikan bahwa yang pertama maupun yang kedua tidak dibuat.

Selain itu, konflik kacang adalah situasi yang baik karena Anda melihatnya. Jika kami menentukan nama nampan yang sama, kami tidak akan mengalami konflik. Satu kacang hanya akan menimpa yang lain. Dan Anda akan mengerti untuk waktu yang lama di mana ada apa. Sebagai contoh, jika kita membuat semacam sumber data @Bean, itu akan menimpa sumber data yang ada @Bean.
Ngomong-ngomong, jika starter membawa apa yang tidak Anda butuhkan, buat saja tempat sampah dengan ID yang sama dan hanya itu. Benar, maka jika starter di beberapa versi mengubah nama metode, maka itu saja, bin Anda akan menjadi dua.

ConditionalOnPuzzler


Kami memiliki @ConditionalOnClass, @ConditionalOnMissingBeanmungkin ada kelas menulis. Sebagai contoh, perhatikan konfigurasi eksekusi.

Ada sabun, ada tali - kita gantung di tiang gantungan. Ada kursi dan arus - logis untuk meletakkan seseorang di kursi. Ada guillotine dan suasana hati yang baik - itu berarti Anda harus memotong kepala.

 @Configuration public class  { @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public  () { return new ("..."); } @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public  c() { return new (" "); } @Bean @ConditionalOnClass({.class, .class}) @ConditionalOnMissingBean({.class}) public  () { return new (" "); } } 

Bagaimana kita akan mengeksekusinya?

Pertanyaan: Bagaimana cara mengetik anotasi @ConditionalOnMissingClassbekerja secara umum ?

Misalkan saya punya metode yang akan membuat tiang gantungan. Tapi tempat sampah hanya boleh dibuat jika ada sabun dan tali. Tapi tidak ada sabun. Bagaimana saya bisa mengerti bahwa tidak ada sabun atau hanya tali. Apa yang terjadi jika saya mencoba membaca anotasi dari suatu metode, dan anotasi ini merujuk ke kelas yang tidak? Bolehkah saya mengambil anotasi semacam itu?

Opsi Jawaban:
  • ClassDefNotFound? , . - , ClassDefNotFound , reflection- , conditional as long as;
  • , . reflection . , .
  • ;
  • .

: , . reflection . exception, , โ€” . reflection? , , , , โ€” ClassDefNotFound .

Ini akan bekerja menggunakan ASM. Melihat hal itu melalui refleksi - tidak ada sama sekali, Spring akan mengurai bytecode, dengan syarat, secara manual. Dia membaca file tersebut agar tidak mengunduh file ini sebelum waktunya, dan mengerti apa yang ada @Conditionaldengan sabun, tali. Dia sudah dapat memeriksa keberadaan kelas-kelas ini dalam konteks secara terpisah. Tetapi ASM, seperti yang mereka katakan, bukan tentang kecepatan. Ini adalah kesempatan untuk membaca kelas tanpa memuatnya dan memahami informasi metode.

Tetapi Juergen Hoeller merekomendasikan untuk tidak terikat pada nama kelas, menentukan kondisi, meskipun pada kenyataannya ada anotasi OnMissingClassyang dapat menggunakan nama kelas (String) sebagai parameter. Jika Anda mengikuti rekomendasi ini, semuanya bekerja lebih cepat, dan ASM tidak diperlukan. Tapi dilihat dari sumbernya, tidak ada yang melakukan itu.

Hukum Besi 1.5. Nyalakan dan matikan gagak




Kami membutuhkan properti lain - kemampuan untuk mengaktifkan atau menonaktifkan gagak secara manual. Agar tidak mengirim siapa pun dijamin. Ini adalah kondisi terakhir yang kami tunjukkan kepada Anda.

Starter kami tidak memberikan apapun kecuali gagak. Karena itu, Anda mungkin bertanya, mengapa bisa dinyalakan / dimatikan, bisakah Anda tidak menerimanya? Tetapi di bagian kedua, hal-hal berguna tambahan akan dimasukkan ke dalam starter ini. Secara khusus, gagak mungkin tidak diperlukan - mahal, bisa dimatikan. Pada saat yang sama, itu tidak terlalu baik untuk menghapus titik akhir di mana untuk mengirimnya - itu terlihat seperti tongkat penyangga.

Karena itu, kami akan melakukan semuanya @ConditionalOnProperty(".").

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnProperty(".") public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

Dan dia bersumpah pada kita bahwa ini tidak dapat dilakukan: Duplikat anotasi. Masalahnya adalah bahwa jika kita memiliki anotasi dengan beberapa parameter, itu tidak dapat diulang. Kami tidak dapat melakukan ini pada dua properti.

Kami memiliki metode untuk anotasi ini, ada String, dan ini adalah array - Anda dapat menentukan beberapa properti di sana.

 @Conditional(OnPropertyCondition.class) public @interface ConditionalOnProperty { String[] value() default {}; String prefix() default ""; String[] name() default {}; String havingValue() default ""; boolean matchIfMissing() default false; boolean relaxedNames() default true; } 

Dan semuanya baik-baik saja, sampai Anda mencoba untuk menyesuaikan nilai untuk setiap elemen dalam array ini secara terpisah. Kami memiliki properti , yang seharusnya falsedan beberapa properti lain, yang harus stringdengan nilai tertentu. Tetapi Anda hanya dapat menentukan satu nilai di semua properti.

Yaitu, Anda tidak bisa melakukan ini:

 @ConditionalOnProduction @ConditionalOnProperty(name = ".", havingValue="true") @ConditionalOnProperty(name = ".",  havingValue="true") @ConditionalOnProperty(name = ".",havingValue="false") public IronBankApplicationListener applicationListener() { ... } 

 @ConditionalOnProduction @ConditionalOnProperty( name = {    ".",    ".",    "." }, havingValue = "true" ) public IronBankApplicationListener applicationListener() { ... } 

Yang disorot di sini bukan array.

Ada penyimpangan yang memungkinkan Anda untuk bekerja dengan beberapa properti, dengan hanya satu nilai untuk mereka: AllNestedConditionsdan AnyNestedCondition.



Terus terang, ini terlihat aneh. Tapi itu berhasil. Mari kita coba membuat konfigurasi - kondisi baru yang akan mempertimbangkan keduanya .dan ..

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnProperty(".") @ConditionalOnRaven public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

Kami menempatkan anotasi kami @Conditional()dan di sini kami harus mendaftarkan beberapa kelas.

 @Retention(RUNTIME) @Conditional({OnRavenCondional.class}) public @interface CondionalOnRaven { } 


Kami membuatnya.

 public class OnRavenCondional implements Condition { } 

Selanjutnya, kami harus menerapkan semacam pengkondisian, tetapi kami mungkin tidak melakukan ini karena kami memiliki anotasi berikut:

 public class CompositeCondition extends AllNestedConditions { @ConditionalOnProperty(  name = ".",  havingValue = "false") public static class OnRavenProperty { } @ConditionalOnProperty(  name = ".enabled",  havingValue = "true",  matchIfMissing = true) public static class OnRavenEnabled { } ... } 

Kami memiliki satu komposit Conditional, yang juga akan diwarisi dari kelas lain - baik AllNestedConditions, atau AnyNestedCondition- dan itu akan berisi kelas-kelas lain yang berisi anotasi biasa dengan bumbu. Yaitusebaliknya @Conditionkita harus menentukan:

 public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } } 

Dalam hal ini, Anda perlu membuat konstruktor di dalamnya.

Sekarang kita harus membuat kelas statis di sini. Kami membuat semacam kelas (sebut saja R).

 public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } public static class R {} } 

Kami membuat nilai kami (pasti persis true).

 public class OnRavenCondional extends AllNestedConditions { public OnRavenCondional() { super(ConfigurationPhase.REGISTER_BEAN); } @ConditionalOnProperty(".") public static class R {} @ConditionalOnProperty(value= ".", havingValue = "true") public static class C {} } 

Untuk mengulanginya, ingat nama kelasnya. Musim semi memiliki dermaga Jawa yang bagus. Anda dapat meninggalkan IDEA, membaca Java dock, dan memahami apa yang perlu dilakukan.

Kami mengatur milik kita @ConditionalOnRaven. Pada prinsipnya, Anda dapat membungkus keduanya @ConditionalOnProduction, dan , dan @ConditionalOnMissingBean, tetapi sekarang kami tidak akan melakukan ini. Lihat saja apa yang terjadi.

 @Configuration @EnableConfigurationProperties(RavenProperties.class) public class IronConfiguration { @Bean @ConditionalOnProduction @ConditionalOnRaven @ConditionalOnMissingBean public RavenListener ravenListener(RavenProperties r) { return new RavenListener(r); } } 

Dengan tidak adanya gagak tidak harus terbang. Dia tidak terbang.
Saya tidak ingin bertaruh , karena kita harus terlebih dahulu membuat autocomplete - ini adalah salah satu persyaratan kami.

 @Data @ConfigurationalProperties("") public class RavenProperties { List<String> ; boolean ; } 

Itu saja. secara default false, set trueke
application.yml:

 jpa.hibernate.ddl-auto: validate ironbank: ---: -  : : ,   : true 

Kami meluncurkan, dan gagak kami terbang.

Dengan demikian, kita dapat membuat anotasi komposit dan memasukkan sejumlah anotasi yang ada, bahkan jika mereka tidak dapat diulang. Ini berfungsi di semua Java. Itu akan menyelamatkan kita.

Di bagian kedua artikel, yang akan dirilis dalam beberapa hari ke depan, kami akan fokus pada profil dan seluk-beluk meluncurkan aplikasi.




Menit periklanan.Konferensi Joker 2018 akan diadakan pada 19-20 Oktober, di mana Evgeny Borisov, bersama dengan Baruch Sadogursky, akan membuat presentasi berjudul "Petualangan Senor Holmes dan Junior Watson di Dunia Pengembangan Perangkat Lunak [Edisi Joker]" , dan Kirill Tolkachev dan Maxim Gorelikov akan menyajikan laporan "Micronaut vs Spring Boot" Atau siapa yang terkecil di sini? โ€ . Secara umum, akan ada banyak laporan yang lebih menarik dan patut diperhatikan di Joker. Tiket dapat dibeli di situs resmi konferensi.

Dan kami juga memiliki survei kecil untuk Anda!

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


All Articles