🇰🇭 ជាភាសាខ្មែរ · Spring Framework

ការបញ្ចូលភ្ជាប់
ភារកិច្ចពឹងផ្អែក Dependency Injection

ការពន្យល់លម្អិតអំពី Dependency Injection — គំនិត ប្រភេទ និងឧទាហរណ៍ជាក់ស្ដែងនៅក្នុង Spring Framework

3ប្រភេទ DI
5ចំណុចសំខាន់
10+ឧទាហរណ៍ Code
01

DI គឺជាអ្វី?What is Dependency Injection?

Dependency Injection (DI) គឺជា Design Pattern មួយ ដែលអនុញ្ញាតឱ្យ Object មួយទទួល (inject) នូវ dependencies របស់វា ពីខាងក្រៅ ជំនួសឱ្យការបង្កើតវាដោយខ្លួនឯង។

ភាពសាមញ្ញ: ជំនួសឱ្យការ new() Object ដោយខ្លួនឯង — ឱ្យអ្នកផ្សេង (Spring Container) ធ្វើជំនួស!

// flow នៃ DI
📦 Spring Container IoC Container
🔧 បង្កើត Object Create Beans
💉 Inject Dependencies
App ដំណើរការ Ready to use
💡 IoC (Inversion of Control) DI គឺជារបៀបអនុវត្ត IoC — ការផ្ទេរការគ្រប់គ្រង Object Creation ពីកូដ App របស់អ្នក ទៅ Framework (Spring)។ "Control" ត្រូវបាន "Invert" ហេតុនេះហៅថា Inversion of Control។
02

ហេតុអ្វីត្រូវប្រើ DI?Why use Dependency Injection?

🔓

Loose Couplingភ្ជាប់ដោយរលុង

Class មិន depend លើ Implementation ជាក់លាក់ទេ — ចង់ប្ដូរ Service, ប្ដូរបាន មិនប៉ះ Code ដទៃ

🧪

Testabilityងាយធ្វើ Test

អាច inject Mock Objects ចូលបាន ងាយស្រួលធ្វើ Unit Test ដោយមិនចាំបាច់ Database ពិតប្រាកដ

♻️

Reusabilityប្រើម្ដងទៀតបាន

Components អាចប្រើម្ដងទៀតបាន ក្នុង Contexts ផ្សេងគ្នា ដោយ inject dependencies ខុសៗ

🏗️

Maintainabilityថែទាំបានងាយ

Code មានការរៀបចំល្អ ងាយយល់ ងាយ debug ហើយប្ដូរ feature ថ្មីក៏ងាយ

ឧទាហរណ៍ — មុនប្រើ DI (បញ្ហា)

❌ Without DI
// UserService ត្រូវការ EmailService
public class UserService {

  // ❌ បង្កើតដោយខ្លួនឯង — tightly coupled!
  private EmailService emailService = new EmailService();

  public void registerUser(String email) {
    // save user...
    emailService.sendWelcome(email); // ❌ hard dependency
  }
}
🚫 បញ្ហា ប្រសិនបើ EmailService ត្រូវការប្ដូរ (e.g., ប្រើ SmsService ជំនួស) — ត្រូវ edit UserService ផ្ទាល់។ ហើយ unit test ក៏ debug ពិបាក ព្រោះ EmailService ពិតប្រាកដចាប់ផ្ដើម!

ឧទាហរណ៍ — ប្រើ DI (ដំណោះស្រាយ)

✅ With DI
// Interface — abstract ការ communicate
public interface NotificationService {
  void sendWelcome(String email);
}

@Service
public class UserService {

  private final NotificationService notificationService;

  // ✅ Constructor Injection — Spring inject ឱ្យ
  public UserService(NotificationService notificationService) {
    this.notificationService = notificationService;
  }

  public void registerUser(String email) {
    // save user...
    notificationService.sendWelcome(email); // ✅ ប្រើ interface
  }
}
✅ អត្ថប្រយោជន៍ ឥឡូវ UserService មិនដឹងថាប្រើ EmailService ឬ SmsService ទេ! Spring inject ណាក៏បានដែរ។ ក្នុង test ក៏ inject Mock ចូលបានដោយងាយ។
03

ប្រភេទ DI ទាំង ៣3 Types of Dependency Injection

✅ Constructor Injection — ល្អបំផុត (Recommended) inject dependencies តាម Constructor Parameter។ Dependencies ត្រូវបាន set នៅពេល Object ត្រូវបាន បង្កើត — ធានា immutability!
Java — Constructor DI
@Service
public class OrderService {

  private final ProductRepository productRepo;
  private final PaymentService   paymentService;

  // Spring 4.3+ — @Autowired optional ប្រសិនបើ constructor តែ ១
  @Autowired
  public OrderService(
      ProductRepository productRepo,
      PaymentService paymentService) {
    this.productRepo    = productRepo;
    this.paymentService = paymentService;
  }

  public void placeOrder(Long productId) {
    Product p = productRepo.findById(productId);
    paymentService.charge(p.getPrice());
  }
}
Immutable — dependencies ប្ដូរមិនបាន
ងាយធ្វើ Unit Test
NullPointerException ងាយចៀសវាង
⚠️ Setter Injection — ប្រើក្នុងករណីជាក់លាក់ inject dependencies តាម Setter Methods។ ល្អសម្រាប់ Optional dependencies ដែលអាចប្ដូរបន្ទាប់ពី Object ត្រូវបានបង្កើត។
Java — Setter DI
@Service
public class ReportService {

  private EmailService emailService;
  private PdfGenerator pdfGenerator;

  @Autowired
  public void setEmailService(EmailService emailService) {
    this.emailService = emailService;
  }

  @Autowired(required = false) // optional dependency
  public void setPdfGenerator(PdfGenerator pdfGenerator) {
    this.pdfGenerator = pdfGenerator;
  }

  public void generateReport() {
    if (pdfGenerator != null) {
      pdfGenerator.create();
    }
    emailService.send("Report ready!");
  }
}
ល្អសម្រាប់ Optional dependencies
អាចប្ដូរ dependency ក្រោយ
Object ប្រហែល incomplete
🚫 Field Injection — ចៀសវាងប្រើ (Not Recommended) inject ដោយប្រើ @Autowired ត្រង់ Field ផ្ទាល់។ ងាយ code ប៉ុន្តែ IntelliJ ព្រមាន ហើយ unit test ពិបាកណាស់!
Java — Field DI (❌ avoid)
@Service
public class UserService {

  @Autowired  // ❌ field injection — avoid this!
  private UserRepository userRepository;

  @Autowired  // ❌ hidden dependencies
  private EmailService emailService;

  public User findUser(Long id) {
    return userRepository.findById(id);
  }
}
Unit Test ពិបាក (ត្រូវ Reflection)
final field ប្រើមិនបាន
Dependencies hidden ពេក
04

Spring DI AnnotationsKey Annotations in Spring

Annotationប្រើសម្រាប់ពន្យល់
@Autowired Inject dependency ប្រាប់ Spring ឱ្យ inject bean ដែលត្រូវគ្នា
@Component Register bean Mark class ជា Spring bean (generic)
@Service Business layer Specialized @Component — business logic
@Repository Data layer Specialized @Component — database access
@Controller Web layer Specialized @Component — MVC web
@Qualifier ជ្រើស bean ប្រើជាមួយ @Autowired ពេល bean ដូចគ្នាច្រើន
@Primary Default bean ប្រគល់ priority ដល់ bean ពេល conflict
@Lazy Lazy init Bean ត្រូវបានបង្កើតតែពេល ដំបូងប្រើ

@Qualifier — ជ្រើស Bean ត្រូវការ

Java — @Qualifier Example
// ២ implementation ផ្សេងគ្នា
@Service("emailNotification")
public class EmailNotificationService implements NotificationService {
  public void send(String msg) { /* send email */ }
}

@Service("smsNotification")
public class SmsNotificationService implements NotificationService {
  public void send(String msg) { /* send SMS */ }
}

// ជ្រើស bean ជាក់លាក់
@Service
public class AlertService {

  private final NotificationService notificationService;

  public AlertService(
      @Qualifier("smsNotification") NotificationService svc) {
    this.notificationService = svc;  // inject SMS service
  }
}
05

ឧទាហរណ៍ជាក់ស្ដែងReal-World Examples

ឧទាហរណ៍ ១ — User Registration System

Java — Full Example
// 1. Interface
public interface UserRepository {
  void save(User user);
  Optional<User> findByEmail(String email);
}

// 2. Implementation
@Repository
public class UserRepositoryImpl implements UserRepository {
  @Autowired
  private JpaRepository<User, Long> jpaRepo;

  public void save(User user) { jpaRepo.save(user); }
  public Optional<User> findByEmail(String e) {
    return jpaRepo.findByEmail(e);
  }
}

// 3. Service Layer — Constructor Injection
@Service
public class UserService {

  private final UserRepository     userRepository;
  private final NotificationService notificationService;
  private final PasswordEncoder     passwordEncoder;

  public UserService(
      UserRepository     userRepository,
      NotificationService notificationService,
      PasswordEncoder     passwordEncoder) {
    this.userRepository     = userRepository;
    this.notificationService = notificationService;
    this.passwordEncoder     = passwordEncoder;
  }

  public User register(String email, String password) {
    User user = new User(email, passwordEncoder.encode(password));
    userRepository.save(user);
    notificationService.sendWelcome(email);
    return user;
  }
}

// 4. Controller
@RestController
@RequestMapping("/api/users")
public class UserController {

  private final UserService userService;

  public UserController(UserService userService) {
    this.userService = userService;  // Spring inject ឱ្យ
  }

  @PostMapping("/register")
  public ResponseEntity<User> register(@RequestBody RegisterDto dto) {
    User user = userService.register(dto.getEmail(), dto.getPassword());
    return ResponseEntity.ok(user);
  }
}

ឧទាហរណ៍ ២ — Unit Test ជាមួយ MockBean

Java — Unit Test
@ExtendWith(MockitoExtension.class)
class UserServiceTest {

  @Mock  // inject Mock ជំនួស real implementation
  private UserRepository userRepository;

  @Mock
  private NotificationService notificationService;

  @Mock
  private PasswordEncoder passwordEncoder;

  @InjectMocks  // inject Mocks ចូល UserService
  private UserService userService;

  @Test
  void shouldRegisterUser() {
    // Arrange
    Mockito.when(passwordEncoder.encode("pass123"))
           .thenReturn("hashed_pass");

    // Act
    User result = userService.register("[email protected]", "pass123");

    // Assert
    Mockito.verify(userRepository).save(Mockito.any(User.class));
    Mockito.verify(notificationService).sendWelcome("[email protected]");
  }
}
🧪 ការ Test ងាយ ព្រោះ DI ព្រោះ UserService ទទួល dependencies តាម Constructor — ក្នុង test យើង inject Mock Objects ចូលបាន ដោយមិនចាំបាច់ Database ពិតប្រាកដ ឬ Email Server ទាល់តែ!
06

ប្រៀបធៀប ៣ ប្រភេទ DIComparing 3 DI Types

លក្ខណៈ Constructor ✅ Setter ⚠️ Field ❌
Immutability ✅ final ប្រើបាន ❌ ប្ដូរបាន ❌ ប្ដូរបាន
Unit Test ✅ ងាយណាស់ ⚠️ ល្មមៗ ❌ ពិបាក (Reflection)
Circular Dep. ⚠️ Error ច្បាស់ ✅ Resolve បាន ⚠️ Hidden
Optional Dep. ⚠️ ពិបាកបន្ថែម ✅ ល្អ ✅ ងាយ
Spring Recommendation ✅ ណែនាំ ⚠️ ករណីជាក់លាក់ ❌ ចៀសវាង
07

វិធីប្រើ DI ល្អបំផុតBest Practices

❌ ចៀសវាង

  • Field injection (@Autowired លើ field)
  • Circular dependencies
  • Inject concrete class ជំនួស interface
  • Service ច្រើន dependencies (>5)
  • New Object ដោយ manual ក្នុង service

✅ ថ្លៃ

  • Constructor injection ជានិច្ច
  • Inject Interface មិនមែន Implementation
  • final keyword ជានិច្ចសម្រាប់ fields
  • @Qualifier ពេល beans ច្រើន
  • Lombok @RequiredArgsConstructor

Lombok — ជំនួស Constructor ដោយ Annotation

Java — Lombok Shortcut
import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor  // Lombok generate constructor ជូន!
public class ProductService {

  // Lombok inject final fields automatically
  private final ProductRepository productRepository;
  private final CategoryService    categoryService;
  private final CacheService       cacheService;

  // No need to write constructor manually!
  public Product findProduct(Long id) {
    return cacheService.getOrLoad(id,
      () -> productRepository.findById(id));
  }
}
🚀 Tip: ប្រើ Lombok @RequiredArgsConstructor នៅក្នុង Spring Boot Project ជាមួយ Lombok — ប្រើ @RequiredArgsConstructor ដើម្បីជៀសវាង boilerplate constructor code ខណៈពេលនៅតែប្រើ Constructor Injection!
📖 សង្ខេប — Key Takeaways DI = ឱ្យ Spring គ្រប់គ្រង Object Creation ជំនួសអ្នក · ប្រើ Constructor Injection ជានិច្ច · Inject Interfaces មិនមែន Classes · ធ្វើ Unit Test ងាយ ព្រោះ inject Mock ចូលបាន · @Autowired · @Service · @Repository · @Qualifier ជា tools ចំបង