Java 依賴注入 設計模式允許我們消除硬編碼的依賴關係,使我們的應用程序鬆散耦合、可擴展和可維護。我們可以在 Java 中實現 依賴注入 ,將依賴關係的解析從編譯時移至運行時。
Java 依賴注入
Java 依賴注入在理論上似乎難以理解,因此我將舉一個簡單的例子,然後我們將看到如何使用依賴注入模式來實現應用程序中的鬆散耦合和可擴展性。假設我們有一個應用程序,在其中使用 EmailService
來發送電子郵件。通常我們會像下面這樣實現它。
package com.journaldev.java.legacy;
public class EmailService {
public void sendEmail(String message, String receiver){
//發送郵件的邏輯
System.out.println("Email sent to "+receiver+ " with Message="+message);
}
}
EmailService
類保存了將郵件消息發送到收件人電子郵件地址的邏輯。我們的應用程序代碼將如下所示。
package com.journaldev.java.legacy;
public class MyApplication {
private EmailService email = new EmailService();
public void processMessages(String msg, String rec){
//執行一些消息驗證、操作邏輯等
this.email.sendEmail(msg, rec);
}
}
我們的客戶代碼將使用 MyApplication
類來發送電子郵件消息,如下所示。
package com.journaldev.java.legacy;
public class MyLegacyTest {
public static void main(String[] args) {
MyApplication app = new MyApplication();
app.processMessages("Hi Pankaj", "[email protected]");
}
}
乍看之下,上述實現似乎沒有問題。但上述代碼邏輯存在一定的限制。
MyApplication
類別負責初始化電子郵件服務,然後使用它。這導致了硬編碼的依賴。如果將來要切換到其他高級電子郵件服務,就需要在 MyApplication 類別中進行代碼更改。這使得我們的應用程序難以擴展,如果電子郵件服務在多個類別中使用,那將更加困難。- 如果我們想要擴展我們的應用程序以提供其他的消息功能,比如短信或 Facebook 消息,那麼我們需要為此編寫另一個應用程序。這將涉及應用程序類和客戶端類的代碼更改。
- 測試應用程序將非常困難,因為我們的應用程序直接創建電子郵件服務實例。我們無法在測試類中模擬這些對象。
有人可能會認為我們可以通過在 MyApplication
類中刪除電子郵件服務實例的創建來解決問題,而是通過構造函數要求電子郵件服務作為參數。
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){
//執行一些消息驗證、操作邏輯等
this.email.sendEmail(msg, rec);
}
}
但在這種情況下,我們要求客戶端應用程序或測試類來初始化電子郵件服務,這不是一個好的設計決策。現在讓我們看看如何應用 Java 依賴注入模式來解決上述實現的所有問題。Java 依賴注入至少需要以下幾點:
- 服務組件應該設計為基類或接口。最好使用界面或抽象類來定義服務的契約。
- 使用者類應該根據服務界面編寫。
- 注入器類別將初始化服務,然後是使用者類別。
Java 依賴注入 – 服務元件
對於我們的案例,我們可以有 MessageService
來宣告服務實作的契約。
package com.journaldev.java.dependencyinjection.service;
public interface MessageService {
void sendMessage(String msg, String rec);
}
現在假設我們有電子郵件和簡訊服務來實作上述介面。
package com.journaldev.java.dependencyinjection.service;
public class EmailServiceImpl implements MessageService {
@Override
public void sendMessage(String msg, String rec) {
// 發送電子郵件的邏輯
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) {
// 發送簡訊的邏輯
System.out.println("SMS sent to "+rec+ " with Message="+msg);
}
}
我們的依賴注入 Java 服務已準備就緒,現在我們可以撰寫我們的使用者類別。
Java 依賴注入 – 服務使用者
我們不需要為使用者類別建立基礎介面,但我會有一個宣告使用者類別契約的 Consumer
介面。
package com.journaldev.java.dependencyinjection.consumer;
public interface Consumer {
void processMessages(String msg, String rec);
}
我的使用者類別實作如下。
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){
// 做一些訊息驗證、處理邏輯等
this.service.sendMessage(msg, rec);
}
}
通知,我们的应用程序类只是使用服务。它不初始化导致更好的“关注点分离”的服务。还使用服务接口,可以通过模拟MessageService轻松测试应用程序,并在运行时而非编译时绑定服务。现在我们准备编写Java依赖注入器类,将初始化服务和消费者类。
Java依赖注入 – 注入器类
让我们拥有一个名为MessageServiceInjector
的接口,其中包含返回Consumer
类的方法声明。
package com.journaldev.java.dependencyinjection.injector;
import com.journaldev.java.dependencyinjection.consumer.Consumer;
public interface MessageServiceInjector {
public Consumer getConsumer();
}
现在,对于每个服务,我们将需要创建如下的注入器类。
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());
}
}
现在让我们看看我们的客户端应用程序如何使用一个简单的程序。
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 = "[email protected]";
String phone = "4088888888";
MessageServiceInjector injector = null;
Consumer app = null;
//发送电子邮件
injector = new EmailServiceInjector();
app = injector.getConsumer();
app.processMessages(msg, email);
//发送短信
injector = new SMSServiceInjector();
app = injector.getConsumer();
app.processMessages(msg, phone);
}
}
正如您所见,我们的应用程序类仅负责使用服务。服务类在注入器中创建。此外,如果我们必须进一步扩展应用程序以允许Facebook消息传递,我们只需编写服务类和注入器类。因此,依赖注入实现解决了硬编码依赖的问题,并帮助我们使应用程序更灵活且易于扩展。现在让我们看看如何通过模拟注入器和服务类轻松测试我们的应用程序类。
Java 依賴注入 – 使用模擬注入器和服務的 JUnit 測試案例
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(){
// 使用匿名類別模擬注入器
injector = new MessageServiceInjector() {
@Override
public Consumer getConsumer() {
// 模擬消息服務
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", "[email protected]");
}
@After
public void tear(){
injector = null;
}
}
正如您所看到的,我正在使用匿名類別來模擬注入器和服務類別,以便輕松測試應用程式方法。我在上述測試類別中使用的是 JUnit 4,請確保它在您的專案構建路徑中,如果您正在執行上述測試類別。我們已經使用構造函數在應用程式類別中注入依賴項,另一種方法是使用 setter 方法在應用程式類別中注入依賴項。對於 setter 方法的依賴注入,我們的應用程式類別將實現如下。
package com.journaldev.java.dependencyinjection.consumer;
import com.journaldev.java.dependencyinjection.service.MessageService;
public class MyDIApplication implements Consumer{
private MessageService service;
public MyDIApplication(){}
//setter 依賴注入
public void setService(MessageService service) {
this.service = service;
}
@Override
public void processMessages(String msg, String rec){
//執行一些消息驗證、操作邏輯等
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;
}
}
其中setter依賴注入的最佳例子之一是Struts2 Servlet API Aware接口。是否使用基於構造函數的依賴注入還是基於setter方法的依賴注入是一個設計決策,取決於您的需求。例如,如果我的應用程序在沒有服務類的情況下無法工作,那麼我會選擇基於構造函數的DI,否則我會選擇基於setter方法的DI,只在真正需要時使用它。Java中的依賴注入是通過將對象綁定從編譯時期移動到運行時期來實現控制反轉(IoC)的一種方式。我們可以通過工廠模式、模板方法設計模式、策略模式和服務定位器模式等方式實現IoC。Spring依賴注入、Google Guice和Java EE CDI框架通過使用Java反射API和Java注解來促進依賴注入的過程。我們只需要對字段、構造函數或setter方法進行註解,然後在配置XML文件或類中配置它們。
Java 依賴注入的好處
使用 Java 依賴注入的一些好處包括:
- 關注點分離
- 通過注入器組件處理初始化依賴項的所有工作,從而減少應用程序類中的樣板代碼
- 可配置的組件使應用程序易於擴展
- 使用模擬對象進行單元測試變得簡單
Java 依賴注入的缺點
Java 依賴注入也有一些缺點:
- 如果濫用,可能會導致維護問題,因為更改的影響在運行時才能知曉
- Java 依賴注入隱藏了服務類的依賴性,可能導致在編譯時本應檢測到的運行時錯誤
這就是有關Java 中的依賴注入模式的全部內容。當我們掌握服務時使用它是很有益的。