مقدمة
لقد واجهت مؤخرًا مهمة تطبيق وحدة تحكم تتعامل مع المواقف بطرق مختلفة حيث توجد معلمات طلب وحيث لا توجد. تفاقمت المشكلة من خلال حقيقة أن هناك حاجة لطريقتين مختلفتين في وحدة التحكم. الميزات القياسية لـ Spring MVC لم تسمح بذلك. اضطررت لحفر أعمق قليلا. من يهتم - مرحبا بكم في القط.
ماذا نريد
إليك اختبار قصير يصف جوهر المهمة.
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())); } }
نأمل أن يتم حل كل شيء بمفرده
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; } }
ومع ذلك ، لم يكن Spring صديقًا كما هو متوقع ، وكان الناتج:
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.`
لا تستسلم
بذلت محاولات لفصل تعيين هذه الأساليب عن طريق @RequestMapping(params = "some condition")
، لكن لسوء الحظ ، في الإصدار المستخدم من Spring 5.1.8 لا توجد طريقة لتعيين الشرط لطلب احتواء أي معلمات. يمكنك تحديد وجود معلمات بأسماء محددة أو عدم وجود معلمات بأسماء محددة. لا أحد ولا الآخر كان مناسبًا ، لأن المعلمات يمكن أن تكون مختلفة في كل طلب. أود أن أكتب شيئًا @RequestMapping(params = "*")
للإشارة إلى أن الطلب يجب أن يحتوي على بعض المعلمات أوRequestMapping (params = "! *") `للإشارة إلى ما لا ينبغي أن يكون في الطلب أن لا توجد معلمات.
وما هو في الوثائق؟
بعد تدخين الوثائق ، تم العثور على قسم CustomAnnotations ، والذي نرى فيه:
Spring MVC also supports custom request-mapping attributes with custom request-matching logic.
تقرر تقديم تعليق توضيحي ، مما سمح لي بتحديد الشروط التي أحتاجها لتوافر معلمات الاستعلام.
هدف
أريد إضافة تعليق توضيحي @NoRequestParams
إلى الطريقة ، مع الإشارة إلى أن هذه الطريقة تعالج الطلبات بدون معلمات.
@Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface NoRequestParams { }
المراقب المالي مع الشرح لدينا:
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
لنبدأ
يتم وصف التكوين Spring-MVC القياسي ، الذي يتم تنشيطه بواسطة التعليق التوضيحي EnableWebMvc ، في فئة WebMvcConfigurationSupport . إنشاء مثيل لقول لفئة RequestMappingHandlerMapping ، والتي تنفذ التعيين باستخدام كائنات فئة RequestMappingInfo ، والتي بدورها تقوم بتغليف المعلومات حول تعيين طلبات الطريقة. يتم تقديم هذه المعلومات في شكل شروط - كائنات فئة تنفذ واجهة RequestCondition. الربيع لديه 6 تطبيقات جاهزة:
بالإضافة إلى هذه التطبيقات ، يمكننا تحديد الخاصة بنا. للقيام بذلك ، نحتاج إلى تطبيق واجهة RequestCondition ، واستخدام هذا التطبيق لتلبية احتياجاتنا. يمكنك تطبيق الواجهة مباشرة أو استخدام فئة مجردة
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; } }
يتم استخدام أول طريقتين لسلسلة تمثل حالتنا.
هناك حاجة لطريقة T combine (T other) لدمج الشروط ، على سبيل المثال ، إذا كان هناك تعليق توضيحي على الطريقة والفئة. في هذه الحالة ، يتم استخدام طريقة الجمع لدمج. لا يعني التعليق التوضيحي الخاص بنا تركيبة - وبالتالي ، فإننا ببساطة نعيد حالة الحالة الحالية الخاصة بنا.
يتم استخدام الأسلوب int comparTo (T other ، طلب HttpServletRequest) لمقارنة الشروط في سياق طلب معين. أي إذا كانت هناك عدة شروط من نفس النوع للطلب ، فسيكتشف أي منها هو الأكثر تحديدًا. ولكن مرة أخرى ، حالتنا هي الحالة الوحيدة الممكنة ، وبالتالي فإننا ببساطة نعيد 0 ، أي جميع شروطنا متساوية.
ويرد المنطق الأساسي للعمل في أسلوب T getMatchingCondition (طلب HttpServletRequest) . في هذه الطريقة ، يجب أن نقرر الاستعلام عما إذا كانت حالتنا تنطبق عليها أم لا. إذا كان الأمر كذلك ، فأعد كائن الشرط. إن لم يكن ، والعودة null
. في حالتنا ، نرجع كائن شرط إذا كان الطلب لا يحتوي على أي معلمات.
الآن نحن بحاجة إلى تضمين حالتنا في عملية رسم الخرائط. للقيام بذلك ، سوف نرث من التطبيق القياسي لـ RequestMappingHandlerMapping
وإعادة تعريف getCustomMethodCondition(Method method)
، والتي يتم إنشاؤها للتو من أجل إضافة الشروط المخصصة الخاصة بك. علاوة على ذلك ، يتم استخدام هذه الطريقة لتحديد شروط أساليب التحكم. هناك أيضًا طريقة getCustomTypeCondition(Class<?> handlerType)
، والتي يمكن استخدامها لتحديد الشروط بناءً على معلومات حول فئة وحدة التحكم. في حالتنا ، نحن لسنا في حاجة إليها.
نتيجة لذلك ، لدينا التنفيذ التالي:
CustomRequestMappingHandlerMapping.java public class CustomRequestMappingHandlerMapping extends RequestMappingHandlerMapping { @Override protected RequestCondition<?> getCustomMethodCondition(Method method) { return method.isAnnotationPresent(NoRequestParams.class) ? NO_PARAMS_CONDITION : null; } }
المنطق ليس معقدًا - فنحن نتحقق من وجود تعليقاتنا التوضيحية ، وإذا كان موجودًا ، نرجع كائن حالتنا.
لتمكين تطبيق التعيين لدينا ، سنقوم بتوسيع تهيئة Spring MVC القياسية:
WebConfig.java @Configuration public class WebConfig extends WebMvcConfigurationSupport { @Override protected RequestMappingHandlerMapping createRequestMappingHandlerMapping() { return new CustomRequestMappingHandlerMapping(); } @Bean public TestController getTestController() { return new TestController(); } }
أضف تعليقنا التوضيحي إلى وحدة التحكم:
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; } }
وتحقق من النتيجة:
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
وبالتالي ، يمكننا تغيير منطق التعيين وفقًا لاحتياجاتنا والتوصل إلى شروطنا الخاصة. على سبيل المثال ، الشروط على عنوان IP أو المستخدم المستخدم. قد تكون هناك حلول أبسط ، ولكن على أي حال ، فإن تجميع دراجتك مفيد أيضًا في بعض الأحيان.
شكرا لاهتمامكم
جيثب رمز المثال