概览
轻量级的企业应用开发越来越受到广大JEE应用开发者的追捧而Spring框架又是轻量级容器的杰出代表由于Spring的使用日渐广泛因此已有许多基于WebSphere应用服务器(WAS)的应用采用了Spring框架本文首先介绍使用Spring开发Web应用的基本问题然后结合WebSphere应用服务器讲述Spring应用如何结合容器提供的服务文章目的是与大家一起探讨如何更好的采用Spring框架开发基于WebSphere应用服务器的应用
Spring框架的主要思想描述
Spring框架的核心思想我们可以用两个字来描述那就是解耦应用程序的各个部分之间(包括代码内部和代码与平台之间)尽量形成一种松耦合的结构使得应用程序有更多的灵活性应用内部的解耦主要通过一种称为控制反转(IOC)的技术来实现控制反转的基本思想就是本来由应用程序本身来主动控制的调用等逻辑转变成由外部配置文件来被动控制通常我们用一个所谓的好莱坞原则(Dont call me I will call you)来比喻这种控制反转的关系由于控制反转的概念相对比较广泛很多应用服务器实际上也实现了不同程度的控制反转技术只是这些应用服务器对应用程序的侵入性太强因此Martin Fowler专门写了一篇文章讨论控制反转这个概念并提出一个描述更为准确的概念叫依赖注入(Dependency Injection)
Spring框架中的各个部分都充分使用了这种依赖注入的技术实现从而给应用以最大的灵活度实际上这种依赖注入的参数化应用控制并不是Spring的首创比如IBM的多渠道应用整合平台(Branch Transformation ToolkitBTT)很早就采用了这种外部参数化控制的技术BTT中的对象工厂与Spring框架中的BeanFactory也有着异曲同工之妙
Spring框架另外一个比较重要的技术是它对于面向切面的编程(AOP)的支持随着应用复杂度的逐渐上升和对应用灵活性要求的提高IT逻辑和业务逻辑尽量分离的呼声也越来越高AOP技术作为实现这种分离的一种比较好的途径而越来越受到大家的重视Spring提供的是一种动态AOP实现也即通过代理模式动态地在目标对象的方法前后插入相应的处理代码应用程序与底层应用服务器平台的解耦也可以借助AOP技术来实现Spring内置的AOP支持是一种锦上添花的功能它使得一些本来必须由容器支持的功能比如事务控制可以脱离开容器运行从而达到瘦身的目的这也是为什么Spring框架常被人成为轻量级容器的一个原因
Spring框架可以与许多已有的框架技术结合使用JEE技术应用的一个重要特点是相关的开源社区非常活跃Web应用的不同层次都有非常多优秀的开源框架存在比如Web层的StrutsOR映射层的Hibernate等Spring框架并不重新发明轮子它的出现不是为了替代这些已有的框架相反Spring框架在设计上可以独立构建应用或者结合已有的框架一起构建应用另外一个值得指出的地方是Spring框架的几大模块之间相互耦合度很小因此Spring框架的使用可以根据实际需要选其部分模块循序渐进的使用而非必须统统照搬
基于Spring的Web应用基础
Web应用的典型层次
Web应用一般在逻辑上根据功能分为以下几层
.展示层
这一层主要如何生成展示给最终用户的界面尽可能少的包含业务逻辑处理对于基于JEE的Web应用JSP是其最为常见的一种技术Spring对于展示层的支持非常灵活除了直接支持JSP之外它还支持基于FreeMarker模板基于Velocity模板或其它文档类型的界面等的表现层实现
.业务层
业务层一般包含主要的业务逻辑尤其是与用例相对应的那些业务逻辑另外这一层也适合包含事务管理和安全控制方面的逻辑良好的业务层设计可以使得展示层可以采用不同的技术而不影响业务层业务层的功能上可以类比于JEE技术中的无状态会话BEAN层次
.数据访问对象(DAO)接口层
DAO实际上就是数据接口层在应用中建议通过接口来体现DAO的存在使得数据访问可以与底层持久化层的具体实现相分离一般在DAO接口中主要就是实现数据对象的查询存储删除等操作从理论上讲DAO层与底层数据的存储方式是独立的也就是说并不一定要求是关系型数据库Spring框架在设计的时候也考虑到了其它非关系型数据库数据源的情况
.持久业务对象
持久业务对象是问题域中业务对象的持久化表示比如一个用户对象一个银行帐户等我们一般通过某种O/R映射技术来实现这些业务对象的持久化持久业务对象是可以包含业务逻辑的与业务层所包含的业务逻辑不同的地方是持久业务对象所包含的是与具体业务对象直接相关且更为通用的业务逻辑
.企业信息系统
企业信息系统泛指Web应用需要连接的后台系统一般可以分为三大类即ERP系统企业传统的遗留系统和关系型数据库大部分Web应用都是基于关系型数据库的这也是像Spring等常见框架所主要考虑的企业信息系统
设计良好的Web应用在层次一般是上一层依赖下一层但是下一层不依赖上一层我们可以暂时概括为向下而不向上依赖原则为了使得不同层次之间的依赖降到最低建议使用接口耦合这一点又是Spring框架发挥它外部配置优势的地方
MVC的选择
虽然说MVC这种模式早在Java语言出现前就有了但是这种模式在JEE时代才大行其道为广大Web应用开发者所接受对于各种基于MVC的框架而言其要解决的问题主要可以分为以下几部分
.将Web页面中的输入封装成一个数据对象比如像Struts的表单BEANSpring MVC中的命令类等
.根据请求的不同由负责分发的控制器来映射和调用相应的逻辑处理单元并将上面的数据对象作为参数传入
.逻辑处理单元完成相应的处理之后又把结果放入一个数据对象
.在选择的展现界面中把返回的数据对象通过某种方式显示出来
在使用Spring构建MVC的时候可以选择直接使用Spring自己的MVC实现或者利用Spring对已有的一些MVC框架的支持比如Spring可以支持StrutsWebWork等与它们结合使用Spring引以为傲的非侵入的特性在Spring MVC上表现得并不如人意它与Servlet API的耦合度较其它部分高而且需要使用一些Spring的接口和类
Spring MVC的主要分发器实现是orgspringframeworkwebservletDispatcherServlet这是Spring MVC的访问入口Spring提供SimpleFormControllerAbstractCommandController等类来帮助应用构建各种控制器动作并用ModelAndView类来联系展示和逻辑返回数据如上节所述Spring MVC能够支持不同的界面展示技术而且界面的展示和其后面控制器的实现是分离的也即界面展示技术的变化不用修改控制器的实现只需要利用Spring的控制反转技术修改外部配置文件即可比如在使用JSP展示技术时外部配置文件的viewResolver定义如下
<bean id=viewResolver
class=orgspringframeworkwebservletviewInternalResourceViewResolver>
<property name=viewClass>
<value>orgspringframeworkwebservletviewJstlView</value>
</property>
<property name=prefix><value>/view/</value></property>
<property name=suffix><value>jsp</value></property>
</bean>
如果切换到FreeMaker模板技术那么除了页面模板的修改之外主要就是把对应的外部配置文件更改一下即可如下所示具体的展示逻辑部分不用做什么修改
<bean id=viewResolver
class=orgspringframeworkwebservletviewfreemarkerFreeMarkerViewResolver>
<property name=viewClass>
<value>
orgspringframeworkwebservletviewfreemarkerFreeMarkerView
</value>
</property>
<property name=suffix><value>ftl</value></property>
</bean>
<bean id=freemarkerConfig
class=orgspringframeworkwebservletviewfreemarkerFreeMarkerConfigurer>
<property name=templateLoaderPath>
<value>/view/</value>
</property>
</bean>
如果不使用Spring的MVC框架而想结合已有的一些MVC框架Spring也是支持的Spring对于常见的MVC框架都提供了支持包括StrutsWebWorkTapestry和JSF等结合使用这些框架的一个好处是可以使用一些已有的熟悉的技术另外结合Spring的AOP拦截器可以相对比较容易地处理框架动作共有的事情比如动作的日志处理等如果选择这些MVC框架那么在使用框架的配置文件和应用的Spring配置文件都需要做相应的修改比如使用Struts的时候Strutsconfigxml配置文件中的映射动作类型一般会设置成orgspringframeworkwebstrutsDelegatingActionProxy或者设置控制器为orgspringframeworkwebstrutsDelegatingRequestProcessor然后需要在相应应的WebApplicationContext中定义与Struts Action对应的Bean这样就可以充分利用Spring的控制反转技术来管理Struts的Action了
另外在使用这些框架的时候要解决的一个问题是上下文的装载比如使用Struts可以使用ContextLoaderPlugin来装载Web上下文这个ContextLoaderPlugin替换了原来通过DispacherServlet装载的方式需要在strutsconfigxml文件中添加如下条目<plugin className=orgspringframeworkwebstrutsContextLoaderPlugIn/>这种方式可以使Spring的Web上下文随着Struts ActionServlet的初始化而装载
因此如果用户已有的应用是基于某个MVC框架或者用户熟悉某个框架那么可以利用Spring对这些框架的支持结合使用因为我们的目的本来就是为了解决问题而不是为了使用某种技术但是对其它用户而言如果不是对已有的一些MVC框架比较熟悉的话那就直接使用Spring的MVC框架就可以了
Web Context设置
对于不依赖于应用服务器的Spring 上下文(Context)设置通常在应用代码中通过FileSystemXmlApplicationContext或ClasspathXmlApplicationContext来获取比如使用这样的代码来得到上下文
ApplicationContext ctx = new FileSystemXmlApplicationContext(configxml);
但是按照控制反转的原则应用程序代码应该尽可能少的知道上下文的设置因此在基于Spring的Web应用中这样的代码也可以省去Spring可以通过配置让Web容器自动装载上下文配置文件从本质上讲Web应用的ServletContext就是Spring用来存放应用上下文的地方Spring中与Web Context装载相关的有几个类
.ContextLoaderListener一般的应用服务器如WAS都能先装载Listener如果不是的话那么只能使用ContextLoaderServlet
.ContextLoaderServlet需要配置<loadonstartup>使得它率先装载真正装载Context的类是ContextLoader上面两个类只是两种调用ContextLoader的不同途径ContextLoader内部实际调用的是XmlWebApplicationContext其缺省配置文件为/WEBINF/applicationContextxml
如果使用ContextLoaderListener其在webxml中的配置一般如下
<listener>
<listenerclass>
orgsprntextContextLoaderListener
</listenerclass>
</listener>
如果使用ContextLoaderServlet其在webxml中的配置一般如下
<servlet>
<servletname>context</servletname>
<servletclass>
orgsprntextContextLoaderServlet
</servletclass>
<loadonstartup></loadonstartup>
</servlet>
应用本身可能除了基于HTTP的Web渠道之外还通过别的渠道对外提供服务因此一个比较好的做法是把展示相关的配置与后面业务处理部分的配置分开这样如果更改了应用的访问渠道只需要修改对应的配置文件即可因此Spring提供了一个WebApplicationContext的概念在WebApplicationContext中一般包含与Web访问相关的配置定义包括各种控制动作的定义界面展示的定义等等
WebApplicationContext一般由DispatcherServlet来初始化在上下文层次结构上可以把它看成是ApplcationContext的子上下文在缺省的情况下DispatcherServlet装载的配置文件名称为其Servlet名称Servletxml但是可以通过contextConfigLocation参数来定制DispatcherServlet在webxml中的定义示例如下
<servlet>
<servletname>Dispatcher</servletname>
<servletclass>
orgspringframeworkwebservletDispatcherServlet
</servletclass>
<loadonstartup></loadonstartup>
</servlet>
<initparam>
<paramname>contextConfigLocation</paramname>
<paramvalue>/WEBINF/context/Webcontrollersxml</paramvalue>
</initparam>
数据持久化层
虽然使用JEE技术的Web应用可以连接多种不同的企业信息系统(EIS)但是毫无疑问数据库是其中最为重要和常见的一种正因如此Spring对数据库访问提供了非常完备的支持数据访问对象(DAO)模式是JEE模式中非常重要的一种它的主要目的是使得持久化层与业务逻辑层分离从而屏蔽持久化层的具体实现我们可以把Spring的DAO支持分为两大类一是直接基于Spring JDBC模板的数据访问另一类是基于某种O/R映射框架的数据访问这里刚好可以使用Spring的控制反转特性通过外部配置文件来定义DAO接口和实际实现类之间的关系Spring框架目前支持的O/R映射框架包括HibernateJDOTopLinkiBATIS等
假设我们定义了一个userDAO当使用JDBC来实现这个DAO的时候定义的类可以如下所示
public class userDAOJDBC extends JdbcDaoSupport implements userDAO{ … }
如果使用Hibernate来实现这个DAO的时候定义的类如下
public class UserDAOHibernate extends HibernateDaoSupport implements UserDAO { … }
Spring对于其它的O/R映射机制都有相应的抽象类供应用使用比如对于iBATIS有SqlMapClientDaoSupport对于JDO有JdoDaoSupport等
下面我们看一下如何在Spring的配置文件中定义上述DAO与具体实现的关系假设我们的userDAO具体实现是通过Hibernate那么在applicationContextxml中的一个DAO可以定义如下
<bean id=userDAO class=comfgwdaohibernateUserDAOHibernate>
<property name=sessionFactory>
<ref local=sessionFactory/>
</property>
</bean>
在这里我们实际DAO接口定义是comfgwdaoUserDAO而具体实现类为comfgwdaohibernateUserDAOHibernate显然对于其它DAO的实现我们只需要在配置文件中修改相应的实现类(具体实现类当然是比不可少的)和属性即可比如对于JDBC的DAO实现属性就定义成相应的数据源
Spring与WebSphere应用服务器的配合
Spring与底层JEE应用服务器还是存在一些需要结合的地方这里给出WAS中的一些结合点
使用WAS数据源
在Java应用程序中数据库的连接一般有两种方式来得到一种是通过javasqlDriverManager的方式来得到数据库连接这种方式不依赖于应用服务的支持但是也不提供数据库连接池的功能另外一种方式是通过javaxsqlDataSource的方式来得到数据库连接在传统基于JEE的应用需要通过JNDI来得到数据源(javaxsqlDataSource)对象然后再通过数据源来得到相应的数据库连接常见的应用服务器都支持这种方式且一般都提供了数据库连接池的支持虽然说我们一般推荐使用数据库连接池但是也有一些时候我们需要脱离开应用服务器的环境使用数据库(比如单元测试比如应用移植等)然而应用程序使用这两种方式的时候代码是不一样的因此只能通过代码来应变Spring提供了一个统一使用数据源的解决方案然后通过控制反转的机制用外部配置文件来指定使用的数据源这样一方面可以统一这两种得到数据库连接的方式另一方面也不需要像通常的JEE应用通过繁琐的JNDI代码来得到数据源这样应用程序也就不需要知道使用的何种数据源
Spring提供了一个DriverManagerDataSource类来统一第一种方式的数据源获取如果使用WAS中的Cloudscape数据库用外部配置文件可配置如下
<bean id=dataSource class=orgspringframeworkjdbcdatasourceDriverManagerDataSource>
<property name=driverClassName>
<value>comibmdbjjdbcDBjDriver</value>
</property>
<property name=url>
<value>jdbc:dbj:D:\\DBName</value>
</property>
</bean>
Spring提供了JndiObjectFactoryBean类来支持第二种方式的数据源获取假设WAS中已经配置好的数据源名称为jdbc /MyDB那么用外部配置文件可配置如下
<bean id=dataSource
class=orgspringframeworkjndiJndiObjectFactoryBean>
<property name=jndiName><value>java:comp/env/jdbc/MyDB</value></property>
</bean>
或者
<bean id=dataSource
class=orgspringframeworkjndiJndiObjectFactoryBean>
<property name=jndiName><value>jdbc/MyDB</value></property>
<property name=resourceRef><value>true</value></property>
</bean>
从上面配置我们可以得知通过使用Spring应用程序能够统一使用不同的数据源实现如果使用环境发生变化那么只需要修改Spring的配置文件即可对于部署在WAS上的Web应用在生产环境中推荐使用WAS实现的数据库连接池一方面是因为连接池实现地比较完善另一方面使用WAS提供的数据库连接池可以很完善地支持JTA事务
使用WAS的JTA
Web应用程序在使用事务的时候常常会涉及一个事务类型的选择是选择像JDBC事务这样的本地事务呢还是使用JTA支持的全局事务这个与应用程序需要涉及到的事务管理器类型和个数密切相关Spring本身不支持分布式事务因此分布式事务需要底层的JTA但是Spring提供了事务的抽象即底层真正事务实现可以切换而不影响应用程序代码这样应用程序可以依赖于底层WAS也可以轻易地脱离开应用服务器的环境这一点与前面数据源的抽象非常类似
WAS本身对于事务划分有两种支持方式一种是声明式的当然这种管理方式需要EJB容器的支持即所谓的容器管理事务(CMT)另外一种方式是编程式的通过程序代码来直接使用JTA编程接口Spring对于事务的划分也可以分为声明式和编程式两种方式对于Spring编程式的事务划分方式总体上可以分为两大类一类是通过直接使用实现PlatformTransactionManager接口的类另一类是通过使用TransactionTemplate模板类模板类的使用可以简化事务控制代码Spring对于声明式事务划分的支持实际上是利用了它的AOP机制相对于编程式事务划分这种基于AOP的方式比较灵活而且对代码的侵入性几乎为零因此如果没有特殊需要推荐使用这种事务划分方式基于AOP的常用事务划分方式可以使用ProxyFactoryBean加TransactionInterceptor方式或者使用TransactionPorxyFactoryBean的方式前一种方式相对比较灵活而后一种则对使用相对比较简单
无论是哪一种事务划分方式底层都需要一个事务管理机制作为支撑如果是单一的事务资源管理器那么根据所使用的后台事务管理资源不同的类型可以选择的PlatformTransactionManager实现有DataSourceTransactionManagerHibernateTransactionManager JdoTransactionManager PersistenceBrokerTransactionManager和JmsTransactionManager等无论是单个还是多个事务资源管理器都可以使用JtaTransactionManager类如果使用JtaTransactionManager那么所有事务管理实际都会委托给底层应用服务器的JTA实现
例如如果使用JDBC或iBATIS那么我们可以使用简单的DataSourceTransactionManager外部配置文件片断如下
<bean id=transactionManager
class=orgspringframeworkjdbcdatasourceDataSourceTransactionManager>
<property name=dataSource>
<ref local=dataSource />
</property>
</bean>
如果使用Hibernate那么我们可以使用HibernateTransactionManager外部配置文件片断如下
<bean id=transactionManager class=orgspringframeworkormhibernate
HibernateTransactionManager>
<property name=sessionFactory><ref local=sessionFactory/></property>
</bean>
使用WAS的JTA支持我们只需要把上述对应bean中的class属性改成class属性改为orgspringframeworktransactionjtaJtaTransactionManager然后再把属性改为WebSphere对应的TransactionManager参考如下
<bean id=wasTxMgr
class=orgspringframeworktransactionjtaWebSphereTransactionManagerFactoryBean/>
<bean id=transactionManager
class=orgspringframeworktransactionjtaJtaTransactionManager>
<property name=transactionManager>
<ref local=wasTxMgr/>
</property>
</bean>
通过采用Spring的事务支持底层事务采用何种方式的决定就不必在一开始开发就做出决定因为我们能够通过Spring的外部配置文件来进行切换真正的事务支持不过虽然也有第三方的JTA支持但是WAS能够提供非常稳定的XA支持因此推荐使用WAS的JTA尤其是当应用涉及到分布事务处理的时候这样无论应用涉及几个事务资源都可以统一解决
如何加载Spring的JAR包
Spring框架的核心JAR包是springjar但是根据实际使用情况需要一些扩展JAR包和依赖JAR包那在WAS中如何处理这些JAR包文件呢?在Web应用中一个简单而直接的处理方式放是把这些使用到的JAR文件都拷贝到对应的WEBINF/lib目录下面这种方法虽然简单但是当有多个Spring应用程序的时候这种处理方式就需要在每个应用的WEBINF/lib目录下都拷贝一份相同的JAR文件这里可以通过共享库的方式来统一解决类库共享这个问题
共享库就是WAS专门用来解决不同应用程序之间共享JAR或本地库文件的一种机制共享库由一个名字一个JAVA类路径和/或一个装载JNI库本地库路径组成它可以分别在单元节点和服务器级别定义但是共享库定义了并不意味着它会被装载只有当这个共享库与某个应用程序或应用服务器关联之后它才会被加载如果一个共享库与一个应用程序关联那么这个共享库由应用程序类加载器加载如果一个共享库与应用服务器关联那么这个共享库就需要一个专门定义的类加载器来加载这个类加载器需要用户自己定义其操作如下选应用服务器比如server类加载器新建一个类加载器加载器与共享库关联
在创建这个类加载器之前一般都需要预先定义好共享库 根据上面的介绍可知通过共享库解决Spring应用的JAR包共享问题主要就是两个步骤一是把Spring应用中需要共享的JAR包定义成为一个共享库二是选定相应的WAS服务器实例把它与上面创建的共享库关联起来这样此WAS服务器实例上的所有应用都能够使用共享库中定义的JAR包使用共享库这种方式的时候要注意理解类的装载次序和方式如果是这种与WAS服务器实例关联的共享库JAR包其类加载器在层次结构上在应用程序类加载器上面即是它的父加载器关于WAS的类装载器结构和策略可以进一步参考WAS信息中心
结束语
Spring框架的核心内容并不依赖于任何容器但是显然基于Web的应用是Spring主要的应用类型了解和使用Spring框架一方面可以简化应用的开发和测试另一方也可以加深对JEE技术的理解另外轻量级的Web应用开发正在成为一种趋势因此何乐而不为之上面所讨论的只是Spring使用中常见的一些内容Spring框架自己也正变得越来越复杂当然SpringHibernate等框架中体现的一些思想也正被JEE 规范所借鑒尤其是EJB 中也有了控制反转的应用和POJO的大量使用实际上无论是JEE技术标准还是Spring等框架其目的都是如何简化企业应用的开发只是作为标准JEE要考虑的内容更为广泛一些程度也更为深入一些