اختبار: IoC وأساسيات Spring
اختبر معرفتك بعكس التحكم وأساسيات Spring.
30 سؤالاً - انقر على كل سؤال لكشف الإجابة.
الأسئلة 1-10: المفاهيم الأساسية
السؤال 1: ماذا يعني IoC (عكس التحكم)؟
الإجابة: IoC (عكس التحكم) هو مبدأ تصميم حيث يتم نقل التحكم في إنشاء وإدارة الكائنات من كود التطبيق إلى حاوية أو إطار عمل خارجي (مثل Spring). بدلاً من أن يقوم الكود بإنشاء تبعياته الخاصة، تقوم الحاوية بحقنها.
السؤال 2: ما الفرق بين IoC وDI (حقن التبعية)؟
الإجابة:
- IoC هو المبدأ العام: يتم عكس التحكم، الإطار يدير الكائنات
- DI هو تنفيذ محدد لـ IoC: يتم حقن التبعيات بواسطة الحاوية
DI هو طريقة لتحقيق IoC. يمكن أيضًا تحقيق IoC بواسطة أنماط أخرى مثل محدد الخدمة.
السؤال 3: ما هي حاوية Spring (ApplicationContext)؟
الإجابة: ApplicationContext هو حاوية IoC لـ Spring. وهو مسؤول عن:
- إنشاء beans
- تكوين beans
- تجميع التبعيات بين beans
- إدارة دورة حياة beans
إنه قلب تطبيق Spring الذي يحتفظ بجميع الكائنات المدارة.
السؤال 4: ما هو Bean Spring؟
الإجابة: Bean Spring هو كائن يتم إنشاؤه وتجميعه وإدارته بواسطة حاوية Spring IoC. إنه ببساطة كائن Java (POJO) يتم التحكم في دورة حياته بواسطة Spring بدلاً من كود التطبيق.
السؤال 5: ما هي الطرق الرئيسية الثلاثة لتعريف Bean؟
الإجابة:
- تعليقات توضيحية للنمط:
@Component,@Service,@Repository,@Controller - @Bean في فئة @Configuration: طريقة مشروحة بـ @Bean
- تكوين XML:
bean(أقل استخدامًا اليوم)
// 1. تعليق توضيحي للنمط
@Service
public class UserService { }
// 2. @Bean
@Configuration
public class AppConfig {
@Bean
public UserService userService() {
return new UserService();
}
}
السؤال 6: أي تعليق توضيحي يقوم بتفعيل مسح المكونات؟
الإجابة: @ComponentScan يقوم بتفعيل مسح المكونات. يتم تضمينه تلقائيًا في @SpringBootApplication.
@SpringBootApplication // يتضمن @ComponentScan
public class MyApplication { }
// أو بشكل صريح:
@ComponentScan(basePackages = "com.example")
السؤال 7: في أي حزمة يقوم Spring بمسح المكونات افتراضيًا؟
الإجابة: يقوم Spring بمسح حزمة الفئة المشروحة بـ @SpringBootApplication وجميع الحزم الفرعية لها افتراضيًا.
إذا كانت @SpringBootApplication في com.example.myapp, فإن Spring سيمسح:
com.example.myappcom.example.myapp.servicecom.example.myapp.controller- إلخ.
السؤال 8: ما الفرق بين @Component و@Service؟
الإجابة: تقنيًا، لا يوجد أي فرق وظيفي. كلاهما أنماط تشير إلى أن الفئة هي Bean Spring.
الفرق هو دلالي:
@Component: مكون عام@Service: منطق الأعمال@Repository: الوصول إلى البيانات (+ ترجمة الاستثناءات)@Controller: وحدة تحكم ويب MVC
هذا من أجل وضوح وهدف الكود.
السؤال 9: لماذا نستخدم IoC/DI بدلاً من إنشاء الكائنات باستخدام new؟
الإجابة: فوائد IoC/DI:
- اقتران ضعيف: الفئات تعتمد على الواجهات، وليس على التنفيذات
- قابلية الاختبار: من السهل محاكاة التبعيات
- المرونة: تغيير التنفيذ دون تعديل كود العميل
- قابلية إعادة الاستخدام: مكونات مستقلة
- فصل الاهتمامات: الكود الخاص بالأعمال لا يدير إنشاء الكائنات
السؤال 10: ما هو الاقتران القوي (tight coupling)؟
الإجابة: يحدث الاقتران القوي عندما تعتمد فئة مباشرة على تنفيذ ملموس بدلاً من تجريد.
// اقتران قوي (سيء)
public class OrderService {
private EmailService emailService = new EmailService(); // new = اقتران قوي
}
// اقتران ضعيف (جيد)
public class OrderService {
private final NotificationService notificationService; // واجهة
public OrderService(NotificationService ns) { // حقن
this.notificationService = ns;
}
}
الأسئلة 11-20: حقن التبعيات
السؤال 11: ما هي الأنواع الثلاثة لحقن التبعيات؟
الإجابة:
- حقن بواسطة المنشئ (موصى به)
- حقن بواسطة setter (@Autowired على setter)
- حقن بواسطة الحقل (@Autowired على field)
// 1. منشئ (موصى به)
public class UserService {
private final UserRepository repo;
public UserService(UserRepository repo) { this.repo = repo; }
}
// 2. Setter
@Autowired
public void setRepository(UserRepository repo) { this.repo = repo; }
// 3. Field (تجنب)
@Autowired
private UserRepository repo;
السؤال 12: لماذا يوصى بحقن بواسطة المنشئ؟
الإجابة: يوصى بحقن بواسطة المنشئ لأن:
- عدم التغيير: التبعيات
final, غير قابلة للتغيير - إجباري: من المستحيل إنشاء الكائن بدون تبعيات
- قابلية الاختبار: من السهل محاكاة في الاختبارات
- بدون @Autowired: ضمني منذ Spring 4.3
- كشف الدورات: خطأ في التجميع
السؤال 13: متى يكون @Autowired اختياريًا؟
الإجابة: @Autowired اختياري منذ Spring 4.3 عندما:
- تحتوي الفئة على منشئ واحد فقط
- يأخذ هذا المنشئ التبعيات كمعلمات
يقوم Spring بالكشف التلقائي وحقن التبعيات.
@Service
public class UserService {
private final UserRepository repo;
// @Autowired اختياري هنا (منشئ واحد فقط)
public UserService(UserRepository repo) {
this.repo = repo;
}
}
السؤال 14: ماذا يحدث إذا وجد Spring عدة beans من نفس النوع؟
الإجابة: يقوم Spring بإلقاء استثناء NoUniqueBeanDefinitionException.
الحلول:
@Primaryعلى الـ bean المفضل@Qualifier("nomDuBean")لتحديد أي واحد- حقن بواسطة الاسم (اسم المعلمة = اسم الـ bean)
@Primary
@Service
public class EmailNotification implements Notification { }
// أو
@Autowired
@Qualifier("smsNotification")
private Notification notification;
السؤال 15: ما فائدة @Primary؟
الإجابة: @Primary تشير إلى أي bean يتم استخدامه افتراضيًا عندما يوجد عدة beans من نفس النوع.
@Service
@Primary // سيتم حقنه افتراضيًا
public class EmailNotificationService implements NotificationService { }
@Service
public class SmsNotificationService implements NotificationService { }
يتم اختيار الـ bean @Primary تلقائيًا إلا إذا حدد @Qualifier آخر.
السؤال 16: ما فائدة @Qualifier؟
الإجابة: @Qualifier يسمح بتحديد أي bean يتم حقنه بالضبط عندما يوجد عدة مرشحين.
@Service("emailService")
public class EmailNotification implements Notification { }
@Service("smsService")
public class SmsNotification implements Notification { }
// الاستخدام
@Autowired
@Qualifier("smsService")
private Notification notification;
السؤال 17: هل يمكن حقن قائمة بجميع beans من نوع معين؟
الإجابة: نعم! يمكن لـ Spring حقن List تحتوي على جميع beans من النوع T.
@Service
public class NotificationManager {
private final List services;
public NotificationManager(List services) {
this.services = services; // تحتوي على جميع beans NotificationService
}
public void notifyAll(String message) {
services.forEach(s -> s.send(message));
}
}
السؤال 18: كيف تجعل التبعية اختيارية؟
الإجابة: عدة طرق:
@Autowired(required = false)Optionalكنوع- تعليق توضيحي
@Nullable
// الخيار 1
@Autowired(required = false)
private CacheService cacheService;
// الخيار 2 (موصى به)
public MyService(Optional cacheService) {
this.cacheService = cacheService.orElse(null);
}
السؤال 19: ما هي التبعية الدائرية؟
الإجابة: تحدث التبعية الدائرية عندما يعتمد beanان أو أكثر على بعضهما البعض، مكونين دورة.
@Service
public class A {
public A(B b) { } // A يعتمد على B
}
@Service
public class B {
public B(A a) { } // B يعتمد على A -> دورة!
}
يقوم Spring بإلقاء استثناء. الحلول: إعادة الهيكلة، استخدام @Lazy، أو حقن setter.
السؤال 20: كيف تحل التبعية الدائرية باستخدام @Lazy؟
الإجابة: @Lazy يؤخر الحقن حتى الوصول الأول، مما يكسر الدورة.
@Service
public class A {
public A(@Lazy B b) { // سيتم إنشاء B لاحقًا
this.b = b;
}
}
@Service
public class B {
public B(A a) {
this.a = a;
}
}
ملاحظة: من الأفضل إعادة الهيكلة لإزالة الدورة!
الأسئلة 21-30: التكوين والمتقدم
السؤال 21: ما الفرق بين @Component و@Bean؟
الإجابة:
| @Component | @Bean |
|---|---|
| على فئة | على طريقة |
| يتم اكتشافه بواسطة المسح | في فئة @Configuration |
| الاسم = الفئة (بحروف صغيرة) | الاسم = اسم الطريقة |
| لا يوجد تحكم في الإنشاء | تحكم كامل في الإنشاء |
@Component // يتم اكتشافه تلقائيًا
public class MyService { }
@Configuration
public class AppConfig {
@Bean // إنشاء يدوي
public ExternalService externalService() {
return new ExternalService("config");
}
}
السؤال 22: متى تستخدم @Bean بدلاً من @Component؟
الإجابة: استخدم @Bean عندما:
- تأتي الفئة من مكتبة خارجية (غير قابلة للتعديل)
- تحتاج إلى منطق إنشاء معقد
- تحتاج إلى تكوين قبل الإنشاء
- إنشاء كائنات طرف ثالث (RestTemplate, ObjectMapper)
@Bean
public RestTemplate restTemplate() {
RestTemplate rt = new RestTemplate();
rt.setErrorHandler(new CustomErrorHandler());
return rt;
}
السؤال 23: ما هي فئة @Configuration؟
الإجابة: فئة @Configuration هي فئة خاصة التي:
- تحتوي على طرق
@Bean - هي نفسها bean Spring
- يتم معالجتها بواسطة CGLIB لضمان singleton للـ beans
- تحل محل ملفات تكوين XML
@Configuration
public class AppConfig {
@Bean
public DataSource dataSource() { ... }
@Bean
public JdbcTemplate jdbcTemplate(DataSource ds) {
return new JdbcTemplate(ds); // ds محقون
}
}
السؤال 24: ماذا يفعل @PostConstruct؟
الإجابة: @PostConstruct يحدد طريقة للتنفيذ بعد حقن التبعيات، من أجل التهيئة.
@Service
public class CacheService {
private final DataRepository repo;
private Map cache;
public CacheService(DataRepository repo) {
this.repo = repo;
}
@PostConstruct
public void init() {
// يتم تنفيذه بعد حقن repo
this.cache = repo.loadInitialData();
}
}
السؤال 25: ماذا يفعل @PreDestroy؟
الإجابة: @PreDestroy يحدد طريقة للتنفيذ قبل تدمير الـ bean، من أجل التنظيف.
@Service
public class ConnectionPool {
private List connections;
@PreDestroy
public void cleanup() {
// يتم تنفيذه قبل تدمير الـ bean
connections.forEach(Connection::close);
System.out.println("Connections fermées");
}
}
السؤال 26: ما هو النطاق الافتراضي لbean Spring؟
الإجابة: النطاق الافتراضي هو Singleton: يتم إنشاء نسخة واحدة من الـ bean ومشاركتها في جميع أنحاء سياق Spring.
نطاقات أخرى:
prototype: نسخة جديدة في كل حقنrequest: نسخة واحدة لكل طلب HTTPsession: نسخة واحدة لكل جلسة HTTPapplication: نسخة واحدة لكل ServletContext
السؤال 27: كيف تغير نطاق bean؟
الإجابة: باستخدام التعليق التوضيحي @Scope:
@Service
@Scope("prototype") // نسخة جديدة في كل حقن
public class RequestHandler { }
// أو باستخدام الثابت
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
// لنطاقات الويب
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
السؤال 28: ما هو @Value وما فائدته؟
الإجابة: @Value يحقن القيم من ملفات الخصائص أو القيم الافتراضية.
@Service
public class EmailService {
@Value("${app.email.sender}")
private String senderEmail;
@Value("${app.email.enabled:true}") // افتراضي: true
private boolean enabled;
@Value("#{systemProperties['user.name']}") // SpEL
private String systemUser;
}
السؤال 29: ما هو @ConfigurationProperties؟
الإجابة: @ConfigurationProperties يربط مجموعة من الخصائص بفئة Java، أكثر نظافة من استخدام @Value متعدد.
@ConfigurationProperties(prefix = "app.email")
public class EmailProperties {
private String sender;
private boolean enabled;
private int maxRetries;
// getters/setters
}
// application.yml
app:
email:
sender: noreply@example.com
enabled: true
max-retries: 3
السؤال 30: كيف تختبر خدمة مع تبعيات محقونة؟
الإجابة: استخدام mocks مع Mockito:
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void shouldFindUser() {
// Given
when(userRepository.findById(1L))
.thenReturn(Optional.of(new User(1L, "John")));
// When
User user = userService.findById(1L);
// Then
assertThat(user.getName()).isEqualTo("John");
}
}
الحقن بواسطة المنشئ يجعل ذلك ممكنًا بدون Spring.
النتيجة
احسب إجاباتك الصحيحة:
| النتيجة | المستوى |
|---|---|
| 27-30 | ممتاز - أنت تتقن IoC Spring |
| 21-26 | جيد - بعض المراجعات الطفيفة |
| 15-20 | متوسط - أعد قراءة الفصل |
| أقل من 15 | بحاجة للمراجعة - أعد الأساسيات |