- 内容提要
- 作者简介
- 译者简介
- 前言
- HTTP
- Servlet 和 JSP
- 下载 Spring 或使用 STS 与 Maven/Gradle
- 手动下载 Spring
- 使用 STS 和 Maven/Gradle
- 下载 Spring 源码
- 本书内容简介
- 下载示例应用
- 第 1 章Spring 框架
- 第 2 章模型 2 和 MVC 模式
- 第 3 章Spring MVC 介绍
- 第 4 章基于注解的控制器
- 第 5 章数据绑定和表单标签库
- 第 6 章转换器和格式化
- 第 7 章验证器
- 第 8 章表达式语言
- 第 9 章JSTL
- 第 10 章国际化
- 第 11 章上传文件
- 第 12 章下载文件
- 第 13 章应用测试
- 附录 A Tomcat
- 附录 B Spring Tool Suite 和 Maven
- 附录 C Servlet
- 附录 D JavaServer Pages
- 附录 E 部署描述符
2.6 依赖注入
在过去数年间,依赖注入技术作为代码可测试性的一个解决方案已经广泛应用。实际上,Spring、Struts2 等伟大框架都采用了依赖注入技术。那么,什么是依赖注入技术?
关于这个,Martin Fowler 写一篇优秀的文章:
http://martinfowler.com/articles/injection.html
在 Fowler 创造术语“依赖注入”之前,术语“控制反转”通常用于表示同样的事情。 正如 Fowler 在他的文章中指出的,两者不完全相同。
有两个组件 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);
}
}
servlet 支持两种 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 应用。
http://localhost:8080/appdesign4/form
应用将展示一个表单(见图 2.7)。
图 2.7 PDF 表单
如果在文本字段中输入一些内容并按提交按钮,服务器将创建一个 PDF 文件并发送重定向到浏览器(见图 2.8)。
图 2.8 PDF 文件
请注意,重定向网址将采用此格式。
http://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 {
...
}
}
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论