《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应用。

http://localhost:8080/appdesign4/form
应用将展示一个表单(见图2.7)。

screenshot

图2.7 PDF表单

如果在文本字段中输入一些内容并按提交按钮,服务器将创建一个PDF文件并发送重定向到浏览器(见图2.8)。

screenshot

图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 {
    ... 
  }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值