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

الفصل 4: التعليقات التوضيحية الأساسية في Spring Boot

مقدمة​

التعليقات التوضيحية هي جوهر Spring Boot. إنها تتيح تكوين التطبيق بطريقة إعلانية، بدون XML، وتجعل الكود أكثر قابلية للقراءة والصيانة.

أهداف الفصل​

في نهاية هذا الفصل، ستكون قادرًا على:

  • فهم دور التعليقات التوضيحية الرئيسية في Spring Boot
  • استخدام @RestController, @Service, @Repository
  • إتقان التعليقات التوضيحية لتخطيط HTTP
  • حقن التبعيات باستخدام @Autowired
  • إنشاء beans في Spring

ما هي التعليقات التوضيحية؟​

تعريف​

التعليق التوضيحي هو شكل من أشكال البيانات الوصفية التي توفر معلومات حول الكود دون أن تكون جزءًا منه مباشرة.

الصيغة:

@NomAnnotation
@NomAnnotation(paramettre=valeur)

مثال:

@RestController
@RequestMapping("/api")
public class UserController {
// ...
}

لماذا التعليقات التوضيحية؟​

المزايا:

  • كود أكثر موجزًا
  • تكوين قريب من الكود
  • آمن من النوع (التحقق أثناء الترجمة)
  • يسهل إعادة الهيكلة

@SpringBootApplication​

التعليق التوضيحي الرئيسي​

package com.example.greeting;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class GreetingApplication {

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

تكوين @SpringBootApplication​

@SpringBootApplication هو تعليق توضيحي ميتا يجمع بين ثلاثة تعليقات توضيحية:

@SpringBootApplication
=
@SpringBootConfiguration
+ @EnableAutoConfiguration
+ @ComponentScan

تفاصيل كل تعليق توضيحي​

1. @SpringBootConfiguration​

الدور: يحدد الفئة كمصدر لتعريفات beans

ما يعادل:

@Configuration

يسمح بتعريف beans:

@SpringBootApplication
public class GreetingApplication {

@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}

2. @EnableAutoConfiguration​

الدور: يفعل التكوين التلقائي لـ Spring Boot

مثال: إذا اكتشف Spring Boot:

  • spring-boot-starter-web → يكوّن Tomcat, Spring MVC
  • spring-boot-starter-data-jpa → يكوّن Hibernate, DataSource
  • PostgreSQL Driver → يكوّن PostgreSQL

التكوين التلقائي:

3. @ComponentScan​

الدور: يمسح الحزمة والفرعيات بحثًا عن مكونات Spring

الحزمة الممسوحة:

package com.example.greeting;  // الحزمة الأساسية

// سيقوم Spring بمسح:
// - com.example.greeting
// - com.example.greeting.controller
// - com.example.greeting.service
// - إلخ.

المكونات المكتشفة:

  • @Component
  • @Controller / @RestController
  • @Service
  • @Repository
  • @Configuration

التعليقات التوضيحية للستريوتايب​

تسلسل المكونات​

@Component​

الدور: تعليق توضيحي عام لأي مكون Spring

الاستخدام:

import org.springframework.stereotype.Component;

@Component
public class EmailValidator {

public boolean isValid(String email) {
return email.contains("@");
}
}

متى تستخدمه: للفئات المساعدة التي لا تندرج تحت الفئات الأخرى

@Controller​

الدور: يحدد الفئة كتحكم MVC (يعيد العروض)

الاستخدام:

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HomeController {

@GetMapping("/")
public String home(Model model) {
model.addAttribute("message", "مرحبًا!");
return "home"; // يعيد العرض "home.html"
}
}

الهيكل:

@RestController​

الدور: تحكم REST (يعيد بيانات JSON/XML)

ما يعادل:

@RestController
=
@Controller + @ResponseBody

الاستخدام:

import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.GetMapping;

@RestController
public class GreetingController {

@GetMapping("/greeting")
public Greeting greeting() {
return new Greeting(1L, "Hello, World!");
}
}

الهيكل:

الفرق بين @Controller و @RestController:

الجانب@Controller@RestController
العودةاسم العرض (String)بيانات (JSON/XML)
الاستخدامتطبيقات الويب التقليديةREST APIs
التسلسللاتلقائي (Jackson)
@ResponseBodyمطلوب لكل طريقةضمني

@Service​

الدور: يحدد الفئة التي تحتوي على منطق الأعمال

الاستخدام:

import org.springframework.stereotype.Service;
import java.util.concurrent.atomic.AtomicLong;

@Service
public class GreetingService {

private final AtomicLong counter = new AtomicLong();
private static final String template = "Hello, %s!";

public Greeting createGreeting(String name) {
return new Greeting(
counter.incrementAndGet(),
String.format(template, name)
);
}
}

المسؤوليات:

  • منطق الأعمال
  • الحسابات المعقدة
  • تنسيق المستودعات
  • المعاملات

@Repository​

الدور: يحدد الفئة للوصول إلى البيانات

الاستخدام مع JPA:

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface UserRepository extends JpaRepository<User, Long> {
// يقوم Spring Data JPA بإنشاء التنفيذ تلقائيًا
}

المسؤوليات:

  • استعلامات قاعدة البيانات
  • عمليات CRUD
  • استعلامات مخصصة

الميزة: الترجمة التلقائية لاستثناءات SQL إلى استثناءات Spring


التعليقات التوضيحية لتخطيط HTTP​

نظرة عامة​

@RequestMapping​

الدور: تعليق توضيحي عام لتخطيط طلبات HTTP

الصيغة:

@RequestMapping(value = "/path", method = RequestMethod.GET)

الاستخدام على مستوى الفئة:

@RestController
@RequestMapping("/api/users")
public class UserController {

@RequestMapping(value = "/{id}", method = RequestMethod.GET)
public User getUser(@PathVariable Long id) {
// GET /api/users/123
return userService.findById(id);
}
}

@GetMapping​

الدور: اختصار لطلبات GET

ما يعادل:

@GetMapping("/path")
=
@RequestMapping(value = "/path", method = RequestMethod.GET)

أمثلة:

@RestController
@RequestMapping("/api")
public class ProductController {

// GET /api/products
@GetMapping("/products")
public List<Product> getAllProducts() {
return productService.findAll();
}

// GET /api/products/123
@GetMapping("/products/{id}")
public Product getProduct(@PathVariable Long id) {
return productService.findById(id);
}

// GET /api/products?category=books
@GetMapping("/products")
public List<Product> getProductsByCategory(@RequestParam String category) {
return productService.findByCategory(category);
}
}

@PostMapping​

الدور: اختصار لطلبات POST (إنشاء)

الاستخدام:

@RestController
@RequestMapping("/api/users")
public class UserController {

@PostMapping
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}

الطلب:

POST /api/users
Content-Type: application/json

{
"name": "John Doe",
"email": "john@example.com"
}

@PutMapping​

الدور: اختصار لطلبات PUT (تحديث كامل)

الاستخدام:

@PutMapping("/{id}")
public User updateUser(@PathVariable Long id, @RequestBody User user) {
return userService.update(id, user);
}

الطلب:

PUT /api/users/123
Content-Type: application/json

{
"name": "John Doe Updated",
"email": "john.updated@example.com"
}

@DeleteMapping​

الدور: اختصار لطلبات DELETE (حذف)

الاستخدام:

@DeleteMapping("/{id}")
public void deleteUser(@PathVariable Long id) {
userService.delete(id);
}

الطلب:

DELETE /api/users/123

@PatchMapping​

الدور: اختصار لطلبات PATCH (تحديث جزئي)

الاستخدام:

@PatchMapping("/{id}")
public User partialUpdate(@PathVariable Long id, @RequestBody Map<String, Object> updates) {
return userService.partialUpdate(id, updates);
}

الطلب:

PATCH /api/users/123
Content-Type: application/json

{
"email": "newemail@example.com"
}

جدول ملخص​

طريقة HTTPالتعليق التوضيحيالإجراءمتكرر
GET@GetMappingقراءة✅
POST@PostMappingإنشاء❌
PUT@PutMappingتحديث (كامل)✅
PATCH@PatchMappingتحديث (جزئي)❌
DELETE@DeleteMappingحذف✅

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

@PathVariable​

الدور: استخراج قيمة من URL

الصيغة:

@GetMapping("/users/{id}")
public User getUser(@PathVariable Long id) {
// URL: /users/123
// id = 123
return userService.findById(id);
}

عدة متغيرات:

@GetMapping("/users/{userId}/orders/{orderId}")
public Order getOrder(
@PathVariable Long userId,
@PathVariable Long orderId
) {
return orderService.findByUserAndId(userId, orderId);
}

اسم مختلف:

@GetMapping("/users/{id}")
public User getUser(@PathVariable("id") Long userId) {
return userService.findById(userId);
}

@RequestParam​

الدور: استخراج معلمات من سلسلة الاستعلام

الصيغة:

@GetMapping("/users")
public List<User> getUsers(@RequestParam String name) {
// URL: /users?name=John
// name = "John"
return userService.findByName(name);
}

معلمة اختيارية:

@GetMapping("/users")
public List<User> getUsers(
@RequestParam(required = false) String name
) {
if (name != null) {
return userService.findByName(name);
}
return userService.findAll();
}

قيمة افتراضية:

@GetMapping("/users")
public List<User> getUsers(
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "10") int size
) {
return userService.findAll(page, size);
}

عدة معلمات:

@GetMapping("/users")
public List<User> searchUsers(
@RequestParam String name,
@RequestParam int age,
@RequestParam String city
) {
// URL: /users?name=John&age=30&city=Montreal
return userService.search(name, age, city);
}

@RequestBody​

الدور: مطابقة جسم طلب HTTP مع كائن Java

الاستخدام:

@PostMapping("/users")
public User createUser(@RequestBody User user) {
return userService.save(user);
}

طلب HTTP:

POST /users
Content-Type: application/json

{
"name": "John Doe",
"email": "john@example.com",
"age": 30
}

كائن Java المستلم:

User user = new User();
user.setName("John Doe");
user.setEmail("john@example.com");
user.setAge(30);

@RequestHeader​

الدور: استخراج رأس HTTP

الاستخدام:

@GetMapping("/secure")
public String secureEndpoint(
@RequestHeader("Authorization") String token
) {
// token = "Bearer eyJhbGciOiJIUzI1NiIs..."
return "Authenticated!";
}

جدول مقارن​

التعليق التوضيحيالمصدرمثال URLالاستخدام
@PathVariableمسار URL/users/123معرفات الموارد
@RequestParamسلسلة الاستعلام/users?name=Johnالفلاتر، الترقيم
@RequestBodyجسم HTTPجسم POST/PUTإرسال البيانات
@RequestHeaderرؤوس HTTPAuthorization: Bearer tokenالمصادقة

حقن التبعيات​

@Autowired​

الدور: يحقن تلقائيًا تبعية

أنواع الحقن​

1. الحقن بواسطة المنشئ (موصى به)​

@RestController
public class UserController {

private final UserService userService;

@Autowired
public UserController(UserService userService) {
this.userService = userService;
}

@GetMapping("/users")
public List<User> getUsers() {
return userService.findAll();
}
}

منذ Spring 4.3: @Autowired اختياري إذا كان هناك منشئ واحد

@RestController
public class UserController {

private final UserService userService;

// @Autowired غير ضروري
public UserController(UserService userService) {
this.userService = userService;
}
}

المزايا:

  • عدم التغيير (final)
  • يسهل الاختبار
  • إلزامي (لا يوجد NullPointerException)

2. الحقن بواسطة Setter​

@RestController
public class UserController {

private UserService userService;

@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
}

المزايا:

  • التبعيات الاختيارية
  • إعادة التكوين ممكنة

العيوب:

  • قابل للتغيير
  • خطر NullPointerException

3. الحقن بواسطة الحقل (غير موصى به)​

@RestController
public class UserController {

@Autowired
private UserService userService;
}

العيوب:

  • صعب الاختبار
  • ارتباط قوي
  • لا يمكن إنشاء كائن غير قابل للتغيير

التوصية: دائمًا استخدم الحقن بواسطة المنشئ

مثال كامل للهيكل​

// الكيان
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String email;
// Getters/Setters
}

// المستودع
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
}

// الخدمة
@Service
public class UserService {

private final UserRepository userRepository;

public UserService(UserRepository userRepository) {
this.userRepository = userRepository;
}

public List<User> findAll() {
return userRepository.findAll();
}

public User save(User user) {
return userRepository.save(user);
}
}

// التحكم
@RestController
@RequestMapping("/api/users")
public class UserController {

private final UserService userService;

public UserController(UserService userService) {
this.userService = userService;
}

@GetMapping
public List<User> getAllUsers() {
return userService.findAll();
}

@PostMapping
public User createUser(@RequestBody User user) {
return userService.save(user);
}
}

تدفق التبعيات:


تمارين عملية​

التمرين 1: إنشاء تحكم REST​

الهدف: استخدام @RestController و @GetMapping

التعليمات:

  1. أنشئ فئة HelloController
  2. علقها بـ @RestController
  3. أنشئ طريقة sayHello() التي تعيد "Hello, Spring Boot!"
  4. علقها بـ @GetMapping("/hello")
  5. اختبرها باستخدام المتصفح

التمرين 2: استخدام @PathVariable​

الهدف: استخراج معلمات من URL

التعليمات:

  1. أنشئ نقطة نهاية /greet/\{name\}
  2. أعد "Hello, {name}!"
  3. اختبرها باستخدام /greet/John

التمرين 3: إنشاء خدمة​

الهدف: فصل منطق الأعمال

التعليمات:

  1. أنشئ فئة CalculatorService مع @Service
  2. أضف طريقة add(int a, int b)
  3. أنشئ CalculatorController
  4. حقن الخدمة بواسطة المنشئ
  5. أنشئ نقطة نهاية /add?a=5&b=3

التمرين 4: هيكل كامل​

الهدف: إنشاء هيكل من 3 طبقات

التعليمات:

  1. أنشئ فئة Product (نموذج)
  2. أنشئ ProductService مع قائمة في الذاكرة
  3. أنشئ ProductController
  4. نفذ:
    • GET /products (الكل)
    • GET /products/{id}
    • POST /products

أسئلة المراجعة​

  1. ما هي التعليقات التوضيحية التي تتكون منها @SpringBootApplication؟

  2. ما الفرق بين @Controller و @RestController؟

  3. متى تستخدم @Service مقابل @Component؟

  4. ما الفرق بين @PathVariable و @RequestParam؟

  5. لماذا يوصى بالحقن بواسطة المنشئ؟

  6. ماذا تفعل التعليق التوضيحي @RequestBody؟

  7. ما هي طريقة HTTP التي تتوافق مع @PostMapping؟

  8. ما هو دور @Repository؟


ملخص الفصل​

النقاط الرئيسية​

  • @SpringBootApplication يجمع بين التكوين، التكوين التلقائي والمسح
  • @RestController = @Controller + @ResponseBody
  • @Service لمنطق الأعمال، @Repository للوصول إلى البيانات
  • التعليقات التوضيحية لتخطيط: @GetMapping, @PostMapping, إلخ.
  • @PathVariable لـ URL، @RequestParam لسلاسل الاستعلام
  • يوصى بالحقن بواسطة المنشئ

الممارسات الجيدة​

  • استخدام التعليقات التوضيحية المتخصصة (@Service, @Repository) بدلاً من @Component
  • الحقن بواسطة المنشئ مع final
  • تحكم واحد لكل مورد REST
  • فصل المسؤوليات بوضوح (Controller → Service → Repository)
  • تسمية نقاط النهاية بطريقة متسقة (REST conventions)

الخطوات التالية​

تهانينا! لقد أكملت الوحدة 2.

في الوحدة 3 (القادمة)، ستتعلم بعمق:

  • الانقلاب في التحكم بالتفصيل
  • دورة حياة beans
  • النطاقات
  • التكوين المتقدم

التطبيق العملي​

قبل الانتقال إلى الوحدة التالية، تأكد من:

  • إكمال TP2.1: تطبيق التحية
  • إعادة تنفيذ التمارين لكل فصل
  • إنشاء مشروع صغير خاص بك لتعزيز المكتسبات

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