《Spring MVC学习指南(第2版)》——2.6 依赖注入

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)。

图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 {
    ...
  }
}
时间: 2024-08-08 07:16:59

《Spring MVC学习指南(第2版)》——2.6 依赖注入的相关文章

《Spring MVC学习指南(第2版)》——第1章 Spring框架 1.1XML配置文件

第1章 Spring框架 Spring框架是一个开源的企业应用开发框架,作为一个轻量级的解决方案,它包含20多个不同的模块.本书主要关注Core.Spring Bean.Spring MVC和Spring MVC Test模块. 本章主要介绍Core和Spring Bean这两个模块,以及它们如何提供依赖注入解决方案.为方便初学者,本书会深入讨论依赖注入概念的细节.后续介绍开发MVC应用的章节将会使用到本章介绍的技能. 依赖注入 简单来说,依赖注入的情况如下. 有两个组件A和B,A依赖于B.假定

《Spring MVC学习指南(第2版)》——2.7 小结

2.7 小结 在本章中,我们学习了基于MVC模式的模型2架构以及如何基于servlet控制器或者filter分发器开发一个模型2应用.两个示例分别为appdesign1和appdesign2.使用servlet作为过滤器上的控制器,一个明显的优点是你可以将servlet配置为欢迎页面. 在模型2应用程序中,JSP页面通常用做视图,当然也可以使用其他技术,如Apache Velocity和FreeMarker. 如果JSP页面用做模型2体系结构中的视图,那些页面仅用于显示值,并且不应在其中显示脚本

《Spring MVC学习指南(第2版)》——导读

前言 Spring MVC是Spring框架中用于Web应用快速开发的一个模块.Spring MVC的MVC是Model-View-Controller的缩写.它是一个广泛应用于图形化用户交互开发中的设计模式,不仅常见于Web开发,也广泛应用于如Swing和JavaFX等桌面开发. 作为当今业界最主流的Web开发框架,Spring MVC(有时也称Spring Web MVC)的开发技能相当热门.本书可供想要学习如何通过Spring MVC开发基于Java的Web应用的开发人员阅读. HTTP使

《Spring MVC学习指南(第2版)》——1.3 小结

1.3 小结 本章学习了依赖注入的概念以及基于Spring容器的实践,后续将在此基础之上配置Spring应用.

《Spring MVC学习指南(第2版)》——1.2 Spring控制反转容器的使用

1.2 Spring控制反转容器的使用 本节主要介绍Spring如何管理bean和依赖关系.1.2.1 通过构造器创建一个bean实例 前面已经介绍,通过调用ApplicationContext的getBean方法可以获取一个bean的实例.下面的配置文件中定义了一个名为product的bean(见清单1.1). 清单1.1 一个简单的配置文件 < ?xml version="1.0" encoding="UTF-8"?> < beans xmln

《Spring MVC学习指南(第2版)》——第2章 模型2和MVC模式 2.1模型1介绍

第2章 模型2和MVC模式 Java Web应用开发中有两种设计模型,为了方便,分别称为模型1和模型2.模型1是以页面中心,适合于小应用开发.而模型2基于MVC模式,是Java Web应用的推荐架构(简单类型的应用除外). 本章将会讨论模型2,并展示4个不同示例应用.第一个应用是一个基本的模型2应用,采用Servlet作为控制器:第二个应用也是模型2应用,但采用了Filter作为控制器:第三个应用引入了验证控件来校验用户的输入:最后一个应用则采用了一个自研的依赖注入器.在实践中,应替换为Spri

《Spring MVC学习指南(第2版)》——2.5 校验器

2.5 校验器 在Web应用执行action时,很重要的一个步骤就是进行输入校验.校验的内容可以是简单的,如检查一个输入是否为空,也可以是复杂的,如校验信用卡号.实际上,因为校验工作如此重要,Java社区专门发布了JSR 303 Bean Validation以及JSR 349 Bean Validation 1.1版本,将Java世界的输入检验进行标准化.现代的MVC框架通常同时支持编程式和声明式两种校验方法.在编程式中,需要通过编码进行用户输入校验,而在声明式中,则需要提供包含校验规则的XM

《Spring MVC学习指南(第2版)》——2.3 模型2之Servlet控制器

2.3 模型2之Servlet控制器 为了便于对模型2有一个直观的了解,本节将展示一个简单模型2应用.实践中,模型2的应用非常复杂. 示例应用名为appdesign1,其功能设定为输入一个产品信息.具体为:用户填写产品表单(图2.2)并提交:示例应用保存产品并展示一个完成页面,显示已保存的产品信息(见图2.3). 图2.2 产品表单 图2.3 产品详细页 示例应用支持如下两个action. (1)展示"添加产品"表单.该action将图2.2中的输入表单发送到浏览器上,其对应的URI应

《Spring MVC学习指南(第2版)》——2.4 模型2之Filter分发器

2.4 模型2之Filter分发器 虽然servlet是模型2应用程序中最常见的控制器,但过滤器也可以充当控制器.但请注意,过滤器没有作为欢迎页面的权限.仅输入域名时不会调用过滤器分派器.Struts 2使用过滤器作为控制器,是因为该过滤器也用于提供静态内容. 下面的例子(appdesign2)是一个采用filter分发器的模型2应用,目录结构如图2.5所示. 图2.5 appdesign2目录结构 JSP页面和Product类同appdesign1相同,但没有采用servlet作为控制器,而是