`

将系统移植到Spring

阅读更多

Spring已经是一个在Apache 2.0许可下发布的基础构造代码库。它的核心是一个反转控制容器,在容器的外部开发组已经创建了JDBC和JMS代码的样板文件模板,一个web MVC框架,和其他组件。

我之所以参加这个会议晚是因为我不管Spring的成熟度和公开度,我都对它花了很长的时间进行了测试。我的问题是:"Spring到底能给我带来什么?",为了找到答案,我用Spring的组件替换了现有的一个应用的内脏。通过这次替换我懂得了:我在很早以前就应该用Spring了;应用程序的代码已经没有以前那么混乱了,调试和扩展也更加容易,并且变的更轻因为我能够开发一些通用的辅助代码来配合Spring。

我将在这篇文章中分享我在这个试验中的想法和发现。我将清晰的说明我是怎样用Spring组件替换参考应用中的单态注册,JDBC代码,和web的前后端层。我也会描述我遇到的一个问题还有我是怎样来解决它的。

接下来的内容不需要你是一个Spring的专家,我会在后面给出Spring资源链接。样例代码在Linux上Spring 1.2.X和JDK1.5.0_07下已通过测试。

原始(旧的)代码

我不想通过制作一个鲜活的应用来进行试验,所以我从另外一篇文章中摘录下我的测试程序。这是一个有2个servlet页面控制器作为入口点的简单的java web应用。servlets通过DAO的方式访问数据库,DAO从本地数据源取得数据库链接。关联对象通过单态注册方式来相互取得。具体有:
SimpleDAO: 从数据库读出和写入信息对象
DbUtil: 方便程序使用JDBC ResultSets, Connections等等。
ObjectRegistry: 单态注册类,对象之间可以通过它彼此取得。
SetupDataSourceContextListener: 设置JDBC数据源
SetupDBContextListener: 预备(植入的)数据库
GetDataServlet: 页面控制器用来显示数据
PutDataServlet: 页面控制器用来存储数据

这是一个非常简单的应用程序,但它是完全独立的并且展现了一个多层应用程序的行为。这个小型试验的一些观测资料可用于由真实世界转换的工程应用中。

修改内脏:对象注册

首先来分析ObjectRegistry类,它是关系型对象粘合层。

package pool_test.util ;
public class ObjectRegistry {
  private static ObjectRegistry _instance =    new ObjectRegistry() ;
  public static ObjectRegistry getInstance(){
    return( _instance ) ;
  }
  private Map _singletons ;
  public void put(final String key , final Object obj){
    _singletons.put( key , obj ) ;
  }
  public Object get( final String key ){
    return( _singletons.get( key ) ) ;
  }
}



ObjectRegistry 实际上是一个String:Object键值对的一个Map.你可以将一个对象注册在一个地方(put()方法),然后在另外的地方得到这个对象(get()方法)。用注册可以削弱对象依赖因为代码可以仅仅通过它的通用类型(接口或超类)和查找关键字得到一个对象。而细节-实现,实例化和配置-就留给这段调用put()方法存储对象的代码。

它能工作,而且我发现它工作比较频繁,但它并不完美。缺少put()或者将它放错地方可能会引起空指针错误或栈溢出错误。你必须跟踪对象被存储注册的次序,为了确保你不会试图取的一个不存在的对象。像我在这做的一样,在小的应用中用ContextListener(一个监听器)处理实例化命令,但是在一个大的应用中可能就需要你努力去避免一些问题。

旧的单态注册的另一个问题是暴露的put()操作是java调用?意思是说:一个存储的对象实现的变更需要重新编译注册类-比如为了测试存根你想替换基于数据库的DAO。我以前犯过一个小错误,产品应用中用了DAO 的stub(这里不知道译成什么意思好?)。在一个比较大的应用中,它可能很难捕获到,因为它被隐藏在代码里了。
写点Spring代码就能处理这些缺点。这是新的注册类:

package pool_test.util ;
import org.springframework....ApplicationContext ;
import org.springframework....ClasspathXMLApplicationContext ;
public class ObjectRegistry {
  private ApplicationContext _singletons ;
  private ObjectRegistry(){
    _singletons = new ClassPathXmlApplicationContext(
        new String[] { "spring-objectregistry.xml" }
      );
  }
  public Object get( final String key ){
    return( _singletons.getBean( key ) ) ;
  }
}



注意,我已经用一个Spring的ApplicationContext(应用上下文)替换了旧的Map。和Map类似,ApplicationContext保存对象并且让你通过名字来取得它们。比较起来,ApplicationContext是通过一个外部配置的Xml文件来读取所有的对象定义。修改spring-objectregistry.xml中的对象定义需要重启应用,但不是全编译。
摘自spring-objectregistry.xml:

<bean  id="ObjectPool"  class="org.apache...GenericObjectPool"
  singleton="true"><-- omitted for brevity -->
</bean>

<bean  id="DataSource"  class="org.apache...PoolingDataSource"
  singleton="true">
  <property name="pool">
    <ref local="ObjectPool" />
  </property>
</bean>

<bean  id="DAO"  class="pool_test.data.jdbc.SimpleDAO" singleton="true">
  <property name="dataSource">
    <ref local="DataSource"/>
  </property>             
</bean>



XML元素符合反射调用:外部的<bean/>元素定义一个对象,里面的<property/>元素调用对象上的方法。例如,对于ID为Dao的bean来说,Spring首先实例化一个SimpleDAO类型的对象并且调用它的setDataSource()方法。setDataSource()参数是id为DataSource的bean,在文件的前面有定义。

在后台,Spring配置DataSource并且把它指派到DAO.其他的用Spring管理的对象要引用DAO只能通过它的bean名字"DAO",同样的,它们不知道这(就是"SimpleDAO")实现的改变。

既然Spring管理了对象,那ObjectRegistry对客户端代码来讲,只有只读。我可以移出ObjectRegistry类put()方法和其他的类中外部调用的put()方法。例如:SetupDataSourceContextListener现在仅用来组装初始化链接的池。

在web.xml部署描述下面也有一些固定负载。例如,一个用来指向本地jdbc和其他属性文件的上下文参数。Spring现在通过这些文件了来装配对象,并且自己分配这些值。

Spring也关心在spring-objectregistry.xml文件中对象间的依赖跟踪。我以前在代码中进行处理。因为在这个应用中我用了更多的依赖注入,现在Spring确认这些被引用的对象在客户端代码试图使用他们之前,按照属性顺序创建。也就是说我已经放下了笔记性的工作只剩下清理我的代码了。

有人可能在讨论关于一个完美的排除外在的需要的Ioc实现,可调用的ObjectRegistry类,并且让Spring在运行时管理对象间的关系。考虑到将来重构,以后它将会引起一点麻烦,但是现在我还是需要注册。

修改数据层:Spring JDBC

配置通用的数据源大概是一个装饰XML的工作。除了模板JDBC代码外,Spring也提供基于DAO的类。也就是把对Connection的管理以及ResultSet和PreparedStatement的关闭交给Spring框架。而只留下我应用的一些特殊代码。
新的DAO和旧接口一样

package pool_test.data.jdbc ;

public class SimpleDAO {
  public void setupTable() ;
  public void putData() ;
  public Collection getData() ;
  public void destroyTable() ;
}



实际上,它是个截然不同的东西。老版本的DAO里面有很多内嵌的JDBC代码,而新版本中把那个烦心的工作交给了Spring。

package pool_test.data.jdbc ;

public class SimpleDAO extends JdbcDaoSupport {

  private GetDataWorker _getDataWorker ;
  private PutDataWorker _putDataWorker ;
  private CreateTableWorker _createTableWorker ;
  private DestroyTableWorker _destroyTableWorker ;

  // constructor is now empty

  protected void initDao() throws Exception {
    
    super.initDao() ;
    
    _getDataWorker =
      new GetDataWorker( getDataSource() ) ;

    _putDataWorker =
      new PutDataWorker( getDataSource() ) ;

    _createTableWorker =
      new CreateTableWorker( getDataSource() ) ;

    _destroyTableWorker =
      new DestroyTableWorker( getDataSource() ) ;

    return ;
    
  } // initDao()

  public void setupTable() {
    _createTableWorker.update() ;
  }

  public Collection getData() {
    return( _getDataWorker.execute() ) ;
  }

  // ... destroyTable() and getData()
  //   follow similar conventions ...

}



首先的变更是父类:SimpleDAO。它现在继承了JdbcDaoSupport,JdbcDaoSupport有一些方法和内部类用来进行数据库的工作。第一个方法是setDataSource(),它用来给这个对象指派一个DataSource。子类通过调用getDataSource()得到数据源。
initDao()是另一个从JdbcDaoSupport继承来的方法。父类通过调用该方法以让子类进行以前的初始化方法。在这里,SimpleDAO给它的成员变量赋值。

成员变量也是新的:移除Spring JDBC也就是移出SimpleDAO的数据存取功能,而是指定像GetDateWorker和PutDataWorker.这样的内部类。每个DAO动作都有一个这样的内部类。例如,用来存储数据的PutDataWorker类

package pool_test.data.jdbc ;

import org.springframework ... SqlUpdate ;

public class SimpleDAO {

 ...
   private class PutDataWorker extends SqlUpdate {
    
     public PutDataWorker( final DataSource ds ){
      
       super( ds , SQL_PUT_DATA ) ;
    
       declareParameter(
             new SqlParameter( Types.VARCHAR ) ) ;
       declareParameter(
             new SqlParameter( Types.INTEGER ) ) ;

     }

     // a real app would load the SQL statements
     //   from an external source...
     private static final String SQL_PUT_DATA =
       "INSERT INTO info VALUES( ? , ? )" ;

   }

   ...
}



PutDataWorker继承了SqlUpdate类,SqlUpdate是一个用来处理繁重的SQL插入和修改调用的Spring模板类。declareParameter()方法是用来告诉Spring,这个SQL statement中的数据类型分别是字符串和数字类型。
注意,PutDataWorker是一个非常瘦的类。它调用super()方法传递一个DataSource和
SQL statement给它的父类,用declareParameter()来描述这个查询。SqlUpdate通过几个JDBC关联对象和闭合连接(closing connections,就是打开一个链接,处理完毕,关闭链接)来处理真正的工作。SimpleDAO.putDate()方法也是相当简洁的:

public class SimpleDAO {

  public void putData() {

    for( ... ){

      // ... "nameParam" and "numberParam" are
      // local loop variables ...

      Object[] params = {
        nameParam , // variable is a Java String 
        numberParam // some Java numeric type
      } ;

      _putDataWorker.update( params ) ;

    }
  }

}



putData()方法用来把数据存储到数据库。注意这个方法是把工作委派给了它的worker类(也就是_putDataWorker),实际上是它的worker类继承SqlUpdate而来的方法。SqlUpdate.update()方法负责取数据,关闭JDBC连接和其他相关的Statement对象。也就是我可以丢弃一些我写的通用的JDBC代码类甚至是整个DbUtil类,DbUtil类提供了一些方法,可以用来很方便的关闭Connection,Statement和ResultSet.

SqlUpdate是用来修改数据,Spring的MappingSqlQuery则用来查询。注意看GetDateWorker中的mapRow()方法:

package pool_test.data.jdbc ;

import org.springframework ... MappingSqlQuery ;


// inside class SimpleDAO ...

private class GetDataWorker
  extends MappingSqlQuery {

  // ...constructor similar to PutDataWorker...
  
  protected Object mapRow( final ResultSet rs ,
       final int rowNum ) throws SQLException
  {
      
      final SimpleDTO result = new SimpleDTO(
        rs.getString( "names" ) ,
        rs.getInt( "numbers" )
      ) ;

      return( result ) ;
      
   } // mapRow()
}



这个方法负责把ResultSet的表列数据转换为可用的SimpleDTO对象,一条数据对应一个对象。对结果集中的每一行Spring都调用此方法。当GetDataWorker.mapRow()和ResultSet两者相结合时,它不再负责关闭记录集或者去检查是否在处理过程中
拉下了某行。
        
用SpringMVC修改Web层

既然我有了Spring的基础结构和数据层代码,接下来就该处理web层了。SpringMVC的控制器接口是web层中很重要的类:

package org.springframework ...

interface Controller {
  public ModelAndView handleRequest(
    HttpServletRequest request ,
    HttpServletResponse response
  ) throws Exception ;
}



handleRequest()方法是入口点,和Struts Action的情况类似,是自己的页面控制器:框架提供了servlet请求和响应对象,控制器的实现类则返回业务逻辑的结果(一个模型对象)和一个指示器,指示器用来说明怎样显示结果(也就是视图)。ModelAndView的模型是一个将要显示到视图层的一个业务对象或者其他数据的一个Map。在例子代码中,视图是要个JSP,但是Spring 的MVC也支持 Velocity templates 和 XSLT。

例子应用的servlet页面控制器太简单以致于没有看到Spring MVC的强大之处。因此,最新的页面控制器都藏在大多数的教科书的例子中。

旧的页面控制器也有类似的策略,将一个Map对象从servlets传递到jsps.结果是,我不需要对传递到jsp的工作做任何的改变。我只需要用一些spring特殊的标签库。

对于新的基于Spring的控制器,还有一些工作要做,尽管他们可以通过调用ObjectRegistry类查找DAO.但是在一个干净的spring中,DAO应该在一个特殊的web xml配置文件中指定,这里是通过WEB-INF/SpringMVC-servlet.xml来指定的。

听上去相当简单,对不对?我现在只需要将对象注册的Spring配置文件(spring-objectregistry.xml)添加到web.xml中,像这样:

<context-param>
  <param-name>
    contextConfigLocation
  </param-name>
  <param-value>
    classpath*:spring-objectregistry.xml
  </param-value>
<context-param>



现在在spring-objectregistry.xml文件中定义的对象,对于WEB-INF/SpringMVC-servlet.xml中定义的那些对象是可见的。
这样做就产生了另外一个问题:MVC层将spring-objectregistry.xml和SpringMVC-servlet.xml文件装载到一个ApplicationContext中。对象注册将spring-objectregistry.xml装载到一个不同的ApplicationContext中。这两个ApplicationContext分别在各自的环境中,默认情况下,它们彼此看不到bean的定义。

定义在spring-objectregistry.xml文件中的对象被依次注册2次:一次是通过MVC层,然后是对象注册。这样,spring-objectregistry.xml文件中单态对象实际上已经不在是单个的了。在例子代码中这个都是无状态对象,但在一个稍大的应用中一些单个对象可能确实要保持状态。如果我不仅仅只是装载一次这些对象的话,可能会有一个同步的问题。就像如果一个对象要执行一些以前的资源优化(resource-intensive)的操作,那我的应用的性能将有所损失(译者:因为2个对象不一致)。

我的第一个反应就是重构那些ObjectRegistry中不需要的代码。那是一个简单的事情,但这是一个学习的过程。假设这是一个大的工程,其中移除注册类不是个一次就能做好的任务,我决定留着它并且解决怎样让2个环境工作的问题。

简而言之,我需要一些途径把ObjectRegistry (spring-objectregistry.xml)中的对象暴露给那些在web层(WEB-INF/SpringMVC-servlet.xml)中的对象。Spring的解决方案是用BeanFactoryLocator,它是一个applicationContext的注册点。我可以告诉对象注册点和MVC层从BeanFactoryLocator中装载这些共用对象。

首先,我必须修改ObjectRegistry类,像这样,它不在明确装载spring-objectregistry.xml文件:

import org.springframework....BeanFactoryLocator ;
import org.springframework.
    ...ContextSingletonBeanFactoryLocator ;
import org.springframework.
    ...BeanFactoryReference ;

// this was an ApplicationContext before
private final BeanFactoryReference _singletons ;

private ObjectRegistry(){

  // ContextSingletonBeanFactoryLocator loads
  //   contents of beanRefContext.xml

  BeanFactoryLocator bfl =
    ContextSingletonBeanFactoryLocator
      .getInstance() ;

  BeanFactoryReference bf =
    bfl.useBeanFactory( "OBJ_REGISTRY_DEFS" );
                         
  _singletons = bf ;

}

public Object get( final String key ){
  return( _singletons.getFactory().getBean( key ) ) ;
}



这个代码将ApplicationContext用一个BeanFactoryReference替换,从一个BeanFactoryLocator中取得名字为OBJ_REGISTRY_DEFS的对象。

接下来,将OBJ_REGISTRY_DEFS对象定义在一个叫beanRefContext.xml的文件中.

<beans>

  <bean
    id="OBJ_REGISTRY_DEFS"
    class="...ClassPathXmlApplicationContext"
  >
    <constructor-arg>
      <list>
        <value>spring-objectregistry.xml</value>
      </list>
    </constructor-arg>
  </bean>
</beans>



名字为OBJ_REGISTRY_DEFS的bean实际上是一个基于原来的对象注册配置文件spring-objectregistry.xml的ApplicationContext。在BeanFactoryReference上调用getBean()方法仅仅是传递了一个潜在的ApplicationContext。

那只是照顾了ObjectRegistry自己。为了让web层也能用OBJ_REGISTRY_DEFS,使得对象在web层的配置文件(SpringMVC-config.xml)可见,需要在web.xml中添加一些扩展点。

  <context-param>
     <param-name>
       parentContextKey
     </param-name>
     <param-value>
       OBJ_REGISTRY_DEFS
     </param-value>
  </context-param>

  <context-param>

     <param-name>
       locatorFactorySelector
     </param-name>

     <param-value>
       classpath*:beanRefContext.xml
     </param-value>
  </context-param>



第一个入口告诉web层的Spring配置,对于找不到的对象,它应该访问名字为OBJ_REGISTRY_DEFS的BeanFactoryReference.第二个告诉框架,装载classpath下的名字为beanRefContext.xml文件。

现在定义在spring-objectregistry.xml中对象对于web层(在SpringMVC-config.xml)的对象是可见的。意思是说我可以慢慢的淘汰ObjectRegistry,而不是一步就要做出很大的改动。

难看吗?是。耦合代码?是。当你已经有可自己的单态注册?(Absolutely)时,这是一种将你的应用移植到Spring的方法。现在这个应用从将来的重构中被屏蔽了,这个ObjectRegistry(和明确从ApplicationContext中加载的)的移出将仅仅影响ObjectRegistry的客户段代码。

然而,有一点警告:Spring的文档中也注明BeanFactoryLocator不是常用的,就像上面那样,它应该被用来移植对象。如果你打算在一个新的应用中用Spring,通过比较,你的设计从一开始应该说明合适的Ioc注入。

分享到:
评论

相关推荐

    Daycare-Spring:日托管理系统移植到 Spring Web 应用程序

    【标题】"Daycare-Spring:日托管理系统移植到 Spring Web 应用程序"涉及到的是将一个原有的日托管理系统转化为基于Spring框架的Web应用程序。这是一个关键的IT实践,因为Spring框架是Java领域中最广泛使用的开源框架...

    鲲鹏BoostKit Web使能套件 移植指南(Spring Cloud).pdf

    鲲鹏BoostKit Web使能套件移植...鲲鹏BoostKit Web使能套件移植指南提供了鲲鹏BoostKit Web使能套件的移植过程中的所有步骤和要求,旨在帮助开发者快速、成功地移植鲲鹏BoostKit Web使能套件到 Spring Cloud 环境下。

    javaee Struts2+Hibernate+Spring学生成绩管理系统

    在"javaee Struts2+Hibernate+Spring学生成绩管理系统"中,这三种技术被集成在一起,形成了一个强大的开发框架,用于实现对学生成绩的有效管理。 Struts2是Apache软件基金会的一个开源项目,它是基于MVC(Model-...

    TurboShop网店系统 v4.0.0 spring版

    TurboShop是一套使用强大、安全的JAVA语言开发,基于企业级J2EE架构设计的免费商城系统。整个商城逻辑业务搭建在我们自主研发的TurboPortal平台上,...本版本主要是对TurboPortal做了重大升级,全面移植到spring上。

    HR人事管理系统 spring mvc + spring + hibernate + bootstrap + mysql

    Hibernate支持多种数据库,包括MySQL,使得系统具有良好的可移植性。 Bootstrap是一款流行的前端框架,用于快速构建响应式和移动优先的网页。它提供了一系列预先设计的CSS和JavaScript组件,如导航栏、按钮、表单、...

    TurboShop网店系统 v4.0.0 spring版.rar

    TurboShop是一套使用强大、安全的JAVA语言开发,基于企业级J2EE架构设计的免费商城系统。整个商城逻辑业务搭建在我们自主研发的TurboPortal平台上,...本版本主要是对TurboPortal做了重大升级,全面移植到spring上。

    基于Spring MVC+Spring+Mybatis+Mysql 客户关系管理系统 SSM毕业设计

    这个基于SSM的客户关系管理系统(CRM)毕业设计,利用了Maven进行项目构建,确保了依赖管理的便捷性和项目的可移植性。 1. **Spring MVC**: - Spring MVC是Spring框架的一个模块,负责处理HTTP请求和响应。它提供了...

    车辆管理系统(struts+hibernate+spring+oracle).rar

    车辆管理系统是一种基于软件技术的解决方案,用于有效管理和跟踪组织内部的车辆信息,包括车辆状态、维修记录...这样的架构设计使得系统具有良好的可扩展性、可维护性和可移植性,能够满足不同规模组织的车辆管理需求。

    XmlRpc+Spring+Hibernate系统(源码)

    通过阅读和理解这些源码,你可以深入学习如何将XML-RPC、Spring和Hibernate集成到一个项目中,以及它们如何协同工作来构建一个呼叫系统。这样的项目对于理解分布式系统设计、数据库操作和依赖管理等核心概念非常有...

    spring boot 、spring cloud 、docker 微服务实战

    最后,《Spring Boot实战》由丁雪丰翻译,这本书将引导你逐步掌握Spring Boot的核心特性,从基础设置到高级主题,如Actuator的监控和健康检查、Spring Data的使用、安全配置等。 通过阅读这些实战指南,你不仅可以...

    spring详细教程

    这种设计使得业务逻辑更加纯净,并且易于移植到其他框架中。 - **容器管理**:Spring 提供了一个强大的容器,用于管理对象的生命周期和依赖关系。通过配置文件或注解,可以轻松地定义对象及其依赖关系。 - **IoC ...

    基于Spring Boot框架搭建的药店库存追踪与管理系统源码.zip

    7. 部署与运行:Spring Boot应用可以通过Maven或Gradle构建,然后使用Docker进行容器化部署,确保系统的可移植性和稳定性。 总的来说,这个基于Spring Boot的药店库存追踪与管理系统展示了如何利用现代Java技术栈...

    Nhibernate+Spring.net+asp.net mvc2 web系统框架

    其优势在于减少了数据库依赖,提高了代码可读性和可维护性,同时支持多种数据库,增强了系统的可移植性。 其次,Spring.Net是.NET平台上的轻量级框架,它提供了反转控制(IoC,Inversion of Control)和面向切面...

    springsecurity6.x实战学习笔记,可完美的移植到生产环境

    总之,这个实战学习笔记涵盖了SpringBoot 3.x与SpringSecurity 6.x的集成,以及如何构建一个适应生产环境的安全系统。通过学习和实践,你可以掌握如何利用这些工具设计和实现多账号登录、分离的数据库设计、精细的...

    dwr与spring集成的方式

    ##### 第二种方式:将DWR配置移植到Spring容器 这种方式不再使用DWR自带的dwr.xml文件,而是将所有配置直接放入Spring的配置文件中,如applicationContext.xml。这种方式的优点是可以进一步简化配置,使得整个系统...

    使用Spring Cloud和Docker构建微服务

    使用Spring Cloud和Docker构建微服务的过程中,Docker容器技术提供了一种轻量级、可移植、自给自足的运行环境,这使得微服务能够在任何支持Docker的机器上运行,而且对环境的依赖性降到最低。这在开发、测试和生产...

    Spring Persistence with Hibernate

    Spring框架是一个全面的后端开发框架,而Hibernate则是一个流行的对象关系映射(ORM)工具,它简化了数据库操作,将Java对象直接映射到数据库记录。两者结合,可以实现高效、灵活的数据管理。 Spring框架提供了多种...

    SpringBoot、 SpringCloud学习书籍

    通过这四本书的学习,读者可以系统地掌握SpringBoot和SpringCloud的全貌,从基础理论到实际操作,从单个服务的构建到整个微服务生态的搭建,全面提升对微服务架构的理解和应用能力。在企业级应用开发中,SpringBoot...

    论坛系统(Struts 2+Hibernate+Spring实现)

    Struts 2、Hibernate 和 Spring 是Java开发领域中三大著名的企业级开源框架,它们共同构建了一个强大的MVC(Model-View-Controller)架构,用于构建高效、可维护性高的Web应用,如本案例中的论坛系统。以下是这三大...

Global site tag (gtag.js) - Google Analytics