- Sản phẩm & Dịch vụSản phẩm & Dịch vụ
- Giải phápGiải pháp
- Bảng giáBảng giá
- Công tyCông ty
- Tài liệuTài liệu
vi
vi
Chuyên mục với các bài viết hướng dẫn, nghiên cứu và phân tích chi tiết về kỹ thuật, các xu hướng công nghệ mới nhất dành cho lập trình viên.
Web Developer
I’m passionate about web development and sharing my insights through articles, with over 8 years of experience. I hope these sharings inspire you and help build a strong web development community. @#@ Tôi đam mê phát triển web và chia sẻ những hiểu biết của mình thông qua các bài viết, với hơn 8 năm kinh nghiệm. Tôi hy vọng những chia sẻ này sẽ truyền cảm hứng cho các bạn và giúp xây dựng một cộng đồng phát triển web mạnh mẽ.
Thảo luận (0)
Đăng nhập để thảo luận
Trong quá trình phát triển phần mềm bằng Java, việc xử lý các phụ thuộc giữa các thành phần là điều không thể tránh khỏi. Và đôi khi, nó khiến mã nguồn trở nên rối rắm, khó kiểm soát. Đó là lúc Dependency Injection trong Java phát huy tác dụng.

Đây không chỉ là một kỹ thuật thiết kế phần mềm, mà còn là một tư duy giúp bạn tách biệt các mối quan hệ phức tạp trong hệ thống, từ đó nâng cao tính linh hoạt, dễ kiểm thử và dễ bảo trì. Bằng cách chuyển việc khởi tạo đối tượng từ thời điểm biên dịch sang thời điểm chạy, DI giúp bạn viết mã “sạch” hơn, chuyên nghiệp hơn, và quan trọng nhất là dễ mở rộng theo thời gian.
Dependency Injection có thể khó hiểu nếu chỉ tiếp cận qua lý thuyết, vì vậy hãy bắt đầu bằng một ví dụ đơn giản và sau đó áp dụng mô hình thiết kế này để đạt được sự tách biệt rõ ràng giữa các thành phần, cũng như khả năng mở rộng ứng dụng. Giả sử chúng ta sử dụng EmailService để gửi email. Thông thường, cách triển khai sẽ như sau:
package com.journaldev.java.legacy;public class EmailService {
public void sendEmail(String message, String receiver){ //logic to send email System.out.println("Email sent to "+receiver+ " with Message="+message); }}
Lớp EmailService chứa logic riêng gửi email tới người nhận. Mã nguồn của ứng dụng như sau:
package com.journaldev.java.legacy;public class MyApplication {
private EmailService email = new EmailService(); public void processMessages(String msg, String rec){ //do some msg validation, manipulation logic etc this.email.sendEmail(msg, rec); }}
Lớp MyApplication sẽ sử dụng EmailService để gửi email:
package com.journaldev.java.legacy;public class MyLegacyTest {
public static void main(String[] args) { MyApplication app = new MyApplication(); app.processMessages("Hi Pankaj", "pankaj@abc.com"); }}
Thoạt nhìn, cách triển khai trên có vẻ không có vấn đề gì, nhưng thực tế có một số hạn chế nhất định:
MyApplication chịu trách nhiệm khởi tạo EmailService và sử dụng nó. Điều này làm cho mã nguồn khó thay đổi và dẫn đến sự phụ thuộc mã hóa cứng. Nếu chúng ta muốn chuyển sang một số dịch vụ email nâng cao khác trong tương lai, sẽ cần phải thay đổi mã trong lớp MyApplication. Kết quả khiến ứng dụng của chúng ta khó mở rộng và nếu dịch vụ email được sử dụng trong nhiều lớp thì điều đó sẽ còn khó hơn nữa.MyApplication đang trực tiếp khởi tạo EmailService, không có cách nào chúng ta có thể mô phỏng các đối tượng này trong các lớp kiểm thử của mình.Có quan điểm rằng chúng ta có thể loại bỏ việc tạo phiên bản dịch vụ email khỏi lớp MyApplication bằng cách có một hàm tạo yêu cầu dịch vụ email làm đối số.
package com.journaldev.java.legacy;public class MyApplication {
private EmailService email = null; public MyApplication(EmailService svc){ this.email=svc; } public void processMessages(String msg, String rec){ //do some msg validation, manipulation logic etc this.email.sendEmail(msg, rec); }}
Tuy nhiên, đây không phải là quyết định thiết kế tốt vì trong trường hợp này ta đang yêu cầu các ứng dụng khách hoặc các lớp kiểm tra khởi tạo dịch vụ email. Giờ hãy xem cách có thể áp dụng mô hình DI để giải quyết tất cả các vấn đề trên. Dependency Injection trong Java yêu cầu ít nhất ba thành phần:
Trong trường hợp này, chúng ta có thể sử dụng MessageService để khai báo triển khai dịch vụ.
package com.journaldev.java.dependencyinjection.service;public interface MessageService {
void sendMessage(String msg, String rec);}
Giả sử có dịch vụ Email và SMS triển khai các giao diện trên.
package com.journaldev.java.dependencyinjection.service;public class EmailServiceImpl implements MessageService {
@Override public void sendMessage(String msg, String rec) { //logic to send email System.out.println("Email sent to "+rec+ " with Message="+msg); }}
package com.journaldev.java.dependencyinjection.service;
public class SMSServiceImpl implements MessageService {
@Override
public void sendMessage(String msg, String rec) {
//logic to send SMS
System.out.println("SMS sent to "+rec+ " with Message="+msg);
}
}
Mô hình Dependency Injection hiện giờ đã sẵn sàng và có thể bắt đầu viết lớp người dùng.
Không bắt buộc phải có giao diện cơ sở cho các lớp người dùng nhưng cần có một giao diện Consumer đã khai báo hành vi cho lớp người dùng.
package com.journaldev.java.dependencyinjection.consumer;public interface Consumer {
void processMessages(String msg, String rec);}
Lớp người dùng được triển khai như bên dưới.
package com.journaldev.java.dependencyinjection.consumer;import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplication implements Consumer{
private MessageService service; public MyDIApplication(MessageService svc){ this.service=svc; } @Override public void processMessages(String msg, String rec){ //do some msg validation, manipulation logic etc this.service.sendMessage(msg, rec); }}
Lưu ý rằng lớp ứng dụng của chúng ta chỉ sử dụng dịch vụ. Nó không khởi tạo dịch vụ dẫn đến “phân tách các mối quan tâm” tốt hơn. Ngoài ra, việc sử dụng giao diện dịch vụ cho phép chúng ta dễ dàng kiểm tra ứng dụng bằng cách mô phỏng MessageService và liên kết các dịch vụ tại thời điểm chạy thay vì thời gian biên dịch. Bây giờ đã sẵn sàng để viết các lớp injector trong mô hình Dependency Injection, từ đó khởi tạo dịch vụ và cả các lớp người dùng.
Hãy nhận diện MessageServiceInjector với phương thức khai báo trả kết quả về lớp Consumer.
package com.journaldev.java.dependencyinjection.injector;import com.journaldev.java.dependencyinjection.consumer.Consumer;
public interface MessageServiceInjector {
public Consumer getConsumer();}
Tại đây với mỗi dịch vụ, chúng ta sẽ phải tạo các lớp injector như bên dưới.
package com.journaldev.java.dependencyinjection.injector;import com.journaldev.java.dependencyinjection.consumer.Consumer; import com.journaldev.java.dependencyinjection.consumer.MyDIApplication; import com.journaldev.java.dependencyinjection.service.EmailServiceImpl;
public class EmailServiceInjector implements MessageServiceInjector {
@Override public Consumer getConsumer() { return new MyDIApplication(new EmailServiceImpl()); }}
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.SMSServiceImpl;
public class SMSServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
return new MyDIApplication(new SMSServiceImpl());
}
}
Bây giờ chúng ta hãy quan sát các ứng dụng khách sẽ sử dụng như thế nào với một chương trình đơn giản.
package com.journaldev.java.dependencyinjection.test;import com.journaldev.java.dependencyinjection.consumer.Consumer; import com.journaldev.java.dependencyinjection.injector.EmailServiceInjector; import com.journaldev.java.dependencyinjection.injector.MessageServiceInjector; import com.journaldev.java.dependencyinjection.injector.SMSServiceInjector;
public class MyMessageDITest {
public static void main(String[] args) { String msg = "Hi Pankaj"; String email = "pankaj@abc.com"; String phone = "4088888888"; MessageServiceInjector injector = null; Consumer app = null; //Send email injector = new EmailServiceInjector(); app = injector.getConsumer(); app.processMessages(msg, email); //Send SMS injector = new SMSServiceInjector(); app = injector.getConsumer(); app.processMessages(msg, phone); }}
Như bạn có thể thấy, các lớp ứng dụng chỉ chịu trách nhiệm sử dụng dịch vụ. Các lớp dịch vụ được tạo ra trong injector. Ngoài ra, nếu chúng ta phải mở rộng thêm ứng dụng của mình để cho phép nhắn tin trên Facebook, chúng ta sẽ chỉ phải viết các lớp dịch vụ và lớp injector.
Vì vậy, việc triển khai dependency injection đã giải quyết được vấn đề với các lớp phụ thuộc (dependency) được mã hóa cứng và giúp cho ứng dụng linh hoạt và dễ mở rộng hơn. Giờ thì chúng ta có thể dễ dàng kiểm tra lớp ứng dụng của mình như thế nào bằng cách mô phỏng các lớp injector và các lớp dịch vụ.
package com.journaldev.java.dependencyinjection.test;import org.junit.After; import org.junit.Before; import org.junit.Test;
import com.journaldev.java.dependencyinjection.consumer.Consumer; import com.journaldev.java.dependencyinjection.consumer.MyDIApplication; import com.journaldev.java.dependencyinjection.injector.MessageServiceInjector; import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplicationJUnitTest {
private MessageServiceInjector injector; @Before public void setUp(){ //mock the injector with anonymous class injector = new MessageServiceInjector() { @Override public Consumer getConsumer() { //mock the message service return new MyDIApplication(new MessageService() { @Override public void sendMessage(String msg, String rec) { System.out.println("Mock Message Service implementation"); } }); } }; } @Test public void test() { Consumer consumer = injector.getConsumer(); consumer.processMessages("Hi Pankaj", "pankaj@abc.com"); } @After public void tear(){ injector = null; }}
Tôi đang sử dụng các lớp ẩn danh để mô phỏng các lớp injector và lớp service, từ đó kiểm tra thiết bị của mình một cách dễ dàng. Tôi sử dụng JUnit 4 cho lớp kiểm tra ở trên, vì vậy hãy đảm bảo rằng nó nằm trong đường dẫn dự án của bạn.
Chúng tôi sử dụng các hàm tạo để inject các dependency trong các lớp ứng dụng, một cách khác là sử dụng phương thức setter để “tiêm” các lớp phụ thuộc (dependency) trong các lớp ứng dụng. Đối với phương thức setter, lớp ứng dụng của chúng tôi sẽ được triển khai như bên dưới.
package com.journaldev.java.dependencyinjection.consumer;import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplication implements Consumer{
private MessageService service; public MyDIApplication(){} //setter dependency injection public void setService(MessageService service) { this.service = service; } @Override public void processMessages(String msg, String rec){ //do some msg validation, manipulation logic etc this.service.sendMessage(msg, rec); }}
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
import com.journaldev.java.dependencyinjection.consumer.MyDIApplication;
import com.journaldev.java.dependencyinjection.service.EmailServiceImpl;
public class EmailServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
MyDIApplication app = new MyDIApplication();
app.setService(new EmailServiceImpl());
return app;
}
}
Một trong những ví dụ tốt nhất về phương thức Setter Injection là giao diện Struts2 Servlet API Aware. Việc sử dụng các hàm tạo (Constructor Injection) hay dựa trên setter là quyết định thiết kế phụ thuộc vào yêu cầu của bạn.
Ví dụ, nếu ứng dụng của tôi không thể hoạt động nếu không có lớp dịch vụ thì tôi sẽ thích Constructor Injection hoặc ngược lại, tôi sẽ sử dụng Setter Injection để chỉ sử dụng khi thực sự cần thiết.
Dependency Injection trong Java là cách để đạt được Inversion of control (IoC) trong ứng dụng của chúng ta bằng cách di chuyển các đối tượng liên kết từ thời gian biên dịch sang thời gian chạy. Chúng ta cũng có thể đạt được IoC thông qua các kỹ thuật như Factory Pattern, Template Method Design Pattern, Strategy Pattern và Service Locator pattern.
Một số thư viện và Framework như Spring Dependency Injection, Google Guice và Java EE CDI có thể hỗ trợ triển khai DI thông qua việc sử dụng Java Reflection API và chú thích java. Tất cả những gì chúng ta cần là chú thích trường, phương thức constructor hoặc setter và xác định cấu hình chúng trong các tệp xml hoặc lớp cấu hình.
Bài viết trên là tổng hợp tất cả về kỹ thuật phần mềm Dependency Injection trong Java. Đây là một kỹ thuật quan trọng trong lập trình hướng đối tượng, đóng vai trò tối ưu hóa việc quản lý và kiểm soát các phụ thuộc (dependencies) trong hệ thống. Bằng cách tách biệt các thành phần và cung cấp chúng một cách linh hoạt, DI giúp mã nguồn trở nên dễ bảo trì, mở rộng và kiểm thử hơn.
Tuy nhiên, việc áp dụng DI không chỉ đơn thuần là tuân theo một mô hình có sẵn mà đòi hỏi sự cân nhắc cẩn thận để đảm bảo rằng nó thực sự phù hợp với yêu cầu của dự án. Vì vậy, trước khi quyết định sử dụng DI, bạn cần đánh giá kỹ lưỡng những lợi ích và hạn chế để có thể tận dụng tối đa sức mạnh của kỹ thuật này trong phát triển phần mềm.