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

اختبار: حقن التبعيات

اختبر معرفتك بحقن التبعيات المتقدمة.

30 سؤالاً - انقر على كل سؤال لكشف الإجابة.


الأسئلة 1-15: أنواع الحقن​

السؤال 1: ما هي الأنواع الثلاثة لحقن التبعيات؟

الإجابة:

  1. الحقن عبر المنشئ (موصى به)
  2. الحقن عبر المُعدِّل
  3. الحقن عبر الحقل (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: كيف يتم حل التبعية الدائرية؟

الإجابة:

  1. إعادة الهيكلة (أفضل خيار)
  2. استخدام @Lazy على إحدى التبعيات
  3. استخدام حقن المُعدِّل
@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: ما هي المشاكل الشائعة في الحقن؟

الإجابة:

  1. NoSuchBeanDefinitionException: bean مفقود
  2. NoUniqueBeanDefinitionException: عدة مرشحين
  3. BeanCurrentlyInCreationException: تبعية دائرية
  4. UnsatisfiedDependencyException: فشل الحقن

الحلول: تحقق من التعليقات التوضيحية، @Qualifier, @Primary, @Lazy.


النتيجة​

النتيجةالمستوى
27-30ممتاز - إتقان الحقن
21-26جيد - بعض المراجعات
15-20متوسط - أعد قراءة الفصل
أقل من 15بحاجة إلى مراجعة

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