《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