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

الفصل 4: Annotations الأمان، الاختبار والتقدم

1. Annotations Spring Security​

@EnableWebSecurity​

يُفعل إعدادات الأمان المخصصة.

@Configuration
@EnableWebSecurity
public class SecurityConfig {

@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
return http
.csrf(csrf -> csrf.disable())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/auth/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
)
.sessionManagement(session ->
session.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
.build();
}
}

@EnableMethodSecurity​

يُفعل الأمان على مستوى الطرق.

@Configuration
@EnableMethodSecurity // يُفعل @PreAuthorize, @PostAuthorize, @Secured
public class MethodSecurityConfig {
}

@PreAuthorize​

يتحقق من الأذونات قبل تنفيذ الطريقة.

@RestController
@RequestMapping("/api/admin")
public class AdminController {

@GetMapping("/users")
@PreAuthorize("hasRole('ADMIN')")
public List<User> getAllUsers() { ... }

@GetMapping("/stats")
@PreAuthorize("hasAnyRole('ADMIN', 'MANAGER')")
public Stats getStats() { ... }

@DeleteMapping("/users/{id}")
@PreAuthorize("hasRole('ADMIN') and #id != principal.id")
public void deleteUser(@PathVariable Long id) { ... }

@GetMapping("/users/{id}")
@PreAuthorize("#id == principal.id or hasRole('ADMIN')")
public User getUser(@PathVariable Long id) { ... }

@PreAuthorize("isAuthenticated()")
@GetMapping("/profile")
public User getProfile() { ... }
}

@PostAuthorize​

يتحقق من الأذونات بعد التنفيذ.

@GetMapping("/{id}")
@PostAuthorize("returnObject.owner == authentication.name or hasRole('ADMIN')")
public Document getDocument(@PathVariable Long id) {
return documentService.findById(id);
}

@Secured​

يحدد الأدوار المطلوبة (أبسط من @PreAuthorize).

@Secured("ROLE_ADMIN")
public void adminOnly() { ... }

@Secured({"ROLE_ADMIN", "ROLE_MANAGER"})
public void adminOrManager() { ... }

@RolesAllowed​

معيار JSR-250، مشابه لـ @Secured.

@RolesAllowed("ADMIN")
public void adminOnly() { ... }

ملخص الأمان​

Annotationالاستخدام
@EnableWebSecurityيُفعل إعدادات الأمان
@EnableMethodSecurityيُفعل الأمان على مستوى الطرق
@PreAuthorizeالتحقق قبل التنفيذ (SpEL)
@PostAuthorizeالتحقق بعد التنفيذ
@Securedالأدوار المطلوبة (بسيط)
@RolesAllowedالأدوار المطلوبة (JSR-250)

2. Annotations الاختبار​

@SpringBootTest​

يُحمل السياق الكامل لـ Spring للاختبارات التكاملية.

@SpringBootTest
class UserServiceIntegrationTest {

@Autowired
private UserService userService;

@Test
void shouldCreateUser() {
User user = userService.create(new UserDto("Jean", "jean@test.com"));
assertThat(user.getId()).isNotNull();
}
}

// مع خادم ويب عشوائي
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class ApiIntegrationTest {

@Autowired
private TestRestTemplate restTemplate;

@Test
void shouldReturnProducts() {
ResponseEntity<List> response = restTemplate.getForEntity("/api/products", List.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
}

@WebMvcTest​

يختبر فقط طبقة المتحكم (بدون قاعدة بيانات، بدون خدمات حقيقية).

@WebMvcTest(ProductController.class)
class ProductControllerTest {

@Autowired
private MockMvc mockMvc;

@MockBean
private ProductService productService;

@Test
void shouldReturnProducts() throws Exception {
when(productService.findAll()).thenReturn(List.of(new Product("Laptop", 999.99)));

mockMvc.perform(get("/api/products"))
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].name").value("Laptop"));
}
}

@DataJpaTest​

يختبر فقط طبقة JPA.

@DataJpaTest
class UserRepositoryTest {

@Autowired
private UserRepository userRepository;

@Autowired
private TestEntityManager entityManager;

@Test
void shouldFindByEmail() {
entityManager.persistAndFlush(new User("Jean", "jean@test.com"));

Optional<User> found = userRepository.findByEmail("jean@test.com");

assertThat(found).isPresent();
assertThat(found.get().getName()).isEqualTo("Jean");
}
}

@MockBean​

يستبدل bean من Spring بـ mock من Mockito.

@WebMvcTest(UserController.class)
class UserControllerTest {

@MockBean
private UserService userService; // Mock بدلاً من الخدمة الحقيقية

@Test
void shouldReturnUser() {
when(userService.findById(1L)).thenReturn(new User("Jean"));
// ...
}
}

@SpyBean​

يغلف bean حقيقي بـ spy من Mockito (استدعاءات حقيقية باستثناء تلك المستبدلة).

@SpringBootTest
class NotificationTest {

@SpyBean
private EmailService emailService;

@Test
void shouldSendEmail() {
doNothing().when(emailService).sendEmail(anyString(), anyString());
// بقية الطرق تستدعي الكود الحقيقي
}
}

@TestConfiguration​

يوفر إعدادات خاصة بالاختبارات.

@TestConfiguration
public class TestSecurityConfig {

@Bean
public SecurityFilterChain testFilterChain(HttpSecurity http) throws Exception {
return http.csrf(c -> c.disable())
.authorizeHttpRequests(a -> a.anyRequest().permitAll())
.build();
}
}

@ActiveProfiles​

يُفعل ملف تعريف Spring محدد للاختبار.

@SpringBootTest
@ActiveProfiles("test")
class ServiceTest {
// يستخدم application-test.yml
}

@Sql / @SqlGroup​

ينفذ سكربتات SQL قبل/بعد اختبار.

@DataJpaTest
@Sql("/test-data.sql")
class ProductRepositoryTest {
// test-data.sql يُنفذ قبل كل اختبار
}

@Sql(scripts = "/cleanup.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD)

ملخص الاختبار​

Annotationالنطاقالاستخدام
@SpringBootTestالسياق الكاملاختبارات تكاملية
@WebMvcTestطبقة الويب فقطاختبارات المتحكمات
@DataJpaTestطبقة JPA فقطاختبارات المستودعات
@MockBeanbean مقلداستبدال bean
@SpyBeanbean مراقبتغليف bean حقيقي
@ActiveProfilesملف تعريفاختيار إعدادات الاختبار
@Sqlقاعدة البياناتتحميل بيانات SQL

3. Annotations غير متزامنة والجدولة​

@EnableAsync / @Async​

ينفذ طريقة بشكل غير متزامن (في خيط منفصل).

@Configuration
@EnableAsync
public class AsyncConfig {
}

@Service
public class EmailService {

@Async
public CompletableFuture<String> sendEmailAsync(String to, String body) {
// يُنفذ في خيط منفصل
// ... إرسال البريد الإلكتروني
return CompletableFuture.completedFuture("Email envoyé à " + to);
}
}

@EnableScheduling / @Scheduled​

يُجدول التنفيذ الدوري لطريقة.

@Configuration
@EnableScheduling
public class SchedulingConfig {
}

@Service
public class CleanupService {

@Scheduled(fixedRate = 60000) // كل 60 ثانية
public void cleanupExpiredTokens() {
tokenRepository.deleteExpired();
}

@Scheduled(fixedDelay = 30000) // 30 ثانية بعد انتهاء التنفيذ السابق
public void processQueue() {
queueService.processNext();
}

@Scheduled(cron = "0 0 2 * * ?") // كل يوم في الساعة 2 صباحًا
public void dailyReport() {
reportService.generateDailyReport();
}

@Scheduled(cron = "0 0 * * * MON-FRI") // كل ساعة، من الاثنين إلى الجمعة
public void hourlySync() {
syncService.sync();
}
}

صيغة Cron​

┌───────── الثانية (0-59)
│ ┌─────── الدقيقة (0-59)
│ │ ┌───── الساعة (0-23)
│ │ │ ┌─── اليوم من الشهر (1-31)
│ │ │ │ ┌─ الشهر (1-12)
│ │ │ │ │ ┌ اليوم من الأسبوع (0-7, MON-SUN)
│ │ │ │ │ │
* * * * * *
التعبيرالوصف
0 0 * * * *كل ساعة
0 0 2 * * *كل يوم في الساعة 2
0 0 9-17 * * MON-FRIكل ساعة من 9 صباحًا إلى 5 مساءً في أيام الأسبوع
0 */15 * * * *كل 15 دقيقة
0 0 0 1 * *الأول من كل شهر عند منتصف الليل

4. Annotations التخزين المؤقت​

@EnableCaching / @Cacheable / @CacheEvict / @CachePut​

@Configuration
@EnableCaching
public class CacheConfig {
}

@Service
public class ProductService {

@Cacheable(value = "products", key = "#id")
public Product findById(Long id) {
// النتيجة مخزنة مؤقتًا؛ الاستدعاءات التالية لا تصل إلى قاعدة البيانات
return productRepository.findById(id).orElseThrow();
}

@Cacheable(value = "productList", unless = "#result.size() == 0")
public List<Product> findAll() {
return productRepository.findAll();
}

@CachePut(value = "products", key = "#product.id")
public Product update(Product product) {
// يُحدث التخزين المؤقت بالنتيجة الجديدة
return productRepository.save(product);
}

@CacheEvict(value = "products", key = "#id")
public void delete(Long id) {
// يُحذف الإدخال من التخزين المؤقت
productRepository.deleteById(id);
}

@CacheEvict(value = "products", allEntries = true)
public void clearCache() {
// يُفرغ كل التخزين المؤقت "products"
}
}

5. Annotations البرمجة الموجهة للجوانب (AOP)​

@Aspect / @Before / @After / @Around​

@Aspect
@Component
public class LoggingAspect {

@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
log.info("Appel: {}", joinPoint.getSignature().getName());
}

@After("execution(* com.example.service.*.*(..))")
public void logAfter(JoinPoint joinPoint) {
log.info("Fin: {}", joinPoint.getSignature().getName());
}

@Around("@annotation(com.example.annotation.Timed)")
public Object measureTime(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
Object result = joinPoint.proceed();
long duration = System.currentTimeMillis() - start;
log.info("{} exécuté en {} ms", joinPoint.getSignature().getName(), duration);
return result;
}
}

6. Annotations التكوين التلقائي لـ Spring Boot​

@EnableAutoConfiguration​

مضمنة في @SpringBootApplication. تُفعل التكوين التلقائي بناءً على التبعيات.

@EnableAutoConfiguration(exclude = {DataSourceAutoConfiguration.class})
// يُعطل التكوين التلقائي لـ DataSource

@ConditionalOn...​

عائلة من Annotations للتكوين الشرطي.

Annotationالشرط
@ConditionalOnPropertyالخاصية لها قيمة معينة
@ConditionalOnClassالفئة موجودة في classpath
@ConditionalOnMissingClassالفئة غير موجودة في classpath
@ConditionalOnBeanbean موجود في السياق
@ConditionalOnMissingBeanbean غير موجود في السياق
@ConditionalOnExpressionتعبير SpEL صحيح
@ConditionalOnWebApplicationالتطبيق هو تطبيق ويب
@ConditionalOnNotWebApplicationالتطبيق ليس تطبيق ويب

7. Annotations المراسلة والأحداث​

@EventListener​

يستمع للأحداث في Spring.

@Component
public class UserEventListener {

@EventListener
public void handleUserCreated(UserCreatedEvent event) {
log.info("Nouvel utilisateur: {}", event.getUser().getEmail());
emailService.sendWelcomeEmail(event.getUser());
}

@Async
@EventListener
public void handleUserCreatedAsync(UserCreatedEvent event) {
// يُعالج بشكل غير متزامن
}
}

@TransactionalEventListener​

يستمع للأحداث المتعلقة بالمعاملة.

@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleAfterCommit(OrderCreatedEvent event) {
// يُنفذ فقط إذا نجحت المعاملة
notificationService.notifyNewOrder(event.getOrder());
}

8. Annotations عميل REST​

@FeignClient (Spring Cloud)​

يُعلن عن عميل REST وصفي.

@FeignClient(name = "user-service", url = "http://localhost:8081")
public interface UserClient {

@GetMapping("/api/users/{id}")
UserDto getUser(@PathVariable Long id);

@PostMapping("/api/users")
UserDto createUser(@RequestBody CreateUserDto dto);
}

جدول ملخص شامل​

أكثر من 50 Annotation مستخدمة​

#Annotationالفئةالدور
1@SpringBootApplicationBootنقطة الدخول
2@ComponentCorebean عام
3@ServiceCoreمنطق الأعمال
4@RepositoryCoreالوصول إلى قاعدة البيانات
5@ControllerWebمتحكم MVC
6@RestControllerWebمتحكم REST
7@ConfigurationCoreفئة التكوين
8@BeanCoreإعلان bean
9@AutowiredCoreحقن
10@QualifierCoreاختيار bean
11@ValueCoreحقن قيمة
12@ProfileCoreالتفعيل حسب ملف التعريف
13@ScopeCoreنطاق bean
14@LazyCoreتهيئة مؤجلة
15@PostConstructCoreتهيئة بعد الحقن
16@PreDestroyCoreتنظيف قبل التدمير
17@RequestMappingWebتعيين URL
18@GetMappingWebHTTP GET
19@PostMappingWebHTTP POST
20@PutMappingWebHTTP PUT
21@DeleteMappingWebHTTP DELETE
22@PatchMappingWebHTTP PATCH
23@PathVariableWebمتغير URL
24@RequestParamWebمتغير استعلام
25@RequestBodyWebجسم JSON
26@RequestHeaderWebرأس HTTP
27@ResponseStatusWebكود استجابة HTTP
28@ExceptionHandlerWebمعالجة الاستثناءات
29@RestControllerAdviceWebمعالجة الأخطاء العامة
30@CrossOriginWebCORS
31@ValidValidationيُفعل التحقق
32@NotNullValidationليس null
33@NotBlankValidationليس فارغًا
34@SizeValidationحجم الحد الأدنى/الأقصى
35@EmailValidationتنسيق البريد الإلكتروني
36@EntityJPAكيان = جدول
37@TableJPAإعدادات الجدول
38@IdJPAالمفتاح الأساسي
39@GeneratedValueJPAتوليد تلقائي للمعرف
40@ColumnJPAإعدادات العمود
41@OneToManyJPAعلاقة 1:N
42@ManyToOneJPAعلاقة N:1
43@ManyToManyJPAعلاقة N:N
44@QueryJPAاستعلام مخصص
45@TransactionalJPAمعاملة
46@EnableWebSecuritySecurityإعدادات الأمان
47@PreAuthorizeSecurityإذن قبل التنفيذ
48@SpringBootTestTestاختبار تكاملي
49@WebMvcTestTestاختبار المتحكم
50@DataJpaTestTestاختبار المستودع
51@MockBeanTestMock لـ bean
52@Asyncمتقدمتنفيذ غير متزامن
53@Scheduledمتقدممهمة مجدولة
54@Cacheableمتقدمتخزين مؤقت
55@EventListenerمتقدماستماع للأحداث

⬅️ العودة إلى الوحدة 17 | ⬅️ العودة إلى الصفحة الرئيسية