اختبار: حقن التبعيات
اختبر معرفتك بحقن التبعيات المتقدمة.
30 سؤالاً - انقر على كل سؤال لكشف الإجابة.
الأسئلة 1-15: أنواع الحقن
السؤال 1: ما هي الأنواع الثلاثة لحقن التبعيات؟
الإجابة:
- الحقن عبر المنشئ (موصى به)
- الحقن عبر المُعدِّل
- الحقن عبر الحقل (field injection)
السؤال 2: لماذا يُوصى بالحقن عبر المنشئ؟
الإجابة:
- تبعيات غير قابلة للتغيير (final)
- إلزامية - لا يوجد كائن بدون تبعيات
- قابلة للاختبار - سهلة المحاكاة
- كشف الدورات عند الترجمة
- لا حاجة إلى
@Autowired(Spring 4.3+)
السؤال 3: كيف يتم الحقن عبر المُعدِّل؟
الإجابة:
@Service
public class UserService {
private UserRepository userRepository;
@Autowired
public void setUserRepository(UserRepository userRepository) {
this.userRepository = userRepository;
}
}
يُستخدم للتبعيات الاختيارية أو القابلة لإعادة التكوين.
السؤال 4: لماذا يجب تجنب الحقن عبر الحقل؟
الإجابة:
- لا يمكن جعل التبعية
final - صعب الاختبار بدون Spring
- يخفي التبعيات (غير مرئي في المنشئ)
- يسهل انتهاكات SRP (الكثير من التبعيات)
// يُفضل تجنبه
@Autowired
private UserRepository repo;
السؤال 5: متى يكون @Autowired اختياريًا؟
الإجابة: منذ Spring 4.3، إذا كان لدى الفئة منشئ واحد فقط، فإن @Autowired يكون ضمنيًا.
@Service
public class UserService {
private final UserRepository repo;
// @Autowired اختياري
public UserService(UserRepository repo) {
this.repo = repo;
}
}
السؤال 6: كيف يتم التعامل مع وجود عدة منشئين؟
الإجابة: تحديد المنشئ الذي سيتم استخدامه بشكل صريح:
@Service
public class UserService {
public UserService() { }
@Autowired // Spring سيستخدم هذا
public UserService(UserRepository repo) {
this.repo = repo;
}
}
السؤال 7: كيف تجعل التبعية اختيارية؟
الإجابة: عدة طرق:
// 1. required = false
@Autowired(required = false)
private CacheService cache;
// 2. Optional (موصى به)
public MyService(Optional<CacheService> cache) {
this.cache = cache.orElse(null);
}
// 3. @Nullable
public MyService(@Nullable CacheService cache) { }
السؤال 8: كيف يتم حقن جميع تطبيقات واجهة معينة؟
الإجابة:
@Service
public class NotificationManager {
private final List<NotificationService> services;
public NotificationManager(List<NotificationService> services) {
this.services = services;
}
public void notifyAll(String msg) {
services.forEach(s -> s.send(msg));
}
}
السؤال 9: كيف يتم حقن خريطة من beans؟
الإجابة:
@Service
public class ProcessorManager {
// المفتاح = اسم الـ bean، القيمة = المثيل
private final Map<String, Processor> processors;
public ProcessorManager(Map<String, Processor> processors) {
this.processors = processors;
}
public void process(String type, Data data) {
processors.get(type).process(data);
}
}
السؤال 10: ما فائدة @Primary؟
الإجابة: تحديد الـ bean الافتراضي عندما توجد عدة تطبيقات:
@Service
@Primary
public class EmailNotification implements NotificationService { }
@Service
public class SmsNotification implements NotificationService { }
// سيتم حقن EmailNotification بشكل افتراضي
السؤال 11: ما فائدة @Qualifier؟
الإجابة: تحديد أي bean سيتم حقنه:
@Service("email")
public class EmailNotification implements NotificationService { }
@Service("sms")
public class SmsNotification implements NotificationService { }
// الحقن
public MyService(@Qualifier("sms") NotificationService notif) { }
السؤال 12: كيف يتم إنشاء @Qualifier مخصص؟
الإجابة:
@Qualifier
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.PARAMETER})
public @interface Premium { }
@Service
@Premium
public class PremiumNotification implements NotificationService { }
// الاستخدام
public MyService(@Premium NotificationService notif) { }
السؤال 13: ما هو @Lazy؟
الإجابة: يؤخر إنشاء الـ bean حتى الوصول الأول:
@Service
@Lazy
public class HeavyService { }
// أو عند الحقن
public MyService(@Lazy HeavyService heavy) { }
يُستخدم من أجل: الأداء عند البدء، التبعيات الدائرية.
السؤال 14: كيف يتم حل التبعية الدائرية؟
الإجابة:
- إعادة الهيكلة (أفضل خيار)
- استخدام
@Lazyعلى إحدى التبعيات - استخدام حقن المُعدِّل
@Service
public class A {
public A(@Lazy B b) { this.b = b; }
}
@Service
public class B {
public B(A a) { this.a = a; }
}
السؤال 15: كيف يتم حقن قيم التكوين؟
الإجابة: باستخدام @Value:
@Service
public class EmailService {
@Value("${app.email.sender}")
private String sender;
@Value("${app.email.enabled:true}")
private boolean enabled;
// أو عبر المنشئ
public EmailService(@Value("${app.email.host}") String host) { }
}
الأسئلة 16-30: حالات متقدمة
السؤال 16: ما الفرق بين @Autowired و@Resource؟
الإجابة:
@Autowired: Spring، الحقن عبر النوع@Resource: Java EE (JSR-250)، الحقن عبر الاسم
@Autowired // عبر النوع
private UserService userService;
@Resource(name = "specificService") // عبر الاسم
private UserService userService;
السؤال 17: كيف يتم حقن سياق Spring نفسه؟
الإجابة:
@Service
public class DynamicBeanLoader implements ApplicationContextAware {
private ApplicationContext context;
@Override
public void setApplicationContext(ApplicationContext ctx) {
this.context = ctx;
}
public Object getBean(String name) {
return context.getBean(name);
}
}
السؤال 18: كيف يتم الحصول على bean بطريقة برمجية؟
الإجابة:
@Service
public class BeanFactory {
private final ApplicationContext context;
public BeanFactory(ApplicationContext context) {
this.context = context;
}
public UserService getUserService() {
return context.getBean(UserService.class);
}
}
السؤال 19: ما هو ObjectProvider؟
الإجابة: طريقة آمنة للحصول على beans اختيارية أو متعددة:
@Service
public class MyService {
private final ObjectProvider<CacheService> cacheProvider;
public MyService(ObjectProvider<CacheService> cacheProvider) {
this.cacheProvider = cacheProvider;
}
public void doWork() {
cacheProvider.ifAvailable(cache -> cache.put(...));
}
}
السؤال 20: كيف يتم حقن bean prototype في singleton؟
الإجابة: استخدام ObjectProvider أو @Lookup:
// الخيار 1: ObjectProvider
@Service
public class SingletonService {
private final ObjectProvider<PrototypeBean> provider;
public void doWork() {
PrototypeBean bean = provider.getObject(); // مثيل جديد
}
}
// الخيار 2: @Lookup
@Service
public abstract class SingletonService {
@Lookup
protected abstract PrototypeBean getPrototype();
}
السؤال 21: ما هو @DependsOn؟
الإجابة: يفرض ترتيب إنشاء الـ beans:
@Service
@DependsOn("databaseInitializer")
public class UserService {
// يُنشأ بعد databaseInitializer
}
السؤال 22: كيف يتم استبعاد bean من التكوين التلقائي؟
الإجابة:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class MyApp { }
// أو في application.yml
spring:
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
السؤال 23: كيف يتم إنشاء bean شرطي؟
الإجابة:
@Bean
@ConditionalOnProperty(name = "cache.enabled", havingValue = "true")
public CacheManager cacheManager() { }
@Bean
@ConditionalOnMissingBean
public DefaultService defaultService() { }
@Bean
@Profile("dev")
public DevService devService() { }
السؤال 24: ما هو @Order؟
الإجابة: يحدد ترتيب الأولوية للـ beans المجمعة:
@Component
@Order(1)
public class FirstFilter implements Filter { }
@Component
@Order(2)
public class SecondFilter implements Filter { }
// الحقن بالترتيب
public MyService(List<Filter> filters) {
// FirstFilter, SecondFilter
}
السؤال 25: كيف يتم حقن بيئة Spring؟
الإجابة:
@Service
public class ConfigService {
private final Environment env;
public ConfigService(Environment env) {
this.env = env;
}
public String getProperty(String key) {
return env.getProperty(key);
}
public boolean isDev() {
return Arrays.asList(env.getActiveProfiles()).contains("dev");
}
}
السؤال 26: كيف يتم اختبار خدمة مع الحقن؟
الإجابة:
@ExtendWith(MockitoExtension.class)
class UserServiceTest {
@Mock
private UserRepository userRepository;
@InjectMocks
private UserService userService;
@Test
void shouldFindUser() {
when(userRepository.findById(1L)).thenReturn(Optional.of(user));
User result = userService.findById(1L);
assertThat(result).isNotNull();
}
}
السؤال 27: كيف يتم حقن خصائص مجمعة؟
الإجابة: باستخدام @ConfigurationProperties:
@ConfigurationProperties(prefix = "app.mail")
public class MailProperties {
private String host;
private int port;
private boolean ssl;
// getters/setters
}
// الحقن
@Service
public class MailService {
public MailService(MailProperties props) { }
}
السؤال 28: ما الفرق بين @Component و@Bean؟
الإجابة:
| @Component | @Bean |
|---|---|
| على الفئة | على الطريقة |
| يُكتشف تلقائيًا عبر المسح | يُعلن عنه صراحة |
| تكوين محدود | تحكم كامل |
استخدم @Bean للفئات الخارجية أو التكوين المعقد.
السؤال 29: كيف يمكن الحصول على عدة مثيلات لنفس الـ bean؟
الإجابة:
@Configuration
public class DataSourceConfig {
@Bean
@Qualifier("primary")
public DataSource primaryDataSource() { }
@Bean
@Qualifier("secondary")
public DataSource secondaryDataSource() { }
}
// الحقن
public MyService(
@Qualifier("primary") DataSource primary,
@Qualifier("secondary") DataSource secondary) { }
السؤال 30: ما هي المشاكل الشائعة في الحقن؟
الإجابة:
- NoSuchBeanDefinitionException: bean مفقود
- NoUniqueBeanDefinitionException: عدة مرشحين
- BeanCurrentlyInCreationException: تبعية دائرية
- UnsatisfiedDependencyException: فشل الحقن
الحلول: تحقق من التعليقات التوضيحية، @Qualifier, @Primary, @Lazy.
النتيجة
| النتيجة | المستوى |
|---|---|
| 27-30 | ممتاز - إتقان الحقن |
| 21-26 | جيد - بعض المراجعات |
| 15-20 | متوسط - أعد قراءة الفصل |
| أقل من 15 | بحاجة إلى مراجعة |