
إصلاح spring retry 1.3.x باستخدام مجموعة أدوات متخصصة لـ CVE-2026-41710
يوفر هذا المشروع دعمًا تصريحيًا لإعادة المحاولة لتطبيقات Spring. ويُستخدم في Spring Batch وSpring Integration وغيرهما. كما أن إعادة المحاولة الأمرية مدعومة أيضًا للاستخدام الصريح.
إصلاح الإصدار spring-retry مع 2.0.13 لـ java8 وspring4 أو spring5، لمزيد من المعلومات، يُرجى الرجوع إلى إصدار Spring 2.0.13 الرسمي
يقدم هذا القسم مقدمة سريعة للبدء مع Spring Retry. ويتضمن مثالًا تصريحيًا ومثالًا أمريًا.
يوضح المثال التالي كيفية استخدام Spring Retry بأسلوبه التصريحي:
@Configuration
@EnableRetry
public class Application {
@Bean
public Service service() {
return new Service();
}
}
@Service
class Service {
@Retryable(RemoteAccessException.class)
public void service() {
// ... do something
}
@Recover
public void recover(RemoteAccessException e) {
// ... panic
}
}
يستدعي هذا المثال الدالة service، وإذا فشلت مع RemoteAccessException، فإنه يعيد المحاولة
(افتراضيًا، حتى ثلاث مرات)، ثم يحاول استدعاء الدالة recover إذا لم تنجح.
توجد خيارات متنوعة في سمات التعليق التوضيحي @Retryable لتضمين أنواع الاستثناءات واستبعادها،
وتحديد عدد مرات إعادة المحاولة، وتعيين سياسة التراجع التدريجي.
يتضمن النهج التعريفي لتطبيق معالجة إعادة المحاولة باستخدام التعليق التوضيحي @Retryable الموضح سابقًا
اعتمادًا إضافيًا في وقت التشغيل على فئات AOP. للحصول على تفاصيل حول كيفية حل هذا الاعتماد في مشروعك، راجع
قسم 'Java Configuration for Retry Proxies'.
يوضح المثال التالي كيفية استخدام Spring Retry بأسلوبه الأمرِي (متاح منذ الإصدار 1.3):
RetryTemplate template = RetryTemplate.builder()
.maxAttempts(3)
.fixedBackoff(1000)
.retryOn(RemoteAccessException.class)
.build();
template.execute(ctx -> {
// ... do something
});
بالنسبة للإصدارات السابقة لـ 1.3، راجع الأمثلة في قسم RetryTemplate.
يتطلب Spring Retry إصدار Java 1.7 وMaven 3.0.5 (أو أحدث). للقيام بعملية البناء، شغّل أمر Maven التالي:
$ mvn install
يناقش هذا القسم ميزات Spring Retry ويوضح كيفية استخدام واجهة برمجة التطبيقات الخاصة به.
RetryTemplateلجعل المعالجة أكثر متانة وأقل عرضة للفشل، يساعد أحيانًا في
إعادة محاولة عملية فاشلة تلقائيًا، في حال نجاحها في محاولة لاحقة.
الأخطاء القابلة لهذا النوع من المعالجة هي أخطاء عابرة بطبيعتها. على
سبيل المثال، قد يُحلّ استدعاء بعيد لخدمة ويب أو خدمة RMI يفشل بسبب خلل
في الشبكة أو DeadLockLoserException في تحديث قاعدة بيانات من تلقاء نفسه بعد
انتظار قصير. لأتمتة إعادة محاولة مثل هذه العمليات، يوفّر Spring Retry
استراتيجية RetryOperations. فيما يلي تعريف واجهة RetryOperations:
public interface RetryOperations {
<T> T execute(RetryCallback<T> retryCallback) throws Exception;
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback)
throws Exception;
<T> T execute(RetryCallback<T> retryCallback, RetryState retryState)
throws Exception, ExhaustedRetryException;
<T> T execute(RetryCallback<T> retryCallback, RecoveryCallback<T> recoveryCallback,
RetryState retryState) throws Exception;
}
إن رد الاتصال الأساسي هو واجهة بسيطة تتيح لك إدراج بعض منطق الأعمال لإعادة محاولة تنفيذه:
public interface RetryCallback<T> {
T doWithRetry(RetryContext context) throws Throwable;
}
يُجرَّب الاستدعاء الراجع (callback)، وإذا فشل (بإلقاء Exception)، يُعاد المحاولة حتى ينجح أو تقرر التطبيقة الإجهاض. هناك عدد من طرق execute المحمّلة بشكل زائد (overloaded) في واجهة RetryOperations، للتعامل مع حالات استخدام متنوعة للاسترداد عندما تُستنفد جميع محاولات إعادة المحاولة، وللتعامل مع حالة إعادة المحاولة، التي تتيح للعملاء والتطبيقات تخزين المعلومات بين الاستدعاءات (المزيد عن ذلك لاحقًا).
أبسط تطبيقة للأغراض العامة لواجهة RetryOperations هي RetryTemplate. يوضح المثال التالي كيفية استخدامها:
RetryTemplate template = new RetryTemplate();
TimeoutRetryPolicy policy = new TimeoutRetryPolicy();
policy.setTimeout(30000L);
template.setRetryPolicy(policy);
Foo result = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// Do stuff that might fail, e.g. webservice operation
return result;
}
});
في المثال السابق، ننفّذ استدعاء خدمة ويب ونعيد النتيجة إلى المستخدم. إذا فشل ذلك الاستدعاء، تتم إعادة المحاولة حتى الوصول إلى مهلة محددة.
منذ الإصدار 1.3، أصبح التكوين السلس لـ RetryTemplate متاحًا أيضًا، على النحو التالي:
RetryTemplate.builder()
.maxAttempts(10)
.exponentialBackoff(100, 2, 10000)
.retryOn(IOException.class)
.traversingCauses()
.build();
RetryTemplate.builder()
.fixedBackoff(10)
.withinMillis(3000)
.build();
RetryTemplate.builder()
.infiniteRetry()
.retryOn(IOException.class)
.uniformRandomBackoff(1000, 3000)
.build();
RetryContextمعامل الطريقة الخاص بـ RetryCallback هو RetryContext. تتجاهل العديد من عمليات الاستدعاء
السياق. ومع ذلك، إذا لزم الأمر، يمكنك استخدامه كحاوية سمات لتخزين البيانات طوال
مدة التكرار.
يمتلك RetryContext سياقًا أبًا إذا كانت هناك إعادة محاولة متداخلة قيد التقدم في نفس
الخيط. يكون السياق الأب مفيدًا أحيانًا لتخزين البيانات التي يجب مشاركتها
بين استدعاءات التنفيذ.
RecoveryCallbackعندما تُستنفد إعادة المحاولة، يمكن لـ RetryOperations تمرير التحكم إلى عملية استدعاء
مختلفة: RecoveryCallback. لاستخدام هذه الميزة، يمكن للعملاء تمرير عمليات الاستدعاء
معًا إلى نفس الطريقة، كما يوضح المثال التالي:
Foo foo = template.execute(new RetryCallback<Foo>() {
public Foo doWithRetry(RetryContext context) {
// business logic here
},
new RecoveryCallback<Foo>() {
Foo recover(RetryContext context) throws Exception {
// recover logic here
}
});
إذا لم تنجح منطقية العمل قبل أن يقرر القالب الإجهاض، تُمنح العميلة الفرصة لتنفيذ بعض المعالجة البديلة عبر دالة الاسترداد.
في أبسط الحالات، ليست إعادة المحاولة سوى حلقة while: يمكن لـ RetryTemplate أن يستمر في المحاولة حتى ينجح أو يفشل. يحتوي RetryContext على بعض الحالة لتحديد ما إذا كان ينبغي إعادة المحاولة أو الإجهاض. ومع ذلك، فإن هذه الحالة موجودة على المكدس، ولا حاجة لتخزينها في أي مكان على نحو شامل. وبالتالي، نسمّي هذا "إعادة المحاولة عديمة الحالة". يكمن التمييز بين إعادة المحاولة عديمة الحالة وذات الحالة في تنفيذ RetryPolicy (يمكن لـ RetryTemplate التعامل مع كليهما). في إعادة المحاولة عديمة الحالة، تُنفَّذ دالة الاستدعاء دائمًا في الخيط نفسه الذي فشلت فيه عند إعادة المحاولة.
حيث تسبّب الفشل في أن يصبح أحد الموارد المعاملاتية غير صالح، توجد بعض الاعتبارات الخاصة. لا ينطبق هذا على استدعاء بعيد بسيط، لأنه لا يوجد (عادةً) مورد معاملاتي، لكنه ينطبق أحيانًا على تحديث قاعدة بيانات، لا سيما عند استخدام Hibernate. في هذه الحالة، لا يكون من المنطقي سوى إعادة طرح الاستثناء الذي استدعى الفشل فورًا حتى تتمكن المعاملة من التراجع ويمكننا بدء معاملة جديدة (وصالحة).