Ketika proyek berskala besar mengalami pembaruan berskala besar, semuanya tidak pernah sederhana: mau tidak mau ada nuansa yang tidak jelas (dengan kata lain, rake). Dan kemudian, tidak peduli sebagus apa pun dokumentasi itu, hanya pengalaman - pengalaman Anda sendiri atau orang lain - yang akan membantu.
Pada konferensi Joker 2018, saya berbicara tentang masalah apa yang saya temui ketika beralih ke Spring Boot 2 dan bagaimana mereka dipecahkan. Dan sekarang khusus untuk Habr - versi teks dari laporan ini. Untuk kenyamanan, pos memiliki rekaman video dan daftar isi: Anda tidak dapat membaca semuanya, tetapi langsung menuju ke masalah yang membuat Anda khawatir.
Daftar isi
Hari baik! Saya ingin memberi tahu Anda tentang beberapa fitur (sebut saja garu) yang mungkin Anda temui saat memperbarui kerangka Boot Spring ke versi kedua dan operasi selanjutnya.
Nama saya Vladimir Plizgá (
GitHub ), saya bekerja untuk CFT, salah satu pengembang perangkat lunak terbesar dan tertua di Rusia. Selama beberapa tahun terakhir, saya telah melakukan pengembangan backend di sana, bertanggung jawab atas pengembangan teknis bank kartu prabayar online. Di proyek inilah saya menjadi penggagas dan pelaksana transisi dari arsitektur monolitik ke arsitektur layanan mikro (yang masih berlangsung). Ya, karena sebagian besar pengetahuan yang saya putuskan untuk dibagikan kepada Anda telah terakumulasi pada contoh proyek khusus ini, saya akan memberi tahu Anda sedikit lebih banyak tentangnya.
Secara singkat tentang produk eksperimental
Ini adalah bank Internet yang melayani sendiri lebih dari dua lusin perusahaan mitra di seluruh Rusia: bank ini memberikan kemampuan kepada pelanggan akhir untuk mengelola uang mereka melalui layanan perbankan jarak jauh (aplikasi mobile, situs). Salah satu mitra adalah Beeline dan kartu pembayarannya. Ternyata Internet banking yang baik, dilihat dari peringkat
Markswebb Mobile Banking Rank , di mana produk kami mengambil posisi yang baik untuk pemula.
"Nyali" masih dalam masa transisi, jadi kami memiliki satu monolit, yang disebut inti, di mana sekitar 23 layanan mikro dibangun. Di dalamnya, microservices Spring Cloud Netflix, Spring Integration, dan banyak lagi. Dan pada Spring Boot 2, semua ini telah terbang sejak sekitar bulan Juli. Dan tepat di tempat ini kita tinggal lebih detail. Menerjemahkan proyek ini ke versi kedua, saya menemukan beberapa fitur yang ingin saya ceritakan.
Garis besar laporan

Ada banyak area di mana fitur Spring Boot 2 muncul, kami akan mencoba untuk membahas semuanya. Untuk melakukan ini dengan cepat, kita membutuhkan detektif atau penyelidik berpengalaman - seseorang yang akan menggali semua ini seolah-olah untuk kita. Karena Holmes dan Watson sudah
melakukan presentasi di Joker, kami akan dibantu oleh spesialis lain, Letnan Colombo. Silakan!
Boot musim semi / 2
Pertama, beberapa kata tentang Spring Boot secara umum dan versi kedua pada khususnya. Pertama, versi ini dirilis, dengan kata lain, bukan kemarin: pada 1 Maret 2018, sudah dalam Ketersediaan Umum. Salah satu tujuan utama yang dikejar pengembang adalah untuk sepenuhnya mendukung Java 8 di tingkat sumber. Yaitu, itu tidak dapat dikompilasi pada versi yang lebih kecil, meskipun runtime kompatibel. Kerangka Spring dari versi kelima, yang dirilis sedikit lebih awal dari Spring Boot 2, diambil sebagai dasarnya, dan ini bukan satu-satunya ketergantungan. Dia juga memiliki konsep seperti
BOM (Bill Of Material) - ini adalah XML besar, yang mencantumkan semua (transitif untuk kita) semua dependensi pada semua jenis perpustakaan pihak ketiga, kerangka kerja tambahan, alat, dan banyak lagi.
Dengan demikian, tidak semua efek khusus yang dibawa oleh Spring Boot kedua berasal dari dirinya sendiri atau dari ekosistem Spring. Dua dokumen bagus telah ditulis untuk seluruh peternakan ini:
Catatan Rilis dan
Panduan Migrasi . Dokumen itu keren, Musim semi dalam pengertian ini umumnya dilakukan dengan baik. Tetapi, untuk alasan yang jelas, adalah jauh dari mungkin untuk mencakup semuanya di sana: ada beberapa hal khusus, penyimpangan, dll. Yang tidak dapat atau tidak boleh dimasukkan di sana. Kami akan berbicara tentang fitur tersebut.
Waktu kompilasi. API mengubah contoh
Mari kita mulai dengan menyapu lebih atau kurang sederhana dan jelas: ini adalah yang muncul dalam waktu kompilasi. Artinya, sesuatu yang bahkan tidak akan memungkinkan Anda untuk mengkompilasi proyek jika Anda hanya mengubah angka 1 di skrip Boot menjadi 2.
Sumber utama perubahan, yang menjadi dasar untuk pengeditan tersebut di Spring Boot, tentu saja adalah transisi Spring ke Java 8. Selain itu, tumpukan web Spring 5 dan Spring Boot 2 terpecah, secara relatif, menjadi dua. Sekarang servlet, tradisional untuk kita, dan reaktif. Selain itu, perlu untuk mempertimbangkan sejumlah kekurangan dari versi sebelumnya. Perpustakaan pihak ketiga dimulai (dari luar Spring). Jika Anda melihat Catatan Rilis, Anda tidak akan melihat jebakan dengan cepat dan, terus terang, ketika saya pertama kali membaca Catatan Rilis, saya merasa bahwa semuanya baik-baik saja di sana. Dan bagi saya itu terlihat seperti ini:
Tapi, seperti yang Anda duga, semuanya tidak begitu baik.
Kompilasi akan pecah (contoh 1):- Mengapa : kelas
WebMvcConfigurerAdapter
sudah tidak ada lagi; - Mengapa : untuk mendukung chip Java 8 (metode default di antarmuka);
- Apa yang harus dilakukan : gunakan antarmuka
WebMvcConfigurer
.
Sebuah proyek mungkin tidak dapat mengkompilasi setidaknya karena fakta bahwa beberapa kelas tidak lagi ada. Mengapa Ya, karena di Jawa 8 tidak diperlukan. Jika ini adalah adapter dengan implementasi metode primitif, maka tidak ada yang istimewa untuk dijelaskan, metode default menyelesaikan semua ini dengan sempurna. Berikut ini adalah contoh dari kelas ini, jelas bahwa itu sudah cukup untuk menggunakan antarmuka itu sendiri, dan tidak ada adaptor yang diperlukan.
Kompilasi apa yang akan rusak (contoh 2):- Mengapa : metode
PropertySourceLoader#load
mulai mengembalikan daftar sumber alih-alih satu; - Mengapa : untuk mendukung sumber daya multi-dokumen, misalnya, YAML;
- Apa yang harus dilakukan : bungkus jawaban dalam
singletonList()
(saat diganti).
Contoh dari area yang sama sekali berbeda. Beberapa metode bahkan telah mengubah tanda tangan. Jika Anda pernah menggunakan metode load PropertySourceLoader, maka sekarang akan mengembalikan koleksi. Dengan demikian, ini memungkinkan dukungan sumber daya multi-dokumen. Misalnya, dalam YAML, melalui tiga tanda hubung, Anda dapat menentukan banyak dokumen dalam satu file. Jika sekarang Anda perlu bekerja dengannya dari Jawa, perlu diingat bahwa ini harus dilakukan melalui koleksi.
Kompilasi apa yang akan rusak (contoh 3):- Mengapa : beberapa kelas dari paket
org.springframework.boot.autoconfigure.web
telah org.springframework.boot.autoconfigure.web
dari paket .servlet
- .servlet
dan .reactive
; - Why : untuk mendukung jet stack setara dengan tradisional;
- Apa yang harus dilakukan : perbarui impor.
Bahkan lebih banyak perubahan telah diperkenalkan sehingga menumpuk. Misalnya, apa yang dulunya ada dalam paket web yang sama kini telah dipecah menjadi dua paket dengan banyak kelas. Ini adalah
.servlet
dan
.reactive
. Mengapa ini dilakukan? Karena jet stack tidak seharusnya menjadi penopang besar di atas servlet. Itu perlu untuk melakukan ini sehingga mereka dapat mempertahankan siklus hidup mereka sendiri, berkembang ke arah mereka sendiri dan tidak saling mengganggu. Apa yang harus dilakukan? Cukup untuk mengubah impor: sebagian besar kelas ini tetap kompatibel di tingkat API. Sebagian besar, tetapi tidak semua.
Kompilasi apa yang akan rusak (contoh 4):- Mengapa : tanda tangan metode dari kelas
ErrorAttributes
telah ErrorAttributes
: alih-alih RequestAttributes
, WebRequest(servlet)
dan ServerRequest(reactive)
mulai digunakan; - Why : untuk mendukung jet stack setara dengan tradisional;
- Apa yang harus dilakukan : ganti nama kelas dalam tanda tangan.
Misalnya, di kelas ErrorAttributes, sekarang, alih-alih RequestAttributes, dua kelas lain mulai digunakan dalam metode: ini adalah WebRequest dan ServerRequest. Alasannya sama. Dan apa yang harus dilakukan? Jika Anda beralih dari Boot Musim Semi yang pertama ke yang kedua, maka Anda perlu mengubah RequestAttributes ke WebRequest. Nah, jika Anda sudah menggunakan yang kedua, maka gunakan ServerRequest. Tentunya, bukan?
Bagaimana menjadi
Ada beberapa contoh seperti itu, kami tidak akan memilah semuanya. Apa yang harus dilakukan? Pertama-tama, perlu dicermati Panduan Migrasi Spring Boot 2.0 untuk mengetahui perubahan yang terjadi pada Anda. Misalnya, ia menyebutkan penggantian nama kelas yang sama sekali tidak jelas. Namun, jika sesuatu telah berpisah dan rusak, perlu dipertimbangkan bahwa konsep "web" dibagi menjadi 2: "servlet" dan "reaktif". Dengan orientasi di semua kelas dan paket, ini dapat membantu. Selain itu, harus diingat bahwa tidak hanya kelas dan paket itu sendiri yang diganti namanya, tetapi juga seluruh dependensi dan artefak. Karena ini, misalnya, terjadi dengan Spring Cloud.
Jenis-Konten. Menentukan jenis respons HTTP
Cukup tentang hal-hal sederhana dari waktu kompilasi, semuanya jelas dan sederhana di sana. Mari kita bicara tentang apa yang bisa terjadi pada saat runtime dan, karenanya, dapat menembak, bahkan jika Spring Boot 2 telah bekerja untuk Anda untuk waktu yang lama. Mari kita bicara tentang definisi tipe konten.

Bukan rahasia lagi bahwa Spring dapat menulis aplikasi web, baik API laman dan REST, dan mereka dapat membuat konten dengan berbagai jenis, baik itu XML, JSON, atau yang lainnya. Dan salah satu pesona yang sangat disukai Spring adalah Anda tidak perlu repot dengan definisi tipe yang diberikan dalam kode Anda. Anda bisa berharap untuk sihir. Sihir ini bekerja, secara relatif, dalam tiga cara yang berbeda: bergantung pada header Terima yang berasal dari klien, atau pada ekstensi file yang diminta, atau pada parameter khusus dalam URL, yang, tentu saja, juga dapat diarahkan.
Pertimbangkan contoh sederhana (
kode sumber lengkap ). Selanjutnya saya akan menggunakan notasi dari Gradle, tetapi bahkan jika Anda adalah penggemar Maven, tidak akan sulit bagi Anda untuk memahami apa yang tertulis di sini: kami membangun aplikasi kecil pada Boot Musim Semi pertama dan hanya menggunakan satu web starter.
Contoh (v1.x):
dependencies { ext { springBootVersion = '1.5.14.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") }
Sebagai kode yang dapat dieksekusi, kami memiliki satu kelas tunggal di mana metode pengontrol dideklarasikan dengan segera.
@GetMapping(value = "/download/{fileName: .+}", produces = {TEXT_HTML_VALUE, APPLICATION_JSON_VALUE, TEXT_PLAIN_VALUE}) public ResponseEntity<Resource> download(@PathVariable String fileName) {
Dibutuhkan nama file tertentu sebagai input, yang seharusnya akan dibentuk dan diberikan. Itu benar-benar membentuk isinya dalam salah satu dari tiga jenis yang ditunjukkan (menentukan ini dengan nama file), tetapi tidak menentukan jenis konten dengan cara apa pun - kami memiliki Spring, ia akan melakukan semuanya sendiri.

Secara umum, Anda bahkan dapat mencoba melakukannya. Memang, jika kami meminta dokumen yang sama dengan ekstensi yang berbeda, itu akan dikembalikan dengan tipe konten yang benar tergantung pada apa yang kami kembalikan: jika Anda ingin - json, jika Anda ingin - txt, jika Anda ingin - html. Ini bekerja seperti dongeng.
Memperbarui ke v2.x
dependencies { ext { springBootVersion = '2.0.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") }
Saatnya untuk meningkatkan ke Boot Musim Semi kedua. Kami hanya mengubah angka 1 menjadi 2.
Spring MVC Path Matching Mengubah Perilaku DefaultTapi kami adalah insinyur, kami akan melihat Panduan Migrasi, dan tiba-tiba sesuatu dikatakan tentang itu. Tapi itu menyebutkan semacam "pencocokan jalur suffix". Ini tentang cara memetakan metode di Java dengan URL dengan benar. Tapi ini bukan kasus kami, meski agak mirip.

Karena itu, kami mencetak skor, memeriksa, dan menggedor! - tiba-tiba tidak berfungsi. Untuk beberapa alasan, hanya teks / html mulai diberikan di mana-mana, dan jika Anda menggalinya, itu bukan hanya teks / html, tetapi hanya jenis pertama yang Anda tentukan dalam atribut menghasilkan pada penjelasan @GetMapping. Kenapa begitu Tampaknya, secara halus, tidak bisa dipahami.

Dan di sini, tidak ada Catatan Rilis yang akan membantu, Anda harus membaca sumbernya.
ContentNegotiationManagerFactoryBean
public ContentNegotiationManagerFactoryBean build() { List<ContentNegotiationStrategy> strategies = new ArrayList<>(); if (this.strategies != null) { strategies.addAll(this.strategies); } else { if (this.favorPathExtension) { PathExtensionContentNegotiationStrategy strategy;
Di sana Anda dapat menemukan klasik dengan nama pendek singkat yang sangat dimengerti, yang menyebutkan bendera tertentu yang disebut "pertimbangkan ekstensi di jalan" (favorPathExtension). Nilai dari flag ini "true" berhubungan dengan penerapan strategi tertentu dengan nama ringkas pendek yang dapat dimengerti, dari mana jelas bahwa itu hanya bertanggung jawab untuk menentukan tipe konten dengan ekstensi file. Seperti yang Anda lihat, jika flag salah, maka strategi tidak akan berlaku.

Ya, mungkin, banyak yang memperhatikan bahwa di Spring, ternyata, ada semacam pedoman, sehingga namanya harus baik, setidaknya dua puluh karakter.

Jika Anda menggali sedikit lebih dalam, Anda dapat menggali hanya fragmen seperti itu. Dalam kerangka Spring itu sendiri, dan bukan dalam versi kelima, seperti yang mungkin diharapkan, tetapi sejak dahulu kala, flag ini secara default telah diatur ke "true". Sementara di Spring Boot dan di versi kedua itu diblokir oleh yang lain, yang sekarang tersedia untuk kontrol dari pengaturan. Yaitu, sekarang kita bisa menjauhkan mereka dari pengelolaan lingkungan, dan ini hanya dalam versi kedua. Apakah anda merasa Di sana dia sudah mengasumsikan arti "salah." Artinya, mereka ingin, semacam, untuk melakukan yang terbaik, meletakkan bendera ini di pengaturan (dan ini hebat), tetapi nilai defaultnya beralih ke yang lain (ini tidak terlalu).
Pengembang kerangka kerja adalah orang-orang juga, mereka juga cenderung melakukan kesalahan. Apa yang harus dilakukan? Jelas bahwa Anda perlu mengganti parameter dalam proyek Anda, dan semuanya akan baik-baik saja.
Satu-satunya hal yang layak dilakukan berjaga-jaga, untuk membersihkan hati nurani Anda, adalah melihat ke dalam dokumentasi Boot Musim Semi hanya untuk menyebutkan bendera ini. Dan di sana dia benar
- benar
disebutkan , tetapi hanya dalam konteks yang aneh:
Jika Anda memahami peringatan dan masih ingin aplikasi Anda menggunakan pencocokan pola sufiks, konfigurasi berikut diperlukan:
spring.mvc.contentnegotiation.favor-path-extension = true
...
Ada tertulis, kata mereka, jika Anda memahami semua trik dan masih ingin menggunakan pencocokan jalur suffix, maka centang kotak ini. Rasakan perbedaannya? Sepertinya kita berbicara tentang definisi tipe konten dalam konteks bendera ini, tetapi di sini kita berbicara tentang pencocokan metode dan URL Java. Sepertinya tidak bisa dimengerti.
Kita harus menggali lebih jauh. Ada
permintaan tarik di GitHub:

Dalam kerangka permintaan tarikan ini, perubahan ini dibuat - mengalihkan nilai default - dan ada salah satu penulis kerangka mengatakan bahwa masalah ini memiliki dua aspek: satu hanya pencocokan jalur, dan yang kedua adalah definisi tipe konten . Artinya, dengan kata lain, bendera berlaku untuk keduanya, dan keduanya terkait erat.
Anda bisa, tentu saja, menemukannya langsung di GitHub jika Anda hanya tahu di mana mencarinya.
Pertandingan sufiksSelain itu, dokumentasi untuk Kerangka Spring itu sendiri juga menyatakan bahwa penggunaan ekstensi file diperlukan sebelumnya, tetapi sekarang tidak lagi dianggap suatu keharusan. Selain itu, terbukti bermasalah dalam sejumlah kasus.
Ringkaslah
Mengubah nilai flag default sama sekali bukan bug, tetapi fitur. Ini terkait erat dengan definisi pencocokan jalur dan dirancang untuk melakukan
tiga hal :
- mengurangi risiko keamanan (mana yang akan saya perjelas);
- menyelaraskan perilaku WebFlux dan WebMvc, mereka berbeda dalam aspek ini;
- sejajarkan pernyataan dalam dokumentasi dengan kode kerangka kerja.
Bagaimana menjadi
Pertama, jika memungkinkan, Anda tidak harus bergantung pada definisi tipe konten dengan ekstensi. Contoh yang saya tunjukkan adalah contoh tandingan, tidak perlu melakukan ini! Serta tidak perlu bergantung pada kenyataan bahwa permintaan dari bentuk "GET something.json", misalnya, hanya akan "GET sesuatu". Ini adalah kasus di Spring Framework 4 dan di Spring Boot 1. Ini tidak berfungsi lagi. Jika Anda perlu memetakan ke file dengan ekstensi, Anda harus melakukan ini secara eksplisit. Alih-alih, lebih baik mengandalkan tajuk Terima atau pada parameter URL, yang namanya dapat Anda kendalikan. Nah, jika Anda tidak dapat melakukannya dengan cara apa pun, katakanlah Anda memiliki beberapa klien seluler lama yang berhenti memperbarui pada abad terakhir, Anda harus mengembalikan tanda ini, mengaturnya menjadi "benar", dan semuanya akan berfungsi seperti sebelumnya.
Selain itu, untuk pemahaman umum, Anda dapat membaca bab "Pencocokan sufiks" dalam dokumentasi untuk Kerangka Kerja Musim Semi, hal ini dianggap oleh para pengembang sebagai semacam kumpulan praktik terbaik di bidang ini, dan membiasakan diri Anda dengan apa yang disebut
serangan Unduhan File Download , hanya diimplementasikan menggunakan manipulasi dengan ekstensi file.
Penjadwalan. Tugas terjadwal atau berkala
Mari kita sedikit mengubah ruang lingkup dan berbicara tentang menyelesaikan tugas sesuai jadwal atau secara berkala.
Contoh tugas. Log pesan setiap 3 detik
Apa yang dikatakan, saya pikir, bisa dimengerti. Kami memiliki beberapa kebutuhan bisnis, untuk melakukan sesuatu dengan semacam pengulangan, jadi kami akan segera melanjutkan ke contoh. Misalkan kita memiliki tugas mega-kompleks: untuk mengeluarkan beberapa kotoran ke log setiap 3 detik.

Ini dapat dilakukan, tentu saja, dalam berbagai cara, bagi mereka dengan cara apa pun sudah ada sesuatu di Spring. Dan menemukannya - banyak cara.
Opsi 1: cari contoh di proyek Anda
@Service public class ReallyBusinessService {
Kita dapat melihat dalam proyek kita sendiri dan kita mungkin akan menemukan sesuatu seperti ini. Satu anotasi akan bergantung pada metode publik, dan akan menjadi jelas darinya bahwa begitu Anda menggantungnya, semuanya berfungsi seperti dalam dongeng.
Opsi 2: cari anotasi yang diinginkan

Anda dapat mencari anotasi secara langsung berdasarkan nama, dan itu juga akan menjadi jelas dari dokumentasi bahwa Anda menggantungnya - dan semuanya berfungsi.
Opsi 3: Googling
Jika Anda tidak memiliki kepercayaan pada diri sendiri, maka Anda dapat google itu, dan dari apa yang Anda
temukan, juga akan jelas bahwa semuanya akan dimulai dengan satu anotasi.
@Component public class EventCreator { private static final Logger LOG = LoggerFactory.getLogger(EventCreator.class); private final EventRepository eventRepository; public EventCreator(final EventRepository eventRepository) { this.eventRepository = eventRepository; } @Scheduled(fixedRate = 1000) public void create() { final LocalDateTime start = LocalDateTime.now(); eventRepository.save( new Event(new EventKey("An event type", start, UUID.randomUUID()), Math.random() * 1000)); LOG.debug("Event created!"); } }
Siapa yang melihat hasil tangkapan ini? Bagaimanapun, kita adalah insinyur, mari kita periksa bagaimana ini bekerja dalam kenyataan.
Tunjukkan kodenya!
Pertimbangkan tugas tertentu (tugas itu sendiri dan kode berada
di repositori saya ).
Siapa yang tidak ingin membaca, Anda dapat menonton fragmen video ini dengan demonstrasi (hingga menit ke-22):
Sebagai ketergantungan, kami akan menggunakan Spring Boot pertama dengan dua permulaan. Yang pertama adalah untuk web, sepertinya kami sedang mengembangkan server web, dan yang kedua adalah aktuator musim semi, sehingga kami memiliki fitur yang siap produksi, sehingga kami setidaknya sedikit seperti sesuatu yang nyata.
dependencies { ext { springBootVersion = '1.5.14.RELEASE'
Dan kode yang dapat dieksekusi kami akan lebih sederhana.
package tech.toparvion.sample.joker18.schedule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.Scheduled; @SpringBootApplication public class EnableSchedulingDemoApplication { private static final Logger log = LoggerFactory.getLogger(EnableSchedulingDemoApplication.class); public static void main(String[] args) { SpringApplication.run(EnableSchedulingDemoApplication.class, args); } @Scheduled(fixedRate = 3000L) public void doOnSchedule() { log.info(“ 3 …”); } }
Secara umum, hampir tidak ada yang luar biasa, kecuali satu-satunya metode di mana kita menggantungkan anotasi. Kami menyalinnya di suatu tempat dan berharap itu berfungsi.
Mari kita periksa, kita adalah insinyur. Kita mulai. Kami berasumsi bahwa setiap tiga detik pesan seperti itu akan dicatat. Semuanya harus bekerja di luar kotak, kami memastikan bahwa semuanya diluncurkan pada Boot Musim Semi pertama, dan kami mengharapkan output dari baris yang diinginkan. Tiga detik berlalu - garis adalah output, enam lewat - garis ditampilkan. Optimis menang, semuanya berfungsi.

Hanya saatnya tiba untuk meningkatkan ke Spring Boot kedua. Kami tidak akan repot, hanya beralih dari satu ke yang lain:
dependencies { ext {
Secara teori, Panduan Migrasi tidak memperingatkan kami tentang apa pun, dan kami berharap semuanya akan berjalan tanpa penyimpangan. Dari sudut pandang kode yang dapat dieksekusi, kami tidak memiliki rake lain yang saya sebutkan sebelumnya (ketidakcocokan di tingkat API atau sesuatu yang lain), karena aplikasi ini sesederhana mungkin.
Kita mulai. Pertama-tama, kami yakin bahwa kami sedang mengerjakan Boot Musim Semi kedua, jika tidak, sepertinya tidak ada penyimpangan.

Namun, 3 detik berlalu, 6, 9, tetapi masih belum ada Herman - tidak ada kesimpulan, tidak ada yang berhasil.
Seperti yang sering terjadi, harapan bertentangan dengan kenyataan. Mereka sering menulis kepada kami dalam dokumentasi bahwa, pada kenyataannya, semuanya berjalan di luar kotak di Spring Boot, yang secara umum kita dapat memulai dengan masalah minimal, dan tidak diperlukan konfigurasi. Tapi begitu kenyataan, sering kali kita harus membaca dokumentasi. Secara khusus, jika Anda menggali lebih dalam, Anda dapat menemukan di sini garis-garis ini:
7.3.1. Aktifkan Penjadwalan Penjelasan
Untuk mengaktifkan dukungan untuk penjelasan @Scheduled dan Async , Anda dapat menambahkan @EnableScheduling dan @EnableAsync ke salah satu kelas @Configuration Anda.
Agar anotasi terjadwal berfungsi, Anda perlu menggantung anotasi lain di kelas dengan anotasi lain. Nah, seperti biasa di Spring. Tapi mengapa itu berhasil sebelumnya? Kami tidak melakukan hal seperti itu. Jelas, anotasi ini di suatu tempat digantung sebelumnya di Spring Boot pertama, tetapi sekarang untuk beberapa alasan itu tidak di yang kedua.

Kita mulai mencari-cari kode sumber dari Spring Boot pertama. Kami menemukan bahwa ada beberapa kelas yang seharusnya hang. Kami melihat lebih dekat, ini disebut "MetricExportAutoConfiguration" dan, tampaknya, bertanggung jawab untuk memberikan metrik kinerja ini di luar, ke beberapa agregator terpusat, dan ini benar-benar memiliki anotasi ini.

Selain itu, ia bekerja sedemikian rupa sehingga mencakup perilakunya pada seluruh aplikasi sekaligus, tidak perlu digantung pada kelas yang terpisah. Kelas inilah yang menjadi pemasok perilaku ini, dan kemudian karena suatu alasan tidak. Mengapa

Semua GitHub yang sama mendorong kita ke penggalian arkeologis seperti itu: sebagai bagian dari transisi ke versi kedua dari Boot Musim Semi, kelas ini dipangkas bersama dengan anotasi. Mengapa Ya, karena mesin pengiriman metrik juga telah berubah: mereka tidak lagi menggunakan skrip mereka sendiri, tetapi beralih ke Mikrometer - solusi yang sangat berarti. Itu hanya sesuatu yang berlebihan yang tertinggal dengannya. Mungkin ini bahkan benar.
Siapa yang tidak ingin membaca, lihat demo singkat selama 30 detik:
Oleh karena itu, jika kita sekarang mengambil dan secara manual menggantung anotasi yang hilang di kelas asli kita, maka, secara teori, perilaku harus menjadi benar.
package tech.toparvion.sample.joker18.schedule; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.scheduling.annotation.EnableScheduling; import org.springframework.scheduling.annotation.Scheduled; @SpringBootApplication @EnableScheduling public class EnableSchedulingDemoApplication { private static final Logger log = LoggerFactory.getLogger(EnableSchedulingDemoApplication.class); public static void main(String[] args) { SpringApplication.run(EnableSchedulingDemoApplication.class, args); } @Scheduled(fixedRate = 3000L) public void doOnSchedule() { log.info(“ 3 …”); } }
Apakah Anda pikir itu akan berhasil? Mari kita periksa. Kita mulai.

Dapat dilihat bahwa setelah 3 detik, setelah 6 dan setelah 9, pesan yang kami harapkan masih ditampilkan di log.
Bagaimana menjadi
Apa yang harus dilakukan dengan ini dalam kasus khusus dan lebih umum ini? Tidak peduli seberapa moralistik hal ini kedengarannya, pertama, ada baiknya membaca tidak hanya menyalin fragmen dokumentasi, tetapi juga sedikit lebih luas, hanya untuk mencakup aspek-aspek tersebut.
Kedua, ingat bahwa di Spring Boot meskipun banyak fitur di luar kotak (penjadwalan, async, caching, ...), mereka tidak selalu termasuk, mereka harus diaktifkan secara eksplisit.
Ketiga, itu tidak repot-repot aman: tambahkan
Aktifkan * anotasi (dan seluruh keluarga mereka) ke kode Anda, tidak mengharapkan kerangka kerja. Tetapi kemudian muncul pertanyaan: apa yang akan terjadi jika kebetulan saya dan rekan-rekan saya menambahkan beberapa penjelasan, bagaimana mereka akan bersikap? , . : . , .
, @EnableAsync
Enable Caching , , , , , . , . ? javadoc , . , . ,
Enable *, , . ? .
Spring Cloud & Co.

Spring Boot 2 , Spring Cloud — Service Discovery ( ). JavaMelody. - . , , JDBC, H2.

, , JavaMelody — , , . dev-, test, - , Prometheus.
Gradle :
dependencies { ext { springBootVersion = '2.0.4.RELEASE' springCloudVersion = '2.0.1.RELEASE' } compile("org.springframework.boot:spring-boot-starter-web:$springBootVersion") runtime("org.springframework.boot:spring-boot-starter-jdbc:$springBootVersion") runtime group: "org.springframework.cloud", name: "spring-clooud-starter-netflix-eureka-client", version: springCloudVersion runtime("net.bull.javamelody:javamelody-spring-boot-starter:1.72.0")
( )Spring Boot — web jdbc, Spring Cloud eureka (, , Service Discovery), JavaMelody. .
@SpringBootApplication public class HikariJavamelodyDemoApplication { public static void main(String[] args) { SpringApplication.run(HikariJavamelodyDemoApplication.class, args); } }
Kita mulai.

. , , - com.sun.proxy Hikari, HikariDataSource. , Hikari — , Tomcat, C3P0 .
? .
Spring Cloud dataSource
, Spring Cloud , dataSource ( ), . , AutoRefresh RefreshScope — . . CGLIB.
, , Spring Boot Spring : JDK ( , ) CGLIB ( ). BeanPostProcessor' BeanDefinition , .
JavaMelody dataSource
— JavaMelody. DataSource , , . JavaMelody JDK-, , . — BeanPostProcessor.
, , DataSource JDK-, CGLIB-. :

. , .
Spring Boot dataSource.unwrap()
Spring Boot, DataSource#unwrap(), JMX. JDK- ( ), CGLIB-, Spring Cloud, Spring Context. , , JDK-, CGLIB API .
, :
https://jira.spring.io/browse/SPR-17381, , , . , , , , - .

. Hikari?
, Hikari - , Spring Cloud . : Hikari Spring Boot 2. ? - - . , Spring Cloud? , - , ? . , .
…
org.springframework.cloud.autoconfigure.RefreshAutoConfiguration .RefreshScopeBeanDefinitionEnhancer: private Set<String> refreshables = new HashSet<>( Arrays.asList("com.zaxxer.hikari.HikariDataSource"));
Spring Cloud autoconfiguration, Enhancer BeanDefinition', , Hikari. Spring Cloud . .
? Spring Cloud , CGLIB-. , , , , - . (jira.spring.io/browse/SPR-17381). BeanPostProcessor, . BeanDefinition , BeanPostProcessor'. Stack Overflow , - , , proxyTargetClass true false , . , . .
, - , .
:
- (, Tomcat JDBC Pool)
spring.datasource.type=org.apache.tomcat.jdbc.pool.DataSource
runtime 'org.apache.tomcat:tomcat-jdbc:8.5.29'
Hikari , , , , , Tomcat, Spring Boot. - JavaMelody, JDBC-, .
javamelody.excluded-datasources=scopedTarget.dataSource - Spring Cloud.
spring.cloud.refresh.enabled=false
, , , Service Discovery, .
. , .
( *)
* Spring Cloud ( JavaMelody)
@Component @ManagedResource @EnableAsync public class MyJmxResource { @ManagedOperation @Async public void launchLongLastingJob() {
:
github.com/toparvion/joker-2018-samples/tree/master/jmx-resource .
. , Spring Cloud. JavaMelody , Spring-, . , , , JMX . - , Async, JMX- . JMX, @ManagedOperation, , ( Spring — , OK).
, , , , , myJMXResource JMX, . , — , CGLIB JDK.

JDK CGLIB-. , - BeanPostProcessor.
, BeanPostProcessor':
AsyncAnnotationBeanPostProcessor
- : Async
- : org.springframework.scheduling
- : @EnableAsync ( Import )
2. DefaultAdvisorAutoProxyCreator
- : AOP-,
- : org.springframework.aop.framework.autoproxy
- : @Configuration- PointcutAdvisorConfig ( )
DefaultAdvisorAutoProxyCreator @Configuration-. , , JavaMelody, configuration-. , PointcutAdvisorConfig, .

, . PointcutAdvisorConfig, AdvisorConfig, , configuration-, , , , , .
, , , , -.

BeanPostProcessor'. , , , BeanPostProcessor . , Advised ( BeanPostProcessor'), , , , , , JDK-, . .
. , :

JMX . BeanPostProcessor. BeanPostProcessor', , , , , JAR, , .
Bagaimana menjadi
-, , Spring AOP, , . « »? , - Advice Advisor, , .
-, best practices. , JMX- , . - , , , . , autowire' () . . , , - .
Order , . , , , .. proxyTargetClass, .
: , . -, «Keep calm and YAGNI». , . « », - , - , , , . , , : -, , — , . . , Spring , , .
tolkkv , , 436- , . , .
Relax Binding. ()
, .
https://docs.spring.io/spring-boot/docs/2.0.5.RELEASE/reference/htmlsingle/#boot-features-external-config-relaxed-binding, Relax Binding Spring Boot. - . , - firstName , acme.my-project.person, Spring Boot . : camel case, , , - — firstName. Relax Binding.
Spring Boot' , , — . , , , :
, , . - . , .
Sebagai contoh:
dependencies { ext { springBootVersion = '1.5.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter:$springBootVersion") }
(
)
- , web, Spring Boot, - .
@SpringBootApplication public class RelaxBindingApplication implements ApplicationRunner { private static final Logger log = LoggerFactory.getLogger(RelaxBindingDemoApplication.class); @Autowired private SecurityProperties securityProperties; public static void main(String[] args) { SpringApplication.run(RelaxBindingDemoApplication.class, args); } @Override public void main run(ApplicationArguments args) { log.info("KEYSTORE TYPE IS: {}", securityProperties.getKeyStoreType()); } }
, POJO- ( ) , KEYSTORE TYPE. POJO , applications.properties application.yaml, .
keystoreType, private String keystoreType, applications.properties: security.keystoreType=jks.
@Component @ConfigurationProperties(prefix = "security") public class SecurityProperties { private String keystorePath; private String keystoreType; public String getKeystorePath() { return keystorePath; } public void setKeystorePath(String keystorePath) { this.keystorePath = keystorePath; } public String getKeyStoreType() { return keystoreType; } public void setKeystoreType(String keystoreType) { this.keystoreType = keystoreType; } }
Spring Boot .

, , , . , .

, . , , , - , , - key-store-type. , , , .
. , .

. 2 , , . Java properties — , . , , , , , . — Java bean . , , . , «keystore» , : «Key» «Store». …

, , ? .
, , Relax Binding ( getStoreType()). , . , . , keyStoreType, . , Relax Binding, , , .
, - , - , . , . :

, - , , , , -, . .
Bagaimana menjadi
: - . -, , dev- , , YAML properties, — , , . -,
c , Relax Binding, . , , , Spring Boot .
Unit Testing. Mockito 2
, - , Mockito.
Mockito , Spring Boot Starter, Spring, Mockito.
$gradle -q dependencyInsight --configuration testCompile --dependency mockito org.mockito:mockito-core:2.15.0 variant "runtime" /--- org.springframework.boot:spring-boot-starter-test:2.0.2.RELEASE /---testCompile
? . Spring Boot Mockito , 1.5.2 Spring Boot Mockito 2, . - . Mockito 2.
Mockito , 2016- Mockito 2.0 Mockito.2.1— : Java 8 , Hamcrest - . , , .
, , ( ) .

, , JButton Swing, null, , - . , string' null, , null instanceof string. , Mockito 1 , Mockito 2 , anyString null , , . : null, .
, , , Mockito 1.
, , .
public class MyService { public void setTarget(Object target) {
, , . JButton. anyString. , : , — . , . - 10- , . Mockito 1 , :

Mockito 2 , , , anyString :

. , . , , , , SocketTimeoutException, - . , SocketTimeoutException , . Mockito 1.

Mockito 2 , :

, Mockito 1 , , . new SocketTimeoutException new, constructor, Mockito 1 .
Apa yang harus dilakukan? , , RuntimeException, , Mockito .
. , . compile-time. - , Hamcrest. Spring Boot, Mockito 1 @MockBean @SpyBean. , Spring Integration, review.
(:
https://docs.spring.io/spring-integration/docs/5.0.0.RELEASE/reference/htmlsingle/#testing )
Bagaimana menjadi
, , Mockito 1, Mockito 2 (
dzone.com/refcardz/mockito ).
-, , , Spring Boot 1.5.2 Mockito 2.
-, , , Mockito 2, :
,
.
Gradle Plugin. Spring Boot
, , — Spring Boot- Gradle.
Migration Guide , Spring Boot Gradle . , . : Gradle 4 (, settings.gradle ). dependency management plugin, . bootRepackage, , : bootWar bootJar. bootJar .
bootJar:
- , org.springframework.boot java;
- jar;
- mainClassName ( ) ( , - ).
, , — , , Gradle, Spring Boot.
? - Spring Boot 2, , , Gradle 4 Spring Boot-. , , : , , ( , ).

, . app1 , app2, app3 . . app1 lib.
«Show me the code!»
subprojects { repositories { mavenCentral() } apply plugin: 'java' apply plugin: 'org.springframework.boot' }
— : java Spring Boot .
, , , . , , , . .
app1:
dependencies { ext { springBootVersion = '2.0.4.RELEASE' } compile("org.springframework.boot:spring-boot-starter:$springBootVersion") compile project(':lib') }
lib , Spring Boot-.
app1:
@SpringBootApplication public class GradlePluginDemoApplication implements ApplicationRunner {
Util, .
lib:
public abstract class Util { public static String getAppVersion(Class<?> appClass) { return appClass.getPackage().getImplementationVersion(); } }
Util getAppVersion , , ImplementationVersion . .

IDE, , , . gradle build IDE , . Util. , , , , .
:
:
- ;
- , jar ( ImplementationVersion), .
? : .

Spring Boot- , , lib . .
2: SB Gradle Plugin Spring Boot-
bootJar { enabled = false }
, - , , , , , bootJar . , jar , .
Lainnya
, , , Spring Boot. .

Spring Boot : web-, , . - , , Spring Boot properties migrator, , , . , , -, .
Actuator. , () , Spring Security. .

Spring Cloud . , . , Netflix Feign.

Spring Integration, , Spring Framework, . — , Java DSL , , . , , , , handle handleWithAdapter.
.
, , :

, , , Web, .
(Properties Binding), , , Relax Binding.
— , : , AOP , , Spring Boot 2 .
, , , — , Mockito 1 Mockito 2. - , ?
-, , , , YAGNI. , - , . , , .
-, - - , , , . , , , , . Migration Guide. , , , , , .
, Spring Boot. , Spring Boot, , … .
, : 5-6 JPoint , Spring Boot: Spring Boot- Java 8 Java 11. — .