التحقق من صحة أجسام الطلبات باستخدام @Valid
التحقق من صحة أجسام الطلبات باستخدام @Valid
تعلّمت في الدروس السابقة كيفية تزيين فئة نموذجية بقيود Bean Validation. الخطوة الحرجة التالية هي تشغيل هذا التحقق داخل متحكم Spring MVC. بدون مشغّل صريح، تبقى القيود خاملة — يُفكك Spring حمولة JSON ويُسلّمها إليك بصرف النظر عن صحة حقولها. يتناول هذا الدرس كيف يأمر @Valid و@Validated Spring بتشغيل المدقق قبل تنفيذ جسم الدالة، وما يحدث عند فشل التحقق، والأنماط التي ينبغي اتباعها للحفاظ على نظافة المتحكمات.
التأكد من وجود المكتبة المطلوبة
قبل أي شيء: لا يتضمّن Spring Boot 3 دعم التحقق من الصحة في spring-boot-starter-web. يجب إضافة المكتبة المخصصة صراحةً، وإلا يُتجاهل @Valid صمتًا ولا تُشغَّل القيود أبدًا.
يجلب هذا التبعية Hibernate Validator، وهو التطبيق المرجعي لـ Jakarta Validation 3.0. بمجرد وجوده في مسار الفئات، يُهيئ Spring تلقائيًا LocalValidatorFactoryBean ويربطه بطبقة MVC.
تشريح دالة المتحكم التي تُجري التحقق
لنأخذ نقطة نهاية تسجيل المستخدم مثالًا. جسم الطلب هو كائن نقل بيانات RegisterRequest مزيَّن بقيود Bean Validation. وضع @Valid مباشرةً قبل معامل @RequestBody يأمر محلّل الوسيطات في Spring بتمرير الكائن المُفكَّك عبر المدقق قبل أن تستقبله الدالة.
يحمل كائن نقل البيانات نفسه تعليقات القيود:
@Valid من مواصفة Jakarta Validation (jakarta.validation.Valid) ويُشغّل التحقق على الكائن المُزيَّن بما في ذلك التتالي إلى الكائنات المتداخلة المُعلَّمة بـ @Valid أيضًا. أما @Validated فهو تعليق خاص بـ Spring يُضيف دعم مجموعات التحقق (تُغطّى في الدرس التاسع). للتحقق من جسم الطلب البسيط، يُعدّ @Valid الخيار المعياري المتوافق مع المواصفة.
ما يحدث عند فشل التحقق
عند انتهاك قيد واحد على الأقل، يرمي Spring استثناء MethodArgumentNotValidException. يُنتج هذا الاستثناء افتراضيًا استجابة 400 Bad Request. يحمل الاستثناء كائن BindingResult الذي يضم كل خطأ حقل فردي. لا يُستدعى جسم الدالة أبدًا — يُرمى الاستثناء من قِبل الإطار قبل وصول التحكم إلى كودك.
التقاط BindingResult يدويًا
ثمة نمط ثانٍ أقل شيوعًا: تصريح معامل BindingResult مباشرةً بعد معامل @RequestBody. عند فعل ذلك، يُثبّط Spring الاستثناء التلقائي ويتيح لك فحص الأخطاء بنفسك داخل جسم الدالة.
@ControllerAdvice واحد (الدرس السابع) يلتقط MethodArgumentNotValidException عالميًا. استخدم BindingResult فقط عندما تحتاج فعلًا إلى منطق خاص بنقطة النهاية لا يستطيع معالج عالمي التعبير عنه.
التحقق من الكائنات المتداخلة
إذا احتوى كائن نقل البيانات على كائن متداخل، طبّق @Valid على الحقل المتداخل. بدونه، تُتجاهل القيود الموجودة على الفئة المتداخلة صمتًا.
استخدام @Valid على معامل نوع المجموعة (List<@Valid LineItem>) يستفيد من التحقق من عناصر الحاوية المُقدَّم في Bean Validation 2.0 والمدعوم بالكامل في Spring Boot 3. يتحقق Hibernate Validator من كل عنصر في القائمة ويجمع جميع الانتهاكات قبل الرمي.
التحقق من متغيرات المسار ومعاملات الاستعلام
لا يقتصر Bean Validation على أجسام الطلبات. يمكن تطبيق تعليقات القيود مباشرةً على معاملات الدوال لمتغيرات المسار ومعاملات الطلب. لكي يعمل هذا، أضف @Validated على مستوى الفئة — يأمر هذا Spring بإنشاء وكيل حول المتحكم يعترض استدعاءات الدوال ويفحص القيود على مستوى المعامل.
عند انتهاك قيد متغير مسار أو معامل استعلام، يرمي Spring استثناء ConstraintViolationException بدلًا من MethodArgumentNotValidException. هذا الفارق مهم عند كتابة معالج الاستثناءات العالمي: تحتاج إلى معالجة النوعين لمنح العملاء استجابات أخطاء متسقة عبر جميع وجهات الإدخال.
@RequestBody. هذا يربط عقد واجهتك البرمجية بمخطط قاعدة البيانات. عرِّف كائنات نقل بيانات طلب مخصصة — تعمل سجلات Java بشكل نظيف في Java 16 وما بعده — وصلها بالكيانات في طبقة الخدمة. تحتفظ بالتحكم الكامل في الحقول المكشوفة والقيود المُطبَّقة على حدود الواجهة البرمجية وكيفية الإبلاغ عن الأخطاء للعملاء.
الصورة الكاملة: تدفق الطلب
عند وصول طلب POST إلى /api/users تجري الخطوات التالية بالترتيب:
- إلغاء التسلسل: يقرأ Jackson جسم JSON وينشئ نسخة
RegisterRequest. - التحقق من الصحة: لوجود
@Valid، يستدعي Spring مدقق Hibernate Validator على تلك النسخة. - وُجد انتهاك؟ يرمي Spring استثناء
MethodArgumentNotValidException؛ يُتجاوز جسم الدالة. - لا انتهاكات: يُمرَّر الكائن المُتحقَّق منه إلى
register()التي تفوّض إلى طبقة الخدمة.
يُبقي هذا التسلسل مخاوف التحقق خارج منطق الأعمال تمامًا. يمكن لدوال الخدمة أن تثق بأن كل RegisterRequest تستقبله يستوفي القيود المُعلَنة بالفعل — لا حاجة لفحوصات دفاعية يدوية للقيم الفارغة أو فحص الحقول.
الخلاصة
يتطلب تشغيل التحقق في متحكم Spring ثلاثة أشياء: وجود spring-boot-starter-validation في مسار الفئات، وتعليقات القيود على كائن نقل البيانات، ووضع @Valid قبل معامل @RequestBody. عند فشل قيد، يرمي Spring استثناء MethodArgumentNotValidException قبل تشغيل جسم الدالة. تتطلب الكائنات المتداخلة @Valid الخاصة بها لتتالي التحقق. لمتغيرات المسار ومعاملات الاستعلام، استخدم @Validated على مستوى الفئة مع قيود على مستوى المعامل، وانتبه إلى أن الانتهاكات تُنتج ConstraintViolationException بدلًا منه. تركّز الدروس التالية على التقاط هذه الاستثناءات وتشكيل استجابات الخطأ التي يستقبلها عملاء واجهتك البرمجية.