[tips]--基于struts的简单应用开发模式

3 views
Skip to first unread message

James Chan

unread,
Jan 8, 2006, 9:48:01 PM1/8/06
to program...@googlegroups.com

基于struts的简单应用开发模式

Mr. James Chan

Jan 8 2006


Table of Contents

1. 问题产生

2. 开发模型

3. 结论


中国 北京 陈建
<james...@gmail.com>

摘要

struts是开放源码的企业级web应用开发框架。它非常好地解决了服务器端业务处理之前的诸多问题。在业务处理上让开发人员自由发挥。由于技术的多样性,可以提供业务处理的开发框架或者开发模式非常多。虽然使用哪种方式都没有对错之分,但是对于软件系统——尤其是来源于项目的软件系统来说,在开发成本的紧箍之下就有了差别。在小团队、简单项目的环境中,使用复杂的开发框架,除了能够把开发人员累死或者延误开发进度之外没有其他任何好处;同样,在复杂项目的环境中,使用简单的开发框架,往往会产生一个弱不禁风的系统,在诸多可变因素的影响下软件系统有如摇摇欲坠的茅屋。本文地目的正是希望讨论一下如何能够快速、低成本地开发具有适当灵活性、可扩展的简单应用。

1. 问题的产生

在讨论问题之前我们首先明确,什么是简单应用?

业务逻辑几近于无,整个系统几乎可以抽象为对数据的CRUD(增查改删),几乎不存在需要在内存中维护其关系和长时间占用资源处理的对象。简单应用的特点是规模小、资源少、升级改造约等于重新开发。基于这些特点,开发团队必须在质量和成本之间寻求平衡。

那么如何才能达到平衡呢?

非常明确的是,以牺牲质量来降低成本是绝对错误的,必然会带来恶梦般的维护期。为了回答这个问题,我们首先来了解一下常用的企业级复杂应用开发的模式。此类应用往往会被纵向分从展现层到持久化层的n层来实现。对于简单应用来讲,同样也少不了展现层和持久化层。只是中间的n-2层是否需要值得商榷。显而易见,在简单应用中保留展现层和持久化层就为应用从简单到复杂的成长提供了机会,同时也最大程度地重用原有资源。那么被删节的n-2层的业务逻辑该怎么办呢?由于这些逻辑在简单应用中可能根本不存在,即使存在也非常薄弱,所以把它们放在展现层的后端是非常好的选择。这样一来,应用即成为展现层加上持久化层的系统。

如何实现持久化层?

大凡实现持久化层有两种方案:第一种使用成熟的复杂的持久化框架(譬如:Hibernate);第二种自行开发。对于尽量简化开发过程降低开发成本的初衷来讲,第一种方案显然稍有违背——引入了复杂性。自行开发的持久化层只需要提供简单的CRUD功能,仅此而已,多则无益。从简单性效率上来讲自行开发略胜一筹。

需要DAO工厂吗?

DAO工厂的主要职责是封装DAO的创建过程,为应用提供动态变更持久化层的能力。要揭示这个问题,首先得回答这样一个问题——简单应用需要多个不同的持久化层实现吗?答案是显然的,绝大多数情况不需要。既然如此,那么DAO工厂自然可以省略。

需要持久化层外观(Facade)”吗?

许多持久化框架都使用外观来封装内部实现,用于提供灵活的、可配置的持久化功能。在复杂应用环境中,持久化层为上面许多层提供服务,他的变化将是致命的。然而在简单应用环境中,对持久化层的变化往往意味着整个应用的重新开发。从变动几率上来讲展现层的变化比持久化层的变化要多得多。所以持久化层外观也可以省略。

需要为每个实体创建与之对应的TO吗?

在各个层之间以松耦合的方式传递数据是个好主意。出于简单的目的,显然为每个实体创建一个与之对应的TO略显多余。但是又不能因此而违背常理完全抛弃松耦合的优势。那么最好的办法就是找一个可以适用于所有实体的替代品。Map是个不错的选择。

如何解决字典型数据的使用问题?

字典型数据的特点是使用频率高、变更频率低。那么最好的策略就是在应用启动的时候加载之后作为属性放在ServletContext中。在JSP或者Action中随时取用。那么更新了怎么办呢?对于简单应用来讲总会有重新启动一次的机会的,而且又不花多少时间。所以改变之后重新启动一次是个不错的主意。

2. 开发模型

如上所述,简单应用开发模型可以归纳为:

·         建立良好的概念模型,并通过概念模型设计出稳定的数据模型。

·         使用struts解决展现层问题。

·         为每个实体创建一个DAO

·         Action中执行简单的逻辑之后,直接调用对应的DAO访问数据。

·         ActionDao之间使用Map传递数据。

·         创建一个ServletContextListener负责在应用启动的时候初始化(从数据库中读取等)字典类型数据,并将字典类型数据作为属性存放在context中备用。以两种格式存放同一字典数据。它们分别是Collection型和Map型。Collection型字典数据负责提供有序列表;Map型字典数据负责根据字典值获得与该值对应的名称。

3. 结论

对于简单应用,其实现方式也必然突出简单的特点。象事务处理、缓存等复杂问题也就无须过分深究了。以至于开发一个小的平台来配置纯粹的CRUD应用都是很有可能的。试问这种平台的价值,那就要看有多少所谓的企业级应用能够超脱CRUD而真正名副其实了。

 

Yong Chen

unread,
Jan 10, 2006, 8:54:17 PM1/10/06
to program...@googlegroups.com
不好意思,我对Facade,DAO概念一直不太明白,有哪位朋友能讲讲或者推荐本书么?谢谢。

Stephen Suen

unread,
Jan 10, 2006, 9:16:11 PM1/10/06
to program...@googlegroups.com
下面这本书就是出处了,它也是学习J2EE的必读之书:
 
J2EE核心模式(原书第2版)
On 1/11/06, Yong Chen <chen...@gmail.com> wrote:
不好意思,我对Facade,DAO概念一直不太明白,有哪位朋友能讲讲或者推荐本书么?谢谢。



--
Stephen Suen
stephe...@gmail.com
http://spaces.msn.com/members/stephensuen
http://groups.google.com/group/programmercafe

Yong Chen

unread,
Jan 10, 2006, 11:41:04 PM1/10/06
to program...@googlegroups.com
谢谢!
其实我对Java不是很了解,平时主要是用C#,有没有基于C#或者语言无关的讲解这些东西的

 
2006/1/11, Stephen Suen <stephe...@gmail.com>:

Stephen Suen

unread,
Jan 10, 2006, 11:50:37 PM1/10/06
to program...@googlegroups.com
我不了解 C# ,不过 MSDN 上面的 Enterprise Solution Patterns Using Microsoft .NET 和 Core J2EE Pattern 可以相互对照,参见:
 

Yong Chen

unread,
Jan 11, 2006, 12:06:57 AM1/11/06
to program...@googlegroups.com
再次感谢!
我这好像这个的中文版,找找。

 
2006/1/11, Stephen Suen <stephe...@gmail.com>:

James Chan

unread,
Jan 11, 2006, 1:09:35 AM1/11/06
to program...@googlegroups.com

Façade的详细介绍可以参看《设计模式》。这些设计模式并没有实现语言的限制。更直白的应用场景,欢迎大家讨论。

 


发件人: program...@googlegroups.com [mailto:program...@googlegroups.com] 代表 Yong Chen
发送时间: 2006年1月11 12:41
收件人: program...@googlegroups.com
主题: [Programmer Cafe] Re: [tips]--基于struts的简单应用开发模式

Yong Chen

unread,
Jan 11, 2006, 1:37:29 AM1/11/06
to program...@googlegroups.com
 
是《设计模式--可复用面向对象软件的基础》么?
 

James Chan

unread,
Jan 11, 2006, 8:52:48 PM1/11/06
to program...@googlegroups.com

是的。

 

Façade模式被翻译为外观模式。其目的是让内部复杂的系统更加易用;将客户程序对易变的复杂系统的依赖进化为对稳定接口的依赖。譬如:持久化层的Façade可能仅提供带有参数的CRUD等少数方法。客户程序在使用持久化层时,只需要调用Façade的方法即可,而不必关心持久化层是如何设计的或者使用了什么技术(hibernate 或自定制的)。

发件人: program...@googlegroups.com [mailto:program...@googlegroups.com] 代表 Yong Chen

发送时间: 2006年1月11 14:37
收件人: program...@googlegroups.com
主题: [Programmer Cafe] Re: 答复: [Programmer Cafe] Re: [tips]--基于struts的简单应用开发模式

 

 

是《设计模式--可复用面向对象软件的基础》么?
 

项维炜

unread,
Feb 28, 2006, 12:18:50 AM2/28/06
to program...@googlegroups.com

呵呵,简约而不简单哦。首先祝大家新年快乐!
James
Chan在原作中提到在action与dao之间用MAP来传递数据,我的理解是中间简化掉了一个business层。现想请教几个问题:(我把map理解成为hashmap不知道对不对)
1,从页面过来的form中的值如何与map做映射,是在action层做吗?
查询的结果集是否先封装成为map再返回到表示层,然后再将map与form做映射,展现在页面上吗?我在做其他的项目时也有这种困扰,就是表示层form与持续化层vo对象的值对应,这种代码总是少不了,枯燥而乏味!不知道这里有没有好的解决。
2,映射为map后,有些字段的类型是否会丢失?
3,原文提到:dao是直接对实体的映射,那么action层当中必然还要承担起简单的逻辑处理。当然action还要肩负起exception,message,redirect,logger,priviliege。。。。哇!好多好多:),再简单的系统这些应该也避免不了吧?

james...@gmail.com

unread,
Feb 28, 2006, 10:33:45 AM2/28/06
to Programmer Cafe
近期俗事缠身,现在才得暇回复,抱歉!抱歉!

要回答文中提到的问题,我们首先来界定一下讨论基础--"简单应用"。所谓简单应用就是没有复杂业务逻辑的纯粹提供对数据CRUD功能的应用。那么肯定有人会说哪里有这样的"简单应用"呢?其实最明显例子就是为一个大的应用提供数据字典维护之类的功能。讨论基础明确之后,可以开始讨论问题了。
1。随着request请求一起提交的参数都是以String类型提交的,它必然与实际的类型不符。那么form与to(文中使用map替代)之间的映射也就是不可避免的了。有人写过一些工具,以便于让这种映射能够自动完成。对于此,我的个人观点是,有则拿来用,没有则老老实实自己写映射代码。但是当奉行拿来主义的时候要注意,这种自动映射工具在to中有多个重载方法时一般都会出错,因为它不知道要用哪个方法,所以"随机地"选择一个就用上了,结果就可想而知了。Apache的commons工具包的BeanUtil就是一个例子。综合考虑,自己写映射代码是不错的选择。当然对于简单应用来说,这种映射就只好在action中完成了。
2。在Map接口的声明中,无论是key还是value都可以是Object。既然可以从任何对象中获得其类型,那么保存在Map中的值自然也就携带了其类型。
3。如果要求这样的"简单应用"担负起更大的担子,那么只好又action来全权代劳了。一般情况下,象exception、message、log、redirect、authentication
和authorization等功能都会由置于action前面的控制器来担当。这么说来,action中所要做的工作其实也不太多了。

wj1s

unread,
Feb 28, 2006, 3:21:27 PM2/28/06
to Programmer Cafe
我的一点看法:
1.我觉得formbean的作用是将页面表单对象化,这带来的好处是显而易见的,比如程序员因此就可以使用Set
或者Get等面向对象的方法存取网页上的数据了.所以formbean表达的应该只是页面信息,与后边的to之类是没有关系的,比如formbean也许只映射了一个to的数据,也许也映射了一个to的list,不知道说的对不对.所以因为它的不确定性来说,form与to的映射只有手工来完成.但是,根据一些特殊的条件或是特殊的约束,比如在类似的"简单应用"中,我们规定一个form与一个to相映射,并且相对应的属性name相同的话,就可以用一些类反射机制或是java.beans里边的东西来写一个自动映射的static方法:
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import com.arcturus.arctoa.util.*;

public class Util {
public static void copyProperties(Object desObj, Object resObj) {
PropertyDescriptor descriptor = null;
try {
BeanInfo beanInfo =
Introspector.getBeanInfo(resObj.getClass());
PropertyDescriptor[] descriptors
= beanInfo.getPropertyDescriptors();
String[] names = new String[descriptors.length];
int i = 0;
for (/**/; i < names.length; i++) {
String name = descriptors[i].getName();
Class type = descriptors[i].getPropertyType();
if (descriptors[i].getReadMethod() != null) {
Object value
=
descriptors[i].getReadMethod().invoke(resObj);
try {
descriptor
= new PropertyDescriptor(name,
desObj.getClass());
} catch (IntrospectionException ex) {
continue;
}
try {
if
(!descriptor.getPropertyType().isPrimitive()) {
Object obj
= convertValue(value,
descriptor.getPropertyType(),
desObj);
descriptor.getWriteMethod()
.invoke(desObj, new Object[] { obj });
}
} catch (Exception ex) {
System.out
.println("the function name is ----->"
+ name);
ex.printStackTrace();
}
}
}
} catch (Exception e) {
System.out.println(descriptor.getName());
System.out.println(descriptor.getPropertyType().getName());
e.printStackTrace();
}
}

private static Object convertValue(Object resValue, Class desType,
Object desObj) {
if (resValue == null)
return resValue;
if
(resValue.getClass().getName().equalsIgnoreCase(desType.getName()))
return resValue;
if (resValue.getClass().getSuperclass().getName()
.equalsIgnoreCase(desType.getName()))
return resValue;
if ((resValue instanceof Boolean || resValue instanceof Integer
|| resValue instanceof Long || resValue instanceof
Float
|| resValue instanceof Double)
&&
desType.getName().equalsIgnoreCase("java.lang.String"))
return resValue.toString();
if (desType.getName().equalsIgnoreCase("java.lang.Boolean")
&& resValue instanceof String) {
Boolean var_boolean;
try {
var_boolean = new Boolean((String) resValue);
} catch (Exception ex) {
return null;
}
return var_boolean;
}
if (desType.getName().equalsIgnoreCase("java.lang.Integer")
&& resValue instanceof String) {
Integer integer;
try {
integer = new Integer((String) resValue);
} catch (NumberFormatException ex) {
return null;
}
return integer;
}
if (desType.getName().equalsIgnoreCase("java.lang.Long")
&& resValue instanceof String) {
Long var_long;
try {
var_long = new Long((String) resValue);
} catch (NumberFormatException ex) {
return null;
}
return var_long;
}
if (desType.getName().equalsIgnoreCase("java.lang.Float")
&& resValue instanceof String) {
Float var_float;
try {
var_float = new Float((String) resValue);
} catch (NumberFormatException ex) {
return null;
}
return var_float;
}
if (desType.getName().equalsIgnoreCase("java.lang.Double")
&& resValue instanceof String) {
Double var_double;
try {
var_double = new Double((String) resValue);
} catch (NumberFormatException ex) {
return null;
}
return var_double;
}
return null;
}

private Util() {
}
}
不知道这个Util是不是和Apach-e的commons工具包的BeanUtil相同,写在这里大家有兴趣可以研究一下,可能存在关于陈哥说的重载问题,不过我看了java.lang.class中的getMethod(String
name, Class...
parameterTypes)方法可以返回一个确定的参数类型的方法,配合Field根据Field产生getMethod方法中的name参数,应该可以解决这个问题,不过要注意,field一定要记得setAccessible(true).说的远了^^完成这个工具后,只要在别的类中import它,然后就可以用:
Util.copyProperties(formBean, toBean);
Util.copyProperties(toBean, formBean);
来自动进行映射了.如果把这个加入到这个简单框架中,不知道可行不可行.
2有个问题,刚才看那个OA的代码才弄明白,它也是通过类反射机制来实现数据库操作的.比如DBProcessor中的static方法query(String
sql,String
className)中根据传入的sql得到ResultSet,再通过ResultSetMetaData
rsmd =
rs.getMetaData();得到列信息,然后循环自动创建并付值类名为className的对象,以ArrayList或数组的方式返回,大概流程转来网上一段程序:
public ArrayList query(String sql,String className){
ArrayList paraList=new ArrayList();
//传入的sql得到ResultSet
try{
if (conn == null){
Connection();
}
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery();
String recordValue="";
Object c1=null;
paraList=new ArrayList();
//再通过ResultSetMetaData rsmd =
rs.getMetaData();得到列信息
ResultSetMetaData rsmd = rs.getMetaData();
int columnCount = rsmd.getColumnCount();
//循环自动创建并付值类名为className的对象,以ArrayList或数组的方式返回
//循环rs每一行
while (rs.next()){
//创建一个类名为className的对象
c1=Class.forName(className).newInstance();
//循环每一行中的每一列
for (int i=1; i<=columnCount; i++) {
if(rs.getString(rsmd.getColumnName(i))!=null){
recordValue=rs.getString(rsmd.getColumnName(i));
}else{
recordValue="";
}
//得到类中与表当前列名相同的属性的set方法
Method
m=c1.getClass().getMethod(getSetMethodName(rsmd.getColumnName(i))
,new
Class[]{recordValue.getClass()});
//执行set方法,将表当前行当前列下的数据set进对象中
m.invoke (c1, new Object[]{recordValue});
}
paraList.add(c1);
}
}catch(SQLException ex){

}catch(ClassNotFoundException e){
}catch(NoSuchMethodException e) {
}catch(InvocationTargetException e){
}catch (IllegalAccessException e){
}catch(InstantiationException e){
} finaly{
closeConnection();
return paraList;
}
}
这样就不用为每个表建一个dao了都,因为在这种简单的对表的CRUD中,完全可以将FORMBEAN建的与数据库中的字段对应,这样得到的所有不用的formBean对象就可以直接或间接的被query(或是insert,update,delete)方法调用,完成数据库操作:
ArrayList forms=
query(sql,formBean.getClass());//先执行sql,然后循环创建formBean对象,利用类反射机制为每个formBean付值返回
insert((ActionForm)formBean);//根据类反射机制找出formBean的所有属性和对应的值,拼成sql执行
update((ActionForm)formBean);//同上
del((ActionForm)formBean);//同上
不知道表达是否正确清楚.这样只有一个类的几个静态方法就可以完成一堆DAO的任务,当然是在这种简单的对表的CRUD中,完全可以将每一个formBean的属性名称建的与数据库中一个表字段名称完全对应的情况下.而这种情况应该在本文讨论的语境范围内,不知道这种方式是否可行?

Reply all
Reply to author
Forward
0 new messages