迁移到Spring

我是这个团体的后来者。该团体就是 Spring framework团体。对于后来者来说,Spring是基于 Apache 2.0许可证发布的基础架构代码库。其核心是 反转控制容器,开发团队围绕该容器为JDBC样板代码和JMS代码、web MVC框架等创建模板。

  我之所以说自己是后来者,是因为,尽管Spring已非常成熟和公开,但我仍然花了一段时间来试用它。我的问题是,“Spring可以为我做什么?”为了找出答案,我将现有的参考应用程序换成了Spring组件。我认识到很早以前就应该开始使用Spring,现在应用程序的代码没有以前那么混乱,应该更易于调试和扩展,并且它更为清楚,因为借助Spring对应的内容,可以丢弃一些自定义的helper代码。

  在本文中,我将和大家共享我在尝试中得到的想法和发现。具体地讲,我将解释如何使用Spring组件替换参考应用程序的单例注册库(Singleton registry)、JDBC代码和web前端层。还将描述遇到的障碍和我的解决方法。

  要阅读本文,您不必是Spring专家,但是我在后面的内容中提供了Spring资源的链接

  样例代码使用Sun的JDK 1.5.0_07 for Linux,在Spring 1.2.x中进行了测试。

原(旧)代码

  我不想对实际的生产应用程序进行试验,因此从我编写的另一篇文章中摘出了一个测试应用程序。它是一个简单的Java web应用程序,以两个servlet页面控制器作为入口点。这些servlet通过数据访问对象(DAO)与数据库交互,而该DAO从本地DataSource获取数据库链接。相关对象调用单例注册库来相互查找。具体而言:

  • SimpleDAO:向数据库传输信息对象或从中传输信息对象
  • DbUtil:用于处理JDBC ResultSet、Connection等内容的方便例程
  • ObjectRegistry:单例注册库,对象通过它进行相互查找
  • SetupDataSourceContextListener:设置JDBC DataSource
  • SetupDBContextListener:准备(嵌入式)数据库
  • GetDataServlet:用于显示数据的页面控制器
  • PutDataServlet:用于存储数据的页面控制器

  这是一个非常简单的web应用程序,但它是独立的(self-contained)并且能展现出大型N-层应用程序的行为。从这种超小规模的试验观察到的结果可以应用于实际的转换项目。

更改内部结构:对象注册库

  仔细观察的第一个类是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对的大规模映射。可以在该注册库的某个位置存储一个对象(put()),然后从另一个位置获取该对象(get())。使用注册库削弱了对象的依赖关系,因为获取对象的代码只需了解其常规类型(接口或超类)和查找键。具体的实现、实例化和配置由调用put()来存储该对象的代码实现。

  这可以正常工作,我曾经看到过这在更大规模的项目中正常工作,但它绝非完美。put()丢失或位置错误可以导致空指针错误或堆栈溢出。还必须跟踪对象在注册库中的存储顺序,以确保不会尝试获取不存在的对象。在小型应用程序中,可以使用ContextListener(本文中我也是如此)来处理实例化顺序,但是在较大的应用程序中,需要更多工作才能避免出现问题。

  旧的单例注册库的另一个问题在于:显式的put()操作是Java调用。这意味着对存储对象实现的任何更改(比如说,您希望为测试存根转入您的数据库支持的DAO)都需要重新编译。一次错误的签入,我的实际生产应用程序使用了该DAO存根。同样,这在较大的应用程序中也难以跟踪,因为它在代码中隐藏得很深。

  只需一小段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文件中读取所有对象定义,而该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元素对应于Reflection调用:外部的<bean/>元素定义了一个对象,内部的<property/>元素对该对象调用mutator方法。例如,对于id为DAO的bean,Spring首先实例化一个SimpleDAO类型的对象,然后对其调用setDataSource()。setDataSource()的参数是bean DataSource,在这个文件前面的内容中定义了该bean。

  Spring在幕后配置DataSource,并将其指派给此DAO。其他Spring管理的对象只需通过bean名称(“DAO”)即可引用该DAO,这样,它们不必了解其实现中的更改(此处为“SimpleDAO”)。

  既然Spring管理这些对象,ObjectRegistry对于客户端代码就是只读的。我可以从ObjectRegistry类中删除put()方法,同样可以删除其他类中的显式put()调用。例如,SetupDataSourceContextListener现在只需使用其初始连接来填充该池。

  现在web.xml部署描述符中也只有一些必需内容了。例如,一些上下文参数指向JDBC之类的本地属性文件。Spring现在收集使用这些属性文件的对象,自己为其赋值。

  Spring还负责在spring-objectregistry.xml中跟踪这些对象之间的依赖关系。以前我在代码中自己处理这些事务。现在,随着在该应用程序中使用的依赖注入越来越多,Spring将确保在客户端代码尝试使用这些引用对象之前,以正确的顺序创建这些对象。这意味着除了整理代码以外,Spring还承担了一些簿记工作。

  有人可能会说,“纯”IoC方法可以消除对显式的、可调用ObjectRegistry的需求,而让Spring在运行时管理对象关系。这将作为将来的重构考虑。这会在将来造成一点小问题,但是现在我仍然需要注册库。

更改数据层:Spring JDBC

  配置自定义DataSource是XML可以完美实现的工作。Spring还提供了DAO基类,用于消除JDBC样板代码。这意味着Spring framework负责管理连接和关闭ResultSet和PreparedStatement。剩下的是我的应用程序特有的代码。

  新的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现在扩展Spring的JdbcDaoSupport,JdbcDaoSupport有几个用于处理数据库工作的方法和内部类。其中第一个方法是setDataSource(),它为该对象指派一个JDBC DataSource。子类调用getDataSource()来获取该对象。

  initDao()是从JdbcDaoSupport继承的另一个方法。父类调用该方法为其子类提供运行任何一次性初始化代码的机会。此处,SimpleDAO为其成员变量赋值。

  成员变量也是新的:转移到Spring JDBC意味着将的SimpleDAO的功能(获取和存储数据)转变成专门的内部类(如GetDataWorker和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是Spring模板类,用于处理SQL INSERT和UPDATE调用的复杂工作。declareParameter()调用告知Spring该SQL语句使用的数据类型,分别是一个字符串和一个数字。

  注意,PutDataWorker类是一个非常简单的类。它调用super()向其父类传递DataSource和SQL语句,并调用declareParameter()来描述该查询。SqlUpdate处理实际与JDBC相关对象的交互工作,并关闭连接。SimpleDAO.putData()也同样被简化了:

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()使用一些无意义的数据填充该数据库。注意,该方法委托给其工作类。具体而言,委托给其工作类继承的方法。SqlUpdate.update()负责获取数据,并关闭JDBC Connection和相应的Statement对象。这意味着我可以丢弃很多自定义JDBC代码,甚至整个类:旧的DbUtil拥有方便的方法,用于关闭Connection、Statement和ResultSet。

  SqlUpdate对更新调用所作的工作,Spring的MappingSqlQuery对查询也作同样处理。注意,GetDataWorker中的方法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交互,但是它不另外负责close(),或检查是否还有一些行需要处理

更改Web层:SpringMVC

  既然已经使基础架构和数据代码Spring化,下面该处理web层了。此处的中心类是Spring MVC的Controller接口。

package org.springframework ...

interface Controller {

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

  入口点handleRequest()的签名与Struts Action类甚至与自制的页面控制器相似:该框架提供了servlet请求和响应对象,该控制器实现返回了一些业务逻辑的结果(模型),以及用于指导如何显示这些内容的指示器(视图)。ModelAndView模型是将在视图层操作的业务对象或其他对象的Map。在样例代码中,视图是JSP,但Spring MVC还支持Velocity模板和XSLT。

  样例应用程序的servlet页面控制器非常简单,以至于无法表现Spring MVC的强大功能。因此,更新的页面控制器无法与大多数课本示例相比。

  旧的页面控制器在从servlet向JSP传递Map对象时使用类似的策略。因此,我没有为此对JSP进行任何更改。也不必使用任何Spring特有的标签库。

  在新的基于Spring的控制器中仍然需要做一些工作,尽管:它们调用ObjectRegistry来查找DAO。在纯Spring世界中,应该在web特有的XML配置文件(此处为WEB-INF/SpringMVC-servlet.xml)中将DAO指派给控制器。

  这听起来很简单,是不是?我向web.xml添加了该对象注册库的Spring配置文件spring-objectregistry.xml,如下所示:

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

  现在,在WEB-INF/SpringMVC-servlet.xml中就能看到spring-objectregistry.xml中指定的对象。

  这么作会造成另一个问题:MVC层将文件spring-objectregistry.xml和SpringMVC-servlet.xml加载到一个ApplicationContext中。对象注册库将spring-objectregistry.xml加载到不同的ApplicationContext中。这两个ApplicationContext存在于自己的世界中,默认情况下,无法看到对方的bean定义。

  同样,spring-objectregistry.xml中定义的对象也将被加载两次:一次由MVC层加载,另一次由对象注册库加载。然后spring-objectregistry.xml中的“单例对象”实际上不再是单例的。在样例代码中,这些都是无状态的对象。但是在较大的应用程序中,一些单例对象可能会有状态。如果不一次性加载这些对象,并且仅加载一次,就会遇到同步问题。如果这类对象要执行一些一次性的资源密集型操作,应用程序的性能就会降低。

  我的第一反应是重构ObjectRegistry之类的不必要代码。这项工作很简单,但这是一个学习练习。要模拟较大的项目(其中删除注册库不再是一次就能完成的工作),我决定坚持这样作,并找出如何才能使这两个世界工作。

  简而言之,我需要使用某种方式将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,名称为OBJ_REGISTRY_DEFS,是从BeanFactoryLocator中得到的。同样,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。

  ApplicationContext自己负责ObjectRegistry。要使web层也使用OBJ_REGISTRY_DEFS(也就是,要使web层Spring配置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中定义的对象可以被SpringMVC-config.xml中的web层对象看到。这意味着可以逐步淘汰ObjectRegistry,而不是尝试一步实现这样影响深远的更改。

  不好看?是的。粘合代码?是的。这是当您已经拥有自己的单例注册库时,将应用程序迁移到Spring的一种方式?毫无疑问。现在,此应用程序将来不再需要重构:删除ObjectRegistry(及其显式加载的ApplicationContext)只会影响ObjectRegistry的客户端代码。

  但是有一点警告需要注意:Spring文档说明BeanFactoryLocator并非用于日常使用。它应该用于这类迁移项目。作为对照,如果您计划对新应用程序使用Spring,您的设计应该从头考虑正确的IoC注入。

结束语

  将原应用程序与更改后的应用程序进行比较,我首先注意到的是规模上的差异,代码减少了,这更易于调试。此外,让Spring处理这么多的对象实例化和依赖关系跟踪,意味着在系统增长时不会遇到太多烦恼。通过IoC注入还能简化测试,通过更改XML文件可以转出DAO实现。

  从更高的角度来看,我仔细考虑了自己使用的特殊Spring组件。对象查找和JDBC模板看起来很熟悉。在大型项目中,通常拥有处理对象查找或数据库连接的框架代码。这自然会提高Spring的实用价值。以前我看到过这种情况,现在我们从一个项目移动到另一项目时不必再编写这些内容。作为额外的优势,Spring团队可以重点增强自己的产品,而我们则关注自己的产品。

  采用Spring的另一个优势是它不是要么完全成功要么完全失败的行为。可以在不影响其他层的情况下,对各个应用程序层应用Spring,因此在此过程中可以随时停止。例如,如果我只希望利用Spring的JDBC模板,可以只更改DAO,而不修改应用程序的其余部分。

  尽管我是Spring团体的后来者,但是我非常高兴能够成为其中的一员。

资源

  • “What is Spring”的第一部分第二部分(作者Gehtland和Tate),提供了对Spring Framework的简要介绍。
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Spring Boot 是一种基于 Spring 框架的快速开发框架,它通过自动配置和约定大于配置的方式大大简化了 Spring 应用程序的开发和部署。如果你有一个使用 Spring XML 配置的应用程序,并且想将其迁移Spring Boot,可以按照以下步骤操作: 1. 引入 Spring Boot 相关依赖 在 pom.xml 文件中引入 Spring Boot 相关依赖,包括 spring-boot-starter、spring-boot-starter-web 等,以及你所需要的其他依赖,例如数据库驱动等。 2. 删除 Spring XML 配置文件 将原有的 Spring XML 配置文件删除,因为 Spring Boot 默认使用注解配置方式,不需要使用 XML 配置文件。如果你仍然需要使用 XML 配置文件,可以通过在启动类中添加 @ImportResource 注解来加载 XML 配置文件。 3. 创建 Spring Boot 启动类 创建一个类,并在类上添加 @SpringBootApplication 注解,这个注解包含了 @Configuration、@EnableAutoConfiguration 和 @ComponentScan 三个注解的功能,分别用于配置、自动配置和扫描组件。 4. 配置数据源 如果你的应用程序需要使用数据库,可以在 application.properties 文件中配置数据源相关属性,例如数据库 URL、用户名、密码等。 5. 替换 Spring 框架中的类 如果你的应用程序中使用了 Spring 框架中的一些类,例如 JdbcTemplate、JmsTemplate 等,可以将它们替换为 Spring Boot 中提供的对应类,例如 JdbcTemplate 可以替换为 NamedParameterJdbcTemplate。 6. 运行应用程序 运行应用程序,检查是否能够正常启动,并且能够访问你的应用程序。 以上就是将 Spring XML 配置迁移Spring Boot 的基本步骤,根据你的实际情况可能还需要进行其他修改和调整。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值