الفصل 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 MVCspring-boot-starter-data-jpa→ يكوّن Hibernate, DataSourcePostgreSQL 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 | رؤوس HTTP | Authorization: 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
التعليمات:
- أنشئ فئة
HelloController - علقها بـ
@RestController - أنشئ طريقة
sayHello()التي تعيد "Hello, Spring Boot!" - علقها بـ
@GetMapping("/hello") - اختبرها باستخدام المتصفح
التمرين 2: استخدام @PathVariable
الهدف: استخراج معلمات من URL
التعليمات:
- أنشئ نقطة نهاية
/greet/\{name\} - أعد "Hello, {name}!"
- اختبرها باستخدام
/greet/John
التمرين 3: إنشاء خدمة
الهدف: فصل منطق الأعمال
التعليمات:
- أنشئ فئة
CalculatorServiceمع@Service - أضف طريقة
add(int a, int b) - أنشئ
CalculatorController - حقن الخدمة بواسطة المنشئ
- أنشئ نقطة نهاية
/add?a=5&b=3
التمرين 4: هيكل كامل
الهدف: إنشاء هيكل من 3 طبقات
التعليمات:
- أنشئ فئة
Product(نموذج) - أنشئ
ProductServiceمع قائمة في الذاكرة - أنشئ
ProductController - نفذ:
- GET /products (الكل)
- GET /products/{id}
- POST /products
أسئلة المراجعة
-
ما هي التعليقات التوضيحية التي تتكون منها @SpringBootApplication؟
-
ما الفرق بين @Controller و @RestController؟
-
متى تستخدم @Service مقابل @Component؟
-
ما الفرق بين @PathVariable و @RequestParam؟
-
لماذا يوصى بالحقن بواسطة المنشئ؟
-
ماذا تفعل التعليق التوضيحي @RequestBody؟
-
ما هي طريقة HTTP التي تتوافق مع @PostMapping؟
-
ما هو دور @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: تطبيق التحية
- إعادة تنفيذ التمارين لكل فصل
- إنشاء مشروع صغير خاص بك لتعزيز المكتسبات