Java Spring 5 Fitur Baru Kerangka Web Fungsional
Berikan contoh
Mari kita mulai dengan beberapa kutipan dari aplikasi sampel. Di bawah ini adalah pustaka informasi respons yang memaparkan objek orang. Sangat mirip dengan perpustakaan informasi tradisional yang tidak responsif, kecuali bahwa ia mengembalikan fluks <Fone> dan daftar pengembalian tradisional <sone> dan tempat di mana Mono <Fon> mengembalikan orang. Mono <Void> digunakan sebagai bendera penyelesaian: Menunjukkan kapan simpan selesai.
Public Interface PersonRepository {Mono <Fone> getperson (int id); Fluks <sone> allpeople (); Mono <void> saveperson (mono <fone> orang);}Inilah cara kami mengekspos perpustakaan dengan kerangka web fungsional baru:
RouterFunction<?> route = route(GET("/person/{id}"), request -> { Mono<Person> person = Mono.justOrEmpty(request.pathVariable("id")) .map(Integer::valueOf) .then(repository::getPerson); return Response.ok().body(fromPublisher(person, Person.class)); }) .and (rute (get ("/person"), request -> {flux <fone> people = repository.allpeople (); return response.ok (). body (frompublisher (orang, orang.class));})) .dan (rute (post ("/person"), permintaan -> {mono <person> orang = tomono (tomono (tomono (tomono), request -> {mono <person> orang =. Response.ok (). Build (repository.saveperson (orang));}));Di sini kita akan memperkenalkan cara menjalankan, misalnya di reaktor netty:
Httphandler httphandler = routerfunctions.tohttphandler (rute); reactorhttphandlerapter adapter = reactorhttphandlerapter baru (httphandler); httpserver server = httpserver.create ("localhost", 808080);Hal terakhir yang harus dilakukan adalah mencobanya:
$ curl 'http: // localhost: 8080/orang/1' {"name": "John doe", "usia": 42}Ada lebih banyak perkenalan di bawah ini, mari kita gali lebih dalam!
Komponen inti
Saya akan memperkenalkan kerangka kerja dengan menjelaskan komponen inti secara menyeluruh: fungsi handler, fungsi router, dan fungsi filter. Ketiga antarmuka ini, serta semua jenis lain yang dijelaskan dalam artikel, dapat ditemukan di paket org.springframework.web.reactive.function.
Fungsi handler
Titik awal dari kerangka kerja baru ini adalah HandlerFunction <T>, yang pada dasarnya berfungsi <permintaan, respons <T>>, di mana permintaan dan respons baru didefinisikan, dan antarmuka yang tidak berubah ramah untuk menyediakan JDK-8 DSL ke pesan HTTP yang mendasarinya. Ini adalah alat build yang nyaman untuk membangun entitas respons, sangat mirip dengan apa yang Anda lihat sebagai responentity. Sesuai dengan anotasi fungsi handler adalah metode dengan @RequestMapping.
Berikut ini adalah contoh sederhana dari fungsi pemrosesan "Hello World", mengembalikan pesan respons dengan 200 negara bagian dan badan string:
Handlerfunction <string> helloWorld = request -> response.ok (). Body (fromObject ("hello world"));Seperti yang kita lihat dalam contoh di atas, fungsi penanganan sepenuhnya responsif dengan membangun berdasarkan reaktor: mereka menerima fluks, mono, atau penerbit aliran lainnya yang sesuai sebagai jenis respons.
Satu hal yang perlu diperhatikan adalah bahwa fungsi handler itu sendiri tidak memiliki efek samping karena mengembalikan respons alih -alih memperlakukannya sebagai parameter (lihat servlet.service (servletrequest, servletResponse), yang pada dasarnya biconsumer <servletrequest, servletResponse>). Ada banyak manfaat untuk tidak ada efek samping: mudah diuji, menulis dan mengoptimalkan.
Fungsi router
Permintaan yang masuk dialihkan ke fungsi handler dengan fungsi router <t> (mis. Fungsi <permintaan, opsional <surlerFunction <T>>) dan dialihkan ke pawang jika cocok; Kalau tidak, hasil kosong dikembalikan. Metode perutean berfungsi mirip dengan anotasi @Requestmapping. Namun, ada perbedaan signifikan lainnya: ketika menggunakan anotasi, rute akan terbatas pada kisaran yang dapat diungkapkan oleh nilai beranotasi, dan sulit untuk menangani overlay metode ini; Saat menggunakan metode routing, kode ada di sana dan dapat dengan mudah ditimpa atau diganti.
Di bawah ini adalah contoh fungsi perutean dengan fungsi pemrosesan tertanam. Ini terlihat agak panjang, tapi jangan khawatir: kita akan menemukan cara untuk membuatnya lebih pendek.
RouterFunction <string> helloWorldRoute = request -> {if (request.path (). Equals ("/hello -world"))) {return opsional.of (r -> response.ok (). Body (fromObject ("hello world"))); } else {return opsional.empty (); }};Secara umum, tidak perlu menulis metode perutean yang lengkap, tetapi sebaliknya secara statis memperkenalkan routerfunctions.route (), sehingga Anda dapat membuat metode routing menggunakan rumus penilaian permintaan (requestPredicate) (mis. Predikat <Emply>) dan HandlerFunction). Jika penilaian berhasil, metode pemrosesan akan dikembalikan, jika tidak hasil kosong akan dikembalikan. Berikut ini adalah contoh di atas menggunakan metode rute:
RouterFunction <string> helloWorldroute = routerfunctions.Route (request -> request.path (). Equals ("/hello -world"), request -> response.ok (). Body (fromObject ("hello world")));Anda dapat (secara statis) mengimpor permintaan penghargaan.* Untuk mengakses predikat yang umum digunakan, berdasarkan jalur, metode http, jenis konten, dll. Dengan itu, kita dapat membuat HelloWorldRoute lebih sederhana:
Routerfunction <string> helloWorldroute = routerfunctions.route (requestPredicates.path ("/hello -world"), request -> response.ok (). Body (fromObject ("hello world")));Fungsi kombinasi
Dua fungsi perutean dapat membentuk fungsi perutean baru, rute ke fungsi pemrosesan: jika fungsi pertama tidak cocok, maka yang kedua dieksekusi. Anda dapat menggabungkan dua fungsi perutean seperti ini dengan memanggil routerfunction.and ():
RouterFunction <?> Route = route (path ("/hello -world"), request -> response.ok (). Body (fromObject ("hello world"))) .and (rute (path ("/the -anwer"), request -> response.ok (). Body (fromObject ("42"))))));Jika jalur cocok /Hello-World, di atas akan menanggapi "Hello World", dan jika /The-Answer cocok, itu akan mengembalikan "42" pada saat yang sama. Jika tidak ada yang cocok, opsional kosong dikembalikan. Perhatikan bahwa fungsi perutean gabungan dijalankan secara berurutan, sehingga masuk akal untuk menempatkan fungsi generik sebelum fungsi spesifik.
Anda juga dapat menggabungkan predikat permintaan dengan menelepon dan atau atau. Ini berfungsi seperti ini: untuk dan, jika dua predikat yang diberikan cocok, hasil predikat cocok, dan jika salah satu dari dua pertandingan, maka atau cocok. Misalnya:
Routerfunction <?> Route = rute (metode (httpmethod.get) .and (path ("/hello -world")), permintaan -> response.ok (). Body (fromObject ("hello world"))) .and (rute (httpmethod.get) .and (path ("/the -answer"))) Response.ok (). Body (fromObject ("42"))));Faktanya, sebagian besar predikat yang ditemukan di RequestPredicate digabungkan! Misalnya, RequestPredicates.get (String) adalah komposisi dari RequestPredicates.method (httpmethod) dan requestPredicates.path (string). Karena itu, kita dapat menulis ulang kode di atas sebagai:
RouterFunction <?> Route = rute (get ("/hello -world"), request -> response.ok (). Body (fromObject ("hello world"))) .and (rute (get ("/the -walmer"), request -> response.ok (). Body (fromObject (42))));Referensi metode
Ngomong -ngomong: Sejauh ini kami telah menulis semua fungsi pemrosesan sebagai inline lambda ekspresi. Meskipun ini berkinerja baik dalam demo dan contoh pendek, harus dikatakan bahwa ada kecenderungan untuk menyebabkan "kebingungan" karena Anda ingin mencampur dua kekhawatiran: permintaan perutean dan pemrosesan permintaan. Jadi, kami ingin melihat apakah itu bisa membuat segalanya lebih sederhana. Pertama, kami membuat kelas yang berisi kode pemrosesan:
kelas demoHandler {respons publik <string> helloWorld (permintaan permintaan) {return response.ok (). body (fromObject ("hello world")); }/ * http://www.manongjc.com/article/1590.html */respons publik <string> theAnswer (permintaan permintaan) {return response.ok (). body (fromObject ("42")); }}Perhatikan bahwa kedua metode memiliki bendera yang kompatibel dengan fungsi pemrosesan. Ini memungkinkan kami untuk menggunakan referensi metode:
DemoHandler Handler = New DemoHandler (); // atau dapatkan melalui fungsi dirouter <?> route = rute (get ("/hello-world"), handler :: helloworld) .and (rute (get ("/the-answer"), handler :: theAnswer));Fungsi filter
Jalur yang dipetakan oleh fungsi perutean dapat disaring dengan memanggil routerfunction.filter (filterfunction <t, r>), di mana filterfunction <t, r> pada dasarnya bifunction <permintaan, handlerfunction <t>, respons <r>>. Parameter pawang fungsi mewakili item berikutnya di seluruh rantai: ini adalah fungsi handler yang khas, tetapi jika beberapa filter terpasang, itu juga bisa menjadi fungsi filter lain. Mari tambahkan filter log ke rute:
// http: //www.manongjc.croutterfunction <?> route = route (get ("/hello-world"), handler :: helloworld) .and (get ("/the-answer"), handller :: theAnswer) .filter ((request, next)-> {oMoCy. Respons <?> Next.handle (permintaan);Perlu dicatat bahwa apakah akan memanggil penangan berikutnya adalah opsional. Ini sangat berguna dalam skema keamanan dan caching (seperti menelepon berikutnya hanya ketika pengguna memiliki izin yang cukup).
Karena rute adalah fungsi perutean yang tak terbatas, kami tahu jenis informasi respons apa yang akan dikembalikan penangan berikutnya. Inilah sebabnya kami berakhir dengan respons <?> Dalam filter kami dan merespons tubuh dengan objek. Di kelas pawang, kedua metode mengembalikan respons <string>, sehingga harus dimungkinkan untuk memiliki badan respons string. Kita dapat melakukan ini dengan menggunakan routerfunction.andsame () bukan dan (). Metode kombinasi ini mensyaratkan bahwa fungsi routing parameter adalah jenis yang sama. Misalnya, kita dapat membuat semua tanggapan memanfaatkan:
RouterFunction<String> route = route(GET("/hello-world"), handler::helloWorld) .andSame(route(GET("/the-answer"), handler::theAnswer)) .filter((request, next) -> { Response<String> response = next.handle(request); String newBody = response.body().toUpperCase(); return Response.from (respons) .body (fromObject (newbody));Menggunakan anotasi, fungsi serupa dapat diimplementasikan menggunakan @ControllerAdvice dan/atau servletFilter.
Jalankan server
Semua ini baik -baik saja, tetapi satu hal yang saya lupa: Bagaimana kita bisa menjalankan fungsi -fungsi ini di server HTTP yang sebenarnya? Jawabannya tidak diragukan lagi dengan memanggil fungsi lain. Anda dapat mengonversi fungsi perutean ke httphandler dengan menggunakan routerfunctions.tohttphandler (). HTTPHANDLER adalah abstraksi respons yang diperkenalkan pada Spring 5.0 M1: memungkinkan Anda untuk menjalankan berbagai respons: Reactor Netty, RXNetty, Servlet 3.1+, dan Undertow. Dalam contoh ini, kami telah menunjukkan bagaimana itu seperti menjalankan rute di reaktor Netty. Untuk Tomcat, sepertinya ini:
Httphandler httphandler = routerfunctions.toHttphandler (rute); httpservlet servlet = servletHttphandlerapter baru (httphandler); tomcat server = tomcat baru (); konteks rootcontext = server.addcontext ("", " System.getProperty ("java.io.tmpdir")); tomcat.addservlet (rootcontext, "servlet", servlet); rootcontext.adservletmapping ("/", "servlet"); tomcatserver.start ();Satu hal yang perlu diperhatikan adalah bahwa kode di atas tidak tergantung pada konteks aplikasi pegas. Seperti JDBCtemplate dan kelas utilitas pegas lainnya, menggunakan konteks aplikasi adalah opsional: Anda dapat menghubungkan fungsi penangan dan perutean dalam konteks, tetapi tidak diperlukan.
Perhatikan juga bahwa Anda juga dapat mengonversi fungsi perutean menjadi handlermapping sehingga dapat berjalan di DispatcherHandler (mungkin memerlukan @Controllers responsif).
sebagai kesimpulan
Izinkan saya menarik kesimpulan melalui ringkasan singkat:
Untuk memberi Anda pemahaman yang lebih komprehensif, saya telah membuat proyek contoh sederhana menggunakan kerangka kerja web fungsional. Alamat unduhan
Terima kasih telah membaca, saya harap ini dapat membantu Anda. Terima kasih atas dukungan Anda untuk situs ini!