Kata pengantar
Artikel ini akan memperkenalkan beberapa metode untuk mendapatkan objek permintaan dalam sistem web yang dikembangkan oleh Spring MVC dan membahas keamanan utasnya. Saya tidak akan mengatakan banyak hal di bawah ini, mari kita lihat perkenalan terperinci bersama.
Ringkasan
Saat mengembangkan sistem Web menggunakan MVC Spring, Anda sering perlu menggunakan objek permintaan saat memproses permintaan, seperti mendapatkan alamat IP klien, URL yang diminta, atribut dalam header (seperti cookie, informasi otorisasi), data dalam badan, dll. Ketika ada di atas MVC, jika ada objek yang menangani objek yang ditangani oleh pengontrol,. Sejumlah besar permintaan bersamaan, dapatkah dijamin bahwa objek permintaan yang berbeda digunakan dalam berbagai permintaan/utas?
Ada pertanyaan lain yang perlu diperhatikan di sini: di mana saya menggunakan objek permintaan "saat memproses permintaan" yang disebutkan sebelumnya? Mempertimbangkan bahwa ada sedikit perbedaan dalam metode mendapatkan objek permintaan, mereka dapat dibagi secara kasar menjadi dua kategori:
1) Gunakan objek permintaan dalam kacang musim semi: Sertakan kedua biji MVC seperti pengontrol, layanan, repositori, dan biji pegas biasa seperti komponen. Untuk kenyamanan penjelasan, kacang di musim semi dalam teks berikut semuanya disebut sebagai biji.
2) Gunakan objek permintaan dalam non-kacang: seperti dalam metode objek Java biasa, atau dalam metode statis kelas.
Selain itu, artikel ini membahas tentang objek permintaan yang mewakili permintaan, tetapi metode yang digunakan juga berlaku untuk objek respons, InputStream/Reader, OutputStream/Writer, dll.; Di mana InputStream/Reader dapat membaca data dalam permintaan, dan OutputStream/Writer dapat menulis data ke respons.
Akhirnya, metode mendapatkan objek permintaan juga terkait dengan versi Spring dan MVC; Artikel ini dibahas berdasarkan Spring 4, dan percobaan yang dilakukan semuanya menggunakan versi 4.1.1.
Cara menguji keamanan utas
Karena masalah keselamatan utas dari objek permintaan membutuhkan perhatian khusus, untuk memfasilitasi diskusi di bawah ini, mari kita jelaskan bagaimana menguji apakah objek permintaan aman.
Gagasan dasar pengujian adalah untuk mensimulasikan sejumlah besar permintaan bersamaan pada klien, dan kemudian menentukan apakah permintaan digunakan di server.
Cara paling intuitif untuk menentukan apakah objek permintaan sama adalah dengan mencetak alamat objek permintaan. Jika sama, itu berarti objek yang sama digunakan. Namun, di hampir semua implementasi server web, kumpulan utas digunakan, yang mengarah ke dua permintaan yang tiba secara berurutan, yang dapat diproses oleh utas yang sama: setelah permintaan sebelumnya diproses, kumpulan utas merebut kembali utas dan menugaskan kembali utas ke permintaan berikutnya. Di utas yang sama, objek permintaan yang digunakan cenderung sama (alamatnya sama, atributnya berbeda). Oleh karena itu, bahkan untuk metode yang aman-utas, alamat objek permintaan yang digunakan oleh permintaan yang berbeda mungkin sama.
Untuk menghindari masalah ini, satu metode adalah membiarkan utas tidur selama beberapa detik selama proses pemrosesan permintaan, yang dapat membuat setiap utas bekerja cukup lama untuk menghindari utas yang sama mengalokasikan untuk permintaan yang berbeda; Metode lainnya adalah menggunakan atribut lain dari permintaan (seperti parameter, header, body, dll.) Sebagai dasar apakah permintaan tersebut aman, karena bahkan jika permintaan yang berbeda menggunakan utas yang sama satu demi satu (alamat objek permintaan adalah sama), selama objek permintaan dibangun dua kali menggunakan atribut yang berbeda, penggunaan objek permintaan tidak aman. Makalah ini menggunakan metode kedua untuk pengujian.
Kode tes klien adalah sebagai berikut (buat 1000 utas untuk mengirim permintaan secara terpisah):
tes kelas publik {public static void main (string [] args) melempar pengecualian {string prefix = uuid.randomuuid (). tostring (). replaceall ("-", "") + "::"; untuk (int i = 0; i <1000; i ++) {nilai string akhir = awalan+i; utas baru () {@Override public void run () {coba {closeAdleHttpClient httpClient = httpclients.createDefault (); Httpget httpget = httpget baru ("http: // localhost: 8080/test? Key =" + value); httpclient.execute (httpget); httpclient.close (); } catch (ioException e) {e.printstacktrace (); } } } } } }.awal(); }}}Kode pengontrol di server adalah sebagai berikut (kode untuk mendapatkan objek permintaan untuk sementara dihilangkan):
@ControllerPublic Class TestController {// Simpan parameter yang ada untuk menentukan apakah parameter digandakan, dengan demikian menentukan apakah utas tersebut aman set statis public <string> set = hashset baru <> (); @RequestMapping ("/test") public void test () melempar InterruptedException {// …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………. ……………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………. …………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………. ……………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………………. "/T muncul berulang kali, permintaan konkurensi tidak aman!"); } else {System.out.println (value); set.add (nilai); } // Program simulasi telah dieksekusi untuk jangka waktu tertentu, Thread.sleep (1000); }}Jika objek permintaan aman-utas, hasil cetak di server adalah sebagai berikut:
Jika ada masalah keamanan utas, hasil cetak di server mungkin terlihat seperti ini:
Jika tidak ada deskripsi khusus, kode uji akan dihilangkan dari kode nanti dalam artikel ini.
Metode 1: Tambahkan parameter ke pengontrol
Contoh Kode
Metode ini adalah yang paling sederhana untuk diimplementasikan, dan langsung memasukkan kode pengontrol:
@ControllerPublic kelas testController {@RequestMapping ("/test") tes void publik (permintaan httpservletRequest) melempar interruptedException {// program simulasi telah dieksekusi untuk periode waktu utas. Tidur (1000); }}Prinsip metode ini adalah bahwa ketika metode pengontrol mulai memproses permintaan, Spring akan menetapkan objek permintaan ke parameter metode. Selain objek permintaan, ada banyak parameter yang dapat diperoleh melalui metode ini. Untuk detailnya, silakan merujuk ke: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-methods
Setelah mendapatkan objek permintaan dalam pengontrol, jika Anda ingin menggunakan objek permintaan dalam metode lain (seperti metode layanan, metode kelas alat, dll.), Anda perlu meneruskan objek permintaan sebagai parameter saat memanggil metode ini.
Keamanan utas
Hasil Tes: Keamanan Thread
Analisis: Pada saat ini, objek permintaan adalah parameter metode, yang setara dengan variabel lokal, dan tidak diragukan lagi aman.
Pro dan kontra
Kerugian utama dari metode ini adalah bahwa objek permintaan terlalu berlebihan untuk ditulis, yang terutama tercermin dalam dua poin:
1) Jika objek permintaan diperlukan dalam beberapa metode pengontrol, maka parameter permintaan perlu ditambahkan ke setiap metode.
2) Akuisisi objek permintaan hanya dapat dimulai dari pengontrol. Jika tempat di mana objek permintaan digunakan berada di tempat yang lebih dalam dari level panggilan fungsi, maka semua metode pada seluruh rantai panggilan perlu menambahkan parameter permintaan.
Bahkan, selama seluruh proses pemrosesan permintaan, objek permintaan berjalan melalui seluruh permintaan; Artinya, kecuali untuk kasus -kasus khusus seperti timer, objek permintaan setara dengan variabel global di dalam utas. Metode ini setara dengan melewati variabel global ini di sekitar.
Metode 2: Injeksi Otomatis
Contoh Kode
Pertama unggah kode:
@ControllerPublic kelas testController {@Autowired private httpservletRequest permintaan; // permintaan autowired @RequestMapping ("/test") public void test () melempar InterruptedException {// Program simulasi telah dieksekusi untuk periode waktu utas.sleep (1000); }} Keamanan utas
Hasil Tes: Keamanan Thread
Analisis: Di musim semi, ruang lingkup pengontrol adalah Singleton (Singleton), yang berarti bahwa di seluruh sistem web, hanya ada satu testController; tetapi permintaan yang disuntikkan aman, karena:
Dengan cara ini, ketika kacang (testController dalam contoh ini) diinisialisasi, pegas tidak menyuntikkan objek permintaan, tetapi proxy; Ketika kacang perlu menggunakan objek permintaan, objek permintaan diperoleh melalui proxy.
Berikut ini adalah deskripsi implementasi ini melalui kode tertentu.
Tambahkan breakpoint ke kode di atas dan lihat properti objek permintaan seperti yang ditunjukkan pada gambar di bawah ini:
Seperti dapat dilihat pada gambar, permintaan sebenarnya adalah proxy: implementasi proxy ditunjukkan di kelas internal autowireutils
ObjectFactoryDelegatingInvocationHandler: /*** Reflective InvocationHandler untuk akses malas ke objek target saat ini. */@SuppressWarnings ("Serial") Private Static Class ObjectFactoryDelegatingInVocationHandler mengimplementasikan InvocationHandler, serializable {private final ObjectFactory <?> ObjectFactory; ObjectFactoryDelegatingInVocationHandler (ObjectFactory <?> ObjectFactory) {this.ObjectFactory = ObjectFactory; } @Override Public Object Invoke (Proxy Object, Metode Metode, Object [] args) melempar Throwable {// ... menghilangkan kode tidak relevan coba {return method.invoke (this.objectfactory.getObject (), args); // Kode Inti Implementasi Agen} Catch (InvocateTargetException Ex) {Throw Ex.GetTargetException (); }}}Dengan kata lain, ketika kita memanggil metode permintaan, kita benar -benar memanggil metode metode objek yang dihasilkan oleh ObjectFactory.getObject (); Objek yang dihasilkan oleh ObjectFactory.getObject () adalah objek permintaan nyata.
Lanjutkan untuk mengamati angka di atas dan menemukan bahwa tipe ObjectFactory adalah kelas internal RequestObjectyFactory dari WebApplicationContextUtils; dan kode RequestObjectFactory adalah sebagai berikut:
/*** Pabrik yang memaparkan objek permintaan saat ini berdasarkan permintaan. */ @SuppressWarnings ("Serial") Private Static Class RequestObjectory mengimplementasikan ObjectFactory <Ser ServletRequest>, serializable {@Override public servletRequest getObject () {return currentRequestAttributes (). GetRequest (); } @Override Public String ToString () {return "saat ini httpservletRequest"; }}Di antara mereka, untuk mendapatkan objek permintaan, Anda perlu memanggil metode CurrentRequestAttributes () untuk mendapatkan objek RequestAttributes. Implementasi metode ini adalah sebagai berikut:
/*** Kembalikan instance requestTributes saat ini sebagai servletRequestAttributes. */private static servletRequestAttributes currentRequestAttributes () {requestAttributes requestAttr = requestContextholder.currentRequestAttributes (); if (! (requestAttr instance dari servletRequestAttributes)) {lempar baru ilegalstateException ("Permintaan saat ini bukan permintaan servlet"); } return (servletRequestAttributes) requestAttr;}Kode inti yang menghasilkan objek RequestAttributes ada di kelas RequestContextholder, di mana kode yang relevan adalah sebagai berikut (kode yang tidak terkait di kelas dihilangkan):
Publik Abstrak Kelas RequestContExTholder {public static requestAttributes CurrentRequestAttributes () melempar ilegalstateException {requestAttributes atribut = getRequestAttributes (); // Logika yang tidak relevan dihilangkan di sini ...... atribut pengembalian; } public static requestAttributes getRequestAttributes () {requestAttributes atribut = requestAttributeSholder.get (); if (atribut == null) {atributs = wheritableRequestAttributeSholder.get (); } mengembalikan atribut; } private static final threadlocal <EnquestAttributes> requestAttributeSholder = new NamedThreadlocal <EmployAttributes> ("Request Atribut"); Private Static Final Threadlocal <EmployAttributes> OblitableReRequestAttributeSholder = new namedInheritableThreadlocal <EmployAttributes> ("Permintaan Konteks");}Dari kode ini, kita dapat melihat bahwa objek RequestAttributes yang dihasilkan adalah variabel lokal utas (threadlocal), sehingga objek permintaan juga merupakan variabel lokal utas; Ini memastikan keamanan utas dari objek permintaan.
Pro dan kontra
Keuntungan utama dari metode ini:
1) Injeksi tidak terbatas pada pengontrol: Dalam metode 1, hanya parameter permintaan yang dapat ditambahkan ke pengontrol. Untuk Metode 2, itu tidak hanya dapat disuntikkan dalam pengontrol, tetapi juga pada kacang apa pun, termasuk layanan, repositori, dan kacang biasa.
2) Objek yang disuntikkan tidak terbatas pada permintaan: Selain menyuntikkan objek permintaan, metode ini juga dapat menyuntikkan objek lain dengan ruang lingkup sebagai permintaan atau sesi, seperti objek respons, objek sesi, dll.; dan pastikan keamanan utas.
3) Mengurangi redundansi kode: Cukup suntikkan objek permintaan ke dalam kacang yang membutuhkan objek permintaan, dan dapat digunakan dalam berbagai metode kacang, yang sangat mengurangi redundansi kode dibandingkan dengan metode 1.
Namun, metode ini juga memiliki redundansi kode. Pertimbangkan skenario ini: Ada banyak pengontrol dalam sistem web, dan setiap pengontrol menggunakan objek permintaan (skenario ini sebenarnya sangat sering). Saat ini, Anda perlu menulis kode untuk menyuntikkan permintaan berkali -kali; Jika Anda juga perlu menyuntikkan respons, kode akan lebih rumit. Berikut ini menggambarkan peningkatan metode injeksi otomatis dan menganalisis keamanan dan kelebihan utasnya.
Metode 3: Injeksi otomatis ke kelas dasar
Contoh Kode
Dibandingkan dengan metode 2, masukkan bagian kode yang disuntikkan ke dalam kelas dasar.
Kode kelas dasar:
kelas publik Basecontroller {@Autowired Dilindungi permintaan httpservletRequest; }Kode pengontrol adalah sebagai berikut; Berikut adalah dua kelas Basecontroller yang diturunkan. Karena kode uji akan berbeda saat ini, kode uji server tidak dihilangkan; Klien juga perlu membuat modifikasi yang sesuai (mengirim sejumlah besar permintaan bersamaan ke dua URL secara bersamaan).
@ControllerPublic Class TestController memperluas Basecontroller {// Simpan parameter yang ada untuk menentukan apakah nilai parameter diulang, sehingga menentukan apakah utas tersebut aman set statis publik <string> set = hashset baru <> (); @RequestMapping ("/test") public void test () melempar interruptedException {string value = request.getParameter ("key"); // Periksa keselamatan utas jika (set.contains (value)) {System.out.println (value + "/t muncul berulang kali, permintaan konkurensi tidak aman!"); } else {System.out.println (value); set.add (nilai); } // Program simulasi telah dieksekusi untuk periode waktu thread.sleep (1000); }} @ControllerPublic kelas test2Controller memperluas Basecontroller {@RequestMapping ("/test2") public void test2 () melempar InterruptedException {string value = request.getParameter ("KEY"); // menilai keselamatan utas (gunakan set dengan testController untuk menilai) if (testController.set.contains (value)) {System.out.println (nilai + "/t muncul berulang kali, permintaan konkurensi tidak aman!"); } else {System.out.println (value); TestController.set.add (nilai); } // Program simulasi telah dieksekusi untuk jangka waktu tertentu, Thread.sleep (1000); }} Keamanan utas
Hasil Tes: Keamanan Thread
Analisis: Berdasarkan pemahaman keamanan utas dari metode 2, mudah untuk memahami bahwa metode 3 aman-utas: Saat membuat objek kelas turunan yang berbeda, domain di kelas dasar (di sini adalah permintaan yang disuntikkan) akan menempati ruang memori yang berbeda dalam objek kelas turunan yang berbeda, yaitu, menempatkan kode yang disuntikkan pada kelas dasar tidak memiliki dampak pada keamanan benang; Hasil tes juga membuktikan ini.
Pro dan kontra
Dibandingkan dengan Metode 2, injeksi permintaan yang berulang pada pengontrol yang berbeda dihindari; Namun, mengingat bahwa Java hanya memungkinkan warisan satu kelas dasar, jika pengontrol perlu mewarisi kelas lain, metode ini tidak lagi mudah digunakan.
Apakah itu Metode 2 atau Metode 3, Anda hanya dapat menyuntikkan permintaan ke dalam kacang; Jika metode lain (seperti metode statis di kelas alat) perlu menggunakan objek permintaan, Anda perlu melewati parameter permintaan saat memanggil metode ini. Metode 4 yang diperkenalkan di bawah ini dapat digunakan secara langsung dalam metode statis seperti kelas alat (tentu saja, ini juga dapat digunakan dalam berbagai kacang).
Metode 4: Panggil secara manual
Contoh Kode
@ControllerPublic Class TestController {@RequestMapping ("/test") public void test () melempar interruptedException {httpservletRequest request = ((servletRequestAttributes) (getrerecontextholder.currentRequestAttributes ()). // Program simulasi telah dieksekusi untuk periode waktu utas.sleep (1000); }} Keamanan utas
Hasil Tes: Keamanan Thread
Analisis: Metode ini mirip dengan metode 2 (injeksi otomatis), kecuali bahwa ia diimplementasikan melalui injeksi otomatis dalam metode 2, dan metode ini diimplementasikan melalui panggilan metode manual. Oleh karena itu, metode ini juga aman.
Pro dan kontra
Keuntungan: Dapat diperoleh secara langsung di non-kacang. Kerugian: Jika Anda menggunakan lebih banyak tempat, kodenya sangat rumit; Oleh karena itu, dapat digunakan bersama dengan metode lain.
Metode 5: Metode @ModelAttribute
Contoh Kode
Metode berikut dan variannya (mutasi: putar permintaan dan bindRequest dalam subkelas) sering terlihat online:
@ControllerPublic kelas testController {private httpservletRequest permintaan; @Modelattribute public void bindRequest (permintaan httpservletRequest) {this.Request = request; } @RequestMapping ("/test") public void test () melempar InterruptedException {// Program simulasi telah dieksekusi untuk periode waktu utas.sleep (1000); }} Keamanan utas
Hasil tes: Thread tidak aman
Analisis: Ketika anotasi @ModelAttribute digunakan untuk memodifikasi metode dalam pengontrol, fungsinya adalah bahwa metode ini akan dieksekusi sebelum setiap metode @RequestMapping dalam pengontrol dijalankan. Oleh karena itu, dalam contoh ini, fungsi bindRequest () adalah untuk menetapkan nilai ke objek permintaan sebelum tes () dijalankan. Meskipun permintaan parameter di bindRequest () sendiri aman, karena testController adalah singleton, permintaan, sebagai bidang testController, tidak dapat menjamin keselamatan utas.
Meringkaskan
Singkatnya, menambahkan parameter (Metode 1), injeksi otomatis (Metode 2 dan Metode 3), dan panggilan manual (Metode 4) dalam pengontrol semuanya aman dan semua dapat digunakan untuk mendapatkan objek permintaan. Jika objek permintaan lebih sedikit digunakan dalam sistem, salah satu metode dapat digunakan; Jika digunakan lebih banyak, disarankan untuk menggunakan injeksi otomatis (Metode 2 dan Metode 3) untuk mengurangi redundansi kode. Jika Anda perlu menggunakan objek permintaan dalam non-kacang, Anda dapat meneruskannya melalui parameter saat memanggil lapisan atas, atau Anda dapat secara langsung mendapatkannya melalui panggilan manual (metode 4).
Oke, di atas adalah seluruh konten artikel ini. Saya berharap konten artikel ini memiliki nilai referensi tertentu untuk studi atau pekerjaan semua orang. Jika Anda memiliki pertanyaan, Anda dapat meninggalkan pesan untuk berkomunikasi. Terima kasih atas dukungan Anda ke wulin.com.
Referensi