閱讀909 返回首頁    go 阿裏雲 go 技術社區[雲棲]


《Spring MVC學習指南(第2版)》——2.6 依賴注入

本節書摘來自異步社區《Spring MVC學習指南(第2版)》一書中的第2章,第2.6節,作者:【美】Paul Deck著,更多章節內容可以訪問雲棲社區“異步社區”公眾號查看

2.6 依賴注入

在過去數年間,依賴注入技術作為代碼可測試性的一個解決方案已經廣泛應用。實際上,Spring、Struts2等偉大框架都采用了依賴注入技術。那麼,什麼是依賴注入技術?

有兩個組件A和B,A依賴於B。假定A是一個類,且A有一個方法importantMethod使用到了B,如下:

public class A {
  public void importantMethod() {
    B b = ... // get an instance of B
    b.usefulMethod();
    ...
  }
  ...
}

要使用B,類A必須先獲得組件B的實例引用。若B是一個具體類,則可通過new關鍵字直接創建組件B實例。但是,如果B是接口,且有多個實現,則問題就變得複雜了。我們固然可以任意選擇接口B的一個實現類,但這也意味著A的可重用性大大降低了,因為無法采用B的其他實現。

示例appdesign4使用了一個自製依賴注入器。在現實世界的應用程序中,應該使用Spring。

示例應用程序用來生成PDF。它有兩個動作,form和pdf。 第一個沒有action類,隻是轉發到可以用來輸入一些文本的表單;第二個生成PDF文件並使用PDFAction類,操作類本身依賴於生成PDF的服務類。

PDFAction和PDFService類分別見清單2.11和清單2.12。

清單2.11 PDFAction類

package action;
import service.PDFService; 
public class PDFAction {
  private PDFService pdfService; 

  public void setPDFService(PDFService pdfService) {
    this.pdfService = pdfService; 
  } 
  public void createPDF(String path, String input) {
    pdfService.createPDF(path, input); 
  }
}

清單2.12 PDFService類

package service; 
import util.PDFUtil; 

public class PDFService {
  public void createPDF(String path, String input) { 
    PDFUtil.createDocument(path, input);
  } 
}

PDFService使用了PDFUtil類,PDFUtil最終采用了Apache PDFBOx庫來創建PDF文檔,如果對創建PDF的具體代碼有興趣,可以進一步查看PDFUtil類。

這裏的關鍵在於,如代碼2.11所示,PDFAction需要一個PDFService來完成它的工作。換句話說,PDFAction依賴於PDFService。沒有依賴注入,你必須在PDFAction類中實例化PDFService類,這將使PDFAction更不可測試。除此之外,如果需要更改PDFService的實現,你必須重新編譯PDFAction。

使用依賴注入,每個組件都有注入它的依賴項,這使得測試每個組件更容易。對於在依賴注入環境中使用的類,你必須使其支持注入。一種方法是為每個依賴關係創建一個set方法。例如,PDFAction類有一個setPDFService方法,可以調用它來傳遞PDFService。注入也可以通過構造方法或類屬性進行。

一旦所有的類都支持注入,你可以選擇一個依賴注入框架並將它導入你的項目。Spring框架、Google Guice、Weld和PicoContainer是一些好的選擇。

注意
 

依賴注入的Java規範是JSR 330和JSR 299
appdesign4程序使用DependencyInjector類(見清單2.13)來替代依賴注入框架(在現實世界的應用程序中,你會使用一個合適的框架)。這個類專為appdesign4應用設計,可以容易地實例化。一旦實例化,必須調用其start方法來執行初始化,使用後,應調用其shutdown方法以釋放資源。在此示例中,start和shutdown都為空。

清單2.13 DependencyInjector類

package util;
import action.PDFAction; 
import service.PDFService; 

public class DependencyInjector {

  public void start() { 
    // initialization code 
  } 
  public void shutDown() {
    // clean-up code 
  } 

  /* 
   * Returns an instance of type. type is of type Class 
   * and not String because it's easy to misspell a class name 
   */ 
  public Object getObject(Class type) { 
    if (type == PDFService.class) { 
      return new PDFService();
    } else if (type == PDFAction.class) { 
      PDFService pdfService = (PDFService)
          getObject(PDFService.class);
      PDFAction action = new PDFAction();
      action.setPDFService(pdfService);
      return action;
    } 
    return null;
  } 
}

要從DependencyInjector獲取對象,須調用其getObject方法,並傳遞目標對象的Class。 DependencyInjector支持兩種類型,即PDFAction和PDFService。例如,要獲取PDFAction的實例,你將通過傳遞PDFAction.class來調用getObject:

PDFAction pdfAction =(PDFAction)dependencyInjector.getObject(PDFAction.class);

DependencyInjector(和所有依賴注入框架)的優雅之處在於它返回的對象注入了依賴。如果返回的對象所依賴的對象也有依賴,則所依賴的對象也會注入其自身的依賴。例如,從DependencyInjector獲取的PDFAction已包含PDFService。無需在PDFAction類中自己創建PDFService。

appdesign4中的servlet控製器如清單2.14所示。請注意,它在其init方法中實例化DependencyInjector,並在其destroy方法中調用DependencyInjector的shutdown方法。 servlet不再創建它自己的依賴,相反,它從DependencyInjector獲取這些依賴。

清單2.14 appdesign4中ControllerServlet

package servlet;
import action.PDFAction;
import java.io.IOException;
import javax.servlet.ReadListener;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException; 
import javax.servlet.annotation.WebServlet; 
import javax.servlet.http.HttpServlet; 
import javax.servlet.http.HttpServletRequest; 
import javax.servlet.http.HttpServletResponse; 
import javax.servlet.http.HttpSession;
import util.DependencyInjector; 

@WebServlet(name = "ControllerServlet", urlPatterns = { 
  "/form", "/pdf"}) 
public class ControllerServlet extends HttpServlet { 
private static final long serialVersionUID = 6679L; 
  private DependencyInjector dependencyInjector; 

  @Override
  public void init() { 
    dependencyInjector = new DependencyInjector(); 
    dependencyInjector.start();
  } 

  @Override
  public void destroy() {
    dependencyInjector.shutDown(); 
  } 
  protected void process(HttpServletRequest request, 
      HttpServletResponse response) 
      throws ServletException, IOException {
    ReadListener r = null;
    String uri = request.getRequestURI();
    /* 
     * uri is in this form: /contextName/resourceName, 
     * for example: /app10a/product_input. 
     * However, in the case of a default context, the 
     * context name is empty, and uri has this form 
     * /resourceName, e.g.: /pdf 
     */
    int lastIndex = uri.lastIndexOf("/");
    String action = uri.substring(lastIndex + 1);
    if ("form".equals(action)) {
      String dispatchUrl = "/jsp/Form.jsp"; 
      RequestDispatcher rd = 
          request.getRequestDispatcher(dispatchUrl);
      rd.forward(request, response); 
    } else if ("pdf".equals(action)) {
      HttpSession session = request.getSession(true); 
      String sessionId = session.getId();
      PDFAction pdfAction = (PDFAction) dependencyInjector 
          .getObject(PDFAction.class);
      String text = request.getParameter("text");
      String path = request.getServletContext() 
          .getRealPath("/result") + sessionId + ".pdf";
      pdfAction.createPDF(path, text); 

      // redirect to the new pdf 
      StringBuilder redirect = new 
          StringBuilder(); 
      redirect.append(request.getScheme() + "://"); 
      redirect.append(request.getLocalName());
      int port = request.getLocalPort();
      if (port != 80) { 
        redirect.append(":" + port); 
      } 
      String contextPath = request.getContextPath();
      if (!"/".equals(contextPath)) { 
        redirect.append(contextPath); 
      } 
      redirect.append("/result/" + sessionId + ".pdf"); 
      response.sendRedirect(redirect.toString()); 
    } 
  } 

  @Override
  protected void doGet(HttpServletRequest request, 
      HttpServletResponse response) 
      throws ServletException, IOException { 
    process(request, response); 
  } 

  @Override
  protected void doPost(HttpServletRequest request, 
      HttpServletResponse response) 
      throws ServletException, IOException { 
    process(request, response); 
  }
}

ervlet支持兩種URL模式,form和pdf。 對於表單模式,servlet簡單地轉發到表單。 對於pdf模式,servlet使用PDFAction並調用其createDocument方法。此方法有兩個參數:文件路徑和文本輸入。所有PDF存儲在應用程序目錄下的result目錄中,用戶的會話標識符用做文件名,而文本輸入作為PDF文件的內容;最後,重定向到生成的PDF文件。以下是創建重定向URL並將瀏覽器重定向到新URL的代碼:

// redirect to the new pdf
StringBuilder redirect = new 
    StringBuilder();
redirect.append(request.getScheme() + "://"); //http or https 
redirect. append(request.getLocalName()); // the domain
int port = request.getLocalPort();
if (port != 80) { 
  redirect.append(":" + port); 
} 
String contextPath = request.getContextPath();
if (!"/".equals(contextPath)) { 
  redirect.append(contextPath); 
} 
redirect.append("/result/" + sessionId + ".pdf"); 
response.sendRedirect(redirect.toString());

現在訪問如下URL來測試appdesign4應用。

https://localhost:8080/appdesign4/form
應用將展示一個表單(見圖2.7)。

screenshot

圖2.7 PDF表單

如果在文本字段中輸入一些內容並按提交按鈕,服務器將創建一個PDF文件並發送重定向到瀏覽器(見圖2.8)。

screenshot

圖2.8 PDF文件

請注意,重定向網址將采用此格式。

https://localhost:8080/appdesign4/result/sessionId.pdf
由於依賴注入器,appdesign4中的每個組件都可以獨立測試。例如,可以運行清單2.15中的PDFActionTest類來測試類的createDocument方法。

清單2.15 PDFActionTest類

package test;
import action.PDFAction; 
import util.DependencyInjector; 

public class PDFActionTest {
  public static void main(String[] args) { 
    DependencyInjector dependencyInjector = new DependencyInjector();
    dependencyInjector.start();
    PDFAction pdfAction = (PDFAction) dependencyInjector.getObject(
        PDFAction.class); 
    pdfAction.createPDF("/home/janeexample/Downloads/1.pdf", 
        "Testing PDFAction...."); 
    dependencyInjector.shutDown(); 
  } 
}

如果你使用的是Java 7 EE容器,如Glassfish,可以讓容器注入對servlet的依賴。 應用appdesign4中的servlet將如下所示:

public class ControllerServlet extends HttpServlet {
  @Inject PDFAction pdfAction;
  ... 

  @Override
  public void doGet(HttpServletRequest request, 
      HttpServletResponse response) throws IOException, 
      ServletException {
    ... 
  } 

  @Override
  public void doPost(HttpServletRequest request, 
      HttpServletResponse response) throws IOException, 
      ServletException {
    ... 
  }
}

最後更新:2017-05-27 13:31:31

  上一篇:go  《Spring MVC學習指南(第2版)》——2.7 小結
  下一篇:go  《易學C++(第2版)》——第2章 Hello,World!2.1 Visual Studio 2012的安裝和啟動