ការពន្យល់លម្អិតអំពី Dependency Injection — គំនិត ប្រភេទ និងឧទាហរណ៍ជាក់ស្ដែងនៅក្នុង Spring Framework
Dependency Injection (DI) គឺជា Design Pattern មួយ ដែលអនុញ្ញាតឱ្យ Object មួយទទួល (inject) នូវ dependencies របស់វា ពីខាងក្រៅ ជំនួសឱ្យការបង្កើតវាដោយខ្លួនឯង។
ភាពសាមញ្ញ: ជំនួសឱ្យការ new() Object ដោយខ្លួនឯង — ឱ្យអ្នកផ្សេង (Spring Container) ធ្វើជំនួស!
Class មិន depend លើ Implementation ជាក់លាក់ទេ — ចង់ប្ដូរ Service, ប្ដូរបាន មិនប៉ះ Code ដទៃ
អាច inject Mock Objects ចូលបាន ងាយស្រួលធ្វើ Unit Test ដោយមិនចាំបាច់ Database ពិតប្រាកដ
Components អាចប្រើម្ដងទៀតបាន ក្នុង Contexts ផ្សេងគ្នា ដោយ inject dependencies ខុសៗ
Code មានការរៀបចំល្អ ងាយយល់ ងាយ debug ហើយប្ដូរ feature ថ្មីក៏ងាយ
// UserService ត្រូវការ EmailService public class UserService { // ❌ បង្កើតដោយខ្លួនឯង — tightly coupled! private EmailService emailService = new EmailService(); public void registerUser(String email) { // save user... emailService.sendWelcome(email); // ❌ hard dependency } }
// 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 } }
@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()); } }
@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!"); } }
@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); } }
| 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 ត្រូវបានបង្កើតតែពេល ដំបូងប្រើ |
// ២ 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 } }
// 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); } }
@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]"); } }
| លក្ខណៈ | Constructor ✅ | Setter ⚠️ | Field ❌ |
|---|---|---|---|
| Immutability | ✅ final ប្រើបាន | ❌ ប្ដូរបាន | ❌ ប្ដូរបាន |
| Unit Test | ✅ ងាយណាស់ | ⚠️ ល្មមៗ | ❌ ពិបាក (Reflection) |
| Circular Dep. | ⚠️ Error ច្បាស់ | ✅ Resolve បាន | ⚠️ Hidden |
| Optional Dep. | ⚠️ ពិបាកបន្ថែម | ✅ ល្អ | ✅ ងាយ |
| Spring Recommendation | ✅ ណែនាំ | ⚠️ ករណីជាក់លាក់ | ❌ ចៀសវាង |
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)); } }