
Java Dependency injection có vẻ khó nắm bắt về lý thuyết, vì vậy tôi sẽ lấy một ví dụ đơn giản và sau đó chúng ta sẽ xem cách sử dụng mẫu phụ thuộc để đạt được sự liên kết lỏng lẻo và khả năng mở rộng trong ứng dụng.
Giả sử chúng ta có một ứng dụng EmailService để gửi email. Thông thường, chúng tôi sẽ thực hiện điều này như dưới đây.
package com.codetimes90.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);
}
}
package com.codetimes90.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);
}
}
package com.codetimes90.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);
}
}
Thoạt nhìn, có vẻ như không có gì sai với việc thực hiện ở trên. Nhưng mã logic trên có những hạn chế nhất định.
Ø Lớp MyApplication có trách nhiệm khởi tạo dịch vụ email và sau đó sử dụng nó. Điều này dẫn đến sự phụ thuộc được mã hóa cứng. Nếu chúng tôi muốn chuyển sang một số dịch vụ email nâng cao khác trong tương lai, nó sẽ yêu cầu thay đổi mã trong lớp MyApplication. Điều này làm cho ứng dụng của chúng tôi khó mở rộng và nếu dịch vụ email được sử dụng trong nhiều lớp thì điều đó còn khó hơn.
Ø Nếu chúng tôi muốn mở rộng ứng dụng của mình để cung cấp một tính năng nhắn tin bổ sung, chẳng hạn như SMS hoặc tin nhắn Facebook thì chúng tôi sẽ cần phải viết một ứng dụng khác cho điều đó. Điều này sẽ liên quan đến các thay đổi mã trong các lớp ứng dụng và cả trong các lớp máy khách.
Ø Việc kiểm tra ứng dụng sẽ rất khó khăn vì ứng dụng của chúng tôi đang trực tiếp tạo phiên bản dịch vụ email. 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 thử nghiệm của mình.
Người ta có thể tranh luận rằng chúng ta có thể loại bỏ việc tạo cá thể 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ố.
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);
}
}
Nhưng trong trường hợp này, chúng tôi đang yêu cầu các ứng dụng khách hoặc các lớp thử nghiệm khởi tạo dịch vụ email không phải là một quyết định thiết kế tốt.
Bây giờ chúng ta hãy xem làm thế nào chúng ta có thể áp dụng mô hình java dependency injection để giải quyết tất cả các vấn đề với việc triển khai ở trên. Dependency Injection trong java yêu cầu ít nhất những điều sau:
Ø Các thành phần dịch vụ nên được thiết kế với lớp cơ sở hoặc interface. Tốt hơn là nên thích các interface hoặc các abstract class sẽ xác định hợp đồng cho các dịch vụ.
Ø Các lớp Consumer nên được viết dưới dạng service interface.
Ø Các lớp Injector sẽ khởi tạo các dịch vụ và sau đó là các lớp Consumer.
Đối với trường hợp của chúng tôi, chúng tôi có thể có MessageService điều đó sẽ tuyên bố hợp đồng triển khai dịch vụ.
package com.codetimes90.java.dependencyinjection.service;
public interface MessageService {
void sendMessage(String msg, String rec);
}
package com.codetimes90.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.codetimes90.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);
}
}
Chúng tôi không bắt buộc phải có interface cơ sở cho các lớp Consumer nhưng tôi sẽ có một Consumer interface khai báo hợp đồng cho các lớp Consumer.
package com.codetimes90.java.dependencyinjection.consumer;
public interface Consumer {
void processMessages(String msg, String rec);
}
Việc triển khai lớp Consumer của tôi giống như bên dưới.
package com.codetimes90.java.dependencyinjection.consumer;
import com.codetimes90.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);
}
}
Bây giờ chúng ta đã sẵn sàng để viết java dependency injector classes sẽ khởi tạo dịch vụ và cả các lớp Consumer.
Java Dependency Injection - Injectors Classes
Hãy có một interface MessageServiceInjector với khai báo phương thức trả về lớp Consumer.
package com.codetimes90.java.dependencyinjection.injector;
import com.codetimes90.java.dependencyinjection.consumer.Consumer;
public interface MessageServiceInjector {
public Consumer getConsumer();
}
package com.codetimes90.java.dependencyinjection.injector;
import com.codetimes90.java.dependencyinjection.consumer.Consumer;
import com.codetimes90.java.dependencyinjection.consumer.MyDIApplication;
import com.codetimes90.java.dependencyinjection.service.EmailServiceImpl;
public class EmailServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
return new MyDIApplication(new EmailServiceImpl());
}
}
package com.codetimes90.java.dependencyinjection.injector;
import com.codetimes90.java.dependencyinjection.consumer.Consumer;
import com.codetimes90.java.dependencyinjection.consumer.MyDIApplication;
import com.codetimes90.java.dependencyinjection.service.SMSServiceImpl;
public class SMSServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
return new MyDIApplication(new SMSServiceImpl());
}
}
package com.codetimes90.java.dependencyinjection.test;
import com.codetimes90.java.dependencyinjection.consumer.Consumer;
import
com.codetimes90.java.dependencyinjection.injector.EmailServiceInjector;
import
com.codetimes90.java.dependencyinjection.injector.MessageServiceInjector;
import com.codetimes90.java.dependencyinjection.injector.SMSServiceInjector;
public class MyMessageDITest {
public static void main(String[] args) {
String msg = "Hi Lucifer";
String email = "abc@gmail.com";
String phone = "0123456789";
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 rằng các lớp ứng dụng của chúng tôi chỉ chịu trách nhiệm về việc sử dụng dịch vụ. Các lớp dịch vụ được tạo trong bộ phun. 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 Service và lớp bộ tiêm.
Vì vậy, việc triển khai phụ thuộc đã giải quyết được vấn đề với phụ thuộc được mã hóa cứng và giúp chúng tôi làm cho ứng dụng của mình trở nên linh hoạt và dễ dàng mở rộng. Bây giờ, hãy xem chúng ta có thể kiểm tra lớp ứng dụng của mình dễ dàng như thế nào bằng cách giả lập các lớp dịch vụ và bộ phun.
Java Dependency Injection - JUnit Test Case với Mock Injector và Service
package com.codetimes90.java.dependencyinjection.test;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import com.codetimes90.java.dependencyinjection.consumer.Consumer;
import com.codetimes90.java.dependencyinjection.consumer.MyDIApplication;
import
com.codetimes90.java.dependencyinjection.injector.MessageServiceInjector;
import com.codetimes90.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("Message Service");
}
});
}
};
}
@Test
public void test() {
Consumer consumer = injector.getConsumer();
consumer.processMessages("Hi Lucy", "abc@gmail.com");
}
@After
public void tear(){
injector = null;
}
}
Như bạn có thể thấy rằng tôi đang sử dụng các lớp ẩn danh để giả lập các lớp dịch vụ và bộ tiêm và tôi có thể dễ dàng kiểm tra các phương thức ứng dụng của mình. Tôi đang sử dụng JUnit 4 cho lớp thử nghiệm ở trên, vì vậy hãy đảm bảo rằng nó nằm trong đường dẫn xây dựng dự án của bạn nếu bạn đang chạy lớp thử nghiệm phía trên.
Chúng tôi đã sử dụng các hàm tạo để đưa các phụ thuộc vào các lớp ứng dụng, một cách khác là sử dụng phương thức setter để đưa các phụ thuộc vào các lớp ứng dụng. Đối với việc tiêm phụ thuộc phương thức setter, lớp ứng dụng của chúng ta sẽ được triển khai như bên dưới.
package com.codetimes90.java.dependencyinjection.consumer;
import com.codetimes90.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.codetimes90.java.dependencyinjection.injector;
import com.codetimes90.java.dependencyinjection.consumer.Consumer;
import com.codetimes90.java.dependencyinjection.consumer.MyDIApplication;
import com.codetimes90.java.dependencyinjection.service.EmailServiceImpl;
public class EmailServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
MyDIApplication app = new MyDIApplication();
app.setService(new EmailServiceImpl());
return app;
}
}
Việc sử dụng phương thức tiêm phụ thuộc dựa trên Constructor hay dựa trên setter là một quyết định thiết kế và tùy 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 mà không có lớp dịch vụ thì tôi muốn DI dựa trên phương thức khởi tạo hoặc nếu không, tôi sẽ sử dụng DI dựa trên phương thức setter để chỉ sử dụng nó khi nó thực sự cần thiết.
Dependency Injection trong Java là một 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 ràng buộc từ thời gian biên dịch sang thời gian chạy. Chúng ta có thể đạt được IoC thông qua Factory Pattern, Template Method Design Pattern, Strategy Pattern và Định vị dịch vụ.
Spring Dependency Injection, Google Guice và Java EE CDI tạo điều kiện thuận lợi cho quá trình chèn phụ thuộc thông qua việc sử dụng Java Reflection API và java annotations. Tất cả những gì chúng ta cần là chú thích field, constructor hoặc phương thức setter và định cấu hình chúng trong các tệp hoặc lớp xml cấu hình.
Lợi ích của Java Dependency Injection
Một số lợi ích của việc sử dụng Dependency Injection trong Java là:
Ø Tách các mối quan tâm
Ø Boilerplate Code giảm thiểu trong các lớp ứng dụng vì tất cả công việc khởi tạo các phần phụ thuộc được xử lý bởi thành phần bộ phun
Ø Các thành phần có thể định cấu hình giúp ứng dụng có thể dễ dàng mở rộng
Ø Kiểm thử đơn vị dễ dàng với các đối tượng giả
Nhược điểm của Java Dependency Injection
Java Dependency injection cũng có một số nhược điểm:
Ø Nếu lạm dụng quá mức, nó có thể dẫn đến các vấn đề bảo trì vì ảnh hưởng của các thay đổi được biết đến trong thời gian chạy.
Ø Chèn phụ thuộc trong java ẩn các phụ thuộc lớp dịch vụ có thể dẫn đến lỗi thời gian chạy mà có thể đã mắc phải tại thời điểm biên dịch.
0 Nhận xét