《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)。
圖2.7 PDF表單
如果在文本字段中輸入一些內容並按提交按鈕,服務器將創建一個PDF文件並發送重定向到瀏覽器(見圖2.8)。
圖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