Java依賴注入設計原則允許我們移除硬編碼依賴和讓我們的應用低耦合,可擴展和可維護。我們可以通過在Java中實現依賴注入將依賴關系從編譯時移到運行時來解析。
Java依賴注入似乎很難通過理論來掌握。所以我將通過一些簡單的例子,然后我們將會看到如何在應用里使用依賴注入模式來實現低耦合和可擴展性。
一個不使用依賴注入實現的應用案例
假如說我們有一個通過調用EmailService類來發送郵件的應用。一般來說我們會向下面這樣來實現它。
public class EmailService {
public void sendEmail(String message, String receiver){
//logic to send email
System.out.println("Email sent to "+receiver+ " with Message="+message);
}
}
EmailService類接受郵件地址實現發送郵件消息的邏輯。我們的MyApplication類如下。
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);
}
}
我們的客戶端通過使用MyApplication類來發送郵件消息。
public class MyLegacyTest {
public static void main(String[] args) {
MyApplication app = new MyApplication();
app.processMessages("Hi Pankaj", "pankaj@abc.com");
}
}
咋一看,通過上面方式實現似乎沒什么問題。但是上面代碼邏輯有某些限制。
- MyApplication類負責初始化郵件服務並且使用它。這會導致硬編碼依賴。如果我們將來想切換其他一些高級的郵件服務,這需要在MyApplication類中修改代碼。這會讓我們的應用很難擴展並且如果我們的郵件服務在很多類中被用到這將會更加的難。
- 如果我們希望在將來通過提供其他的一些消息通知方式來擴展我們的應用,比如短信或者微信那么我們將需要另外寫一個應用。這在應用類和客戶端也將涉及到修改代碼。
- 測試應用將會變得很難因為我們的應用是直接創建郵件服務實例。我們無法通過在測試類中模擬創建這些對象。
有人說我們可以通過構造器傳入需要的郵件服務作為參數的方法來從MyApplication類中移除郵件服務實例的創建。
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);
}
}
但是在這種情況,我們要求客戶端應用或者測試類來初始化這些郵件服務,這不是一種好的設計決策。
現在讓我們來看看怎么樣通過實現Java依賴注入模式來解決通過上面方式實現出現的這些問題。依賴注入在Java中至少需要以下內容:
- 服務組件應當被設計成基類或者接口。最好是選擇定義服務契約的接口或抽象類。
- 消費者類應該根據服務接口編寫。
- 注入器類將初始化服務,然后是消費者類。
Java依賴注入-服務組件
在我們的例子中,我們可以通過MessageService接口來聲明服務實現的契約。
public interface MessageService {
void sendMessage(String msg, String rec);
}
現在讓我們通過Email和SMS服務來實現上面接口。
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);
}
}
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);
}
}
我們的依賴注入java服務寫好了,現在我們可以寫消費類。
Java依賴注入-服務消費
我們不需要為消費者類提供基本接口,但是我將擁有一個消費者接口為消費者類聲明合約。
public interface Consumer {
void processMessages(String msg, String rec);
}
消費者類實現如下。
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);
}
}
注意到我們的application類剛使用服務。它沒有初始化服務導致更好的“關注點分類”。還有使用服務接口允許我們通過模擬MessageService並在運行時綁定服務而不是編譯時來更加簡單的測試應用。
現在我們將准備寫java依賴注入器類來初始化服務和消費者類。
Java依賴注入-注入器類
讓我們實現一個帶有返回消費者類的方法聲明的MessageServiceInjector接口。
public interface MessageServiceInjector {
public Consumer getConsumer();
}
現在對於每一個服務,我們可以如下來創建注入器類。
public class EmailServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
return new MyDIApplication(new EmailServiceImpl());
}
}
public class SMSServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
return new MyDIApplication(new SMSServiceImpl());
}
}
現在讓我們通過一個簡單的程序來看客戶端怎樣使用application。
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);
}
}
如你所見我們的application類只負責使用服務。服務類在注入器中被創建。 還有如果我們將來希望擴展我們的應用來支持發送微信消息。我們只需要再寫一個服務類和注入器類。
因此依賴注入實現解決了硬編碼帶來的問題並且幫助我們使應用靈活和易擴展。現在讓我們看看怎樣通過模擬注入器和服務類來簡單的測試我們的應用。
Java依賴注入-模擬注射器和服務的JUnit測試用例
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;
}
}
你可以看到我們使用匿名類來模擬注入器和服務類,然后我可以輕易地測試 application的方法。這里為以上的測試類使用了JUnit4,所以如果運行以上測試類,先確認在你項目的構建路徑包含它。
在上面的這些application類中,我們使用構造器來注入依賴關系,另外的方式是使用setter方法。對於setter方法依賴注入的實現如下。
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);
}
}
public class EmailServiceInjector implements MessageServiceInjector {
@Override
public Consumer getConsumer() {
MyDIApplication app = new MyDIApplication();
app.setService(new EmailServiceImpl());
return app;
}
}
一個最好的setter依賴注入的例子是 Struts2 Servlet API Aware interfaces
到底是使用基於構造器依賴注入還是基於setter方法依賴注入取決於你的需求。舉個例子,如果沒有服務類我的應用完全不能運行,那么我會偏向基於構造器的DI,否則我會選擇基於setter方法的DI,只有在真正需要才會使用它。
Java中的依賴注入是一種通過使對象從編譯時綁定移到運行時綁定來實現控制反轉(Inversion of control IoC)的一種方式。我們可以通過工廠模式(Factory Pattern), 模板方法設計模式(Template Method Design Pattern), 策略模式(Strategy Pattern)還有服務定位模式(Service Locator pattern)來實現IoC。
Spring依賴注入,Google Guice還有Java EE CDI框架通過使用Java Reflection API和Java注解來促進依賴注入的過程。我們只需要注解該域,構造器或者setter方法然后在配置xml文件或者配置類中配置它們。
Java依賴注入的好處
一些使用Java依賴注入的好處如下:
- 關注點分離
- 應用程序類中的樣板代碼減少,因為所有用於初始化依賴性的工作都由注入器組件處理
- 配置組件使應用程序易擴展
- 通過模擬對象來單元測試會很簡單
Java依賴注入的缺點
Java依賴注入也有一些缺點:
- 如果過度使用,可能會導致維護問題,因為更改的影響只有在運行時才知道。
- Java中的依賴注入可能會隱藏導致運行時錯誤的服務類的依賴性,這會在編譯時被捕獲。
以上就是Java中的依賴注入模式。當我們控制服務時,了解和使用它是很好的。
原文鏈接:https://www.journaldev.com/2394/java-dependency-injection-design-pattern-example-tutorial
