Skip to content

16. 三层架构中的MVC Web应用——示例2

16.1. 简介

我们编写了应用程序 [personnes-01],其结构如下:

[dao]层通过[ArrayList]对象实现了人员列表的管理。 这使我们无需过多关注 [dao] 和 [service] 层,从而能够专注于 [web] 层。 我们希望将应用程序升级到更贴近实际的环境中,即人员列表将存储在数据库表中。这将导致我们需要修改 [dao] 层,并进而影响另外两层。 为了充分利用 Spring IoC 带来的分层独立性,我们将重构应用程序 [personnes-01],并使用 Spring IoC 进行配置:

新应用程序将命名为 [personnes-02]。 一旦编写完成,我们知道可以修改 [dao] 和 [service] 层,而无需更改 [web] 层的代码。这就是我们想要的效果。

我们通过复制/粘贴项目 [personnes-01] 来创建一个新的 Eclipse 项目 [personnes-02],具体操作如第 6.2 节所述:

在 [1] 中,我们可以看到配置文件的出现,我们将在此定义 [dao] 和 [service] 层的 Bean。其内容如下:


<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<beans>
    <!-- DAO类 -->
    <bean id="dao" class="istia.st.mvc.personnes.dao.DaoImpl" init-method="init"/>
    <!-- 服务类 -->
    <bean id="service" class="istia.st.mvc.personnes.service.ServiceImpl">
        <property name="dao">
            <ref local="dao" />
        </property>
    </bean>
</beans>
  • 第 5 行:将名为 [dao] 的 Bean 定义为 [DaoImpl] 类的实例。实例化后,将执行该实例的 [init] 方法。
  • 第 7-10 行:将名为 [service] 的 Bean 定义为类 [ServiceImpl] 的实例。
  • 第8-10行:实例[DaoImpl]的属性[dao]被初始化为第5行创建的层[dao]的引用。 需要提醒的是,类 [ServiceImpl] 确实拥有属性 [dao] 及其对应的设置器:
public class ServiceImpl implements IService {

    // [dao] 层
    private IDao dao;

    public IDao getDao() {
        return dao;
    }

    public void setDao(IDao dao) {
        this.dao = dao;
    }
...

当然,仅凭这个文件是无法实现任何功能的。控制器 [Application] 将在其方法 [init] 中使用它来实例化 [service] 层。 回顾一下控制器方法 [init] 的上一版本:

@SuppressWarnings("serial")
public class Application extends HttpServlet {
...
    // 服务
    ServiceImpl service = null;

    // 初始化
    @SuppressWarnings("unchecked")
    public void init() throws ServletException {
...
        // [dao] 层的实例化
        DaoImpl dao = new DaoImpl();
        dao.init();
        // [service] 层的实例化
        service = new ServiceImpl();
        service.setDao(dao);
    }

在第 5 行,我们不得不显式指定 [service] 层的实现类,而在第 12 行,则需指定 [dao] 层的实现类。 使用 Spring IoC 时,[init] 方法变为如下形式:

@SuppressWarnings("serial")
public class Application extends HttpServlet {
    // 实例参数
    ...

    // 服务
    private IService service = null;

    // 初始化
    @SuppressWarnings("unchecked")
    public void init() throws ServletException {
    ...
        // [service] 层的实例化
        service = (IService) new XmlBeanFactory(new ClassPathResource("spring-config.xml")).getBean("service");
    }
  • 第 7 行:私有字段 [service] 的类型不再是 [ServiceImpl],而是 [IService]、c.a.d。 类型为 [service] 接口。因此,[web] 层不再与该接口的特定实现相关联。
  • 第 14 行:根据配置文件 [spring-config.xml] 初始化字段 [service]。

只需进行上述修改。将此新应用程序集成到 Tomcat 中,启动 Tomcat,然后访问 URL [http://localhost:8080/personnes-02]:

Image

16.2. Web应用程序归档

我们开发了一个基于 Eclipse / Tomcat 的三层应用项目:

在未来的版本中,人员组将被存储在数据库表中。

  • 这将导致 [dao] 层的重写。这一点很容易理解。
  • [service]层也将随之调整。目前,该层的唯一作用是确保对[dao]层管理的数据进行同步访问。为此,我们已对[service]层的所有方法进行了同步处理。 我们已解释过为何将此同步功能置于该层而非 [dao] 层。在新版本中,[service] 层仍将承担访问同步的唯一职责,但该功能将通过数据库事务实现,而非通过 Java 方法的同步。
  • [web]层将保持不变。

为了方便版本迁移,我们创建了一个新的 Eclipse 项目 [mvc-personnes-02B],该项目是之前项目 [mvc-personnes-02] 的副本,但其中的 [web, service, dao, entites] 层已被打包为 .jar 文件:

[src]文件夹现在仅包含Spring配置文件[spring-config.xml]。 该文件夹此前还包含Java类的源代码。这些元素已消失,被其编译后的文件所取代,这些文件被放入[personnes-*.jar]归档中,如[1]所示:

项目 [mvc-personnes-02B] 已配置为在其 ClassPath 中包含 [personnes-*.jar ] 归档文件。

我们将 Web 项目 [mvc-personnes-02B] 部署到 Tomcat 中:

为测试该项目,我们启动 Tomcat 并访问 URL [http://localhost:8080/personnes02B]:

Image

建议读者进行进一步测试。

在带数据库的版本中,我们将修改 [service] 和 [dao] 这两个层。 我们想说明,只需将前一个项目中的 [personnes-dao.jar] 和 [personnes-service.jar] 归档文件替换为新归档文件,我们的应用程序即可在数据库环境下运行。 我们无需修改 [web] 层和 [entites] 层的存档文件。