مقدمة
ستقدم هذه المقالة عدة طرق للحصول على كائنات طلب في نظام الويب الذي تم تطويره بواسطة Spring MVC ومناقشة سلامة مؤشرات الترابط. لن أقول الكثير أدناه ، دعنا نلقي نظرة على المقدمة التفصيلية معًا.
ملخص
عند تطوير نظام ويب باستخدام Spring MVC ، غالبًا ما تحتاج إلى استخدام كائن طلب عند معالجة الطلبات ، مثل الحصول على عنوان IP للعميل ، وعنوان URL المطلوب ، والسمات في الرأس (مثل ملفات تعريف الارتباط ، ومعلومات التفويض) ، والبيانات في الجسم ، وما إلى ذلك ، كما هو في الربيع mvc ، والمراقبة ، والخدمة وغيرها من الكائنات تتعامل مع طلبات المفرد ، والموضوع الأكثر أهمية للالتقاء هو الحصول على كائنات الطلب هي أن هناك طلبًا مطلوبًا. هل هناك عدد كبير من الطلبات المتزامنة ، هل يمكن ضمان استخدام كائنات الطلب المختلفة في طلبات/مؤشرات ترابط مختلفة؟
هناك سؤال آخر يجب ملاحظته هنا: أين يمكنني استخدام كائن الطلب "عند معالجة طلب" المذكورة سابقًا؟ بالنظر إلى وجود اختلافات طفيفة في طرق الحصول على كائنات الطلب ، يمكن تقسيمها تقريبًا إلى فئتين:
1) استخدم كائنات الطلب في حبوب الربيع: قم بتضمين كلا من حبوب MVC مثل وحدة التحكم والخدمة والمستودع وفاصوليا الربيع العادية مثل المكون. لراحة التفسير ، يشار إلى الفاصوليا في فصل الربيع في النص التالي باسم الفاصوليا لفترة قصيرة.
2) استخدم كائنات الطلب في غير المنحدر: كما هو الحال في طرق كائنات Java العادية ، أو في الطرق الثابتة للفئات.
بالإضافة إلى ذلك ، تناقش هذه المقالة حول كائن الطلب الذي يمثل الطلب ، ولكن الطريقة المستخدمة تنطبق أيضًا على كائن الاستجابة ، inputstream/reader ، OutputStream ، إلخ. ؛ حيث يمكن لـ InputStream/Reader قراءة البيانات في الطلب ، ويمكن لـ OutputStream/الكاتب كتابة البيانات إلى الاستجابة.
أخيرًا ، ترتبط طريقة الحصول على كائن الطلب أيضًا بإصدار Spring و MVC ؛ تمت مناقشة هذه المقالة بناءً على ربيع 4 ، والتجارب التي أجريت كلها تستخدم الإصدار 4.1.1.
كيفية اختبار سلامة الموضوع
نظرًا لأن مشكلات سلامة مؤشرات الترابط في كائن الطلب تحتاج إلى عناية خاصة ، من أجل تسهيل المناقشة أدناه ، دعنا أولاً نوضح كيفية اختبار ما إذا كان كائن الطلب آمنًا لخيط الخيط.
تتمثل الفكرة الأساسية للاختبار في محاكاة عدد كبير من الطلبات المتزامنة على العميل ، ثم تحديد ما إذا كانت الطلبات تستخدم على الخادم.
الطريقة الأكثر سهولة لتحديد ما إذا كان كائن الطلب هو نفسه هو طباعة عنوان كائن الطلب. إذا كان الأمر هو نفسه ، فهذا يعني أنه يتم استخدام نفس الكائن. ومع ذلك ، في جميع تطبيقات خادم الويب تقريبًا ، يتم استخدام تجمعات مؤشرات الترابط ، مما يؤدي إلى طلبين يصلان على التوالي ، والذي يمكن معالجته بواسطة نفس الموضوع: بعد معالجة الطلب السابق ، يقوم تجمع مؤشرات الترابط باستعادة مؤشر الترابط ويعيد تعيين مؤشر الترابط إلى الطلب اللاحق. في نفس الخيط ، من المحتمل أن يكون كائن الطلب المستخدم هو نفسه (العنوان هو نفسه ، والسمات مختلفة). لذلك ، حتى بالنسبة لطرق آمنة مؤشرات الترابط ، قد تكون عناوين كائن الطلب المستخدمة في الطلبات المختلفة هي نفسها.
لتجنب هذه المشكلة ، تتمثل إحدى الطرق في السماح لخيط الخيط ببضع ثوان أثناء عملية معالجة الطلب ، والتي يمكن أن تجعل كل مؤشر ترابط يعمل لفترة كافية لتجنب تخصيص نفس الخيط لطلبات مختلفة ؛ تتمثل الطريقة الأخرى في استخدام سمات أخرى للطلب (مثل المعلمات ، الرأس ، والجسم ، وما إلى ذلك) كأساس لما إذا كان الطلب آمنًا مؤشر ترابط ، لأنه حتى إذا كانت الطلبات المختلفة تستخدم مؤشر الترابط نفسه تلو الآخر (عنوان كائن الطلب هو نفسه) ، حيث يتم إنشاء كائن الطلب مرتين باستخدام سمات مختلفة ، فإن استخدام كائن الطلب هو مؤشر الترابط. تستخدم هذه الورقة الطريقة الثانية للاختبار.
رمز اختبار العميل هو كما يلي (قم بإنشاء 1000 موضوع لإرسال الطلبات بشكل منفصل):
اختبار الفئة العامة {public static void main (string [] args) يرمي الاستثناء {String prefix = uuid.randomuuid (). لـ (int i = 0 ؛ i <1000 ؛ i ++) {final string value = prefix+i ؛ new Thread () {Override public void run () {try {closablehttpclient httpclient = httpclients.createdefault () ؛ httpget httpget = new httpget ("http: // localhost: 8080/test؟ key =" + value) ؛ httpclient.execute (httpget) ؛ httpclient.close () ؛ } catch (ioException e) {E.PrintStackTrace () ؛ } } } } } }.يبدأ()؛ }}}رمز وحدة التحكم في الخادم كما يلي (تم حذف رمز الحصول على كائن الطلب مؤقتًا):
ControllerPublic Class TestController {// تخزين المعلمات الحالية لتحديد ما إذا كانت المعلمات مكررة ، وبالتالي تحديد ما إذا كان مؤشر الترابط هو مجموعة ثابتة عامة ثابتة <string> set = new hassset <> () ؛ requestmapping ("/test") اختبار الفراغ العام () رميات InterruptedException {// ........................................................................................................................................... ........................................................................................................................................... ........................................................................................................................................... ........................................................................................................................................... "/يظهر مرارًا وتكرارًا ، طلب التزامن غير آمن!") ؛ } آخر {system.out.println (value) ؛ set.add (القيمة) ؛ } // تم تنفيذ برنامج المحاكاة لفترة من الزمن ، thread.sleep (1000) ؛ }}إذا كان كائن الطلب آمنًا لتأثير مؤشرات الترابط ، فإن النتيجة المطبوعة في الخادم هي كما يلي:
إذا كانت هناك مشكلة في سلامة مؤشرات الترابط ، فقد تبدو النتيجة المطبوعة في الخادم هكذا:
إذا لم يكن هناك وصف خاص ، فسيتم حذف رمز الاختبار من الرموز لاحقًا في هذه المقالة.
الطريقة 1: إضافة معلمات إلى وحدة التحكم
مثال رمز
هذه الطريقة هي أبسط التنفيذ ، وإدخال رمز وحدة التحكم مباشرة:
ControllerPublic Class TestController {REquestMapping ("/test") اختبار الفراغ العام (طلب httpservletrequest) رمي InterruptedException {// تم تنفيذ برنامج المحاكاة لفترة زمنية من الوقت. }}مبدأ هذه الطريقة هو أنه عندما تبدأ طريقة وحدة التحكم في معالجة الطلب ، ستقوم Spring بتعيين كائن الطلب إلى معلمات الطريقة. بالإضافة إلى كائن الطلب ، هناك العديد من المعلمات التي يمكن الحصول عليها من خلال هذه الطريقة. لمزيد من التفاصيل ، يرجى الرجوع إلى: https://docs.spring.io/spring/docs/current/spring-framework-reference/web.html#mvc-ann-methods
بعد الحصول على كائن الطلب في وحدة التحكم ، إذا كنت ترغب في استخدام كائن الطلب في طرق أخرى (مثل أساليب الخدمة ، وطرق فئة الأدوات ، وما إلى ذلك) ، فأنت بحاجة إلى تمرير كائن الطلب كمعلمة عند الاتصال بهذه الأساليب.
السلامة الموضوع
نتائج الاختبار: سلامة الموضوع
التحليل: في هذا الوقت ، يكون كائن الطلب هو معلمة طريقة ، وهي تعادل متغير محلي ، ولا شك في أن الخيط آمن.
إيجابيات وسلبيات
العيب الرئيسي لهذه الطريقة هو أن كائن الطلب لا لزوم له على الكتابة ، والذي ينعكس بشكل أساسي في نقطتين:
1) إذا كان كائن الطلب مطلوبًا في طرق وحدة تحكم متعددة ، فيجب إضافة معلمة الطلب إلى كل طريقة.
2) لا يمكن أن تبدأ اكتساب كائن الطلب إلا من وحدة التحكم. إذا كان المكان الذي يتم فيه استخدام كائن الطلب في مكان أعمق لمستوى استدعاء الوظيفة ، فإن جميع الطرق في سلسلة المكالمات بأكملها تحتاج إلى إضافة معلمة الطلب.
في الواقع ، أثناء عملية معالجة الطلب بأكملها ، يتم تشغيل كائن الطلب عبر الطلب بأكمله ؛ وهذا هو ، باستثناء الحالات الخاصة مثل أجهزة ضبط الوقت ، فإن كائن الطلب يعادل متغيرًا عالميًا داخل الخيط. هذه الطريقة تعادل تمرير هذا المتغير العالمي حوله.
الطريقة 2: الحقن التلقائي
مثال رمز
قم بتحميل الرمز أولاً:
controllerpublic class testController {Auutowired private httpservletrequest طلب ؛ // request requiredrequestMapping ("/test") اختبار الفراغ العام () يلقي InterruptedException {// تم تنفيذ برنامج المحاكاة لفترة من الوقت thread.sleep (1000) ؛ }} السلامة الموضوع
نتائج الاختبار: سلامة الموضوع
التحليل: في الربيع ، يكون نطاق وحدة التحكم هو Singleton (Singleton) ، مما يعني أنه في نظام الويب بأكمله ، هناك اختبار واحد فقط ؛ لكن الطلب الذي تم حقنه آمن مؤشر الترابط ، لأنه:
وبهذه الطريقة ، عند تهيئة الفول (testController في هذا المثال) ، لا يضخ الربيع كائن طلب ، ولكنه وكيل ؛ عندما تحتاج الفول إلى استخدام كائن الطلب ، يتم الحصول على كائن الطلب من خلال الوكيل.
فيما يلي وصف لهذا التنفيذ من خلال رمز محدد.
أضف نقاط التوقف إلى الكود أعلاه وعرض خصائص كائن الطلب كما هو موضح في الشكل أدناه:
كما يتضح في الشكل ، فإن الطلب هو في الواقع وكيل: يظهر تنفيذ الوكيل في الفئة الداخلية من الأوتار الآلي
ObjectFactoryDelegatingInvocationHandler: /*** invocationHandler العاكس للوصول كسول إلى الكائن الهدف الحالي. */@cumpresswarnings ("Serial") Private Static Class ObjectFactoryDelegatingInvocationHandler تنفذ InvocationHandler ، Serializable {private Final ObjectFactory <؟> ObjectFactory ؛ ObjectFactoryDelegatingInvocationHandler (ObjectFactory <؟> ObjectFactory) {this.ObjectFactory = ObjectFactory ؛ } Override الكائن العام invoke (وكيل الكائن ، طريقة الطريقة ، الكائن [] args) يلقي رمي {// ... حذف الكود غير ذي صلة try {return method.invoke (this.objectfactory.getObject () ، args) ؛ // تنفيذ الوكيل الكود الأساسي} catch (invocationTargetException ex) {throw ex.getTargetException () ؛ }}}بمعنى آخر ، عندما نسمي طريقة الطلب ، فإننا نسمي بالفعل طريقة الطريقة التي تم إنشاؤها بواسطة ObjectFactory.getObject () ؛ الكائن الذي تم إنشاؤه بواسطة ObjectFactory.getObject () هو كائن الطلب الحقيقي.
استمر في مراقبة الشكل أعلاه وابحث عن أن نوع ObjectFactory هو الفئة الداخلية requestObjectFactory من webapplicationContextUtils ؛ ورمز requestObjectFactory كما يلي:
/*** المصنع الذي يعرض كائن الطلب الحالي عند الطلب. */ @cumpresswarnings ("Serial") private static class requestObjectFactory تنفذ ObjectFactory <ServleTRequest> ، serializable {Override public servletrequest getObject () {return currentRequestAttributes (). getRequest () ؛ } Override Public String ToString () {return "HttPservleTrequest الحالي" ؛ }}من بينها ، للحصول على كائن الطلب ، تحتاج إلى استدعاء طريقة CurrentRequestatTributes () للحصول على كائن requestAtTributes. تنفيذ هذه الطريقة على النحو التالي:
/*** إرجاع مثيل requestAtTributes الحالي كـ servletRequestAtTributes. */private static servletrequestattributes currentRequestAtTributes () {requestAtTributes requestAttr = requestContexTholder.CurrentRequestAtTributes () ؛ if (! (requestAttr مثيل servletRequestAttributes)) {رمي جديد غير aluvalstateException ("الطلب الحالي ليس طلب servlet") ؛ } الإرجاع (servletRequestatTributes) requestAttr ؛}الكود الأساسي الذي ينشئ كائن requestAtTributes موجود في فئة requestContextholder ، حيث يكون الرمز ذي الصلة كما يلي (تم حذف الكود غير المرتبط في الفصل):
Public Abstract Class requestContextholder {public static requestAtTributes CurrentRequestAtTributes () remlysalstateException {requestAttributes entributes = getRequestatTributes () ؛ // تم حذف المنطق غير ذي صلة هنا ...... سمات العودة ؛ } requestAttributes getRequestAttributes () {requestAtTributes entributes = requestAtTributeSholder.get () ؛ if (entributes == null) {entributes = errantableRequestAttRibtSholder.get () ؛ } سمات الإرجاع ؛ } private static final threadlocal <quertentTributes> requestAtTributeSholder = new ThereThReadLocal <SquertAttRibutes> ("سمات الطلب") ؛ private Static Final Threadlocal <SquertaTtributes> internitableRequestAttRibtSholder = newInherItableTableTheReadLocal <quertentAtTributes> ("سياق الطلب") ؛}من هذا الرمز ، يمكننا أن نرى أن كائن requestAtTributes الذي تم إنشاؤه هو متغير محلي مؤشر ترابط (ThreadLocal) ، وبالتالي فإن كائن الطلب هو أيضًا متغير محلي مؤشر ترابط ؛ هذا يضمن سلامة مؤشر ترابط كائن الطلب.
إيجابيات وسلبيات
المزايا الرئيسية لهذه الطريقة:
1) لا يقتصر الحقن على وحدة التحكم: في الطريقة 1 ، يمكن إضافة معلمة الطلب فقط إلى وحدة التحكم. بالنسبة للطريقة 2 ، لا يمكن حقنها فقط في وحدة التحكم ، ولكن أيضًا في أي فول ، بما في ذلك الخدمة والمستودعات والفاصوليا العادية.
2) لا يقتصر الكائن الذي تم حقنه على الطلب: بالإضافة إلى حقن كائن الطلب ، يمكن لهذه الطريقة أيضًا ضخ كائنات أخرى ذات نطاق كطلب أو جلسة ، مثل كائنات الاستجابة ، وكائنات الجلسة ، وما إلى ذلك ؛ وضمان سلامة الموضوع.
3) تقليل التكرار في الكود: فقط حقن كائن الطلب في الفول الذي يتطلب كائن الطلب ، ويمكن استخدامه في طرق مختلفة من الفول ، مما يقلل بشكل كبير من التكرار في الكود مقارنة بالطريقة 1.
ومع ذلك ، فإن هذه الطريقة لديها أيضًا تكرار الكود. ضع في اعتبارك هذا السيناريو: هناك العديد من وحدات التحكم في نظام الويب ، ويستخدم كل وحدة تحكم كائن طلب (هذا السيناريو هو في الواقع متكرر للغاية). في هذا الوقت ، تحتاج إلى كتابة رمز لحقن الطلب عدة مرات ؛ إذا كنت بحاجة أيضًا إلى حقن الاستجابة ، فسيكون الرمز أكثر تعقيدًا. يصف ما يلي تحسين طريقة الحقن التلقائي ويحلل سلامة مؤشرات الترابط ومزاياها وعيوبها.
الطريقة 3: الحقن التلقائي في الفئة الأساسية
مثال رمز
مقارنة بالطريقة 2 ، ضع الجزء المحقن من الكود في الفئة الأساسية.
رمز الفئة الأساسية:
الطبقة العامة basecontroller { @Autowired request httpservletrequest ؛ }رمز وحدة التحكم كما يلي ؛ فيما يلي فئتان مشتقتان من Basecontroller. نظرًا لأن رمز الاختبار سيكون مختلفًا في هذا الوقت ، لم يتم حذف رمز اختبار الخادم ؛ يحتاج العميل أيضًا إلى إجراء تعديلات مماثلة (أرسل عددًا كبيرًا من الطلبات المتزامنة إلى اثنين من عناوين URL في نفس الوقت).
ControllerPublic Class TestController يمتد BaseController {// تخزين المعلمات الحالية لتحديد ما إذا كانت قيمة المعلمة تتكرر ، وبالتالي تحديد ما إذا كان مؤشر الترابط هو مجموعة ثابتة عامة ثابتة <string> set = new hassset <> () ؛ requestmapping ("/test") اختبار void public () refruptedException {string value = request.getParameter ("key") ؛ // تحقق من سلامة مؤشر الترابط إذا (set.contains (value)) {system.out.println (value + "/t يظهر بشكل متكرر ، طلب التزامن غير آمن!") ؛ } آخر {system.out.println (value) ؛ set.add (القيمة) ؛ } // تم تنفيذ برنامج المحاكاة لفترة من الوقت thread.sleep (1000) ؛ }} @controllerpublic class test2Controller يمتد BaseController {REquestMapping ("/test2") test2 void test2 () interruptedException {string value = request.getParameter ("key") ؛ // Judge Thread Safety (استخدم مجموعة مع TestController للحكم) if (testController.set.contains (value)) {system.out.println (value + "/t يظهر مرارًا وتكرارًا ، فإن التزامن غير آمن!") ؛ } آخر {system.out.println (value) ؛ testController.set.add (القيمة) ؛ } // تم تنفيذ برنامج المحاكاة لفترة من الزمن ، thread.sleep (1000) ؛ }} السلامة الموضوع
نتائج الاختبار: سلامة الموضوع
التحليل: استنادًا إلى فهم سلامة مؤشر ترابط الطريقة 2 ، من السهل أن نفهم أن الطريقة 3 آمنة مؤشرات الترابط: عند إنشاء كائنات فئة مشتقة مختلفة ، فإن المجالات في الفئة الأساسية (هنا هو الطلب المحقن) سوف تشغل مساحة مختلفة من الذاكرة في كائنات فئة مشتقة مختلفة ، أي ، فإن وضع الطلب الذي تم حقنه في الفئة الأساسية ليس له تأثير على أمان مؤشر الترابط ؛ نتائج الاختبار تثبت هذا أيضا.
إيجابيات وسلبيات
بالمقارنة مع الطريقة 2 ، يتم تجنب الحقن المتكرر للطلبات في وحدات التحكم المختلفة ؛ ومع ذلك ، بالنظر إلى أن Java يسمح فقط براالة فئة أساسية واحدة ، إذا كانت وحدة التحكم بحاجة إلى وراثة فئات أخرى ، فإن هذه الطريقة لم تعد سهلة الاستخدام.
سواء كانت الطريقة 2 أو الطريقة 3 ، يمكنك فقط ضخ الطلبات في الفول ؛ إذا كانت الطرق الأخرى (مثل الطرق الثابتة في فئة الأدوات) بحاجة إلى استخدام كائنات الطلب ، فأنت بحاجة إلى تمرير معلمات الطلب عند استدعاء هذه الأساليب. يمكن استخدام الطريقة 4 المقدمة أدناه مباشرة في طرق ثابتة مثل فئات الأدوات (بالطبع ، يمكن استخدامها أيضًا في مختلف الفول).
الطريقة 4: اتصل يدويًا
مثال رمز
controllerpublic class testController {REquestMapping ("/test") اختبار الفراغ العام () يلقي InterruptedException {httpservletrequest request = ((servletRequestAttRibutes) (requestContextholder.currentRentRequestAttRibutes ())). getRequest () ؛ // تم تنفيذ برنامج المحاكاة لفترة من الوقت thread.sleep (1000) ؛ }} السلامة الموضوع
نتائج الاختبار: سلامة الموضوع
التحليل: تشبه هذه الطريقة الطريقة 2 (الحقن التلقائي) ، باستثناء أنه يتم تنفيذه من خلال الحقن التلقائي في الطريقة 2 ، ويتم تنفيذ هذه الطريقة من خلال استدعاء الطريقة اليدوية. لذلك ، هذه الطريقة هي أيضا آمن مؤشر ترابط.
إيجابيات وسلبيات
المزايا: يمكن الحصول عليها مباشرة في غير المنحدر. العيوب: إذا كنت تستخدم المزيد من الأماكن ، فإن الكود مرهق للغاية ؛ لذلك ، يمكن استخدامه بالاقتران مع طرق أخرى.
الطريقة 5: طريقة modelattribute
مثال رمز
غالبًا ما تُرى الطريقة التالية ومتغيراتها (طفرة: PUT request و BindRequest في الفئات الفرعية) عبر الإنترنت:
controllerpublic class testController {private httpservletrequest request ؛ modelattribute public void bindRequest (طلب httpservletrequest) {this.request = request ؛ } requestmapping ("/test") اختبار الفراغ العام () يلقي InterruptedException {// تم تنفيذ برنامج المحاكاة لفترة من الوقت thread.sleep (1000) ؛ }} السلامة الموضوع
نتيجة الاختبار: الموضوع غير آمن
التحليل: عند استخدام @modelattribute التعليق التوضيحي لتعديل الطريقة في وحدة التحكم ، فإن وظيفتها هي أنه سيتم تنفيذ الطريقة قبل تنفيذ كل طريقة requestMapping في وحدة التحكم. لذلك ، في هذا المثال ، تتمثل وظيفة BindRequest () في تعيين قيمة لكائن الطلب قبل تنفيذ Test (). على الرغم من أن طلب المعلمة في BindRequest () نفسه آمن مؤشر الترابط ، نظرًا لأن TestController هو Singleton ، فإن الطلب ، كحقل من TestController ، لا يمكن أن يضمن سلامة مؤشرات الترابط.
لخص
لتلخيص ، إضافة المعلمات (الطريقة 1) ، الحقن التلقائي (الطريقة 2 والطريقة 3) ، والمكالمات اليدوية (الطريقة 4) في وحدة التحكم كلها آمنة مؤشرات الترابط ويمكن استخدامها جميعًا للحصول على كائنات الطلب. إذا تم استخدام كائن الطلب أقل في النظام ، فيمكن استخدام أي من الطريقة ؛ إذا تم استخدامه أكثر ، فمن المستحسن استخدام الحقن التلقائي (الطريقة 2 والطريقة 3) لتقليل التكرار رمز. إذا كنت بحاجة إلى استخدام كائن طلب في غير محفور ، فيمكنك إما تمريره من خلال المعلمات عند الاتصال بالطبقة العليا ، أو يمكنك الحصول عليه مباشرة من خلال المكالمات اليدوية (الطريقة 4).
حسنًا ، ما سبق هو المحتوى الكامل لهذه المقالة. آمل أن يكون لمحتوى هذه المقالة قيمة مرجعية معينة لدراسة أو عمل الجميع. إذا كان لديك أي أسئلة ، فيمكنك ترك رسالة للتواصل. شكرا لك على دعمك إلى wulin.com.
مراجع