Stripes是一个以让程序员的web开发简单而高效为准则来设计的基于动作的开源Java web框架。本文将介绍Stripes与其它如Struts之类基于动作的框架的区别和其提供的一些存在于Ruby on Rails之中的简单性。
3s_I,l_Z-n s
_T_N$b B$m0^ Stripes是一个以让程序员的web开发简单而高效为准则来设计的基于动作的开源Java web框架。传统的Java web开发着眼于借去耦(Decoupling)来实现其灵活性,但导致多个的配置文件,额外的对象,和其他资源的分散。这些困难造成相当多的程序员的更高的学习时间和低下的效率。其结果是有些Java程序员被一些非Java的框架所吸引去了:Ruby on Rails或者Django。一些Java web框架,如Stripes,正在开始从这些非Java框架中汲取其成功经验:简单而高效的开发。本文将介绍Stripes与其它如Struts之类基于动作的框架的区别和其提供的一些存在于Ruby on Rails之中的简单性。
_G%{_h_C.i_p_P'` g7L#~JAVA中文站社区门户)t n!H2c#f
图1是典型的用Stripes写的应用程序中的正常事件流程和组件。
1Z;gI_S\\8n,J"T&l
@z_vJAVA中文站社区门户4v_r%{_~_Z_U
| *a_lj*Q7U_XI图 1 典型Stripes流程 |
如你所见,其流程基本上就是一个MVC框架。Stripes和其他的基于动作的框架的一个主要的区别是没有一个外部的配置文件。我们随后将看到,Stripes用annotation和约定而非配置来提高产出和减少杂乱。JAVA中文站社区门户_f_Q p_e_j_H
P
JAVA中文站社区门户8W6Y*^ R_x
编写你的第一个Stripe动作(Action)
4b_a7c*^_Z_]`
8Q]+__S_B:k_j 让我们现在就开始通过创建Hello World例程来了解Stripes框架和理解其运作。HelloWorldAction类将提示用户输入姓氏和名字然后在另一个View里面显示,首先我们来编写controller类。JAVA中文站社区门户7S.A6_/L u)H:h_|*i
JAVA中文站社区门户-S!S_m_u9x&v_}$_$s_h
public class HelloWorldAction implements ActionBean
_F"@&J_S A-e t"G_Z5B{
_| W_k6J_KR @ValidateNestedProperties(JAVA中文站社区门户 J_W:u+e_^%]_Y_xu
{
_E+@_\\.f"bH
o @Validate(field = "firstName", required = true, on = {"hello"}),
2x_f/T5^_\\_j$Z7p/E_y_b_oR @Validate(field = "age", required = true, minvalue = 13, on = {"hello"})
_~3j4i'S_x_E,j_N })
3k p_v)H_S7T private Person person; JAVA中文站社区门户_{ e0q:L8T J$BL
private ActionBeanContext context;
_L_N+o+I+y;u3M_I_f @DefaultHandler public Resolution index()JAVA中文站社区门户_~tV_H_v_k(g_t
{
_@ e3j%V_u4} return new ForwardResolution("Hello.jsp");
_i_t B-x_B(N }
8O_B/q-C S public Resolution hello() JAVA中文站社区门户
j%v!F8f)|_U'_'v
{JAVA中文站社区门户 n_C_b_k7h2Va
return new ForwardResolution("SayHello.jsp");
_l2e o/zz8]$E7n'z }JAVA中文站社区门户
v*}5g_O9a/{ H*g'N9Y
public void setPerson(String person) {this.person = person;}
r7M_X!S_[_}_X_~ public String getPerson() { return person;}JAVA中文站社区门户_V `_K0Y_Q
public void setContext(ActionBeanContext c) {this.context = c; }
_l_t_]_g%L_a_Y
| public ActionBeanContext getContext() {return context; }} |
Controller类是一个实现了Stripes特有接口ActionBean的POJO(Plain Old Java Object,译注:读破粥)。所有的Stripes动作类都要实现这一接口以让StripesDispatcher servlet在运行服务时为其注入一个ActionBeanContext对象。ActionBeanContext对象可以让你存取的对象如request、response、和servlet context等servlet API。大多数时候在Stripes应用中是不用读取这些底层API对象的。JAVA中文站社区门户+c_c7H
R_X_o_r0w_\
JAVA中文站社区门户9Q_M_r_N'Nl8v_{5A
ActionBeanContext类还提供当前动作的状态并可以添加信息消息和错误消息到当前动作中。ActionBeanContext的变量和其读写方法可以放在一个基类里面,因为所有的Stripes动作都要实现之。
/B5Z QQ f([j_H__8dJAVA中文站社区门户 r_H_I3E_|_Y_o3d
Controller类的其他部分对于任何Java程序员来说都是很面熟的。有一个Person对象和其读写方法是用来读写用户的姓名给view的。虽然这仅仅是一个简单的嵌套对象,Stripes可以通过Java集合、泛型支持、和下标化的属性来实现更复杂完善的数据捆绑。因为Stripes可以处理复杂数据捆绑,你的领域对象(Domain Object)可以在其他需要它们的层重用。例如:通过Stripes你可以很容易的收集一个领域对象的信息,然后用其他的POJO框架,如Hibernate或者EJB3来对其进行持久化。
_z_W_G_M_I_^_EJAVA中文站社区门户_K_V_I+j(f)I_M4j
Person对象变量上有一个Stripes验证annotation用来保证用户在激活hello方法的时候已经输入了姓名。如果用户没有输入这两个必需的变量,原始页会被返回,并显示一个相关的错误消息。该验证只有在hello事件被申请的时候才会被激活,因为annotation的属性中指定了(on = {"hello"})。Stripes还会使用实用默认法则,根据验证方法和变量名称产生一个错误信息。例如,如果Person类的firstName变量在提交的时候没有提供,用户将看到:JAVA中文站社区门户_G K;r_x$o_Z_U_q_R ]_Q
_O_I_j_j_] G_X+]3v N8n)Z__Person First Name is a required field.JAVA中文站社区门户1X_c"s e_H_s TH,J
2[:Kg5_$^ j6j_c 这条消息是通过将Person.firstName进行刻读化处理后得到的。如果有必要,这些错误消息可以被重载来提供更多的客户自定义功能。
E D;[_K"N_E)?
_n z9Q {_m%T%B#f 另外还有一个Integer类型的变量age,是Person对象的一个属性。Stripes首先试图对request中命为person.age的parameter转换为Integer类型,并将其捆绑到Person对象上。在Person对象的age变量被付值以后,Stripes将验证该Integer值是否小于13。如果用户输入了一个字符串而非整数,用户得到这个消息:JAVA中文站社区门户_t_I_T(Z4_0l
JAVA中文站社区门户F_Tt_i Q1[
The value (Mark) entered in field Person Age must be a valid number.
!?!M)v*Xp2W4}
_?_u_W8W_K_k!`/m 若是用户输入了一个小于13的整数,用户将看到这个消息:JAVA中文站社区门户:C_hH T9R_c)P%O
JAVA中文站社区门户k)l:yQ_U1U%f
The minimum allowed value for Age is 13.
)W0o8b3^,})s(m#F
_v)c_a y3W_iR9a_N 同样地,我们没有必要为这些错误消息提供任何外部的配置文件。Annotation提供的验证与你的变量在同一个位置上,使得程序员定位验证、理解验证的内容、和对验证进行维护变动更容易。
M/y
c*^_\
_d'~_M ]5{f.\\_T_U_~_~ 这个Stripes动作还有两个可被激活的方法(称为事件)。事件是ActionBean类中有如下特征的方法:JAVA中文站社区门户_W-_'b{2P
v_I$G_N)m8A2I_T_dpublic Resolution eventNameJAVA中文站社区门户 U)z_[_k E
\\_L k!z u_E 请注意index方法被标注为@DefaultHandler annotation。因为在本动作中有多个事件,其中一个必须被指定为默认事件。如果调用本动作的URL没有指定哪个事件,Stripes则查找标注有@DefaultHandler annotation的事件,并执行。
_R4g#L_{_j/u.]_~_`_Q6gJAVA中文站社区门户_y_i_?_~_] m2i_Q7d_O
显示层(View)
)B.x
|_n(F&ynJAVA中文站社区门户_O_O0u$lV!L_U2j5p
K
现在让我们给Hello World例程加上显示层的逻辑。Stripes默认支持JSP为显示层的标准技术,不过你也可以用其他的显示层技术,比如FreeMaker。除了Stripes的tag库以外,没有什么新的东西要学。Hello.jsp是初始的显示,可以让用户输入和提交姓名。
!|
u_G*V5m_x!H%M_U)T
\\)?_v_I"E_R3[_y4f
<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld" %>JAVA中文站社区门户_f0}-{_\y_O z_|
......
Q-J_j_~&] v_N_F_i"V
| <stripes:errors/> 5p4a_J_O_R:H#Xg<stripes:form beanclass="com. myco. web. stripes. action. example. HelloWorldAction"> Say hello to: <br> First name: _P$M E#N&U%|3y1l }`<stripes:text name="person.firstName"/> <br> Age:<stripes:text name="person.age"/><br> 2F i k_i;[__)`_c_G B<stripes:submit name="hello" value="Say Hello"/> JAVA中文站社区门户#h4p_lW8W_y ~_P </stripes:form> _j_r_aXk4V s5e_Mb...... |
这个JSP易读易维护。而Stripes用于form和input的tag跟对应的HTML代码非常相似。stripes:form tag包含一个beanclass属性,其值为我们前面定义的controller类的完整类名。我们可以用stripes:form中的action属性来替换beanclass属性,但是beanclass属性可以让你在以后对Stripes动作进行重构的时候更加方便。如果你要用在stripes:form tag中使用action属性,方法如下:
&i7g9J&^0l_`+X(yw_G_R
_Q J5L#V_y
| <stripes:form action="/example/HelloWorld.action"> |
有一个stripes:input tag指定了一个名为person.firstName属性,其作用是将其储存的输入值付给controller的Person对象的firstName变量中。最后,stripes:submit tag指定一个name属性来告诉Stripes的HelloWorldAction类使用哪一个事件。
_}_^_A_k_w_A N_F
_H!A*^_^)N7[;D:A_V_} 我们现在已经完成了提交姓名的值给HelloWorldAction,剩下的就是在另一个view中将其反馈给用户了。
l'M O"p0z_C*[+CJAVA中文站社区门户#^/P2@_V_v_B)L
<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld" %>
2S5}_t_Z_F9u......
7Q3A0S1q_c<stripes:errors/>JAVA中文站社区门户_]9{_\\_J+r
<h2>Hello ${actionBean.person.firstName} your age is ${actionBean.person.age} </h2> <p/>JAVA中文站社区门户5T5v7Z:p} <stripes:link beanclass="com.myco.web.stripes.action. example.HelloWorldAction"> Say Hello Again JAVA中文站社区门户.d_J \\_F_@1n)Y </stripes:link> c"{ g(u_p j...... |
本JSP将自己通过一个对动作的引用读取person的姓名信息并显示。为达到这一目的,Stripes自动在request的属性中添加一个名为actionBean动作对象,以供JSTL存取。最后,我们用了一个strips:link tag来建立一个返回HelloWorldAction地链接从而可以让我们输入不同的姓名。我们以可以通过如下办法显式地创建一个指向index事件的stripes:link:
\/[_J1s/}_vJAVA中文站社区门户k0m6b%x_C2B_n6C
| <stripes:link beanclass="com.myco.web.stripes.action. example.HelloWorldAction" event="index"> _`6a-K'{6s K+nSay Hello AgainJAVA中文站社区门户_T:K_{_z_F8k,x2C4l </stripes:link> |
_@+]_D6N_\ 因为我们已经用annotation把index方法标记为@DefaultHandler,Stripes无须event属性也知道要执行哪一个方法。JAVA中文站社区门户;|)s:O_m)F%z3a U8L
用约定、不用配置文件JAVA中文站社区门户8a_i_i t_M.T_q
_rx_k_V_t_V 我们现在有了Java组件,我们该做配置了,把动作映射的一个URL上,并将其连接到我们的两个view上面去。等一下!我们在用Stripes,我们不需要外部配置文件!
_C_R_B_i9W
A_e_I5iJAVA中文站社区门户_V.Y#k3O_j
虽然这听来好像好得不像是真的,但这的确是Stripes的一项最具生产效率的功能。Stripes使用约定而非配置文件来映射动作到URL上。我们也无须使用一个外部配置文件来把view映射到一个个标记名字上。这意味着程序员不用再为了一个标记名字——比方说SUCCESS——的实际来源,而在配置文件中跳来跳去了。没有必要在Java和view组件的外部进行配线,因而导致更好地维护性和更高的生产率。JAVA中文站社区门户 b1I2Z_W i_t!J_r_l
_m2F+u
Do Y_@_D8F__*q Stripes是如何提供隐式的URL映射而无需在外部配置每一个动作或者而外的annotation呢?这个可以从Stripes在web.xml中的配置以及它是如何通过实用默认法建立URL映射来解释。首先,我们来看看Servlet过滤器:StripesFilter。其在web.xml中的默认配置如下:JAVA中文站社区门户3n_@"]_c_s
JAVA中文站社区门户 A_Y_Z'kO*q/l/L
<filter>
/j7o_l$G_\\ c_Q__<display-name>Stripes Filter</display-name>
W1o Z
u9N1]_y4\<filter-name>StripesFilter</filter-name>
_\\_c5h_]*F3~#_
| <filter-class> net.sourceforge.stripes.controller.StripesFilter </filter-class> JAVA中文站社区门户-~:J9N_B)Z_}0n6D_I-i_G"b <init-param> JAVA中文站社区门户6V_r___z1[ h <param-name>ActionResolver.UrlFilters</param-name>JAVA中文站社区门户!H.@2y_n_H!P_y9g <param-value>/WEB-INF/classes</param-value> #\\ E/\\:X)_9S Z</init-param>JAVA中文站社区门户0b f _0I_l_q*s_z_^6H </filter> |
当Servlet容器启动的时候,StripesFilter对其init-param元素执行初始化。其中最重要的init-param元素就是ActionResolver.UrlFilters参数。这个参数告诉Stripes到哪里查找跟Stripes有关的类。这个例子里面,Stripes将查找/WEB-INF/classes目录下的所有实现ActionBean接口的类。每一个被找到的类和其绑定的URL都将被加入一个Map中。JAVA中文站社区门户3W |_N5j k__
JAVA中文站社区门户_L_Z)v_N:Q_~ V_O
让我们来看看Stripes是如何处理我们的HelloWorldAction动作为例子吧。因为HelloWorldAction类位于/WEB-INF/classes目录下,所以会被认为是一个Stripes servlet。在我们的例子当中,其完整类名是com.myco.web.stripes.action.example.HelloWorldAction。随后,其完整类名将按照如下法则被翻译成一个URL绑定。
_j_i0B F_e/W
_K(o_q_~_N_d_l3F_h2c 1. 将含有www、web、stripes、和action的部分及其以前的内容删掉。在我们的例子有三个上述单词,所以我们得到了example.HelloWorldAction。JAVA中文站社区门户_f_w.__T&`_e5q
;U_V,Q_v3j_A 2. 如果类名中包涵带Action或Bean的尾巴,删掉。因为我们的类名以Action结尾,我们得到了example.HelloWorld。
_u_J_s_s%l ZJAVA中文站社区门户_G8~!n_HV_O4P
3. 将.替换为/,我们得到了example/HelloWorld。
)O_r-{Y_g7v:e
q;y_N
M_m/M/B)x7r/M3~___@8? 4. 最后,添加上一个尾缀(默认是.action)从而完成了URL绑定。最后的结果是example/HelloWorld.action。
_y_b2`(O1o2r `
'D!D(d_b_M%Y W 现在Stripes找到了ActionBean类并为其创建了一个URL绑定,然后存放在一个java.util.Map<String, Class<? extends ActionBean>>之中。其中key参数是URL绑定,value参数是实现ActionBean的类名。下面是我们的例子中的Map:JAVA中文站社区门户3T#yZ_I4R_x'c_g_N
JAVA中文站社区门户_Q!^!m_q'T C_s_q_D"k_g)q
URL绑定:/example/HelloWorld.action
_v1F%U_[#K ActionBean类:com.myco.web.stripes.action.example.HelloWorldActionJAVA中文站社区门户"x_W_N&}%V(k_Q
_y)?_t_S_c#a5L4n_r 我们要看的第二个组件是Stripes如何把URL绑定翻译成你正在做的这个ActionBean类。这是Stripes调度servlet的职责,在web.xml中的配置如下:JAVA中文站社区门户/r_u_M_~_p
JAVA中文站社区门户;rF_s
Q x;K"~-~:b
<servlet>JAVA中文站社区门户_n_m_q(?_A_^
<servlet-name>StripesDispatcher</servlet-name>
_b_g L i_P_]!H<servlet-class>JAVA中文站社区门户3g z(A_Y/A cT
net.sourceforge.stripes.controller.DispatcherServlet </servlet-class>
/k K!J,O_?_?_y ]
| <load-on-startup>1</load-on-startup>JAVA中文站社区门户%c C7I5E6h-R_a_\\)@_f </servlet> Y_g_@;M_C1i_lA<servlet-mapping> "J f)H_w&a3Z<servlet-name>StripesDispatcher</servlet-name> _q0t6O+x0H#r_F__<url-pattern>*.action</url-pattern>JAVA中文站社区门户9kT%b_n_!z_T_w </servlet-mapping> |
,F_m_Q!n q/L e StripesDispatcher的一个职责就是将URL解析为Stripes的ActionBean类。当用户激活URL http://host/uri/example/HelloWorld.action的时候,Stripes调度servlet将在URL映射表中查询并找到com.myco.web.stripes.action.example.HelloWorldAction类,并实例化产生该类的一个实例。最后,index方法被激活,因为在annotation中它被定义为默认局柄而在该URL中并没有指定一个事件。
!M)b"bN(I
_p'Z_j5O(B0A_z 如果我们想要直接执行HelloWorldAction中的hello方法怎么办?应该象下面这个URL这样把事件的名字放在request的参数中:
[&f_p,a8k2q_J9kJAVA中文站社区门户 s&l!]$u_}x_q,}_}
http://host/uri/example/HelloWorld.action?hello=&firstName=Mark&age=13JAVA中文站社区门户+m-Q_a9J h7}+W_F_{
JAVA中文站社区门户_Y,f_e9|!T1]
请注意我们没有给hello这个request参数名指定任何值。在这个情况下,StripesDispatcher会把hello这个这个request参数名和HelloWorldAction类中个一个带有public Resolution hello()签名的函数识别并映射。该方法名城在初始化的时候为了性能而缓存在另一个Map中。JAVA中文站社区门户_NY
O k_a_^:M
JAVA中文站社区门户(A_y$^)@)]_t
我们已经看到Stripes的基础以及如果创建简单的动作和一些该框架是如何运作的细节。通过在web.xml中的初始化,我们能够避免用多个单独的XML配置文件来把我们的显示层组建写在一起。这很重要,原因如下:首先,如果你需要任何改动,你可以看到一个URL就立即知道你该查看哪一个类。其次,我们不需要任何单独的工具来在你的配置文件过大而且不可管理的时候帮助你。通过消除掉配置文件,我们不再需要给框架一大堆的metadata。最后,我们不再需要为一个的用来描述我们的组件是如果相互关联的文件来一刻不停维护了。
%Bo6Z/w ` R x
D*B_l_n_r0H_y_B
t AjaxJAVA中文站社区门户;@O0C"F;q4I
_J f_s_V!a9X_a_D5q 要不要更高级的功能?那我们就来看看Stripes是怎么处理Ajax的。我们将把先前的Hello World例程改成使用Ajax调用Stripes动作。本例子的源代码可以在本文资源中找到。首先,我们对Hello.jsp作改动使其引用Prototype JavaScript函数库。我们还要为Ajax调用增加一个JavaScript函数,并更改提交按钮为其添加一个onclick事件:JAVA中文站社区门户.t5z`9N6\\_d"C
y:U \\_y8ci\\9[ \
<%@ taglib prefix="stripes" uri="http://stripes.sourceforge.net/stripes.tld" %>JAVA中文站社区门户4V_Y'Z1J7i_N'c%V
...... JAVA中文站社区门户1F_I ir_c_`8q_{_V
<script src="${pageContext.request.contextPath}/js/prototype.js" type="text/javascript">JAVA中文站社区门户_]#\\ E_V0j"Y
</script> JAVA中文站社区门户_j)WE3z9|)m!u_c_C
<script type="text/javascript">
_B_r3T_]_J g_Z_m_E$Y_Rfunction sayHelloAjax(){JAVA中文站社区门户*f_{ f:e_}_o4? F
var myAjax = new Ajax.Updater('hello', "<stripes:url beanclass="com. myco. web. stripes. action. example. HelloWorldAction" event="sayHelloAjax"/>", { method: 'get', parameters: Form.serialize('helloForm') }); JAVA中文站社区门户_U3Q_C+J:P_v_t
}JAVA中文站社区门户_y_H
Z6x \\_D_R
J0[_?
</script>
:b(B_J_K-k#X_{_d ......JAVA中文站社区门户,u#d_] q R_z_l_{
<stripes:errors/> 2o2v_Q_o_n9d.w4~:C <stripes:form beanclass="com. myco. web. stripes. action. example. HelloWorldAction" id="helloForm">JAVA中文站社区门户 V_O p_l,H'g_h:k&R'O Say hello to: $E4R S3t_C_i.O <br> First name: _n_o;{2i_n i/I <stripes:text name="person.firstName"/><br> _T4{_Fy_g_i_y ~#B@%~ Age:<stripes:text name="person.age"/><br> i C2n$M$|_C#j }!\\_c <stripes:button name="helloAjax" value="Say Hello" onclick="sayHelloAjax()"/> _u%X!mQ2{)I_C <div id="hello"></div> JAVA中文站社区门户_g3?6W_A/L n_S%S+r </stripes:form> ...... |
stripes:button有一个onclick事件将会调用HelloWorldAction类中的sayHelloAjax方法并将其结果返回在一个叫hello的div tag中。下面是我们要在HelloWorldAction中介绍的一个新方法:JAVA中文站社区门户)h%\\_G.O+E_e_|_{_U B/YU
/J_p_d8E_k_c$l k
| public Resolution sayHelloAjax()JAVA中文站社区门户!J(T8U`_n_u$F { $k)L1?"O ^9b return new ForwardResolution("SayHelloAjax.jsp"); _U+}@_O_~_\\2q2F} |
_G w S [_G 这个方法没有多少工作,因为Stripes已经承担了姓名内容的绑定。因此,本方法唯一的责任就是转发到一个叫SayHelloAjax.jsp的页面片断上去。该叶面片段的内容如下:JAVA中文站社区门户(G_h_m1^_N5T'I6H_K)w
JAVA中文站社区门户5`_j_H_c F3A_@_[
| <h2>Hello ${actionBean.person.firstName} your age is ${actionBean.person.age}!</h2> |
{!H*B_S_o
Spring整合JAVA中文站社区门户_?_n_L$r2g_V_@1b__
JAVA中文站社区门户!r%{_k*b;m_f_`_@7@#E
Stripes还内置了对Spring支持。你可以自动地将Spring bean诸如到你的动作中。按照Stripes的风格,除了Spring上下文配置文件以外不需要任何外部配置文件。如果我们Spring的配置文件如下:
;]Y x_r9e_n+m
_n&~0}_u_o ^0}
| <bean id="personService" parent="abstractTxDefinition">JAVA中文站社区门户_@_J-M \\1Q,T <property name="target"> _f_g_K_F_s3p8?&N!{_K<bean class="com.myco.service.impl.PersonServiceImpl"/> JAVA中文站社区门户&v"U$w.L;eS_c </property>JAVA中文站社区门户5V!v%u_L_?%X;j </bean> |
_L_n4[_z_C Z K$h 要把person服务注入到一个Stripes动作中,得增加一个跟Spring bean的名字一致的属性和setter。Stripes提供了@SpringBean annotation来查询正确的Spring bean以注入到动作之中。下面是我们要在动作类中包含的例子:
H-Yf3[4z2s_Y:t
/h:a
B;]-b_E [_f m
| private PersonService personService;JAVA中文站社区门户:L_|)}_CD_d8y/z6a9La @SpringBeanpublic void setBlogService(BlogService blogService) _V m_v K4_ A {{ ^3{_Q_d4K_m5Y_Hthis.blogService = blogService;JAVA中文站社区门户_|_L9Ve!~%o/F_q#Q_O } |
2E
B_j ~9Y,^ 本文无法囊括Stripes的所有高级功能。但是,Stripes有非常完整的文档。Stripes还包含了一个与Tiles类似但无需外部配置文件的layout管理器。另外,还可以用于生命周期事件的各处、文件上载等等等等。
__6q_X+Q5{
K0z C_q)s.N_M 结论JAVA中文站社区门户_s_A;y)s_O5ce_U_Z'm$@
JAVA中文站社区门户$U_{6O_Q;U ^_g Z_j
Stripes是一个既强大又简单的Java web框架。Stripes利用了Java 5的annotation和泛型功能,从而使得Java程序员避免维护外部配置文件并增加工作效率。Stripes可以简化困难的web开发工作,并使得简单的工作更加简单!下载本文