Kustomisasi pemetaan pengendali Spring MVC

Pendahuluan


Baru-baru ini saya dihadapkan dengan tugas mengimplementasikan controller yang menangani situasi dengan berbagai cara di mana ada parameter permintaan dan di mana tidak ada. Masalahnya diperparah oleh fakta bahwa tepat dua metode yang berbeda diperlukan dalam pengontrol. Fitur standar Spring MVC tidak memungkinkan ini. Saya harus menggali lebih dalam. Siapa yang peduli - selamat datang di kucing.


Apa yang kita inginkan


Berikut ini adalah tes singkat yang menggambarkan esensi dari tugas tersebut.


TestControllerTest.java
@SpringJUnitWebConfig(WebConfig.class) class TestControllerTest {     @Autowired     WebApplicationContext webApplicationContext;     @Test     void testHandleRequestWithoutParams() throws Exception {         MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();         mockMvc.perform(MockMvcRequestBuilders.get("/test"))                 .andExpect(status().isOk())                 .andExpect(result ->                         assertEquals(TestController.HANDLE_REQUEST_WITHOUT_PARAMS, result.getResponse().getContentAsString()));     }     @Test     void testHandleRequestWithParams() throws Exception {         MockMvc mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext).build();         mockMvc.perform(MockMvcRequestBuilders                 .get("/test")                 .param("someparam", "somevalue"))                 .andExpect(status().isOk())                 .andExpect(result ->                         assertEquals(TestController.HANDLE_REQUEST_WITH_PARAMS, result.getResponse().getContentAsString()));     } } 

Kami berharap semuanya akan diselesaikan dengan sendirinya


TestController.java
 @Controller @RequestMapping("/test") public class TestController {    public static final String HANDLE_REQUEST_WITH_PARAMS = "handleRequestWithParams";    public static final String HANDLE_REQUEST_WITHOUT_PARAMS = "handleRequestWithoutParams";    @GetMapping    @ResponseBody    public String handleRequestWithParams(SearchQuery query) {        return HANDLE_REQUEST_WITH_PARAMS;    }    @GetMapping    @ResponseBody    public String handleRequestWithoutParams() {        return HANDLE_REQUEST_WITHOUT_PARAMS;    } } 

Namun, Spring tidak ramah seperti yang diharapkan, dan hasilnya adalah:


 java.lang.IllegalStateException: Ambiguous mapping. Cannot map 'getTestController' method public java.lang.String ru.pchurzin.spring.customannotations.TestController.handleRequestWithoutParams() to {GET /test}: There is already 'getTestController' bean method public java.lang.String ru.pchurzin.spring.customannotations.TestController.handleRequestWithParams(ru.pchurzin.spring.customannotations.SearchQuery) mapped.` 

Jangan menyerah


Upaya telah dilakukan untuk memisahkan pemetaan metode-metode ini melalui @RequestMapping(params = "some condition") , tetapi sayangnya dalam versi Spring 5.1.8 yang digunakan tidak ada cara untuk mengatur kondisi permintaan untuk berisi parameter apa pun . Anda dapat menentukan keberadaan parameter dengan nama tertentu atau tidak adanya parameter dengan nama tertentu. Tidak ada yang cocok, karena parameter bisa berbeda di setiap permintaan. Saya ingin menulis sesuatu @RequestMapping(params = "*") untuk menunjukkan bahwa permintaan harus berisi beberapa parameter atau @RequestMapping (params = "! *") `Untuk menunjukkan apa yang tidak boleh ada dalam permintaan tidak ada parameter.


Dan apa yang ada di dokumentasi?


Setelah merokok dokumentasi, bagian CustomAnnotations ditemukan, di mana kita melihat:


 Spring MVC also supports custom request-mapping attributes with custom request-matching logic. 

Diputuskan untuk membuat anotasi, yang memungkinkan saya menentukan kondisi yang saya perlukan untuk ketersediaan parameter kueri.


Tujuan


Saya ingin menambahkan penjelasan @NoRequestParams ke metode, yang menunjukkan bahwa metode ini memproses permintaan tanpa parameter.


 @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoRequestParams { } 

Pengontrol dengan anotasi kami:


Testcontoller.java
 @Controller @RequestMapping("/test") public class TestController {    public static final String HANDLE_REQUEST_WITH_PARAMS = "handleRequestWithParams";    public static final String HANDLE_REQUEST_WITHOUT_PARAMS = "handleRequestWithoutParams";    @GetMapping    @ResponseBody    public String handleRequestWithParams(SearchQuery query) {        return HANDLE_REQUEST_WITH_PARAMS;    }    @GetMapping    @ResponseBody @NoRequestParams //     public String handleRequestWithoutParams() {        return HANDLE_REQUEST_WITHOUT_PARAMS;    } } 

Mari kita mulai


Konfigurasi Spring-MVC standar, diaktifkan oleh anotasi @EnableWebMvc , dijelaskan dalam kelas WebMvcConfigurationSupport . Itu instantiates kacang dari kelas RequestMappingHandlerMapping , yang mengimplementasikan pemetaan menggunakan objek dari kelas RequestMappingInfo , yang pada gilirannya merangkum informasi tentang pemetaan permintaan metode. Informasi ini disajikan dalam bentuk kondisi - objek kelas yang mengimplementasikan antarmuka RequestCondition. Spring memiliki 6 implementasi siap pakai:



Selain implementasi ini, kita dapat mendefinisikan kita sendiri. Untuk melakukan ini, kita perlu mengimplementasikan antarmuka RequestCondition , dan menggunakan implementasi ini untuk kebutuhan kita. Anda dapat mengimplementasikan antarmuka secara langsung atau menggunakan kelas abstrak
AbstractRequestCondition .


NoRequestParamsCondition.java
 public class NoRequestParamsCondition extends AbstractRequestCondition<NoRequestParamsCondition> {    public final static NoRequestParamsCondition NO_PARAMS_CONDITION = new NoRequestParamsCondition();    @Override    protected Collection<?> getContent() {        return Collections.singleton("no params");    }    @Override    protected String getToStringInfix() {        return "";    }    @Override    public NoRequestParamsCondition combine(NoRequestParamsCondition other) {        return this;    }    @Override    public NoRequestParamsCondition getMatchingCondition(HttpServletRequest request) {        if (request.getParameterMap().isEmpty()) {            return this;        }        return null;    }    @Override    public int compareTo(NoRequestParamsCondition other, HttpServletRequest request) {        return 0;    } } 

Dua metode pertama digunakan untuk merepresentasikan kondisi kita.


Metode T menggabungkan (T lain) diperlukan untuk menggabungkan kondisi, misalnya, jika ada penjelasan tentang metode dan kelas. Dalam hal ini, metode gabungan digunakan untuk menggabungkan. Anotasi kami tidak menyiratkan kombinasi - oleh karena itu, kami hanya mengembalikan contoh kondisi kami saat ini.


Metode int compareTo (T lainnya, permintaan HttpServletRequest) digunakan untuk membandingkan kondisi dalam konteks permintaan tertentu. Yaitu jika ada beberapa kondisi dengan tipe yang sama untuk permintaan, itu akan mencari tahu mana yang paling spesifik. Tetapi sekali lagi, kondisi kami adalah satu-satunya yang mungkin, oleh karena itu kami hanya mengembalikan 0, yaitu Semua kondisi kami sama.


Logika dasar pekerjaan terkandung dalam metode T getMatchingCondition (permintaan HttpServletRequest) . Dalam metode ini, kita harus memutuskan untuk menanyakan apakah kondisi kita berlaku atau tidak. Jika demikian, maka kembalikan objek kondisi. Jika tidak, kembalikan null . Dalam kasus kami, kami mengembalikan objek kondisi jika permintaan tidak mengandung parameter apa pun.


Sekarang kita perlu memasukkan kondisi kita dalam proses pemetaan. Untuk melakukan ini, kami akan mewarisi dari penerapan standar RequestMappingHandlerMapping
dan mendefinisikan kembali metode getCustomMethodCondition(Method method) , yang baru saja dibuat untuk menambahkan kondisi kustom Anda sendiri. Selain itu, metode ini digunakan untuk menentukan kondisi untuk metode pengontrol. Ada juga metode getCustomTypeCondition(Class<?> handlerType) , yang dapat digunakan untuk menentukan kondisi berdasarkan informasi tentang kelas controller. Dalam kasus kami, kami tidak membutuhkannya.


Akibatnya, kami memiliki implementasi berikut:


CustomRequestMappingHandlerMapping.java
 public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping {    @Override    protected RequestCondition<?> getCustomMethodCondition(Method method) {        return method.isAnnotationPresent(NoRequestParams.class) ? NO_PARAMS_CONDITION : null;    } } 

Logikanya tidak rumit - kami memeriksa keberadaan anotasi kami dan, jika ada, kami mengembalikan objek kondisi kami.


Untuk mengaktifkan implementasi pemetaan kami, kami akan memperluas konfigurasi Spring MVC standar:


Webconfig.java
 @Configuration public class WebConfig extends WebMvcConfigurationSupport {       @Override    protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() {        return new CustomRequestMappingHandlerMapping();    }    @Bean    public TestController getTestController() {        return new TestController();    } } 

Tambahkan anotasi kami ke controller:


TestController.java
 @Controller @RequestMapping("/test") public class TestController {    public static final String HANDLE_REQUEST_WITH_PARAMS = "handleRequestWithParams";    public static final String HANDLE_REQUEST_WITHOUT_PARAMS = "handleRequestWithoutParams";    @GetMapping    @ResponseBody    public String handleRequestWithParams(SearchQuery query) {        return HANDLE_REQUEST_WITH_PARAMS;    }    @GetMapping    @ResponseBody    @NoRequestParams    public String handleRequestWithoutParams() {        return HANDLE_REQUEST_WITHOUT_PARAMS;    } } 

dan periksa hasilnya:


 Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.851 s - in ru.pchurzin.spring.customannotations.TestControllerTest Results: Tests run: 2, Failures: 0, Errors: 0, Skipped: 0 

Dengan demikian, kita dapat mengubah logika pemetaan sesuai dengan kebutuhan kita dan muncul dengan kondisi kita sendiri. Misalnya, kondisi pada alamat IP atau agen-Pengguna yang digunakan. Mungkin ada solusi yang lebih sederhana, tetapi dalam hal apa pun, merakit sepeda Anda terkadang juga bermanfaat.


Terima kasih atas perhatian anda


Kode contoh Github

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


All Articles