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

TP5.3 - التكوين حسب ملفات التعريف

الأهداف​

في نهاية هذا التمرين، ستكون قادرًا على:

  1. ✅ إنشاء ملفات شخصية لـ dev، test، و prod
  2. ✅ إنشاء Beans مشروطة حسب ملف التعريف
  3. ✅ استخدام ملفات application-\{profile\}.properties
  4. ✅ تفعيل ملف شخصي واختباره

المدة المقدرة: 1 ساعة و30 دقيقة


المتطلبات الأساسية​

  • ✅ إكمال الفصل 4 من الوحدة 5
  • ✅ إكمال TP5.1 و TP5.2
  • ✅ Maven 3.9+ و Java 17+

التحضير: مشروع جديد​

إنشاء المشروع​

عبر Spring Initializr:

  • Project: Maven
  • Spring Boot: 3.2.0
  • Artifact: profiles-demo
  • Dependencies: Spring Web, Spring Data JPA, H2 Database, PostgreSQL Driver

الهيكل:

src/main/java/com/example/profiles/
├── ProfilesDemoApplication.java
├── service/
│ ├── EmailService.java
│ ├── FakeEmailService.java
│ └── SmtpEmailService.java
├── config/
│ ├── DevConfig.java
│ ├── ProdConfig.java
│ └── DatabaseConfig.java
└── controller/
└── TestController.java

src/main/resources/
├── application.properties
├── application-dev.properties
├── application-test.properties
└── application-prod.properties

التمرين 1: ملفات شخصية لخدمات البريد الإلكتروني​

الهدف​

إنشاء 2 تنفيذات لـ EmailService: واحدة وهمية لـ dev، وواحدة حقيقية لـ prod.

الخطوة 1.1: إنشاء الواجهة​

الملف: service/EmailService.java

package com.example.profiles.service;

public interface EmailService {
void send(String to, String subject, String body);
}

الخطوة 1.2: تنفيذ DEV (وهمي)​

الملف: service/FakeEmailService.java

package com.example.profiles.service;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

@Service
@Profile("dev") // ← نشط فقط في dev
public class FakeEmailService implements EmailService {

@Override
public void send(String to, String subject, String body) {
System.out.println("========== FAKE EMAIL ==========");
System.out.println("To: " + to);
System.out.println("Subject: " + subject);
System.out.println("Body: " + body);
System.out.println("================================");
System.out.println("(Email simulé - pas envoyé réellement)");
}
}

النقاط الرئيسية:

  • @Profile("dev") → يتم إنشاء Bean فقط في ملف dev
  • بريد إلكتروني وهمي → لا يوجد إرسال حقيقي

الخطوة 1.3: تنفيذ PROD (حقيقي)​

الملف: service/SmtpEmailService.java

package com.example.profiles.service;

import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;

@Service
@Profile("prod") // ← نشط فقط في prod
public class SmtpEmailService implements EmailService {

@Override
public void send(String to, String subject, String body) {
System.out.println("========== SMTP EMAIL ==========");
System.out.println("Connexion au serveur SMTP...");
System.out.println("Authentification...");
System.out.println("Envoi à: " + to);
System.out.println("Sujet: " + subject);
System.out.println("================================");
System.out.println("(Email réellement envoyé)");

// En production, ici vous utiliseriez JavaMail:
// Session session = Session.getInstance(properties, authenticator);
// MimeMessage message = new MimeMessage(session);
// Transport.send(message);
}
}

الخطوة 1.4: إنشاء وحدة التحكم للاختبار​

الملف: controller/TestController.java

package com.example.profiles.controller;

import com.example.profiles.service.EmailService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/test")
public class TestController {

private final EmailService emailService;

@Value("${spring.profiles.active:default}")
private String activeProfile;

public TestController(EmailService emailService) {
this.emailService = emailService;
}

@GetMapping("/email")
public String testEmail() {
emailService.send(
"user@example.com",
"Test Email",
"Ceci est un email de test"
);

return "✅ Email envoyé via profil: " + activeProfile +
" - Voir console";
}

@GetMapping("/profile")
public String getActiveProfile() {
return "Profil actif: " + activeProfile;
}
}

الخطوة 1.5: تفعيل ملف DEV​

الملف: src/main/resources/application.properties

# Profil actif
spring.profiles.active=dev

# Nom de l'application
spring.application.name=Profiles Demo

الخطوة 1.6: اختبار في DEV​

ابدأ:

mvn clean install -DskipTests spring-boot:run

وحدة التحكم عند البدء:

The following 1 profile is active: "dev"

اختبر:

http://localhost:8080/api/test/email

وحدة التحكم:

========== FAKE EMAIL ==========
To: user@example.com
Subject: Test Email
Body: Ceci est un email de test
================================
(Email simulé - pas envoyé réellement)

✅ في DEV: بريد إلكتروني وهمي!


الخطوة 1.7: اختبار في PROD​

عدل application.properties:

spring.profiles.active=prod

أعد التشغيل واختبر:

وحدة التحكم:

The following 1 profile is active: "prod"

========== SMTP EMAIL ==========
Connexion au serveur SMTP...
Authentification...
Envoi à: user@example.com
Sujet: Test Email
================================
(Email réellement envoyé)

✅ في PROD: بريد إلكتروني حقيقي!


التمرين 2: تكوين قاعدة البيانات حسب ملف التعريف​

الهدف​

استخدام H2 في dev/test، PostgreSQL في prod.

الخطوة 2.1: ملف application.properties (مشترك)​

الملف: src/main/resources/application.properties

# Profil actif (changez selon besoin)
spring.profiles.active=dev

# Configuration commune
spring.application.name=Profiles Demo
server.port=8080

# JPA commun
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true

الخطوة 2.2: تكوين DEV (H2)​

الملف: src/main/resources/application-dev.properties

# Base de données H2 en mémoire
spring.datasource.url=jdbc:h2:mem:devdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

# Console H2
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console

# Hibernate
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

# Logs
logging.level.root=DEBUG
logging.level.com.example.profiles=DEBUG

الخطوة 2.3: تكوين TEST (H2 مع سكربتات)​

الملف: src/main/resources/application-test.properties

# Base de données H2 en mémoire pour tests
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=

# Hibernate
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

# Logs minimaux pour tests
logging.level.root=WARN
logging.level.com.example.profiles=INFO

الخطوة 2.4: تكوين PROD (PostgreSQL)​

الملف: src/main/resources/application-prod.properties

# Base de données PostgreSQL
spring.datasource.url=jdbc:postgresql://localhost:5432/proddb
spring.datasource.driver-class-name=org.postgresql.Driver
spring.datasource.username=admin
spring.datasource.password=admin123

# Hibernate
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.database-platform=org.hibernate.dialect.PostgreSQLDialect

# Logs minimaux
logging.level.root=WARN
logging.level.com.example.profiles=INFO

# Sécurité (en production, utilisez des variables d'environnement!)
# spring.datasource.username=${DB_USERNAME}
# spring.datasource.password=${DB_PASSWORD}

الخطوة 2.5: إنشاء كيان اختبار​

الملف: model/User.java

package com.example.profiles.model;

import jakarta.persistence.*;

@Entity
@Table(name = "users")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

private String name;
private String email;

// Constructeurs
public User() {}

public User(String name, String email) {
this.name = name;
this.email = email;
}

// Getters et Setters
public Long getId() { return id; }
public void setId(Long id) { this.id = id; }

public String getName() { return name; }
public void setName(String name) { this.name = name; }

public String getEmail() { return email; }
public void setEmail(String email) { this.email = email; }
}

الخطوة 2.6: اختبار قواعد البيانات المختلفة​

في DEV (H2):

spring.profiles.active=dev

ابدأ وتحقق من وحدة التحكم:

H2 console available at '/h2-console'
Hibernate: create table users (...)
Database: H2

الوصول إلى وحدة تحكم H2:

http://localhost:8080/h2-console
JDBC URL: jdbc:h2:mem:devdb
Username: sa
Password: (vide)

في PROD (PostgreSQL):

  1. أنشئ قاعدة البيانات أولاً:
CREATE DATABASE proddb;
  1. عدل ملف التعريف:
spring.profiles.active=prod
  1. ابدأ:
Database: PostgreSQL
Hibernate: validate schema

التمرين 3: Beans التكوين حسب ملف التعريف​

الهدف​

إنشاء Beans مختلفة حسب البيئة.

الخطوة 3.1: تكوين DEV​

الملف: config/DevConfig.java

package com.example.profiles.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile("dev") // ← Toute la classe active en dev
public class DevConfig {

@Bean
public String environmentName() {
return "DEVELOPMENT";
}

@Bean
public boolean debugMode() {
return true;
}

@Bean
public String apiUrl() {
return "http://localhost:8080/api";
}
}

الخطوة 3.2: تكوين PROD​

الملف: config/ProdConfig.java

package com.example.profiles.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;

@Configuration
@Profile("prod") // ← Toute la classe active en prod
public class ProdConfig {

@Bean
public String environmentName() {
return "PRODUCTION";
}

@Bean
public boolean debugMode() {
return false;
}

@Bean
public String apiUrl() {
return "https://api.monsite.com/api";
}
}

الخطوة 3.3: اختبار​

أضف في TestController.java:

@RestController
@RequestMapping("/api/test")
public class TestController {

private final EmailService emailService;
private final String environmentName;
private final boolean debugMode;
private final String apiUrl;

@Value("${spring.profiles.active:default}")
private String activeProfile;

public TestController(
EmailService emailService,
String environmentName,
boolean debugMode,
String apiUrl
) {
this.emailService = emailService;
this.environmentName = environmentName;
this.debugMode = debugMode;
this.apiUrl = apiUrl;
}

// ... autres méthodes ...

@GetMapping("/config")
public String getConfig() {
return String.format(
"Environnement: %s<br>" +
"Profil actif: %s<br>" +
"Mode debug: %s<br>" +
"API URL: %s",
environmentName,
activeProfile,
debugMode,
apiUrl
);
}
}

في DEV:

Environnement: DEVELOPMENT
Profil actif: dev
Mode debug: true
API URL: http://localhost:8080/api

في PROD:

Environnement: PRODUCTION
Profil actif: prod
Mode debug: false
API URL: https://api.monsite.com/api

التمرين 4: ملفات شخصية متعددة و @Profile مركب​

الهدف​

تفعيل ملفات شخصية متعددة وإنشاء شروط معقدة.

الخطوة 4.1: Bean لملفات شخصية متعددة​

أضف في config/DevConfig.java:

@Bean
@Profile({"dev", "test"}) // ← Actif en dev OU test
public String sharedDevTestBean() {
return "Bean partagé entre dev et test";
}

الخطوة 4.2: Bean شرطية معقدة​

@Bean
@Profile("!prod") // ← Actif dans TOUS les profils SAUF prod
public String nonProdBean() {
return "Bean pour non-production (dev, test, etc.)";
}

الخطوة 4.3: تفعيل ملفات شخصية متعددة​

الطريقة 1: application.properties

spring.profiles.active=dev,mock-data,debug

الطريقة 2: سطر الأوامر

mvn spring-boot:run -Dspring-boot.run.profiles=dev,mock-data

الطريقة 3: متغير البيئة

export SPRING_PROFILES_ACTIVE=prod,monitoring
mvn spring-boot:run

التمرين 5: تكوين كامل متعدد البيئات​

الهدف​

إنشاء تكوين جاهز للإنتاج.

الخطوة 5.1: متغيرات البيئة في PROD​

الملف: application-prod.properties

# Base de données (depuis variables d'environnement)
spring.datasource.url=${DATABASE_URL:jdbc:postgresql://localhost:5432/proddb}
spring.datasource.username=${DB_USERNAME:admin}
spring.datasource.password=${DB_PASSWORD}

# SMTP (depuis variables d'environnement)
spring.mail.host=${SMTP_HOST:smtp.gmail.com}
spring.mail.port=${SMTP_PORT:587}
spring.mail.username=${SMTP_USERNAME}
spring.mail.password=${SMTP_PASSWORD}

# API Keys
app.external-api.key=${EXTERNAL_API_KEY}
app.external-api.url=${EXTERNAL_API_URL:https://api.service.com}

الصيغة:

  • $\{VAR_NAME\} → متغير إلزامي (خطأ إذا كان غائبًا)
  • ${VAR_NAME:default} → متغير بقيمة افتراضية

الخطوة 5.2: النشر مع المتغيرات​

Linux/Mac:

export DATABASE_URL="jdbc:postgresql://prod-server:5432/myapp"
export DB_USERNAME="app_user"
export DB_PASSWORD="secret123"
export SMTP_USERNAME="noreply@myapp.com"
export SMTP_PASSWORD="smtp_secret"

mvn spring-boot:run

Windows:

$env:DATABASE_URL="jdbc:postgresql://prod-server:5432/myapp"
$env:DB_USERNAME="app_user"
# ...

mvn spring-boot:run

الملخص​

هيكل الملفات​

application.properties          ← تكوين مشترك
application-dev.properties ← خاص بـ dev
application-test.properties ← خاص بـ test
application-prod.properties ← خاص بـ prod

ترتيب الأولوية:

1. application-{profile}.properties (خاص)
2. application.properties (مشترك)
3. متغيرات البيئة
4. معلمات سطر الأوامر

Beans حسب ملف التعريف​

// Bean لملف شخصي واحد
@Profile("dev")

// Bean لعدة ملفات شخصية
@Profile({"dev", "test"})

// Bean لكل الملفات الشخصية ما عدا واحد
@Profile("!prod")

// الفئة بأكملها
@Configuration
@Profile("dev")
public class DevConfig { }

تفعيل ملف شخصي​

1. application.properties:

spring.profiles.active=dev

2. سطر الأوامر:

--spring.profiles.active=prod

3. متغير البيئة:

export SPRING_PROFILES_ACTIVE=prod

4. IDE (VS Code):

"spring.profiles.active": "dev"

التقييم الذاتي​

  1. ما الفرق بين application.properties و application-dev.properties؟
  2. كيف تنشئ Bean نشط فقط في dev؟
  3. كيف تفعل ملفات شخصية متعددة في نفس الوقت؟
  4. لماذا تستخدم متغيرات البيئة في الإنتاج؟
  5. ما هي الصيغة لمتغير بقيمة افتراضية؟

تحدي إضافي 🎯​

أنشئ تكوينًا كاملاً لتطبيق حقيقي:

  1. 3 ملفات شخصية: dev، test، prod

  2. Dev:

    • H2 في الذاكرة
    • بريد إلكتروني وهمي
    • سجلات DEBUG
    • لا أمان
  3. Test:

    • H2 مع سكربتات
    • بريد إلكتروني وهمي
    • سجلات INFO
  4. Prod:

    • PostgreSQL
    • بريد إلكتروني SMTP حقيقي
    • سجلات WARN
    • الأمان مفعل
    • متغيرات البيئة

إضافة: أضف ملف شخصي docker لـ Docker Compose


تهانينا! 🎉​

لقد أكملت الوحدة 5!

أنت الآن تتقن:

  • ✅ عكس التحكم (IoC)
  • ✅ حقن التبعيات (DI)
  • ✅ Beans ودورة حياتها
  • ✅ النطاقات (Singleton، Prototype)
  • ✅ التكوين حسب ملفات التعريف
  • ✅ البيئات المتعددة (dev/test/prod)

أنت جاهز للمفاهيم المتقدمة!


⬅️ التمرين السابق: Beans والنطاقات

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

➡️ الوحدة التالية: Spring Data JPA متقدم