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

اختبار: النطاقات والتكوين المتقدم

اختبر معرفتك بالنطاقات وخصائص التكوين.

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


الأسئلة 1-15: النطاقات​

السؤال 1: ما هو النطاق الافتراضي لـ Spring bean؟

الإجابة: Singleton - مثيل واحد لكل حاوية Spring.

@Component
public class MyService { } // Singleton افتراضي

السؤال 2: كيف تنشئ bean prototype؟

الإجابة:

@Component
@Scope("prototype")
public class PrototypeBean { }

// أو
@Bean
@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
public MyService myService() { }

مثيل جديد عند كل استدعاء.


السؤال 3: متى تستخدم النطاق prototype؟

الإجابة:

  • كائنات ذات حالة قابلة للتغيير
  • كائنات غير آمنة للخيوط
  • كائنات مكلفة الإنشاء ولكن للاستخدام لمرة واحدة
  • سير العمل أو العمليات المستقلة

مثال: محلل الملفات، معالج الأوامر.


السؤال 4: ما هو النطاق request؟

الإجابة: مثيل واحد لكل طلب HTTP:

@Component
@Scope(value = WebApplicationContext.SCOPE_REQUEST, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestContext {
private User currentUser;
// بيانات خاصة بالطلب
}

يتم تدميره في نهاية الطلب.


السؤال 5: ما هو النطاق session؟

الإجابة: مثيل واحد لكل جلسة HTTP:

@Component
@Scope(value = WebApplicationContext.SCOPE_SESSION, proxyMode = ScopedProxyMode.TARGET_CLASS)
public class ShoppingCart {
private List<Item> items = new ArrayList<>();
}

يستمر بين طلبات نفس المستخدم.


السؤال 6: لماذا يكون proxyMode ضروريًا لـ request/session؟

الإجابة: يتم إنشاء الـ singleton عند بدء التشغيل، قبل أي طلب. يتيح الوكيل الوصول إلى الـ bean المحدد ديناميكيًا:

@Component
@Scope(value = "request", proxyMode = ScopedProxyMode.TARGET_CLASS)
public class RequestData { }

@Service // Singleton
public class MyService {
private final RequestData data; // وكيل، ليس المثيل الحقيقي
}

السؤال 7: ما هي أنواع proxyMode؟

الإجابة:

الوضعالاستخدام
DEFAULTلا يوجد وكيل
NOلا يوجد وكيل
INTERFACESوكيل JDK (واجهة)
TARGET_CLASSوكيل CGLIB (فئة)

استخدم TARGET_CLASS للفئات الملموسة.


السؤال 8: كيف تنشئ نطاقًا مخصصًا؟

الإجابة:

public class TenantScope implements Scope {

private final ThreadLocal<Map<String, Object>> beans = new ThreadLocal<>();

@Override
public Object get(String name, ObjectFactory<?> factory) {
Map<String, Object> scope = beans.get();
if (!scope.containsKey(name)) {
scope.put(name, factory.getObject());
}
return scope.get(name);
}

@Override
public Object remove(String name) {
return beans.get().remove(name);
}
// ...
}

@Configuration
public class ScopeConfig implements BeanFactoryPostProcessor {

@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory factory) {
factory.registerScope("tenant", new TenantScope());
}
}

السؤال 9: كيف تدير الأمان الخيطي مع الـ singletons؟

الإجابة:

  1. Beans بدون حالة (لا توجد حقول قابلة للتغيير)
  2. حقول غير قابلة للتغيير (نهائية)
  3. التزامن إذا لزم الأمر
  4. استخدام ThreadLocal للحالة لكل خيط
@Service
public class StatelessService {
// لا توجد حقول قابلة للتغيير
private final UserRepository repo; // غير قابل للتغيير

public User process(Long id) {
// كل شيء في المتغيرات المحلية
return repo.findById(id).orElseThrow();
}
}

السؤال 10: كيف تتحقق من نطاق bean؟

الإجابة:

@Autowired
private ConfigurableListableBeanFactory beanFactory;

public void checkScope() {
BeanDefinition definition = beanFactory.getBeanDefinition("myBean");
String scope = definition.getScope();
log.info("Scope: {}", scope); // singleton, prototype, إلخ.
}

السؤال 11: كيف تستخدم @RefreshScope؟

الإجابة: للـ beans التي يجب إعادة إنشائها بعد تحديث التكوين:

@RefreshScope
@Component
public class DynamicConfig {

@Value("${app.feature.enabled}")
private boolean featureEnabled;
}

بعد POST /actuator/refresh، يتم إعادة إنشاء الـ bean بالقيم الجديدة.


السؤال 12: كيف تختبر الـ beans المحددة؟

الإجابة:

@SpringBootTest
@WebAppConfiguration // لنطاق request/session
class ScopedBeanTest {

@Autowired
private WebApplicationContext context;

@Test
void testRequestScope() {
MockHttpServletRequest request = new MockHttpServletRequest();
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request));

try {
RequestData data = context.getBean(RequestData.class);
assertThat(data).isNotNull();
} finally {
RequestContextHolder.resetRequestAttributes();
}
}
}

السؤال 13: كيف يتم تدمير الـ beans من نوع prototype؟

الإجابة: Spring لا يدمر الـ beans من نوع prototype تلقائيًا!

@Component
@Scope("prototype")
public class ExpensiveResource implements DisposableBean {

@Override
public void destroy() {
// لا يتم استدعاؤه تلقائيًا!
}
}

يجب إدارتها يدويًا أو استخدام BeanFactoryPostProcessor.


السؤال 14: كيف تحصل على عدة beans من نفس النوع؟

الإجابة:

// قائمة بجميع الـ beans
@Autowired
private List<NotificationService> notificationServices;

// خريطة مع الاسم كمفتاح
@Autowired
private Map<String, NotificationService> notificationMap;

// ObjectProvider للوصول الكسول
@Autowired
private ObjectProvider<NotificationService> provider;

public void notify() {
provider.forEach(service -> service.send(message));
}

السؤال 15: كيف تستخدم @Primary مقابل @Qualifier؟

الإجابة:

// @Primary: bean الافتراضي
@Service
@Primary
public class EmailNotification implements NotificationService { }

@Service
public class SmsNotification implements NotificationService { }

// @Qualifier: اختيار صريح
public MyController(@Qualifier("smsNotification") NotificationService notif) { }

الأسئلة 16-30: التكوين​

السؤال 16: كيف تقرأ خاصية باستخدام @Value؟

الإجابة:

@Value("${app.name}")
private String appName;

@Value("${app.timeout:5000}") // قيمة افتراضية
private int timeout;

@Value("${app.features}") // قائمة: a,b,c
private List<String> features;

@Value("#{${app.map}}") // خريطة: {key1: 'val1', key2: 'val2'}
private Map<String, String> map;

السؤال 17: كيف تستخدم SpEL في @Value؟

الإجابة:

@Value("#{systemProperties['user.home']}")
private String userHome;

@Value("#{T(java.lang.Math).random()}")
private double randomValue;

@Value("#{myBean.property}")
private String fromBean;

@Value("#{${app.timeout} * 2}")
private int doubleTimeout;

السؤال 18: كيف تجمع الخصائص باستخدام @ConfigurationProperties؟

الإجابة:

# application.yml
app:
mail:
host: smtp.example.com
port: 587
username: user
password: secret
@ConfigurationProperties(prefix = "app.mail")
@Validated
public class MailProperties {

@NotBlank
private String host;

@Min(1)
@Max(65535)
private int port;

private String username;
private String password;

// Getters و setters
}

@Configuration
@EnableConfigurationProperties(MailProperties.class)
public class AppConfig { }

السؤال 19: كيف تتحقق من صحة الخصائص؟

الإجابة:

@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {

@NotNull
private String name;

@Min(1000)
@Max(65535)
private int port;

@Email
private String adminEmail;

@Valid // تحقق من صحة الكائنات المتداخلة
private DatabaseProperties database;
}

يفشل التطبيق عند بدء التشغيل إذا كان غير صالح.


السؤال 20: كيف تقوم بتكوين الكائنات المتداخلة؟

الإجابة:

app:
database:
primary:
url: jdbc:postgresql://primary/db
username: user1
secondary:
url: jdbc:postgresql://secondary/db
username: user2
@ConfigurationProperties(prefix = "app.database")
public class DatabaseProperties {
private DataSourceConfig primary;
private DataSourceConfig secondary;

public static class DataSourceConfig {
private String url;
private String username;
// Getters/setters
}
}

السؤال 21: كيف تقوم بتكوين القوائم؟

الإجابة:

app:
servers:
- host: server1.example.com
port: 8080
- host: server2.example.com
port: 8081
@ConfigurationProperties(prefix = "app")
public class AppProperties {
private List<ServerConfig> servers;

public static class ServerConfig {
private String host;
private int port;
}
}

السؤال 22: كيف تستخدم ملفات التكوين؟

الإجابة:

# application.yml (مشترك)
app:
name: My App

---
# application-dev.yml
spring:
config:
activate:
on-profile: dev
logging:
level:
root: DEBUG

---
# application-prod.yml
spring:
config:
activate:
on-profile: prod
logging:
level:
root: WARN

التفعيل:

java -jar app.jar --spring.profiles.active=prod

السؤال 23: كيف تقوم بتخزين التكوين خارجيًا؟

الإجابة: ترتيب الأولوية (من الأعلى إلى الأقل):

  1. وسائط سطر الأوامر
  2. متغيرات البيئة
  3. application-\{profile\}.yml خارجي
  4. application.yml خارجي
  5. application-\{profile\}.yml داخل JAR
  6. application.yml داخل JAR
java -jar app.jar --server.port=9090
export SERVER_PORT=9090

السؤال 24: كيف تقوم بتحميل التكوين من ملف خارجي؟

الإجابة:

# ملف خارجي
java -jar app.jar --spring.config.location=file:/etc/myapp/config/

# عدة مواقع
java -jar app.jar --spring.config.location=classpath:/,file:/config/

# ملف محدد
java -jar app.jar --spring.config.name=custom

السؤال 25: كيف تستخدم متغيرات البيئة؟

الإجابة:

spring:
datasource:
url: ${DB_URL:jdbc:h2:mem:default}
username: ${DB_USER:sa}
password: ${DB_PASSWORD:}

اتفاقية التعيين:

  • app.my-property → APP_MY_PROPERTY
  • . → _
  • - → _

السؤال 26: كيف تقوم بتشفير الخصائص الحساسة؟

الإجابة: باستخدام Jasypt أو Spring Cloud Config:

<dependency>
<groupId>com.github.ulisesbocchio</groupId>
<artifactId>jasypt-spring-boot-starter</artifactId>
</dependency>
jasypt:
encryptor:
password: ${JASYPT_PASSWORD}

spring:
datasource:
password: ENC(encrypted_value_here)

السؤال 27: كيف تنشئ PropertySource مخصص؟

الإجابة:

public class VaultPropertySource extends PropertySource<VaultClient> {

public VaultPropertySource(VaultClient client) {
super("vault", client);
}

@Override
public Object getProperty(String name) {
if (name.startsWith("vault.")) {
return source.getSecret(name.substring(6));
}
return null;
}
}

@Configuration
public class VaultConfig implements EnvironmentPostProcessor {

@Override
public void postProcessEnvironment(ConfigurableEnvironment env, SpringApplication app) {
env.getPropertySources().addFirst(new VaultPropertySource(client));
}
}

السؤال 28: كيف تقوم بتحديث التكوين في الوقت الفعلي؟

الإجابة:

@RefreshScope
@Component
public class DynamicConfig {

@Value("${app.feature.enabled}")
private boolean enabled;
}
# باستخدام Spring Cloud Config
curl -X POST http://localhost:8080/actuator/refresh

يتم إعادة إنشاء الـ beans @RefreshScope.


السؤال 29: كيف تختبر خصائص التكوين؟

الإجابة:

@SpringBootTest(properties = {
"app.name=Test App",
"app.timeout=1000"
})
class ConfigTest {

@Autowired
private AppProperties properties;

@Test
void propertiesLoaded() {
assertThat(properties.getName()).isEqualTo("Test App");
assertThat(properties.getTimeout()).isEqualTo(1000);
}
}

// أو مع ملف اختبار
@SpringBootTest
@TestPropertySource("classpath:test-config.properties")
class ConfigTest { }

السؤال 30: ما هي أفضل ممارسات التكوين؟

الإجابة:

  1. التصنيف باستخدام @ConfigurationProperties
  2. التحقق باستخدام @Validated
  3. تخزين الأسرار خارجيًا
  4. توثيق كل خاصية
  5. قيم افتراضية معقولة
  6. ملفات للبيئات
  7. عدم الالتزام بالأسرار
  8. المركزية باستخدام Config Server في الخدمات المصغرة
@ConfigurationProperties(prefix = "app")
@Validated
public class AppProperties {

/**
* اسم التطبيق المعروض في السجلات.
*/
@NotBlank
private String name = "My Application";
}

النتيجة​

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

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