انتقل إلى المحتوى الرئيسي

الفصل 4: المهام المجدولة وغير المتزامنة

أهداف الفصل​

  • تكوين المهام المجدولة باستخدام @Scheduled
  • إتقان تعبيرات Cron
  • تنفيذ طرق غير متزامنة باستخدام @Async
  • تكوين مجموعات مؤشرات الترابط
  • إدارة الأخطاء والمراقبة

1. التكوين الأساسي​

1.1 تفعيل الجدولة​

package com.example.scheduling;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableScheduling
@EnableAsync
public class SchedulingApplication {

public static void main(String[] args) {
SpringApplication.run(SchedulingApplication.class, args);
}
}

1.2 تكوين مجموعة مؤشرات الترابط​

package com.example.scheduling.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;

@Configuration
public class SchedulingConfig implements SchedulingConfigurer {

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setTaskScheduler(taskScheduler());
}

@Bean
public ThreadPoolTaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(10);
scheduler.setThreadNamePrefix("scheduled-");
scheduler.setAwaitTerminationSeconds(60);
scheduler.setWaitForTasksToCompleteOnShutdown(true);
scheduler.setErrorHandler(t -> {
// تسجيل الأخطاء
System.err.println("Scheduled task error: " + t.getMessage());
});
return scheduler;
}
}

2. @Scheduled - المهام المجدولة​

2.1 معدل ثابت​

تنفيذ بفاصل ثابت منذ بداية التنفيذ الأخير.

@Component
public class ScheduledTasks {

private static final Logger log = LoggerFactory.getLogger(ScheduledTasks.class);

// كل 5 ثواني
@Scheduled(fixedRate = 5000)
public void reportCurrentTime() {
log.info("Current time: {}", LocalDateTime.now());
}

// مع تأخير ابتدائي 10 ثواني
@Scheduled(fixedRate = 5000, initialDelay = 10000)
public void taskWithInitialDelay() {
log.info("Task with initial delay");
}

// استخدام الخصائص
@Scheduled(fixedRateString = "${task.fixed-rate:5000}")
public void configurableTask() {
log.info("Configurable task");
}
}

2.2 تأخير ثابت​

تنفيذ بفاصل ثابت منذ نهاية التنفيذ الأخير.

@Component
public class ScheduledTasks {

// 3 ثواني بعد نهاية التنفيذ السابق
@Scheduled(fixedDelay = 3000)
public void processQueue() {
log.info("Processing queue...");
// محاكاة المعالجة
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
log.info("Queue processed");
}
}

2.3 تعبيرات Cron​

@Component
public class ScheduledTasks {

// كل يوم عند منتصف الليل
@Scheduled(cron = "0 0 0 * * *")
public void dailyCleanup() {
log.info("Running daily cleanup");
}

// كل ساعة، في الدقيقة 0
@Scheduled(cron = "0 0 * * * *")
public void hourlyReport() {
log.info("Generating hourly report");
}

// كل يوم اثنين عند الساعة 9
@Scheduled(cron = "0 0 9 * * MON")
public void weeklyReport() {
log.info("Generating weekly report");
}

// اليوم الأول من كل شهر عند الساعة 6
@Scheduled(cron = "0 0 6 1 * *")
public void monthlyReport() {
log.info("Generating monthly report");
}

// كل 15 دقيقة خلال ساعات العمل
@Scheduled(cron = "0 */15 9-17 * * MON-FRI")
public void businessHoursTask() {
log.info("Business hours task");
}

// مع المنطقة الزمنية
@Scheduled(cron = "0 0 9 * * *", zone = "America/Montreal")
public void taskWithTimezone() {
log.info("Task at 9 AM Montreal time");
}
}

2.4 صيغة Cron​

┌───────────── ثانية (0-59)
│ ┌───────────── دقيقة (0-59)
│ │ ┌───────────── ساعة (0-23)
│ │ │ ┌───────────── يوم من الشهر (1-31)
│ │ │ │ ┌───────────── شهر (1-12 أو JAN-DEC)
│ │ │ │ │ ┌───────────── يوم من الأسبوع (0-7 أو SUN-SAT)
│ │ │ │ │ │
* * * * * *
التعبيرالوصف
*جميع القيم
*/15كل 15
0-23نطاق القيم
1,15قائمة القيم
?غير محدد (اليوم)
Lالأخير (يوم من الشهر)
Wأقرب يوم عمل
#اليوم النوني من الأسبوع (2#1 = أول يوم اثنين)

2.5 أمثلة Cron شائعة​

التعبيرالوصف
0 0 * * * *كل ساعة
0 */10 * * * *كل 10 دقائق
0 0 8-18 * * *كل ساعة من 8 صباحًا إلى 6 مساءً
0 0 0 * * *كل يوم عند منتصف الليل
0 0 0 * * SUNكل يوم أحد عند منتصف الليل
0 0 0 1 * *اليوم الأول من الشهر
0 0 0 L * *اليوم الأخير من الشهر
0 0 0 ? * 6Lآخر جمعة من الشهر

3. @Async - طرق غير متزامنة​

3.1 التكوين​

@Configuration
@EnableAsync
public class AsyncConfig implements AsyncConfigurer {

@Override
@Bean(name = "taskExecutor")
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(10);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("async-");
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(60);
executor.initialize();
return executor;
}

@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> {
System.err.println("Async error in method " + method.getName() + ": " + ex.getMessage());
};
}
}

3.2 طرق غير متزامنة​

@Service
public class EmailService {

private static final Logger log = LoggerFactory.getLogger(EmailService.class);

// Fire and forget
@Async
public void sendEmailAsync(String to, String subject, String body) {
log.info("Sending email to {} on thread {}", to, Thread.currentThread().getName());

// محاكاة الإرسال
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}

log.info("Email sent to {}", to);
}

// مع إرجاع قيمة
@Async
public CompletableFuture<Boolean> sendEmailWithResult(String to, String subject) {
log.info("Sending email to {}", to);

try {
// محاكاة
Thread.sleep(2000);
log.info("Email sent successfully to {}", to);
return CompletableFuture.completedFuture(true);
} catch (Exception e) {
log.error("Failed to send email to {}", to, e);
return CompletableFuture.completedFuture(false);
}
}

// مع منفذ محدد
@Async("emailExecutor")
public void sendWithSpecificExecutor(String to) {
// استخدام bean "emailExecutor"
}
}

3.3 الاستخدام​

@RestController
@RequestMapping("/api/orders")
public class OrderController {

private final OrderService orderService;
private final EmailService emailService;

@PostMapping
public ResponseEntity<Order> createOrder(@RequestBody OrderRequest request) {
// إنشاء الطلب (متزامن)
Order order = orderService.createOrder(request);

// إرسال البريد الإلكتروني (غير متزامن - لا يحجب)
emailService.sendEmailAsync(
request.getEmail(),
"Order Confirmation",
"Your order #" + order.getId() + " has been created"
);

// العودة فورًا
return ResponseEntity.ok(order);
}

@PostMapping("/bulk-email")
public ResponseEntity<String> sendBulkEmails(@RequestBody List<String> emails) {
// إرسال عدة رسائل بريد إلكتروني بالتوازي
List<CompletableFuture<Boolean>> futures = emails.stream()
.map(email -> emailService.sendEmailWithResult(email, "Newsletter"))
.collect(Collectors.toList());

// انتظار جميع النتائج
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> {
long successCount = futures.stream()
.map(CompletableFuture::join)
.filter(Boolean::booleanValue)
.count();
log.info("Sent {} emails successfully", successCount);
});

return ResponseEntity.accepted().body("Emails are being sent");
}
}

4. حالات الاستخدام الشائعة​

4.1 تنظيف البيانات المنتهية​

@Component
public class CleanupTasks {

private final SessionRepository sessionRepository;
private final TempFileService tempFileService;

// تنظيف الجلسات المنتهية كل ساعة
@Scheduled(cron = "0 0 * * * *")
public void cleanupExpiredSessions() {
log.info("Starting session cleanup");

LocalDateTime expirationThreshold = LocalDateTime.now().minusHours(24);
int deleted = sessionRepository.deleteByLastAccessBefore(expirationThreshold);

log.info("Cleaned up {} expired sessions", deleted);
}

// تنظيف الملفات المؤقتة كل يوم عند الساعة 3
@Scheduled(cron = "0 0 3 * * *")
public void cleanupTempFiles() {
log.info("Starting temp files cleanup");

int deleted = tempFileService.deleteOlderThan(Duration.ofDays(7));

log.info("Deleted {} temp files", deleted);
}
}

4.2 توليد التقارير​

@Component
public class ReportTasks {

private final ReportService reportService;
private final EmailService emailService;

// تقرير يومي عند الساعة 6
@Scheduled(cron = "0 0 6 * * *")
public void generateDailyReport() {
log.info("Generating daily report");

DailyReport report = reportService.generateDailyReport(LocalDate.now().minusDays(1));
emailService.sendReportAsync("admin@company.com", report);

log.info("Daily report generated and sent");
}

// تقرير أسبوعي يوم الاثنين عند الساعة 7
@Scheduled(cron = "0 0 7 * * MON")
public void generateWeeklyReport() {
log.info("Generating weekly report");

WeeklyReport report = reportService.generateWeeklyReport();
emailService.sendReportAsync("management@company.com", report);
}
}

4.3 مزامنة البيانات​

@Component
public class SyncTasks {

private final ExternalApiService externalApi;
private final ProductRepository productRepository;

// مزامنة الأسعار كل 15 دقيقة
@Scheduled(fixedRate = 900000) // 15 دقيقة
public void syncPrices() {
log.info("Starting price sync");

try {
List<PriceUpdate> updates = externalApi.fetchPriceUpdates();

updates.forEach(update -> {
productRepository.updatePrice(update.getProductId(), update.getNewPrice());
});

log.info("Synced {} price updates", updates.size());
} catch (Exception e) {
log.error("Price sync failed", e);
}
}
}

4.4 فحص الصحة​

@Component
public class HealthCheckTasks {

private final List<ExternalService> externalServices;
private final AlertService alertService;

// فحص الخدمات الخارجية كل دقيقة
@Scheduled(fixedRate = 60000)
public void checkExternalServices() {
externalServices.forEach(service -> {
boolean healthy = service.healthCheck();

if (!healthy) {
log.warn("Service {} is unhealthy", service.getName());
alertService.sendAlert("Service " + service.getName() + " is down");
}
});
}
}

5. إدارة الأخطاء​

5.1 Try-Catch في المهام​

@Component
public class RobustScheduledTask {

@Scheduled(fixedRate = 60000)
public void robustTask() {
try {
performTask();
} catch (Exception e) {
log.error("Task failed, will retry on next schedule", e);
// لا تعيد الرمي لتجنب إيقاف الجدولة
}
}
}

5.2 معالج الأخطاء العام​

@Configuration
public class SchedulingConfig implements SchedulingConfigurer {

@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setPoolSize(5);
scheduler.setErrorHandler(t -> {
log.error("Scheduled task error", t);
// إرسال تنبيه، مقاييس، إلخ.
alertService.sendAlert("Scheduled task failed: " + t.getMessage());
});
scheduler.initialize();
taskRegistrar.setTaskScheduler(scheduler);
}
}

6. التحكم الشرطي​

6.1 تفعيل/تعطيل بالتكوين​

@Component
@ConditionalOnProperty(name = "app.scheduling.enabled", havingValue = "true")
public class ScheduledTasks {
// هذه المهام لا تُنفذ إلا إذا كان app.scheduling.enabled=true
}

6.2 التحكم بواسطة ملف التعريف​

@Component
@Profile("!test") // ليس في بيئة الاختبار
public class ProductionScheduledTasks {

@Scheduled(cron = "0 0 0 * * *")
public void productionOnlyTask() {
// تُنفذ فقط في الإنتاج
}
}

6.3 انتخاب القائد (العنقود)​

@Component
public class ClusterAwareTask {

private final LockProvider lockProvider;

@Scheduled(cron = "0 0 * * * *")
@SchedulerLock(name = "hourlyTask", lockAtLeastFor = "PT5M", lockAtMostFor = "PT30M")
public void hourlyTask() {
// تُنفذ هذه المهمة بواسطة مثيل واحد فقط في العنقود
log.info("Running hourly task on this instance");
}
}

7. الملخص​

التعليقات التوضيحية​

التعليق التوضيحيالاستخدام
@EnableSchedulingتفعيل الجدولة
@EnableAsyncتفعيل الطرق غير المتزامنة
@Scheduledوضع علامة على طريقة مجدولة
@Asyncوضع علامة على طريقة غير متزامنة

خيارات @Scheduled​

الخيارالوصف
fixedRateالفاصل منذ البداية
fixedDelayالفاصل منذ النهاية
cronتعبير Cron
initialDelayالتأخير الابتدائي
zoneالمنطقة الزمنية لـ Cron

أفضل الممارسات​

  1. دائمًا إدارة الاستثناءات في المهام
  2. استخدام مجموعات مؤشرات الترابط المخصصة
  3. مراقبة تنفيذ المهام
  4. تجنب المهام الطويلة جدًا
  5. استخدام القفل في بيئة العنقود
  6. تسجيل بداية ونهاية المهام

الفصل التالي: WebSockets

العودة إلى الوحدة 14