SMART系统是一个新型智能在线考试信息管理系统,该系统主要实现了学生在线考试与评估以及教师对学生在线考试信息的管理和维护。本文按照SMART系统的非功能性需求,基于Struts、Spring、Hibernate三种开源技术,构建了一个具有良好的可扩展性、可维护性、可靠性的系统框架。整个系统的框架分为三层,分别为表现层、业务层和持久层。 本系统的表现层是基于Struts作扩展设计,结合本系统的需求完成了自定义标签的封装,基本action接口的编写。在业务层则是采用单例模式设计与Spring的IoC模式相结合,实现了公共代理类的编写,各业务逻辑接口的封装。而在持久层的设计中则是采用基于现有持久层框架的实现模式,实现了对产生Session实例的封装,对常用数据库操作的封装。这样设计减少了耦合性且避免了生成大量的临时对象。
该系统框架能达到良好的可拓展性和维护性。它不仅仅适用这个系统的开发,可以应用于J2EE领域中基于SSH来架构的大部分B/S系统。
如图1所示,在整个SMART系统的总体框架中表现层是结合J2EE领域的开源框架Struts来实现的,Struts能充分满足应用开发的需求,简单易用,该框架是基于MVC模式的来构建的,该模式将表达层分解为自包含的和可重用的几个部分,当用户通过浏览器发起HTTP请求时,该框架将利用其ActionForm将请求页面的非对象化的数据转化为对象,交由其对应Action来处理。基于MVC模式的整个交互的序列图如图7所示:
开发人员利用该框架进行开发时,不用再自己实现全套MVC模式,节省了大量的开发时间。
如图1所示,在表现层与业务层之间利用一个公共代理类来完成交互,该代理类采用单例模式设计开发,在整个框架中起到了如下几点作用:
在该代理类,实现一个对相应业务逻辑的处理方法,该方法的参数为一个封装好相应的页面数据对象、要调用的业务类的名称及该业务类中相应的处理方法名的类。
在该层中利用了Spring框架中的IoC模式(英文全名为Inversion of Control即反转模式),该模式类似于著名的好莱坞原则:“Don't call us,we'll call you”,后被Martin Fowler改名为 Dependency Injection 依赖注射,也就是将类之间的关系通过第三方进行注射,不需要类自己去解决调用关系,实现了调用者和被调用者之间的解耦分离。IoC的引入并没有消除接口与实现类之间的联系,它的实质在于只是将这种联系转移了。在Spring的IoC实现中这关系被转移到相应的XML配置文件中,由Spring框架来提供对这种关系的依赖注入。其原理如图8所示:
在SMART系统的整体框架中的持久层,是采用基于现有持久层框架的实现模式,在这种模式中,将最为繁琐的基于JDBC的OR映射工作,交由第三方组件(本框架中采用开源的Hibernate)来完成,这样就会在对数据访问对象进行编码时,大大的简化了一些繁琐而又复杂的编码工作,只需要利用Hibernate提供的API,对持久化对象进行操作。在该持久层框架提供了优秀的性能优化机制,如内置的数据库连接池支持,PreparedStatement缓存、数据缓存等。这些优化机制的综合使用大大提升了系统的性能。
在SMART系统的持久层中,对一些常用的添加、删除、更新数据库操作进行了抽象封装。并在Hibernate中配置相应的数据库连接池实现。
在大部分采用B/S结构的web应用中,用户与系统的交互都是要涉及到相应的交互数据、业务逻辑,因此在本系统的框架设计中考虑到将这些交互中涉及到的因素全部封装到一个Carrier类中,再通过一个单例类来实现表现层与业务层的交互,这样用户操作时不用每次都去new一个临时的对象,也实现各功能模块中子程序之间的解耦。
Java程序设计语言提供了两种机制,可以用来定义一个允许多个实现的类型:接口和抽象类。两种机制之间最明显的区别是,抽象类允许包含某些方法的实现,但是接口是不允许的。一个更为重要的不同之处在于,为了实现一个由抽象类定义的类型,它必须成为抽象类的一个子类。任何一个类,只要它定义了所有要求的方法,并且遵守通用约定,那么它就允许实现一个接口,不管这个类位于类层次的哪个地方。因为Java只允许单继承,所以,抽象类作为类型定义受到了极大的限制。接口使得我们可以构造出非层次结构的类型框架。例如:假设有一个接口代表一个singer(歌唱家),另一个接口代表一个songwriter(作曲家):
public interface Singer{
AudioClip Sing(Song s);
}
public interface SongWriter{
Song compose(boolean hit);
}
在现实生活中,有些歌唱家本身也是作曲家。因为是使用接口而不抽象类来定义这些类型,所以对于一个类而言,它同时实现Singer和Songwriter是完全允许的。实际上还可以定义第三个接口,它同时扩展了Singer和Songwriter,并且加入一些适合于这种组合的新方法:
public interface SingerSongwriter extends Singer,Songwriter{
AudioClip strum();
Void actSensitive();
}
如果要满足这样的一种灵活性,抽象类是不可能完成的。
虽然接口不允许包含方法的实现,但是,使用接口定义类型并不妨碍你为程序员提供实现上的帮助,在本系统的接口设计与实现中借鉴了Java API中的将接口类与抽象类的优点结合起来,将期望导出的每一个重要接口,都提供一个抽象类的骨架实现(skeletal implementation)类。按照惯有编码命名习惯,将该骨架实现类命名为AbstractInterface
在该系统中采用这种方式的来设计的接口有Business(业务)接口,DAO(数据访问对象)接口。
在对系统的实现中,由于在表现层使用的是基于MVC的Struts框架,该框架中为了在表现层的JSP页面中不混合大量的Java代码,及保持JSP页面的程序的容易读性而提供了相应的Struts自己的一套标签,但是考虑到本系统的实际应用的功能实现。且这些功能实现又是Struts标签没法满足要求的,因此在该系统框架中实现了自己的一套标签,主要有
设计意图:
在一般的基于B/S结构的web应用系统中,在页面上经常是会涉及到添加、编辑、删除几种常用的功能,在早期的一些开发编码中,相当一部分人是将这几种功能分为多个页面来实现,例如:添加用一个add.jsp页面,编辑用一个edit.jsp页面,删除用一个delete.jsp页面。而这些的页面的代码80%以上都基本上是一样的,只不过是上显示的按钮及在点击相应的提交给后台处理方法不一样。为了达到在一个页面上面实现添加、编辑、删除功能,并且要保持页面代码的清晰可读性,因此,在本系统框架中,封装了自定义的提交标签。
表1
属性名称 |
属性描述 |
备注 |
Id |
该标签元素的id 。 |
|
Name |
该标签元素的名称。 |
|
Property |
当点击该标签所显示的内容后所提交的参数名称 |
|
Message |
该标签在页面上所显示的值,等同于html标签组里面的input标签的value属性。 |
|
displayControl |
是否要对该标签的内容是否在页面上显示出来作控制。 |
只有两种值 true/false |
Image |
该标签在页面显示的内容的背景图片。 |
|
onClick |
当点击该标签所展现的内容后所触发的事件。 |
设计意图:
在一般的基于B/S结构的web应用系统中,在页面上经常要用到下拉框,且在JSP页面加载出来时就有一组相应的值,为了在加载该页面时动态的、带条件的取其标签相应的键/值对,因此,在本系统框架中,封装了自定义的下拉菜单。
表2
属性名称 |
属性描述 |
备注 |
name |
该标签元素的名称。 |
|
property |
该标签元素属性名称,与Struts中的html标签的property元素是一样的含义,对应于FormBean里面的一个域。 |
|
content |
该标签属性为标签取值的业务代码。 |
|
multiple |
该属性同html标签组中的select标签的multiple属性。 |
|
size |
该属性同html标签组中的select标签的size属性。 |
|
style |
该属性同html标签组中的select标签的style属性。 |
|
relationValue |
该属性为在加载该标签相应的值时的条件。 |
大部分基于B/S结构的web应用系统中,在页面上经常会出现一个以上的功能按钮,而这些功能按钮基本上都是对应于后台的一个操作实现,由于在本系统中的表现层选用较为成熟Struts框架,该框架中最为核心的部分要属控制器控制转发相应的HttpRequest,其中的LookupDispatchAction类是允许你指定一个具有多个方法的类,每一个方法的调用都基于配置文件中指定的一个特殊请求参数值,利用该参数值反向查询资源绑定,并将它与类中的一个方法进行匹配。从这些功能可以看出Struts是满足对系统页面上多个功能按钮与实现的绑定。
因此,在对本系统进行架构设计的时候,考虑建立一个抽象的BaseAction类,该类继承LookupDispatchAction,实现LookupDispatchAction类中的getKeyMethodMap方法,在方法中返回本系统中请求参数值与资源文件中参数值的键/值对。实现一些对于所有的Action都是有可能用到的公共方法,包括从session中得到用户的信息;对页面上按钮是否显示的控制;检查用户权限;对公共业务逻辑接口的调用等等。在涉及到系统的具体开发实现的时候,要求所有开发人员在写自己的Action的时候统一继承BaseAction。
本系统的持久层是基于开源的Hibernate来实现的,在了解到相关的Hibernate特性后,在本系统的框架中,从如下几个方面对其进行了进一步的封装。
设计意图:
Session是Hibernate持久化操作的基础。注意这里Session的含义,它与传统意义上的Web层的HttpSession并没有什么关系。Hibernate Session之与Hibernate,相当于JDBC Connection与JDBC。
Session作为贯穿Hibernate的持久化管理器核心,提供了众多持久化方法,如save、update、delete,find等。通过这些方法即可透明地完成对象的增删改查(CRUD)。但是值得注意的是,Hibernate Session的设计是非线程安全的,也就是说,一个Session实例同时只可由一个线程使用,同一个Session实例的多线程并发调用将导致难以预知的错误。
因此在本框架中对通过SessionFactory所产生的Session。进行了线程安全性的处理,在实现的HibernateSessionFactory类新建一个Java中的ThreadLocal类,将每次产生Session放入该类中,这样就达到了线程安全性的效果了。
当产生Session而创建SessionFactory实例时,也要注意对SessionFactory重用的问题,因为SessionFactory中保存了对应当前数据库配置的所有映射关系,同时也负责维护当前的二级缓存和Statement Pool。由此可见,SessionFactory的创建过程必然非常复杂、代价高昂,而这也意味,在系统设计中要充分考虑到SessionFactory的重用策略。由于SessionFactory采取了线程安全的设计,可由多个线程并发调用,大多数情况下,一个应用中针对一个数据库共享一个SessionFactory实例即可。
设计意图:
在系统的设计架构中考虑到代码的重用性,因此对一些通用的数据库的操作都将其封装到一个公共类中,这样就减少了系统开发人员的代码编写工作量,也避免了同功能代码的重复编写。
在本系统中的表现层实现了一个抽象的BaseAction类,该类继承LookupDispatchAction,实现LookupDispatchAction类中的getKeyMethodMap方法,在方法中返回本系统中请求参数值与资源文件中参数值的键/值对。实现一些对于所有的Action都是有可能用到的公共方法。部分代码如下所示:
public abstract class BaseAction extends LookupDispatchAction {
//实现父类的方法
protected Map getKeyMethodMap() {
Map map = new HashMap();
map.put("button.save", "save");
map.put("button.cancel", "cancel");
…………………..
return map;
}
//统一的业务逻辑调用方法
protected Object call(Carrier vo) {
PublicProxy proxy = PublicProxy.getInstance();
Object obj = null;
try {
obj = proxy.process(vo);
} catch (ApplicationException ex) {
throw ex;
}
return obj;
}
//显示页面按钮
protected void showButton(String buttonName) {
Map btnMap = getButtonMap();
btnMap.put(buttonName, "true");
}
//隐藏页面按钮
protected void hideButton(String buttonName) {
Map btnMap = getButtonMap();
btnMap.put(buttonName, "false");
}
…………………………………
}
在本系统中的业务层实现了一个单例的PublicProxy类,系统中所有的表现层与业务层的交互都要通过这个类来实现。该类结合了Spring框架的相应的API对业务接口与其实现的对应关系的xml文件进行了解析,详见下面的类中的process方法中对xml文件的读取。
public class PublicProxy {
private static PublicProxy instance = null;
private static Object lock = new Object();
private PublicProxy() {
}
//返回唯一的实例
public static PublicProxy getInstance() {
if (instance == null) {
synchronized (lock) {
if (instance == null) {
instance = new PublicProxy();
}
}
}
return instance;
}
//相应的业务处理公共接口
public Object process(Carrier aop) {
Business business = null;
ApplicationContext context
= new ClassPathXmlApplicationContext("beans/*.xml");
business = (Business) context.getBean(aop.getBusiness());
return business.process(aop);
}
}
本系统的持久层是基于开源的Hibernate来实现的,结合Hibernate提供的API提供相应实现的部分代码如下所示:
public class HibernateSessionFactory {
…………………
private static final ThreadLocal thread = new ThreadLocal();
//打开一个新的session
public static Session openSession(boolean useCurrent) {
Session session = null;
if (useCurrent && thread.get() != null) {
session = (Session) thread.get();
}
if (session == null) {
try {
if (factory == null) {
factory = new Configuration().configure(
CONFIG_FILE_LOCATION).
buildSessionFactory();
}
session = factory.openSession();
//将session放入ThreadLocal中实现线程安全性设计
thread.set(session);
} catch (HibernateException ex) {
ex.printStackTrace();
}
}
return session;
}
………………
}
本次测试的环境的硬件环境与系统的开发环境中的硬件环境相一致,只是在软件环境中用了开源的Apache Jmeter,它是一个100%的纯java桌面应用,用于压力测试和性能测量。它最初被设计用于Web应用测试但后来扩展到其他测试领域。
在本次测试中,用Jmeter作了相应的压力测试,其他的测试都是通过手工写程序完成各层的功能测试,在通过对Jmeter的在用户数、循环次数的配置,对本系统框架作的几次压力测试。压力测试部分结果如下:
用Jmeter模拟了1000个用户并发一次请求,循环5次后的结果图如下:
用Jmeter模拟了5000个用户并发一次请求,循环5次后的结果图如下:
在对本系统进行的各层的功能测试中,在分层测试的时候,各层程序功能运行正常,对各层功能的衔接测试也达到了很好运行效果,从功能上、性能上都能够满足系统需求分析中的要求。
在对本系统进行的压力测试中,从JMeter测试结果图显示的样本数目、平均、偏离、吞吐量、中值的数值可以看出本系统是满足可伸缩性、可靠性要求的。
通过本次课题的研究,在结合J2EE领域比较优秀成熟的框架的基础上,对系统进行了分层架构,完成了对基于Struts表现层扩展与开发,加入适合本框架的一些特性。在表现层与业务层之间的衔接上,设计并实现了通过一个公共的代理类来交互,在基于Hibernate的持久层的设计与开发中,实现了对一些常用功能操作的封装。从总体上来说,实现了一个具有实用价值的框架,利用该框架来进行开发的SMART系统具有可扩展性、可维护性等优秀软件所要达到的特点。