- 浏览: 717124 次
- 性别:
- 来自: 上海
最新评论
-
A741841403:
附件怎么看不到了呢?
介绍一个PDF的生成方案 -
chunguang_yao:
写得非常好
《Struts2技术内幕》 新书部分篇章连载(一)—— 如何学习开源框架 -
su_chunlong:
rrredriver 写道极赞的文章,感谢楼主!有一个问题想请 ...
SpringMVC深度探险(三) —— DispatcherServlet与初始化主线 -
qq_32671287:
...
SpringMVC深度探险(一) —— SpringMVC前传 -
shenghuorulan:
看的好爽,可不可以这么笼统的概括一个框架的学习过程1,框架要解 ...
SpringMVC深度探险(二) —— SpringMVC概览
专栏地址:http://www.iteye.com/wiki/struts2/1365-passing-parameters-in-struts2
本篇主要通过实例来讲述Struts2中各种各样的参数传递。这个参数传递的过程主要指数据从View层传递到Control层时Struts2的工作方式。根据前两篇文章的知识,我们知道,Struts2完成参数传递处理工作的基础是OGNL和ValueStack。而在这个过程中,我也把Struts2所要做的工作大致归纳为两个方面:
1. 对OGNL操作进行封装,完成OGNL表达式所表示的值到Java对象的值传递机制
2. 在参数传递的过程中,做恰当的类型转化,保证页面上的字符串能够转化成各式各样的Java对象
接下来,通过四个不同的角度,来具体讲述Struts2在这两个方面的工作。
最简单的参数传递
使用OGNL的最基本的功能,就能完成普通的Java对象的赋值工作。Struts2在内部已经完成了OGNL的基本封装。这些封装包括对OGNL表达式到Java对象的赋值机制,以及对基本的Java类型的类型转化支持。这些基本类型包括String、Number(以及其基本类型int、float、double等)、Boolean(boolean)、数组、Class、Date等类型。
在这里我想额外强调的是XWork对JDK5.0中的Enum类型和Date类型的支持。
Enum类型
枚举类型是JDK5.0引入的新特性。枚举类型也能解决很多实际问题,是J2EE编程中的最佳实践之一。XWork中,有一个专门的EnumTypeConverter负责对Enum类型的数据进行转化。
有了这个类,我们就可以比较轻松的对枚举类型进行数据赋值了。
通过上面的代码,就完成了对枚举类型的赋值。不过这里有一点需要特别指出:那就是XWork在XWork-2.1.X的版本之前,枚举类型不被默认支持。如果你需要获得枚举类型的自动赋值,还需要增加一个配置文件xwork-conversion.properties到classpath下:
对于使用新的版本的XWork的朋友,则不需要增加这个配置文件。
Date类型
XWork默认是支持Date类型的转化的。不过从源码上来看,貌似我们很难用上它默认的类型转化。
这段代码就是XWork处理将String转成Date类型的过程,从整个过程上来看,我们很难用上这段代码,因为我们在界面上的Date类型的表现形式往往是:'yyyy-MM-dd'或者相关的形式,很明显,上面的流程无法匹配这样的日期类型。
所以,针对Date,我们往往会自定义一个日期转化的类进行处理,这个在下面会有具体的介绍。
Array、List、Map等容器类型的参数传递
除了简单的基于JavaBean方式的参数传递支持,Struts2还支持对Array、List、Map等容器类型的数据结构做数据赋值。不过历史一路走来,XWork针对容器类型的数据赋值一直有变化,让我们慢慢解读这些变化,从而也来看看编程思路是如何改变的。
1. 2004年,XWork-1.0.X的年代
当时XWork所支持的针对容器的数据赋值还比较土。这方面moxie在论坛上有一篇文章专门来讲述:http://www.iteye.com/topic/8770。
总的来说,那个年代对于容器的数据赋值,需要依赖于XWork的辅助类。我们可以看到,如果你要对List进行赋值,需要新建一个XWorkList的实现类,并把所需要进行数据赋值的Java类传递到XWorkList的构造函数中。而对Map等对象的赋值,也同理可得。
这种数据赋值的方式的优缺点都非常明显。优点在于简单,你不需要额外定义任何其他的内容,而是直接使用XWork的辅助类来实现类型转化。缺点在于扩展性很弱,很明显,针对某一个具体的容器,就需要一个XWork的实现类,List有XWorkList对应,Map有XWorkMap对应。甚至在那个时候,还没有Set的支持,因为没有XWorkSet的实现。所以使用这种方式,在扩展性方面需要遭受严重的考验。
2. 2006年,XWork-2.0.X的年代
也许是XWork团队看到了扩展性上的问题,所以在XWork和Webwork同时升级以后,采用了新的方式来处理容器赋值。而此时,Javaeye上也涌现出了新的文章,Tin同学对新的方式做了详细的表述:http://www.iteye.com/topic/17939。
不过这个新的整合方式似乎并不被大家所看好。
这种新的整合方式,实际上只是解决了针对容器赋值,不需要依赖XWork的辅助类这样的一个问题,不过其付出的代价,却是多了一个配置文件,这也让人非常郁闷。好好的类型转化,平白无故多出了一个同package下的配置文件,这也无形中增加了编程的复杂度。
3. 现在,拥抱了泛型和Annotation的年代
实际上,在XWork发展到XWork-2.0.X之后,也开始注重了对泛型和Annotation的支持。所以,容器类型的转化,我们也可以尝试一下使用JDK的新特性来进行,当然这也是目前最为推荐的做法。
下面分别给出使用泛型和Annotation的代码示例:
上面的代码中,我们可以看到,如果你使用泛型,那么你无需再使用任何额外的配置文件或者Annotation,XWork会把一切都为你准备好。如果你没有使用泛型,那么你可以使用Annotation来指定你需要进行转化的对象类型。其中,对Map对象使用Annotation时,Element中的value所对应的值,是Map中的value所对应的class。
由此可见,泛型和Annotation,在一定程度上,还是可以简化我们很多工作的。
文件上传
文件上传其实也是参数传递的一种,所以从方案上来讲,Struts2同样使用了一个拦截器来处理。而这个拦截器,同样来自于原来的Webwork,基本上没有做什么很大的改变。有关这个拦截器的详细内容,我们也留一个悬念,在后续章节中详细讲解。目前,你只要知晓,这个拦截器能帮助你完成一切文件上传相关的机制。
早在2005年,Quake Wang就对Webwork的文件上传机制有了详细的讲解:http://www.iteye.com/topic/10697
在这里我简单小结一下在进行文件上传时的三大要点:
1. 在配置文件上传时,拦截器的顺序非常关键
具体来说,upload的拦截器,必须在params的拦截器之前
2. 拦截器额外提供了一些额外的文件信息
3. 拦截器提供的文件上传功能,你得到的是一个临时文件
而时代发展到Struts2的年代,对于文件上传的整体机制没有做什么改变。只是Struts2将apache的common-fileupload作为了其默认的文件上传的机制。
例子归例子,实际情况中,我们还是会遇到一些问题:
1. 默认实现中,文件和文件信息是分开表述的,对于后台处理来说,不是非常方便。
2. common-fileupload的实现,虽然提供了文件上传的机制,也可以让你得到文件的一些属性信息,但是它无法得到客户端的上传路径
对于第一个问题,我们可以使用OGNL的特性,将这些文件和文件名等文件信息做封装:
在这个类中,我定义了upload表示上传的文件,fileName表示上传文件的文件名。请注意我整个文件中的最后一个方法:setUploadFileName。这个方法将保证FileUploadInterceptor在运行时,能够正确设置上传的文件名。
这样,在Action中,我们将面对一个个完整的fileComponent对象,其中包括文件的引用、文件名称和其他文件信息。这样就不会因为上传多个文件而手足无措,你只需要使用fileComponent数组,就能轻松对上传的文件进行管理,而避免了在Action中书写许多个文件、文件名等属性了。
对于第二个问题,目前我也没有找到很好的方法。我所采用的方式与Yulimin是一致的:
自定义的类型转化实现
Struts2在处理参数传递的过程中,需要完成类型转化,保证页面上的字符串能够转化成各式各样的Java对象。而这一点,其实也是由OGNL完成的。还记得我们在讲述OGNL的基础知识的时候列出来过的一个接口嘛?
在这个接口中,我们可以在使用OGNL的时候,注册针对某个Class级别的自己实现的TypeConverter,这样,OGNL就会在进行设值计算和取值计算的时候,使用自定义的类型转化方式了。让我们来看看TypeConverter的接口定义:
知道了原理,就简单了,我们可以自己实现一个TypeConverter的实现类,并且在Struts2中注册一下使用这个TypeConverter的Java类型的对应关系,我们就可以完成自定义的类型转化了。
具体的例子,可以参考一下Quake Wang同学在2005年时的一篇文章:http://www.iteye.com/topic/10507。文章针对的是Webwork2,但是无论是实现方式还是操作步骤,与Struts2是完全相同的。值得提醒的是,这篇文章的回复也非常有价值,在看文章的同时,不要忘记看回复。
不过针对Quake Wang的例子,我也想做一些补充。它的例子中,主要讲述了Struts2中如何去做java.utils.Date的自动类型转化,也正如后面回复中有人提到:
而Quake Wang对此是这样解决的:
我在这里提供一个我在实际项目中采用的方式:
在我采用的方式中,依然采用Application-wide conversion rules,只是在自定义的Converter中,根据不同的日期形式进行逐个匹配,找到第一个匹配的日期类型进行转化。当然,这是一种投机取巧的办法,不过比较实用,适用于项目中不太使用国际化,日期的形式不太复杂的情况。
DTO的争论暂且不提。事实上,我并没有提到DTO,也不推崇所谓的DTO。我只是说,面对日期这种“复杂”的http参数,可以做手动处理;而把领域对象直接暴露在外,我个人并不太喜欢;对于result = DateUtils.parseDate((String) value, new String[] { DATE_PATTERN, DATETIME_PATTERN, MONTH_PATTERN }); 这种不确定的转换机制,我认为可能造成混淆和维护上的困难。我个人更喜欢Explicit is better than implicit,也许这些都只是个人喜好的不同而已,呵呵。另外,谢谢QuakeWang
补充一下:
ParametersInterceptor允许你用白名单/黑名单来指定只接受或者拒绝哪些参数(acceptedParamNames / excludeParams)。
如果需要更细粒度的控制,可以让具体的action实现ParameterNameAware接口的acceptableParameterName方法。
你还可以扩展ParametersInterceptor,比如用java1.5,你可以仿照rails的active record model那样,自己定制一个annotation,保护那些不能被mass assign的attribute:
总之,保护属性并不是选择DTO的原因。
这种确实更简单,但相应地,我认为页面就变得复杂了,如下面的页面:
首先,我不会让entity直接暴露给外部,中间都会有一层自己的转换。比如你要update一个user的birthday,可客户端手动添加了一个user.loginName这样的属性,岂不是把登录名也改了。我的疑惑是,birthday就一个普通的http参数,为什么必须与领域对象挂上勾呢。
有了前面birthday参数一定要加到user这个领域对象里的假设,所以后面的结论
看起来自然也就成立了。可这是实际的问题吗?实际的问题是如何获取参数,对吧。
其实这并非硬编码,而是更加清晰的一种表述,就好比你的birthday可能会用java.sql.Date,而loginTime会用java.util.Timestamp一样,这里根本就不需要任何自定义的子类,事实上jdk自身已经带了,这里与你数据库定义的date、datetime也完全一致。我认为更清晰地表述了领域对象属性所承载的含义。
我认为这是一种对参数格式的猜测,代码的维护人员,会很难知道客户端传过来的到底是yyyy-MM-dd,还是yyyy-MM。
Ivan_Pig 写道一直关注老大的此系列文章!非常感谢!不过目前对Struts2的前途有点怀疑!虽然与struts相比,改进了很多!但是模式基本还是一样的!这样的模式还适合以后的开发吗?疑问而已,学习还是要学习的 再次感谢!目前来说,MVC框架还很难退出历史舞台。主要的原因可能在于所有的Web开发基本上还是基于URL进行的。那些基于event或者组件的Web框架,我其实并不是很看好。可能在于我的思维模式的限制。谁也说不好以后的发展趋势,只能走一步是一步了。就像我文章里面介绍的,时代不断发展,会有新的东西出来,不过旧的东西始终还能能够让我们学到很多的设计思想。
多谢指点。
我之前曾用过JSF,由于速度太慢而最终放弃了!目前基于ZK在做项目。一个基于组件一个基于事件,效率都很有问题!我对这两类框架也不看好!比较偏向RIA。网速提高后,相信RIA会成主流!
最主要东西太多了,三天就有个新概念,什么SOA,云计算,不知所云。看坛子里的牛人说得头头是道的,真不知道说什么呢
目前对这些框架,要说能说出点什么。但真要问个细节还真不敢打包票。对框架的实现大概的都知道,也能以自己的方式实现。但至今没完整的读过一个框架的源码,很是惭愧阿!入行1年多,算是到了一个坎了吧。希望能跟着老大把这坎过了。对Struts2能有深入了解
一直关注老大的此系列文章!非常感谢!不过目前对Struts2的前途有点怀疑!虽然与struts相比,改进了很多!但是模式基本还是一样的!这样的模式还适合以后的开发吗?疑问而已,学习还是要学习的 再次感谢!
目前来说,MVC框架还很难退出历史舞台。主要的原因可能在于所有的Web开发基本上还是基于URL进行的。那些基于event或者组件的Web框架,我其实并不是很看好。可能在于我的思维模式的限制。
谁也说不好以后的发展趋势,只能走一步是一步了。就像我文章里面介绍的,时代不断发展,会有新的东西出来,不过旧的东西始终还能能够让我们学到很多的设计思想。
本篇主要通过实例来讲述Struts2中各种各样的参数传递。这个参数传递的过程主要指数据从View层传递到Control层时Struts2的工作方式。根据前两篇文章的知识,我们知道,Struts2完成参数传递处理工作的基础是OGNL和ValueStack。而在这个过程中,我也把Struts2所要做的工作大致归纳为两个方面:
1. 对OGNL操作进行封装,完成OGNL表达式所表示的值到Java对象的值传递机制
2. 在参数传递的过程中,做恰当的类型转化,保证页面上的字符串能够转化成各式各样的Java对象
接下来,通过四个不同的角度,来具体讲述Struts2在这两个方面的工作。
最简单的参数传递
使用OGNL的最基本的功能,就能完成普通的Java对象的赋值工作。Struts2在内部已经完成了OGNL的基本封装。这些封装包括对OGNL表达式到Java对象的赋值机制,以及对基本的Java类型的类型转化支持。这些基本类型包括String、Number(以及其基本类型int、float、double等)、Boolean(boolean)、数组、Class、Date等类型。
在这里我想额外强调的是XWork对JDK5.0中的Enum类型和Date类型的支持。
Enum类型
枚举类型是JDK5.0引入的新特性。枚举类型也能解决很多实际问题,是J2EE编程中的最佳实践之一。XWork中,有一个专门的EnumTypeConverter负责对Enum类型的数据进行转化。
public class EnumTypeConverter extends DefaultTypeConverter { /** * Converts the given object to a given type. How this is to be done is implemented in toClass. The OGNL context, o * and toClass are given. This method should be able to handle conversion in general without any context or object * specified. * * @param context - OGNL context under which the conversion is being done * @param o - the object to be converted * @param toClass - the class that contains the code to convert to enumeration * @return Converted value of type declared in toClass or TypeConverter.NoConversionPossible to indicate that the * conversion was not possible. */ public Object convertValue(Map context, Object o, Class toClass) { if (o instanceof String[]) { return convertFromString(((String[]) o)[0], toClass); } else if (o instanceof String) { return convertFromString((String) o, toClass); } return super.convertValue(context, o, toClass); } /** * Converts one or more String values to the specified class. * @param value - the String values to be converted, such as those submitted from an HTML form * @param toClass - the class to convert to * @return the converted object */ public java.lang.Enum convertFromString(String value, Class toClass) { return Enum.valueOf(toClass, value); } }
有了这个类,我们就可以比较轻松的对枚举类型进行数据赋值了。
public enum Gender { MALE, FEMALE }
<form method="post" action="/struts-example/enum-conversion.action"> <input type="text" name="user.name" value="downpour" /> <select name="user.gender"> <option value="MALE">男</option> <option value="FEMALE">女</option> </select> <input type="submit" value="submit" /> </form>
public class EnumConversionAction extends ActionSupport { private static final Log logger = LogFactory.getLog(Policy.class); private User user; /* (non-Javadoc) * @see com.opensymphony.xwork2.ActionSupport#execute() */ @Override public String execute() throws Exception { logger.info("user's gender:" + user.getGender()); return super.execute(); } // setters and getters }
通过上面的代码,就完成了对枚举类型的赋值。不过这里有一点需要特别指出:那就是XWork在XWork-2.1.X的版本之前,枚举类型不被默认支持。如果你需要获得枚举类型的自动赋值,还需要增加一个配置文件xwork-conversion.properties到classpath下:
java.lang.Enum=com.opensymphony.xwork2.util.EnumTypeConverter
对于使用新的版本的XWork的朋友,则不需要增加这个配置文件。
Date类型
XWork默认是支持Date类型的转化的。不过从源码上来看,貌似我们很难用上它默认的类型转化。
private Object doConvertToDate(Map context, Object value, Class toType) { Date result = null; if (value instanceof String && value != null && ((String) value).length() > 0) { String sa = (String) value; Locale locale = getLocale(context); DateFormat df = null; if (java.sql.Time.class == toType) { df = DateFormat.getTimeInstance(DateFormat.MEDIUM, locale); } else if (java.sql.Timestamp.class == toType) { Date check = null; SimpleDateFormat dtfmt = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale); SimpleDateFormat fullfmt = new SimpleDateFormat(dtfmt.toPattern() + MILLISECOND_FORMAT, locale); SimpleDateFormat dfmt = (SimpleDateFormat) DateFormat.getDateInstance(DateFormat.SHORT, locale); SimpleDateFormat[] fmts = {fullfmt, dtfmt, dfmt}; for (int i = 0; i < fmts.length; i++) { try { check = fmts[i].parse(sa); df = fmts[i]; if (check != null) { break; } } catch (ParseException ignore) { } } } else if (java.util.Date.class == toType) { Date check = null; SimpleDateFormat d1 = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.LONG, locale); SimpleDateFormat d2 = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.MEDIUM, locale); SimpleDateFormat d3 = (SimpleDateFormat) DateFormat.getDateTimeInstance(DateFormat.SHORT, DateFormat.SHORT, locale); SimpleDateFormat rfc3399 = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss"); SimpleDateFormat[] dfs = {d1, d2, d3, rfc3399}; //added RFC 3339 date format (XW-473) for (int i = 0; i < dfs.length; i++) { try { check = dfs[i].parse(sa); df = dfs[i]; if (check != null) { break; } } catch (ParseException ignore) { } } } //final fallback for dates without time if (df == null) { df = DateFormat.getDateInstance(DateFormat.SHORT, locale); } try { df.setLenient(false); // let's use strict parsing (XW-341) result = df.parse(sa); if (!(Date.class == toType)) { try { Constructor constructor = toType.getConstructor(new Class[]{long.class}); return constructor.newInstance(new Object[]{new Long(result.getTime())}); } catch (Exception e) { throw new XWorkException("Couldn't create class " + toType + " using default (long) constructor", e); } } } catch (ParseException e) { throw new XWorkException("Could not parse date", e); } } else if (Date.class.isAssignableFrom(value.getClass())) { result = (Date) value; } return result; }
这段代码就是XWork处理将String转成Date类型的过程,从整个过程上来看,我们很难用上这段代码,因为我们在界面上的Date类型的表现形式往往是:'yyyy-MM-dd'或者相关的形式,很明显,上面的流程无法匹配这样的日期类型。
所以,针对Date,我们往往会自定义一个日期转化的类进行处理,这个在下面会有具体的介绍。
Array、List、Map等容器类型的参数传递
除了简单的基于JavaBean方式的参数传递支持,Struts2还支持对Array、List、Map等容器类型的数据结构做数据赋值。不过历史一路走来,XWork针对容器类型的数据赋值一直有变化,让我们慢慢解读这些变化,从而也来看看编程思路是如何改变的。
1. 2004年,XWork-1.0.X的年代
当时XWork所支持的针对容器的数据赋值还比较土。这方面moxie在论坛上有一篇文章专门来讲述:http://www.iteye.com/topic/8770。
总的来说,那个年代对于容器的数据赋值,需要依赖于XWork的辅助类。我们可以看到,如果你要对List进行赋值,需要新建一个XWorkList的实现类,并把所需要进行数据赋值的Java类传递到XWorkList的构造函数中。而对Map等对象的赋值,也同理可得。
这种数据赋值的方式的优缺点都非常明显。优点在于简单,你不需要额外定义任何其他的内容,而是直接使用XWork的辅助类来实现类型转化。缺点在于扩展性很弱,很明显,针对某一个具体的容器,就需要一个XWork的实现类,List有XWorkList对应,Map有XWorkMap对应。甚至在那个时候,还没有Set的支持,因为没有XWorkSet的实现。所以使用这种方式,在扩展性方面需要遭受严重的考验。
2. 2006年,XWork-2.0.X的年代
也许是XWork团队看到了扩展性上的问题,所以在XWork和Webwork同时升级以后,采用了新的方式来处理容器赋值。而此时,Javaeye上也涌现出了新的文章,Tin同学对新的方式做了详细的表述:http://www.iteye.com/topic/17939。
不过这个新的整合方式似乎并不被大家所看好。
lllyq 写道
我觉得XWorkList, XWorkMap还是很有用的,挺好的设计,其实没有必要deprecated。
moxie 写道
集合支持不向下兼容。XWorkList已经是@deprecated,用它就错,还不如直接删除掉。在webwork2.2中,它需要为集合另外配置一个conversion.properties文件。真不明白,这样有什么优点?
这种新的整合方式,实际上只是解决了针对容器赋值,不需要依赖XWork的辅助类这样的一个问题,不过其付出的代价,却是多了一个配置文件,这也让人非常郁闷。好好的类型转化,平白无故多出了一个同package下的配置文件,这也无形中增加了编程的复杂度。
3. 现在,拥抱了泛型和Annotation的年代
实际上,在XWork发展到XWork-2.0.X之后,也开始注重了对泛型和Annotation的支持。所以,容器类型的转化,我们也可以尝试一下使用JDK的新特性来进行,当然这也是目前最为推荐的做法。
下面分别给出使用泛型和Annotation的代码示例:
<form method="post" action="/struts-example/ognl-collection-conversion.action"> <input type="text" name="users[0].name" value="aaa" /> <input type="text" name="users[1].name" value="bbb" /> <input type="text" name="users2[0].name" value="ccc" /> <input type="text" name="users2[1].name" value="ddd" /> <input type="text" name="userMap['user1'].name" value="eee" /> <input type="text" name="userMap['user2'].name" value="fff" /> <input type="text" name="userMap2['user3'].name" value="ggg" /> <input type="text" name="userMap2['user4'].name" value="hhh" /> <input type="submit" value="submit" /> </form>
public class OgnlConversionAction extends ActionSupport { private static final long serialVersionUID = 4396125455881691845L; private static final Log logger = LogFactory.getLog(Policy.class); private List<User> users; @Element(value = User.class) private List users2; private Map<String, User> userMap; @Element(value = User.class) private Map userMap2; /* (non-Javadoc) * @see com.opensymphony.xwork2.ActionSupport#execute() */ @Override public String execute() throws Exception { // -> aaa logger.info("users[0].name : " + users.get(0).getName()); // -> bbb logger.info("users[1].name : " + users.get(1).getName()); // -> ccc logger.info("users2[0].name : " + ((User)users2.get(0)).getName()); // -> ddd logger.info("users2[1].name : " + ((User)users2.get(1)).getName()); // -> [user1, user2] logger.info("userMap.key : " + userMap.keySet()); // -> eee logger.info("userMap.key = " + "user1" + " : " + "userMap.value(user1's name) = " + userMap.get("user1").getName()); // -> fff logger.info("userMap.key = " + "user2" + " : " + "userMap.value(user2's name) = " + userMap.get("user2").getName()); // -> [user3, user4] logger.info("userMap2.key : " + userMap2.keySet()); // -> ggg logger.info("userMap2.key = " + "user3" + " : " + "userMap.value(user3's name) = " + ((User)userMap2.get("user3")).getName()); // -> hhh logger.info("userMap2.key = " + "user4" + " : " + "userMap.value(user4's name) = " + ((User)userMap2.get("user4")).getName()); return super.execute(); } // setters and getters }
上面的代码中,我们可以看到,如果你使用泛型,那么你无需再使用任何额外的配置文件或者Annotation,XWork会把一切都为你准备好。如果你没有使用泛型,那么你可以使用Annotation来指定你需要进行转化的对象类型。其中,对Map对象使用Annotation时,Element中的value所对应的值,是Map中的value所对应的class。
由此可见,泛型和Annotation,在一定程度上,还是可以简化我们很多工作的。
文件上传
文件上传其实也是参数传递的一种,所以从方案上来讲,Struts2同样使用了一个拦截器来处理。而这个拦截器,同样来自于原来的Webwork,基本上没有做什么很大的改变。有关这个拦截器的详细内容,我们也留一个悬念,在后续章节中详细讲解。目前,你只要知晓,这个拦截器能帮助你完成一切文件上传相关的机制。
早在2005年,Quake Wang就对Webwork的文件上传机制有了详细的讲解:http://www.iteye.com/topic/10697
在这里我简单小结一下在进行文件上传时的三大要点:
1. 在配置文件上传时,拦截器的顺序非常关键
<interceptor-stack name="uploadStack"> <interceptor-ref name="upload"/> <interceptor-ref name="defaultStack"/> </interceptor-stack>
具体来说,upload的拦截器,必须在params的拦截器之前
2. 拦截器额外提供了一些额外的文件信息
Quake Wang 写道
ContentType: 文件的ContentType(可以用在做download的时候)
FileName: 实际的文件名
在上面的action例子里, 那么有uploadFilesContentType和uploadFilesFileName这2个属性, 也能够被自动绑定
FileName: 实际的文件名
在上面的action例子里, 那么有uploadFilesContentType和uploadFilesFileName这2个属性, 也能够被自动绑定
3. 拦截器提供的文件上传功能,你得到的是一个临时文件
robbin 写道
在webwork的file upload 拦截器功能中,它提供的File只是一个临时文件,Action执行之后就会被自动删除,因此你必须在Action中自己出来文件的存储问题,或者写到服务器的某个目录,或者保存到数据库中。如果你准备写到服务器的某个目录下面的话,你必须自己面临着处理文件同名的问题
而时代发展到Struts2的年代,对于文件上传的整体机制没有做什么改变。只是Struts2将apache的common-fileupload作为了其默认的文件上传的机制。
例子归例子,实际情况中,我们还是会遇到一些问题:
1. 默认实现中,文件和文件信息是分开表述的,对于后台处理来说,不是非常方便。
2. common-fileupload的实现,虽然提供了文件上传的机制,也可以让你得到文件的一些属性信息,但是它无法得到客户端的上传路径
对于第一个问题,我们可以使用OGNL的特性,将这些文件和文件名等文件信息做封装:
public class FileComponent implements Serializable { private static final long serialVersionUID = 4594003082271182188L; private File upload; private String fileName; /** * The default constructor */ public FileComponent() { } /** * @return Returns the upload. */ public File getUpload() { return upload; } /** * @return Returns the fileName. */ public String getFileName() { return fileName; } /** * @param upload * The upload to set. */ public void setUpload(File upload) { this.upload = upload; } /** * @param fileName * The fileName to set. */ public void setFileName(String fileName) { this.fileName = fileName; } /** * @param fileName * The fileName to set. */ public void setUploadFileName(String uploadFileName) { this.fileName = uploadFileName; } }
在这个类中,我定义了upload表示上传的文件,fileName表示上传文件的文件名。请注意我整个文件中的最后一个方法:setUploadFileName。这个方法将保证FileUploadInterceptor在运行时,能够正确设置上传的文件名。
/** * @param fileName * The fileName to set. */ public void setUploadFileName(String uploadFileName) { this.fileName = uploadFileName; }
这样,在Action中,我们将面对一个个完整的fileComponent对象,其中包括文件的引用、文件名称和其他文件信息。这样就不会因为上传多个文件而手足无措,你只需要使用fileComponent数组,就能轻松对上传的文件进行管理,而避免了在Action中书写许多个文件、文件名等属性了。
对于第二个问题,目前我也没有找到很好的方法。我所采用的方式与Yulimin是一致的:
Yulimin 写道
我现在的做法是表单中增加了一个隐藏域,当用户文件选择后,利用JS截取到用户选择的文件名,然后一起提交上去。
不知道有没有最终的解决方法?
不知道有没有最终的解决方法?
自定义的类型转化实现
Struts2在处理参数传递的过程中,需要完成类型转化,保证页面上的字符串能够转化成各式各样的Java对象。而这一点,其实也是由OGNL完成的。还记得我们在讲述OGNL的基础知识的时候列出来过的一个接口嘛?
/** * Appends the standard naming context for evaluating an OGNL expression * into the context given so that cached maps can be used as a context. * * @param root the root of the object graph * @param context the context to which OGNL context will be added. * @return Context Map with the keys <CODE>root</CODE> and <CODE>context</CODE> * set appropriately */ public static Map addDefaultContext( Object root, ClassResolver classResolver, TypeConverter converter, MemberAccess memberAccess, Map context );
在这个接口中,我们可以在使用OGNL的时候,注册针对某个Class级别的自己实现的TypeConverter,这样,OGNL就会在进行设值计算和取值计算的时候,使用自定义的类型转化方式了。让我们来看看TypeConverter的接口定义:
/** * context - OGNL context under which the conversion is being done * target - target object in which the property is being set * member - member (Constructor, Method or Field) being set * propertyName - property name being set * value - value to be converted * toType - type to which value is converted */ public Object convertValue(Map context, Object target, Member member, String propertyName, Object value, Class toType);
知道了原理,就简单了,我们可以自己实现一个TypeConverter的实现类,并且在Struts2中注册一下使用这个TypeConverter的Java类型的对应关系,我们就可以完成自定义的类型转化了。
具体的例子,可以参考一下Quake Wang同学在2005年时的一篇文章:http://www.iteye.com/topic/10507。文章针对的是Webwork2,但是无论是实现方式还是操作步骤,与Struts2是完全相同的。值得提醒的是,这篇文章的回复也非常有价值,在看文章的同时,不要忘记看回复。
不过针对Quake Wang的例子,我也想做一些补充。它的例子中,主要讲述了Struts2中如何去做java.utils.Date的自动类型转化,也正如后面回复中有人提到:
wolfsquare 写道
如果我在界面上有两种格式的日期怎么办?
例如一种短格式: SimpleDateFormat("yyyy-mm-dd"),一种长格式SimpleDateFormat("yyyy-mm-dd hh:MM:ss")
例如一种短格式: SimpleDateFormat("yyyy-mm-dd"),一种长格式SimpleDateFormat("yyyy-mm-dd hh:MM:ss")
而Quake Wang对此是这样解决的:
Quake Wang 写道
可以根据你的应用情况,看哪种方式是比较常见的转换规则,那么把这个规则定成Application-wide conversion rules:
另外的一个转换,可以写成Class-specific conversion rules :
otherDate=com.javaeye.core.webwork.converter.OtherDateConverter
在classpath root下面写一个xwork-conversion.properties: java.util.Date=com.javaeye.core.webwork.converter.DateConverter
另外的一个转换,可以写成Class-specific conversion rules :
otherDate=com.javaeye.core.webwork.converter.OtherDateConverter
我在这里提供一个我在实际项目中采用的方式:
public class DateConverter extends DefaultTypeConverter { private static final Log logger = LogFactory.getLog(DateConverter.class); private static final String DATETIME_PATTERN = "yyyy-MM-dd HH:mm:ss"; private static final String DATE_PATTERN = "yyyy-MM-dd"; private static final String MONTH_PATTERN = "yyyy-MM"; /** * Convert value between types */ public Object convertValue(Map ognlContext, Object value, Class toType) { Object result = null; if (toType == Date.class) { result = doConvertToDate(value); } else if (toType == String.class) { result = doConvertToString(value); } return result; } /** * Convert String to Date * * @param value * @return */ private Date doConvertToDate(Object value) { Date result = null; if (value instanceof String) { // TODO add date converter parse order here result = DateUtils.parseDate((String) value, new String[] { DATE_PATTERN, DATETIME_PATTERN, MONTH_PATTERN }); // all patterns failed, try a milliseconds constructor if (result == null && StringUtils.isNotEmpty((String)value)) { try { result = new Date(new Long((String) value).longValue()); } catch (Exception e) { logger.error("Converting from milliseconds to Date fails!"); e.printStackTrace(); } } } else if (value instanceof Object[]) { // let's try to convert the first element only Object[] array = (Object[]) value; if ((array != null) && (array.length >= 1)) { value = array[0]; result = doConvertToDate(value); } } else if (Date.class.isAssignableFrom(value.getClass())) { result = (Date) value; } return result; } /** * Convert Date to String * * @param value * @return */ private String doConvertToString(Object value) { SimpleDateFormat simpleDateFormat = new SimpleDateFormat(DATETIME_PATTERN); String result = null; if (value instanceof Date) { result = simpleDateFormat.format(value); } return result; } }
在我采用的方式中,依然采用Application-wide conversion rules,只是在自定义的Converter中,根据不同的日期形式进行逐个匹配,找到第一个匹配的日期类型进行转化。当然,这是一种投机取巧的办法,不过比较实用,适用于项目中不太使用国际化,日期的形式不太复杂的情况。
评论
13 楼
yfmine
2009-01-20
引用
原来你是喜欢DTO的朋友。那我们的讨论可以到此结束了,因为在我看来DTO是一个不应该存在的反模式,呵呵。
DTO的争论暂且不提。事实上,我并没有提到DTO,也不推崇所谓的DTO。我只是说,面对日期这种“复杂”的http参数,可以做手动处理;而把领域对象直接暴露在外,我个人并不太喜欢;对于result = DateUtils.parseDate((String) value, new String[] { DATE_PATTERN, DATETIME_PATTERN, MONTH_PATTERN }); 这种不确定的转换机制,我认为可能造成混淆和维护上的困难。我个人更喜欢Explicit is better than implicit,也许这些都只是个人喜好的不同而已,呵呵。另外,谢谢QuakeWang
12 楼
QuakeWang
2009-01-20
fyting 写道
比如你要update一个user的birthday,可客户端手动添加了一个user.loginName这样的属性,岂不是把登录名也改了
补充一下:
ParametersInterceptor允许你用白名单/黑名单来指定只接受或者拒绝哪些参数(acceptedParamNames / excludeParams)。
如果需要更细粒度的控制,可以让具体的action实现ParameterNameAware接口的acceptableParameterName方法。
你还可以扩展ParametersInterceptor,比如用java1.5,你可以仿照rails的active record model那样,自己定制一个annotation,保护那些不能被mass assign的attribute:
public class User { @AttributePotected private String logginName; }
总之,保护属性并不是选择DTO的原因。
11 楼
downpour
2009-01-20
to fyting:
原来你是喜欢DTO的朋友。那我们的讨论可以到此结束了,因为在我看来DTO是一个不应该存在的反模式,呵呵。
不过DTO这个东西本身存在争议,你可以继续保留你的观点,这也没问题。作为我来说,我并不想在我的系统中引入DTO,所以可能我会继续按照上面我说的方式写代码。如果DTO你能够使用的很好,你的代码也是没有问题的。
原来你是喜欢DTO的朋友。那我们的讨论可以到此结束了,因为在我看来DTO是一个不应该存在的反模式,呵呵。
不过DTO这个东西本身存在争议,你可以继续保留你的观点,这也没问题。作为我来说,我并不想在我的系统中引入DTO,所以可能我会继续按照上面我说的方式写代码。如果DTO你能够使用的很好,你的代码也是没有问题的。
10 楼
fyting
2009-01-20
public class FooAction{ private List<User> users; //setter }
这种确实更简单,但相应地,我认为页面就变得复杂了,如下面的页面:
<input type="text" name="users[0].name" value="aaa" /> <input type="text" name="users[1].name" value="bbb" />
引用
当然,这是一个见仁见智的问题了,各人有不同的习惯。
我是不赞成在setter方法中写DateUtils.parse(birthday,"yyyy-MM-dd"); 这样的代码的。因为在实际项目中,birthday这个变量不可能孤立在类似User这样的entity之外而独立存在,而我们也知道,如果你把一个birthday变量放到User里面去,setter方法就可能具备其特殊功能。
我是不赞成在setter方法中写DateUtils.parse(birthday,"yyyy-MM-dd"); 这样的代码的。因为在实际项目中,birthday这个变量不可能孤立在类似User这样的entity之外而独立存在,而我们也知道,如果你把一个birthday变量放到User里面去,setter方法就可能具备其特殊功能。
首先,我不会让entity直接暴露给外部,中间都会有一层自己的转换。比如你要update一个user的birthday,可客户端手动添加了一个user.loginName这样的属性,岂不是把登录名也改了。我的疑惑是,birthday就一个普通的http参数,为什么必须与领域对象挂上勾呢。
引用
比如说,在Hibernate做ORM的时候,会使用setter方法。此时,这句代码是否还能工作的很好,是需要打一个问号的。
有了前面birthday参数一定要加到user这个领域对象里的假设,所以后面的结论
看起来自然也就成立了。可这是实际的问题吗?实际的问题是如何获取参数,对吧。
引用
至于说到LongDate和ShortDate,我觉得看个人的习惯吧。我个人不喜欢这种方式,因为这和硬编码没什么区别。
其实这并非硬编码,而是更加清晰的一种表述,就好比你的birthday可能会用java.sql.Date,而loginTime会用java.util.Timestamp一样,这里根本就不需要任何自定义的子类,事实上jdk自身已经带了,这里与你数据库定义的date、datetime也完全一致。我认为更清晰地表述了领域对象属性所承载的含义。
result = DateUtils.parseDate((String) value, new String[] { DATE_PATTERN, DATETIME_PATTERN, MONTH_PATTERN });
我认为这是一种对参数格式的猜测,代码的维护人员,会很难知道客户端传过来的到底是yyyy-MM-dd,还是yyyy-MM。
9 楼
downpour
2009-01-20
to fyting:
首先谢谢你的回复,不过我对你的这两种做法的实践可操作性都持保留意见。
1. 有关Date类型的转化
我是不赞成在setter方法中写DateUtils.parse(birthday,"yyyy-MM-dd"); 这样的代码的。因为在实际项目中,birthday这个变量不可能孤立在类似User这样的entity之外而独立存在,而我们也知道,如果你把一个birthday变量放到User里面去,setter方法就可能具备其特殊功能。比如说,在Hibernate做ORM的时候,会使用setter方法。此时,这句代码是否还能工作的很好,是需要打一个问号的。
至于说到LongDate和ShortDate,我觉得看个人的习惯吧。我个人不喜欢这种方式,因为这和硬编码没什么区别。
2. 有关数组类型
这种code在实际项目中运用的可能性其实也不大,因为一个User可能具备10来个属性,难道你要在Action中去写10来个数组,还自己拼装?
这样是不是更简单呢?
首先谢谢你的回复,不过我对你的这两种做法的实践可操作性都持保留意见。
1. 有关Date类型的转化
我是不赞成在setter方法中写DateUtils.parse(birthday,"yyyy-MM-dd"); 这样的代码的。因为在实际项目中,birthday这个变量不可能孤立在类似User这样的entity之外而独立存在,而我们也知道,如果你把一个birthday变量放到User里面去,setter方法就可能具备其特殊功能。比如说,在Hibernate做ORM的时候,会使用setter方法。此时,这句代码是否还能工作的很好,是需要打一个问号的。
至于说到LongDate和ShortDate,我觉得看个人的习惯吧。我个人不喜欢这种方式,因为这和硬编码没什么区别。
2. 有关数组类型
public class FooAction{ private String[] name; private int[] age private double salary; //setter }
这种code在实际项目中运用的可能性其实也不大,因为一个User可能具备10来个属性,难道你要在Action中去写10来个数组,还自己拼装?
public class FooAction{ private List<User> users; //setter }
这样是不是更简单呢?
8 楼
fyting
2009-01-20
贴一个我项目里的代码
对于短格式的日期,可以定义类似的Date子类,然后在此基础上稍微改下格式和Date的子类就可以了。
public class FullDate extends Date { private static final long serialVersionUID = 1L; public FullDate() { super(); } public FullDate(long date) { super(date); } public FullDate(Date date) { super(date.getTime()); } } public class FullDateConverter extends StrutsTypeConverter { public static final String FULL_DATE_PATTERN = "yyyy-MM-dd HH:mm:ss"; protected String getDatePattern() { return FULL_DATE_PATTERN; } @Override public Object convertFromString(Map context, String[] values, Class toClass) { if(!toClass.equals(FullDate.class)) return performFallbackConversion(context, context, toClass); String value = values[0]; return new FullDate(DateUtils.parse(value, getDatePattern())); } @Override public String convertToString(Map context, Object o) { if (o instanceof FullDate) { FullDate param = (FullDate) o; return DateUtils.format(param,getDatePattern()); } return (String) performFallbackConversion(context, o, String.class); } }
对于短格式的日期,可以定义类似的Date子类,然后在此基础上稍微改下格式和Date的子类就可以了。
7 楼
fyting
2009-01-20
对于参数的自动转换,我认为不能太过于自动化。比如数组类型,就像robbin举的例子里:
这种就很简洁,也很清晰。如果是一个对象的多个属性,我更愿意写成
然后在action里自己拼起来,这与使用servlet的request.getParameterValues()的接口是很相似的,但是更加方便,并且没有太多的magic。
对于日期的转换,我也这样认为。自定义的convertor可以处理常用的几种格式,比如yyyy-MM-dd。更复杂的特殊情况,可以把参数写成String,然后自己转换,就根本不需要为每个action写conversion配置文件了,让代码更容易阅读。
这样,阅读代码的人可以一下就看出日期如何来的,form传过来的参数是何种格式,并且,也不见得麻烦多少,比起:
otherDate=com.javaeye.core.webwork.converter.OtherDateConverter
我认为清晰一些。
在实际的项目中,我还采用过子类化Date来进行处理。
比如定义两种不同格式的Date子类,分别对应短日期和长日期:
public class ShortDate extends Date
public class LongDate extends Date
然后对应不同的子类分别写不同的DateConvertor,再按照之前的处理方式:
现在可以看看action如何使用
public class FooAction{ private String[] arrayDate; //setter }
这种就很简洁,也很清晰。如果是一个对象的多个属性,我更愿意写成
public class FooAction{ private String[] name; private int[] age private double salary; //setter }
然后在action里自己拼起来,这与使用servlet的request.getParameterValues()的接口是很相似的,但是更加方便,并且没有太多的magic。
对于日期的转换,我也这样认为。自定义的convertor可以处理常用的几种格式,比如yyyy-MM-dd。更复杂的特殊情况,可以把参数写成String,然后自己转换,就根本不需要为每个action写conversion配置文件了,让代码更容易阅读。
public class FooAction{ private Date birthday; public void setBirthday(String birthday){ this.birthday = DateUtils.parse(birthday,"yyyy-MM-dd"); } }
这样,阅读代码的人可以一下就看出日期如何来的,form传过来的参数是何种格式,并且,也不见得麻烦多少,比起:
otherDate=com.javaeye.core.webwork.converter.OtherDateConverter
我认为清晰一些。
在实际的项目中,我还采用过子类化Date来进行处理。
比如定义两种不同格式的Date子类,分别对应短日期和长日期:
public class ShortDate extends Date
public class LongDate extends Date
然后对应不同的子类分别写不同的DateConvertor,再按照之前的处理方式:
在classpath root下面写一个xwork-conversion.properties: java.util.Date=com.javaeye.core.webwork.converter.DateConverter com.javaeye.ShortDate=com.javaeye.ShortDateConvertor com.javaeye.LongDate=com.javaeye.LongDateConvor
现在可以看看action如何使用
public class FooAction{ private ShortDate birthday; private LongDate otherDate; }action的代码同样很清晰,阅读代码的人不用猜测form传递何种格式的参数,通过不同的Date子类,就能够看懂所包含的格式了。
6 楼
downpour
2009-01-09
to rain2005:
如果我没有理解错的话,你可能用的是ajax提交。因为如果不是ajax提交,你每次做了添加或者删除操作后,你的页面都会重新load,这个时候页面应该是根据实际的数目重新分配你的name的。
如果你用的是ajax提交,我也不认为这样的提交和使用list的方式做参数传递有什么很大的关系。你可以尝试把代码写得具体一些,发站内短信给我,大家一起讨论一下。
如果我没有理解错的话,你可能用的是ajax提交。因为如果不是ajax提交,你每次做了添加或者删除操作后,你的页面都会重新load,这个时候页面应该是根据实际的数目重新分配你的name的。
如果你用的是ajax提交,我也不认为这样的提交和使用list的方式做参数传递有什么很大的关系。你可以尝试把代码写得具体一些,发站内短信给我,大家一起讨论一下。
5 楼
rain2005
2009-01-09
向老大请教一个问题。描述如下
我有大量如下的业务。模型1和模型2为一对多的关系。我在同一个页面编辑。模型2可以随意的添加和删除。按照上面的讲解,我把模型2在controller中使用list容器接收参数。
如果我把users[1]这行数据给删除了,那我在提交数据前是不是要使用JS把输入域的name属性值重新按照顺序赋值一遍,否则的话list.get(1)就没有意义了。可不可以这样理解?有没有什么好的办法呢?
我有大量如下的业务。模型1和模型2为一对多的关系。我在同一个页面编辑。模型2可以随意的添加和删除。按照上面的讲解,我把模型2在controller中使用list容器接收参数。
<tr><td> <input type="text" name="users[0].name" value="aaa" />删除 </td><tr/> <tr><td> <input type="text" name="users[1].name" value="bbb" />删除 </td><tr/> <tr><td> <input type="text" name="users[2].name" value="bbb" />删除 </td><tr/>
如果我把users[1]这行数据给删除了,那我在提交数据前是不是要使用JS把输入域的name属性值重新按照顺序赋值一遍,否则的话list.get(1)就没有意义了。可不可以这样理解?有没有什么好的办法呢?
4 楼
Ivan_Pig
2009-01-08
downpour 写道
Ivan_Pig 写道一直关注老大的此系列文章!非常感谢!不过目前对Struts2的前途有点怀疑!虽然与struts相比,改进了很多!但是模式基本还是一样的!这样的模式还适合以后的开发吗?疑问而已,学习还是要学习的 再次感谢!目前来说,MVC框架还很难退出历史舞台。主要的原因可能在于所有的Web开发基本上还是基于URL进行的。那些基于event或者组件的Web框架,我其实并不是很看好。可能在于我的思维模式的限制。谁也说不好以后的发展趋势,只能走一步是一步了。就像我文章里面介绍的,时代不断发展,会有新的东西出来,不过旧的东西始终还能能够让我们学到很多的设计思想。
多谢指点。
我之前曾用过JSF,由于速度太慢而最终放弃了!目前基于ZK在做项目。一个基于组件一个基于事件,效率都很有问题!我对这两类框架也不看好!比较偏向RIA。网速提高后,相信RIA会成主流!
最主要东西太多了,三天就有个新概念,什么SOA,云计算,不知所云。看坛子里的牛人说得头头是道的,真不知道说什么呢
目前对这些框架,要说能说出点什么。但真要问个细节还真不敢打包票。对框架的实现大概的都知道,也能以自己的方式实现。但至今没完整的读过一个框架的源码,很是惭愧阿!入行1年多,算是到了一个坎了吧。希望能跟着老大把这坎过了。对Struts2能有深入了解
3 楼
dnwq
2009-01-08
好文章[img] [/img]
2 楼
downpour
2009-01-08
Ivan_Pig 写道
一直关注老大的此系列文章!非常感谢!不过目前对Struts2的前途有点怀疑!虽然与struts相比,改进了很多!但是模式基本还是一样的!这样的模式还适合以后的开发吗?疑问而已,学习还是要学习的 再次感谢!
目前来说,MVC框架还很难退出历史舞台。主要的原因可能在于所有的Web开发基本上还是基于URL进行的。那些基于event或者组件的Web框架,我其实并不是很看好。可能在于我的思维模式的限制。
谁也说不好以后的发展趋势,只能走一步是一步了。就像我文章里面介绍的,时代不断发展,会有新的东西出来,不过旧的东西始终还能能够让我们学到很多的设计思想。
1 楼
Ivan_Pig
2009-01-08
一直关注老大的此系列文章!
非常感谢!
不过目前对Struts2的前途有点怀疑!
虽然与struts相比,改进了很多!
但是模式基本还是一样的!这样的模式还适合以后的开发吗?
疑问而已,学习还是要学习的
再次感谢!
非常感谢!
不过目前对Struts2的前途有点怀疑!
虽然与struts相比,改进了很多!
但是模式基本还是一样的!这样的模式还适合以后的开发吗?
疑问而已,学习还是要学习的
再次感谢!
发表评论
-
《Struts2技术内幕》 新书部分篇章连载(十)—— XWork概览
2012-01-29 14:43 7421第7章 别具匠心 —— XWo ... -
《Struts2技术内幕》 新书部分篇章连载(九)—— 强大的OGNL
2012-01-29 13:17 5112第6章 灵丹妙药 —— OGN ... -
《Struts2技术内幕》 新书部分篇章连载(八)—— XWork容器概览
2012-01-29 11:56 4718第5章 生命之源 —— XWork中的容器 对象的生命周期管 ... -
Struts2的一些不尽人意的地方,兼答hantsy
2012-01-06 10:21 4779hantsy 写道 在 Webwork 合并到Apache S ... -
《Struts2技术内幕》 新书部分篇章连载(七)—— ThreadLocal模式
2012-01-05 14:39 14739第4章 源头活水 —— Str ... -
《Struts2技术内幕》 新书部分篇章连载(六)—— 框架的本质
2012-01-05 14:02 4856第2章 固本清源 —— Web ... -
《Struts2技术内幕》自评 —— 尚未完成的话题
2011-12-30 11:11 4210此文接我另外一篇博客:新书上市:《Struts2技术内幕》 ... -
新书上市:《Struts2技术内幕》
2011-12-26 14:28 10564我的新书《Struts2技术内 ... -
《Struts2技术内幕》 新书部分篇章连载(四)—— 核心分发器
2011-10-27 20:15 75469.2核心分发器 —— Dispa ... -
《Struts2技术内幕》 新书部分篇章连载(五)—— 请求响应哲学
2011-10-27 20:01 14058第7章 别具匠心 —— XWork设计原理 众所周知,现代电 ... -
《Struts2技术内幕》 新书部分篇章连载(三)—— 多视角透析Struts2
2011-10-27 19:09 88403.3 多视角透析Struts2 Str ... -
《Struts2技术内幕》 新书部分篇章连载(一)—— 如何学习开源框架
2011-10-27 18:40 161982.6 如何学习开源框架 ... -
《Struts2技术内幕》 新书部分篇章连载(二)—— 面向对象浅谈
2011-10-26 19:46 9731第2章 固本清源 —— Web ... -
《Struts2技术内幕》 新书样章和导读
2011-10-27 20:40 5071由于本书尚未出版,我 ... -
忘记李刚,一步一步跟我学Struts2 —— 标签库,永恒的争论话题
2009-02-08 22:52 7665专栏地址:http://www.iteye ... -
忘记李刚,一步一步跟我学Struts2 —— Result机制,让视图更丰富
2009-02-04 23:56 12658专栏地址:http://www.iteye.com/wiki/ ... -
忘记李刚,一步一步跟我学Struts2 —— 拦截器详解
2009-02-01 12:49 10856专栏地址:http://www.iteye.com/wiki/ ... -
忘记李刚,一步一步跟我学Struts2 —— MVC框架的困惑
2009-01-21 11:43 10221专栏地址:http://www.iteye.com/wiki/ ... -
忘记李刚,一步一步跟我学Struts2 —— Struts2配置详解
2009-01-19 10:06 6353专栏地址:http://www.iteye.com/wiki/ ... -
忘记李刚,一步一步跟我学Struts2 —— Struts2中的Action
2009-01-15 15:02 7110专栏地址:http://www.iteye.com/wiki/ ...
相关推荐
李刚的Struts 2.1权威指南:基于WebWork核心的MVC开发,写的很详细。一共有九个压缩文件。
- Portlet支持:增加了对portlet应用的支持,便于在portlet容器中部署Struts2应用。 - **内容组织**:本书遵循读者的学习规律,先介绍基本概念与操作,再深入讲解每个知识点及其用法。全书共包含近100个实例,旨在...
《Struts2权威指南》是由著名Java技术专家李刚编著的一本经典教材,这本书深入浅出地讲解了Struts2的核心概念、架构和使用方法。书中不仅涵盖了Struts2的基础知识,还详细讨论了如何将Struts2与其他流行技术如Spring...
在这个实战教程中,作者李刚聚焦于Struts 2、Spring和Hibernate这三个核心组件的整合,它们是Java EE领域中非常流行的开源框架,用于构建现代化的Web应用程序。 Struts 2是一个强大的MVC(模型-视图-控制器)框架,...
《Struts2权威指南》是李刚先生撰写的一本深入解析Struts2框架的专业书籍,其源代码的提供对于读者理解和实践Struts2框架具有极大的帮助。 源代码通常包含了书中讲解的各种示例和实战项目,使得学习者能够通过实际...
在这个亲测Struts2 3.4.1必需包中,我们可以找到李刚系列书中推荐使用的JAR包,这些库文件对于成功运行和开发基于Struts2 3.4.1的应用至关重要。 1. **Struts2核心组件**:Struts2的核心库包含了处理请求、响应、...
本教程将引导你逐步了解和掌握Struts2的基础知识,结合李刚老师的《轻量级JAVA EE 企业应用开发实践》教材,帮助你深入理解Struts2的核心概念和使用方法。 在Web开发中,MVC模式是一种常见的架构模式,它将应用程序...
李刚老师编著的,Struts2权威指南完整版(上).pdf。可以帮助初学者入门,帮助入门者提高。
学习JEEE的经典教材,由李刚老师编著,这是最新的第三版,第3版保持了第2版内容全面、深入的特点,主要完成全部知识的升级。该书在2011年2月由电子工业出版社出版 由于上传文件大小限制本书被压缩为四个部分,此附件...
《Struts2权威指南》这本书由李刚撰写,电子工业出版社出版,为开发者提供了全面的Struts2学习资源。 Struts2的核心特性包括: 1. **拦截器(Interceptors)**:Struts2的拦截器是基于AOP(面向切面编程)的概念,...
李刚编著的《Struts2权威指南》是一本深入剖析该框架的书籍,它以丰富的实例和详尽的参数解析,帮助开发者从基础到高级全面掌握Struts2的使用。 1. **Struts2框架概述** - Struts2是Apache软件基金会下的一个开源...
总之,Struts2是Java Web开发中的重要工具,李刚老师的《Struts2权威指南》为开发者提供了全面的学习资源,无论你是初学者还是经验丰富的开发者,都能从中受益匪浅,提升自己的开发技能。通过阅读本书,你将能够深入...
《Struts2权威指南》由李刚编著,全面深入地介绍了Struts2框架的各个方面,是学习和掌握Struts2不可或缺的参考资料。 在Struts2框架中,主要涉及以下核心知识点: 1. **基础概念**:Struts2是Struts1的升级版,...
总的来说,李刚的《Struts2权威指南》源码是一个宝贵的学习资源,它可以帮助开发者深入理解Struts2框架的内部工作原理,提升实际开发中的问题解决能力。通过阅读和分析源码,不仅可以巩固理论知识,还能提高实战技能...
Struts 2是Java Web开发中的一个MVC框架,它提供了强大的控制层解决方案,能够简化视图与业务逻辑的交互。Struts 2的核心是Action和Result,Action负责处理用户请求,而Result负责展示结果。此外,Struts 2还支持...
《轻量级Java EE企业应用实战(第4版)》这本书深入探讨了Struts 2、Spring 4和Hibernate这三大框架的整合开发,旨在帮助读者掌握如何在实际项目中高效构建Java EE应用。SSH(Struts 2、Spring、Hibernate)是Java ...
《Struts2权威指南》是李刚所著的一本详细解析Struts2框架的书籍,其完整版涵盖了Struts2的核心概念、配置、实践应用以及与其他流行技术如Spring、JSF、Hibernate的整合。 首先,让我们深入理解Struts2的基础知识。...
《Struts2权威指南》是李刚编著的一本深入讲解Struts2框架的书籍,包含了全面的理论知识和实践案例。 在提供的源代码中,我们可以看到以下几个文件和目录: 1. **目录.txt**:这本书的完整章节目录,它将帮助读者...