??xml version="1.0" encoding="utf-8" standalone="yes"?> 2.如何~写U程安全的代?
struts下的action也类|同样在多U程环境下。可以参考struts user guide: http://struts.apache.org/struts-action/userGuide/building_controller.html 中的Action Class Design Guidelines一? Write code for a multi-threaded environment - Our controller servlet creates only one instance of your Action class, and uses this one instance to service all requests. Thus, you need to write thread-safe Action classes. Follow the same guidelines you would use to write thread-safe Servlets.
?为多U程环境~写代码。我们的controller servlet指挥创徏你的Action cȝ一个实例,用此实例来服务所有的h。因此,你必ȝ写线E安全的ActioncR遵循与写线E安全的servlet同样的方针?
1.什么是U程安全的代?
在多U程环境下能正确执行的代码就是线E安全的?
安全的意思是能正执行,否则后果是程序执行错误,可能出现各种异常情况?/p>
很多书籍里都详细讲解了如何这斚w的问题,他们主要讲解的是如何同步U程对共享资源的使用的问题。主要是对synchronized关键字的各种用法Q以及锁的概c?
Java1.5中也提供了如d锁这cȝ工具cR这些都需要较高的技巧,而且相对难于调试?
但是Q线E同步是不得以的Ҏ,是比较复杂的,而且会带来性能的损失。等效的代码中,不需要同步在~写Ҏ度和性能上会更好些?
我这里强调的是什么代码是始终为线E安全的、是不需要同步的。如?
1)帔R始终是线E安全的Q因为只存在L作?
2)Ҏ造器的访?new 操作)是线E安全的Q因为每ơ都新徏一个实例,不会讉K׃n的资源?
3)最重要的是:局部变量是U程安全的。因为每执行一个方法,都会在独立的I间创徏局部变量,它不是共享的资源。局部变量包括方法的参数变量?
struts user guide里有Q?
Only Use Local Variables - The most important principle that aids in thread-safe coding is to use only local variables, not instance variables , in your Action class.
?只用用局部变量?-~写U程安全的代码最重要的原则就是,在ActioncM只用局部变量,不用实例变量?/p>
ȝQ?
在Java的Web服务器环境下开发,要注意线E安全的问题。最单的实现方式是在Servlet和Struts Action里不要用类变量、实例变量,但可以用类帔R和实例常量?
如果有这些变量,可以它们{换ؓҎ的参C入,以消除它们?
注意一个容易淆的地方Q被Servlet或Action调用的类?如值对象、领域模型类)中是否可以安全的使用实例变量Q如果你在每ơ方法调用时
新徏一个对象,再调用它们的ҎQ则不存在同步问?--因ؓ它们不是多个U程׃n的资源,只有׃n的资源才需要同?--而Servlet和Action的实例对于多个线E是׃n的?
换句话说QServlet和Action的实例会被多个线E同时调用,而过了这一?如果在你自己的代码中没有另外启动U程Q且每次调用后箋业务对象旉是先新徏一个实例再调用Q则都是U程安全的?
]]>
|
+--javax.servlet.GenericServlet
|
+--javax.servlet.http.HttpServlet
|
+--org.apache.struts.action.ActionServlet
Struts提供了一个缺省版本的ActionServletc,你可以承这个类Q覆盖其中的一些方法来辑ֈ你的Ҏ处理的需要。ActionServletl承与javax.servlet.http.HttpServletQ所以在本质上它和一个普通的servlet没有区别Q你完全可以把它当做一个servlet来看待,只是在其中完成的功能不同|了。ActionServlet主要完成如下功能Q?br>
一个来自客L的URI映射C个相应的Actionc?br>
省版本的ActionServlet会从配置文gweb.xml中读取如下初始化参数Q?br>
应用使用的资源包(resources bundle)的基c?br>
用于创徏应用的MessageResources对象的MessageResourcesFactory的类名。确省是org.apache.struts.util.PropertyMessageResourcesFactory?br>
Struts的配|文Ӟ省?WEB-INF/struts-config.xml。注意这儿是与应用Context兌的相对\径?br>
定义了确省的内容cd和编码格式,它会被自动地被设|到每个response中,如果JSP/Servlet中没有明的讄。确省是text/html?br>
调试信息的别。默认ؓ0Q比当前U别高的调试信息会被log到日志文件中?br>
与debug的作用类|只是q个detail是initMapping()时专用的。调试信息会被打印到System.outQ而不是日志文件?br>
ActionFormBean的实现类Q确省ؓorg.apache.struts.action.ActionFormBean
应用中用的ActionForwardc,省是org.apache.struts.action.ActionForward?br>
指定了确省用的Locale对象。设为trueQ当得到一个sessionӞ会自动在session中存储一个以Action.LOCALE_KEY标示的Locale对象Q如果session中还没有与Action.LOCALE_KEYl定的Locale对象?br>
应用中用的ActionMappingc,省是org.apache.struts.action.ActionMapping?br>
文g上传使用的MutipartRequestHandler的实现类。确省ؓorg.apache.struts.upload.DiskMultipartRequestHandler
如果设ؓtrueQ那么ActionServlet会自动在每个到客L的响应中dnocache的HTML_q样客户端就不会对应用中的页面进行缓存。确省ؓfalse
如果讄为trueQ那么应用在得到一个未定义的message资源Ӟ会返回nullQ而不是返回一个错误信息。确省是true?br>
文g上传的大上限,省?50M
文g上传时的~冲区的大小Q确省ؓ4M
讄用于上传时的临时目录。工作目录会作ؓ一个Servlet环境QContextQ的属性提供?br>
Are we using the new configuration file format?省为true?br>
在解析配|XML文g是是否进行有效性的验证。确省ؓtrue
ActionServlet中应用了命o设计模式?br>
一个Servlet在由容器生成Ӟ首先会调用init()Ҏq行初始化,在接C个HTTPhӞ调用相应的方法进行处理;比如GETh调用doGet()ҎQPOSTh调用doPost()Ҏ。所以首先看看ActionServlet的init()ҎQ你׃很清楚ؓ什么ActionServlet可以完成q些功能了?br>
init()
在它的init()Ҏ中,ActionServlet依次调用如下protected的方法完成初始化Q?br>
首先把actions设ؓslow模式Q这时对FastHashMap的访问是U程同步的,然后清除actions中的所有的已存在的?值对Q最后再把actions的模式设为fast。由于FastHashMap是struts在java.util.HashMap的基上的一个扩展类Q是Z适应多线E、ƈ且对HashMap的访问大部分是只ȝҎ环境的需要。大家知道java.util.HashMap是非U程安全的,所以HashMap一般适用于单U程环境下。org.apache.struts.FastHashMap是l承于java.util.HashMapQ在其中d多线E的支持产生的。在fast模式下的工作方式是这LQ读取是非线E同步的Q写入时首先克隆当前mapQ然后在q个克隆上做写入操做Q完成后用这个修改后的克隆版本替换原来的map。那么在什么时候会把ActionscL加到q个map中呢Q我们已l提Cstruts是动态的生成Actioncȝ实例的,在每ơActionServlet接收C个GET或POST的HTTPhӞ会在q个map中查扑֯应的Actioncȝ实例Q如果不存在Q那么就实例化一个,q放入map中。可见这个actions属性vC对Actioncd例的~存的作用?br>
说明Q文中引用的代码片断可能会省略了一些例外检查等非主U的内容Q敬h意?br>
首先从配|文件中dfactory参数Q如果这个参C为空Q那么就在MessageResourcesFactory中用这个指定的Factoryc;否则Q用默认的工厂corg.apche.struts.util.PropertyMessageResourceFactory。然后调用MessageResourcesFactory的静态createFactory()ҎQ生成一个具体的MessageResourceFactory对象Q注意:MessageResourcesFactory是抽象类Q。这样就可以调用q个具体的MessageResourceFactory的createResource()Ҏ得到配置文g(web.xml)中定义的资源文g了?br>上面的application对象cd为MessageResources。在web.xml中在配置ActionServlet时可以指定一个特定的工厂cR不能直接MessageResourcesFactory的createResources()ҎQ因个方法是abstract的。创建factoryObject的过E如下:
<li>initMapping() Q?nbsp; 为应用初始化mapping信息ActionServlet有一个protected的属性:mappingQ封装了一个ActionMapping的对象集合,以便于管理、查找ActionMapping。mappings是org.apache.struts.action.ActionMappingscȝ实例。主要有两个ҎQaddMapping(ActionMapping mapping)和findMapping(String path)。ActionMapping也是使用上面提到的org.apache.struts.util.FastHashMapcL存储所有的ActionMapping对象?br>
在initMapping()中,首先链接mappings对象到本servlet实例。其实这句话的作用很单,在ActionMappings中会有一个ActionServletcd的属性,q个属性就界定了这个ActionMappings对象所属的ActionServlet。Struts的实现比较灵z,其中的ActionFormBean、ActionForward、ActionMappingcM完全可以使用自己实现的子c,来定制Struts的工作方式。上面的代码׃配置文gQweb.xmlQ中dformBean、forward、mapping参数Q这些参数就是你定制的ActionFormBean、ActionForward、ActionMappingcd?br>
从web.xmldStruts的配|文件的位置。用org.apache.struts.digester.Digester解析config参数标示的配|文Ӟ通常?#8220;/WEB-INF/struts-config.xml”Q解析出所有的data-source、form-bean、action-mapping、forward。从上面的程序片断看刎ͼDigester仅仅调用了一个parse()ҎQ那么,Digester是怎样把解析struts-config.xml文gq把解析的结果form-bean{信息存储到属性变量formBeans{中的呢Q你可以注意到在调用digester.parse(InputStream)之前Q首先调用了initDigester()ҎQ?br>
在这个方法中首先生成一个Digester对象Q然后设|解析的规则和回调,如果你对XML、SAX不是很熟Q这儿不必纠~太深。要注意的是addSetNext()ҎQ设|了每一个要解析元素的Set Next回调ҎQ而这个方法就是由digester解析器的父提供的。上面的片断中的“addMapping”是ActionServlet本n定义的一个方法,由Digester回调。Digester是c此把解析出的每一个FormBean、ActionForward、ActionMapping{存储到属性变量formBeans、forwards、mappings{中的?br>
getServletContext().setAttribute(key,dataSource);
getServletContext().setAttribute(Action.FORWARDS_KEY, forwards);
getServletContext().setAttribute(Action.MAPPINGS_KEY, mappings);
]]>
request.getSession(true) will return the current session if one exists, if one doesn't exits a new one will be created.
So there is actually no difference between the two methods.
HOWEVER, if you use request.getSession(false), it will return the current session if one exists and if one DOES NOT exist a new one will NOT be cretaed.
]]>
struts启动spring的WebApplicationContext
spring有三U启动方?使用ContextLoaderServlet,ContextLoaderListener和ContextLoaderPlugIn.
看一下ContextLoaderListener的源?q是一个ServletContextListener
/**
* Initialize the root web application context.
*/
public void contextInitialized(ServletContextEvent event) {
this.contextLoader = createContextLoader();
this.contextLoader.initWebApplicationContext(event.getServletContext());
}
/**
* Create the ContextLoader to use. Can be overridden in subclasses.
* @return the new ContextLoader
*/
protected ContextLoader createContextLoader() {
return new ContextLoader();
}
contextLoader的源?br /> public WebApplicationContext initWebApplicationContext(ServletContext servletContext)
throws BeansException {
long startTime = System.currentTimeMillis();
if (logger.isInfoEnabled()) {
logger.info("Root WebApplicationContext: initialization started");
}
servletContext.log("Loading Spring root WebApplicationContext");
try {
// Determine parent for root web application context, if any.
ApplicationContext parent = loadParentContext(servletContext);
WebApplicationContext wac = createWebApplicationContext(servletContext, parent);
servletContext.setAttribute(
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac);
if (logger.isInfoEnabled()) {
logger.info("Using context class [" + wac.getClass().getName() +
"] for root WebApplicationContext");
}
if (logger.isDebugEnabled()) {
logger.debug("Published root WebApplicationContext [" + wac +
"] as ServletContext attribute with name [" +
WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
}
if (logger.isInfoEnabled()) {
long elapsedTime = System.currentTimeMillis() - startTime;
logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
}
return wac;
}
catch (RuntimeException ex) {
logger.error("Context initialization failed", ex);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
throw ex;
}
catch (Error err) {
logger.error("Context initialization failed", err);
servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
throw err;
}
}
注意WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE,q里面放了WebApplicationContext,需要用时从ServletContext取出
可以使用WebApplicationContextUtils得到WebApplicationContext
public static WebApplicationContext getWebApplicationContext(ServletContext sc) {
Object attr = sc.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
if (attr == null) {
return null;
}
if (attr instanceof RuntimeException) {
throw (RuntimeException) attr;
}
if (attr instanceof Error) {
throw (Error) attr;
}
if (!(attr instanceof WebApplicationContext)) {
throw new IllegalStateException("Root context attribute is not of type WebApplicationContext: " + attr);
}
return (WebApplicationContext) attr;
}
关键的问题在于struts如何启动的spring?ContextLoaderPlugIn的源?br />
// Publish the context as a servlet context attribute.
String attrName = getServletContextAttributeName();
getServletContext().setAttribute(attrName, wac);
public String getServletContextAttributeName() {
return SERVLET_CONTEXT_PREFIX + getModulePrefix();
}
不同加蝲的Key竟然不同,原因是WebApplicationContext攑֜那里的问?可spring调用的时候会ҎWebApplicationContext里面定义的那个名字去扄,问题出在q里
在struts-config.xml中配|?br /> <plug-in className="org.springframework.web.struts.ContextLoaderPlugIn">
<set-property property="contextConfigLocation" value="/WEB-INF/applicationContext.xml" />
</plug-in>
<controller>
<set-property property="processorClass" value="org.springframework.web.struts.DelegatingRequestProcessor" />
</controller>
原理是这L,Struts虽然只能有一个ActionServlet实例,但是对于不同的子应用分别能有自己的RequestProcessor实例每个RequestProcessor实例分别对应不同的struts配置文g?br /> 子应用的ProcessorClasscd重写一般就是承RequestProcessorc,然后再其配置文g的controller元素中的<processorClass>属性中作出修改。那么当
getRequestProcessor(getModuleConfig(request)).process(request,response);pҎrequest选择相应的moduleconfig,再根据其<processorClass>属性选择相应的RequestProcessor子类来处理相应的h了?/p>
?/span> hello.jsp |页上,不输入姓名,直接单击?/span> Submit ?/span> 按钮Q会看到如图 2-6 所C的|页?/span>
?/span> 2-6 表单验证p|?/span> hello.jsp |页
当客h?/span> HelloForm 表单Ӟ h路径?/span> ?/span> /HelloWorld.do ”:
<html:form action="/HelloWorld.do" focus="userName" >
服务器端执行表单验证程如下?/span>
Q?/span> 1 Q?/span> Servlet 容器?/span> web.xml 文g中寻?/span> <url-pattern> 属性ؓ?/span> *.do ”的 <servlet-mapping> 元素Q?/span>
<servlet-mapping>
<servlet-name>action</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
Q?/span> 2 Q?/span> Servlet 容器依据以上 <servlet-mapping> 元素?/span> <servlet-name> 属性?/span> action ”,?/span> web.xml 文g中寻扑配的 <servlet> 元素Q?/span>
<servlet>
<servlet-name>action</servlet-name>
<servlet-class>org.apache.struts.action.ActionServlet</servlet-class>
</servlet>
Q?/span> 3 Q?/span> Servlet 容器把请求{发给以上 <servlet> 元素指定?/span> ActionServlet Q?/span> ActionServlet 依据用户h路径 ?/span> /HelloWorld.do ”, ?/span> Struts 配置文g中检?/span> path 属性ؓ ?/span> /HelloWorld ?/span> ?/span> <action> 元素 Q?/span>
<action path = "/HelloWorld"
type = "hello.HelloAction"
name = "HelloForm"
scope = "request"
validate = "true"
input = "/hello.jsp"
>
<forward name="SayHello" path="/hello.jsp" />
</action>
|
更确切地_ ActionServlet 此时索的?/span> ActionMapping 对象Q而不是直接访?/span> Struts 配置文g中的 <action> 元素。因?/span> ?/span> ActionServlet 初始化的时候,会加?/span> Struts 配置文gQ把各种配置信息保存在相应的配置cȝ实例中,例如 <action> 元素的配|信息存攑֜ ActionMapping 对象中?/span> |
Q?/span> 4 Q?/span> ActionServlet Ҏ <action> 元素?/span> name 属性,创徏一?/span> HelloForm 对象Q把客户提交的表单数据传l?/span> HelloForm 对象Q再?/span> HelloForm 对象保存?/span> <action> 元素?/span> scope 属性指定的 request 范围内?/span>
Q?/span> 5 Q由?/span> <action> 元素?/span> validate 属性ؓ true Q?/span> ActionServlet 调用 HelloForm 对象?/span> validate() Ҏ执行表单验证Q?/span>
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = new ActionErrors();
if ((userName == null) || (userName.length() < 1))
errors.add("username", new ActionMessage("hello.no.username.error"));
return errors;
}
Q?/span> 6 Q?/span> HelloForm 对象?/span> validate() Ҏq回一?/span> ActionErrors 对象Q里面包含一?/span> ActionMessage 对象Q这?/span> ActionMessage 对象中封装了错误消息Q消?/span> key 为?/span> hello.no.username.error ?/span> Q?/span> ?/span> Resource Bundle 中与值匹配的消息文本为:
hello.no.username.error=Please enter a <i>UserName</i> to say hello to!
Q?/span> 7 Q?/span> ActionServlet ?/span> HelloForm ?/span> validate() Ҏq回?/span> ActionErrors 对象保存?/span> request 范围内,然后Ҏ <action> 元素?/span> input 属性,把客戯求{发给 hello.jsp ?/span>
Q?/span> 8 Q?/span> hello.jsp ?/span> <html:errors> 标签?/span> request 范围内读?/span> ActionErrors 对象Q再?/span> ActionErrors 对象中读?/span> ActionMessage 对象Q把它包含的错误消息昄在网上?/span>
Q?/span> 1 Q表单验?/span> 的流E( 1 Q~Q?/span> 4 Q?/span>
Q?/span> 2 Q?/span> ActionServlet 调用 HelloForm 对象?/span> validate() ҎQ这?/span> validate() Ҏq回?/span> ActionErrors 对象中不包含M ActionMessage 对象Q表C单验证成功?/span>
Q?/span> 3 Q?/span> ActionServlet 查找 HelloAction 实例是否存在Q如果不存在创Z个实例。然后调?/span> HelloAction ?/span> execute() Ҏ?/span>
Q?/span> 4 Q?/span> HelloAction ?/span> execute() Ҏ先进行逻辑验证Q由于没有通过逻辑验证Q就创徏一?/span> ActionMessage 对象Q这?/span> ActionMessage 对象装了错误消息,消息 key 为?/span> hello.dont.talk.to.monster ”,?/span> Resource Bundle 中与值匹配的消息文本为:
hello.dont.talk.to.monster=We don't want to say hello to Monster!!!
execute() Ҏ?/span> ActionMessage 对象保存?/span> ActionMessages 对象中,再把 ActionMessages 对象存放?/span> request 范围内。最后返回一?/span> ActionForward 对象Q该对象包含的请求{发\径ؓ <action> 元素?/span> input 属性指定的 hello.jsp ?/span>
以下?/span> execute() Ҏ中进行逻辑验证的代码:
ActionMessages errors = new ActionMessages();
String userName = (String)((HelloForm) form).getUserName();
String badUserName = "Monster";
if (userName.equalsIgnoreCase(badUserName)) {
errors.add("username", new ActionMessage("hello.dont.talk.to.monster", badUserName ));
saveErrors(request, errors);
return (new ActionForward(mapping.getInput()));
}
Q?/span> 5 Q?/span> ActionServlet 依据 HelloAction q回?/span> ActionForward 对象Q再把请求{发给 hello.jsp ?/span>
Q?/span> 6 Q?/span> hello.jsp ?/span> <html:errors> 标签?/span> request 范围内读?/span> ActionMessages 对象Q再?/span> ActionMessages 对象中读?/span> ActionMessage 对象Q把它包含的错误消息昄在网上Q?/span> 如图 所C?/span>
逻辑验证p|时的 hello.jsp |页
Q?/span> 1 Q重?/span> ?/span> 的流E( 1 Q~Q?/span> 3 Q?/span>
Q?/span> 2 Q?/span> HelloAction ?/span> execute() Ҏ先执行逻辑验证Q这ơ通过了验证,然后执行相关的业务逻辑Q最后调?/span> ActionMapping.findForward() ҎQ参Cؓ ?/span> SayHello ”:
// Forward control to the specified success URI
return (mapping.findForward("SayHello"));
Q?/span> 3 Q?/span> ActionMapping.findForward() Ҏ?/span> <action> 元素中寻?/span> name 属性ؓ ?/span> SayHello ”的 <forward> 子元素,然后q回与之对应?/span> ActionForward 对象Q它代表的请求{发\径ؓ?/span> /hello.jsp ”?/span>
|
更确切地_ ActionMapping 从本w包含的 HashMap 中查?/span> name 属性ؓ ?/span> SayHello ?/span> ?/span> ActionForward 对象。在 ActionServlet 初始化时会加?/span> Struts 配置文gQ把 <action> 元素的配|信息存攑֜ ActionMapping 对象中?/span> <action> 元素中可以包含多?/span> <forward> 子元素,每个 <forward> 子元素的配置信息存放在一?/span> ActionForward 对象中,q些 ActionForward 对象存放?/span> ActionMapping 对象?/span> HashMap 中?/span> |
Q?/span> 4 Q?/span> HelloAction ?/span> execute() Ҏ 然后?/span> ActionForward 对象q回l?/span> ActionServlet Q?/span> ActionServlet 再把客户h转发l?/span> hello.jsp ?/span>
Q?/span> 5 Q?/span> hello.jsp ?/span> <bean:message> 标签?/span> Resource Bundle 中读取文本,把它们输出到|页上,最后生?/span> 动态网,如图 所C?/span>
通过数据验证?/span> hello.jsp |页
Struts框架的一个主要好处是它提供了Ҏ收到的表单数据进行验证的内置界面。如果有M验证p|Q则应用E序都会重新昄HTML表单Q这样就可以Ҏ无效的数据了。如果验证成功,则处理过E会l箋q行。Struts框架的简单验证界面会减少与处理数据验证有关的令h头疼的事情,q样你就可以把精力集中到验证代码上,而不是放到捕h据、重新显CZ完整或无效数据的技巧上?/span>
但是QStruts内置的验证界面也有缺炏V例如,在整个应用程序中验证代码常常会大量重复,因ؓ许多域需要相同的验证逻辑。对一些相似字D늚验证逻辑q行M修改都要求在几个地方修改代码Q还要重新编译受影响的代码。ؓ了解册个问题ƈ增强Struts验证界面的功能,作ؓStruts的第三方附加件创ZValidator框架。后来,Validator被集成到核心Struts代码库中Qƈ从Struts中分d来,现在它是一个独立的Jakarta Commons目。虽然Validator是一个独立的框架Q但它仍能与其他E序装在一起后提供Qƈ与Struts无缝集成?/p>
Validator概述
没有ValidatorQ你׃得不~写验证表单数据所需的全部代码,q把它放入Form Bean对象的validate( )Ҏ中。对于想在其上进行数据验证的每个Form Bean域来_都需要编写逻辑代码来实现验证。此外,你还必须~写代码来存储验证失败时的出错消息?
有了ValidatorQ你׃必在Form Bean中编写用于验证或存储错误消息的Q何代码。相反,Form Bean提供了Validator的一个ActionForm子类Q它提供验证或存储错误消息的功能?
可把Validator框架作ؓ一个可用于Form Bean验证的可插入的验证例行程序系l来q行安装。每个验证例行程序都只是一个JavaҎQ负责执行特定类型的验证dQ验证可能通过Q也可能p|?默认情况下,Validator与几个有用的验证例行E序装在一h提供Q这些例行程序能满大多数情况下的验证要求。但是,如果Validator框架没有提供你需要的验证例行E序Q那么你可以自己创徏定制的验证例行程序,q将它插入到该框架中。此外,Validatorq支持服务器端和客户端(JavaScriptQ的验证Q而Form Bean只提供服务器端验证界面?
Validator使用两个XML配置文g来分别确定安装哪个验证例行程序和如何它们应用于l定的应用程序。第一个配|文件validator-rules.xml说明应该被插入到框架中的验证例行E序Qƈ提供每个验证的逻辑的名U。validator-rules.xml文gq定义了每个验证例行E序的客LJavaScript代码。可以配|Validator让它把这个JavaScript代码发送到览器上Q这样验证就可以在客L和服务器端进行了?
W二个配|文件validation.xml定哪个验证例行E序应用到哪个Form Bean。文件中的定义用struts-config.xml文gl出的Form Bean的逻辑名称以及validator-rules.xml文gl出的验证例行程序的逻辑名称Q以便把二者关联v来?/span>
使用Validator框架包括启用Validator插g、配|Validator的两个配|文Ӟ以及创徏提供Validator的ActionForm子类的Form Beans。下面详l解释如何配|和使用Validator?
启用Validator插g
虽然Validator框架是与Struts装在一h供的Q但在默认状况下Validatorq不被启用。ؓ了启用ValidatorQ要向你的应用程序的struts-config.xml文g中添加下面的插g定义?
<!-- Validator Configuration --> <plug-in className="org.apache.struts .validator.ValidatorPlugIn"> <set-property property="pathnames" value="/WEB-INF/ validator-rules.xml, /WEB-INF/ validation.xml"/> </plug-in>
该定义告诉StrutsZ的应用程序加载ƈ初始化Validator插g。在初始化时Q该插g装入p\径名属性指定的、用逗号分隔的Validator配置文g清单。每个配|文件的路径应该用与Web应用E序的相关的路径来指定,如前面的例子所C?
h意,你的应用E序的struts-config.xml文g必须与Struts Configuration Document Type DefinitionQStruts配置文cd定义QDTDQ一_后者规定文件中元素出现的顺序。所以,你必LValidator插g定义攑ֈ该文件的适当位置。确保文件中元素适当排列的最便方法就是用诸如Struts Console的工P它自动格式化你的配置文gQ以便与DTD保持一致?/p>
配置validator-rules.xml
Validator框架可以讄为可插入pȝQ其验证例行E序仅仅是插入到该系l中执行具体验证的JavaҎ。validator-rules.xml文g说明性地插入Validator用于执行验证的验证例行程序中。StrutsCZ应用E序带有q个文g的预配置拯。在大多数情况下Q你不必修改q个预配|拷贝,除非你要向该框架中添加自己定制的验证?/p>
清单1 是一个示例validator-rules.xml文gQ说明如何将验证例行E序插入到Validator中。validator-rules.xml文g中的每个验证例行E序都有自己的定义,它用validator标记声明Q利用name属性ؓ该验证例行程序指定逻辑名,q指定该例行E序的类和方法。该例行E序的逻辑名称供该文g中的其他例行E序以及validation.xml文g中的验证定义用于引用该例行程序?/span>
h意,validator标记攑֜javascript的标CQjavascript标记用于定义客户端JavaScript代码Q以便在客户端执行与服务器端相同的验证?
提供的验证程?/p>
默认情况下,Validator中包括几个基本验证例行程序,你可以用它们来处理大多数验证问题。这些例行程序具有逻辑名称Q如requiredQ用于输入要求的|、CreditCardQ用于输入信用卡L|、emailQ用于输入电子邮件地址|Q等{?/p>
创徏Form Bean
Z使用ValidatorQ你的应用程序的Form Bean必须归到Validator的ActionForm的某一子类Q而不是ActionForm本n。Validator的ActionForm子类提供了ActionForm的validate( )ҎQ它嵌入到Validator框架中)的实施过E。你不必从头~写验证代码q把它投入validate( )Ҏ中,相反Q可以完全忽略该ҎQ因为ValidatorZ提供了验证代码?
与Struts提供的核心功能相cMQValidator提供l你两种可供选择的方法来创徏Form Bean?你可以选择的第一U方法就是像下面q样创徏一个特定的Form Bean对象Q?/p>
package com.jamesholmes.minihr; import org.apache.struts.validator .ValidatorForm; public class LogonForm extends ValidatorForm { private String username; private String password; public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }
q个cM你不是用Validator所创徏的类怼Q但它提供ValidatorForm而不是ActionForm。这个类也不提供ActionForm的空reset( )和validate( )Ҏ的实施过E,因ؓValidatorForm提供了相应过E?/p>
在struts-config.xml文g中配|这个特定Form Bean的方法与配置正则Form Bean的方法相同:
<form-beans> <form-bean name="logonForm" type="com.jamesholmes .minihr.LogonForm"/> </form-beans>
用表单标记的name属性给特定Form Bean指定的逻辑名是在定义validation.xml文g中的验证时所使用的名Uͼ如下所C:
<!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation// DTD Commons Validator Rules Configuration 1.0//EN" "http://jakarta.apache.org/ commons/dtds/validator_1_0.dtd"> <form-validation> <formset> <form name="logonForm"> <field property="username" depends="required"> <arg0 key="prompt.username"/> </field> </form> </formset> </form-validation>
Validator使用该表单标记的name属性的值将验证定义与要应用q些定义的Form Bean的名U相匚w?
创徏Form Bean时可以选择的第二种Ҏ是在struts-config.xml文g中定义一个动态Form BeanQ如下所C:
<form-beans> <form-bean name="logonForm" type="org.apache .struts.validator.DynaValidatorForm"> <form-property name="username" type="java.lang.String"/> <form-property name="password" type="java.lang.String"/> </form-bean> </form-beans>
动态Form Bean不要求创建特定的Form Bean对象Q相反,要定义Form Bean应该h的属性和cdQ而StrutsZ动态创建Form Bean?Validator允许你用这个概念,像在核心Struts中用这个概念一栗与使用Validator的惟一区别是要指定Form Bean是org.apache.struts.validator.DynaValidatorFormcdQ而不是org.apache.struts.action.DynaActionFormcd?/span>
分配l动态Form Bean的逻辑名是在定义validation.xml文g中的验证时用的名称。Validator使用与之相匹配的名称这些验证与Form Bean联系在一赗?
除了创徏Form Bean的这两种标准Ҏ之外QValidatorq提供了一个高U特性,用于多个验证定义与一个Form Bean定义联系h。当你用基于validatorForm或基于DynaValidatorForm的Form BeanӞValidator使用struts-config.xml文g中的Form Bean的逻辑名称Q将Form Bean映射到validation.xml文g中的验证定义。这U机制在大多数情况下非常有用Q但在某些时候,Form Bean要在多个操作中共享?一个操作可能用Form Bean的所有域QfieldsQ,而另一个操作可能只使用q些域的一个子集。因为验证定义被q接到Form BeanQ所以只使用域的一个子集的操作无法绕q对未用域的验证。当验证Form BeanӞ׃Ҏ使用的域生成错误消息Q因为Validator无从知道不去验证未用的域,它只是简单地把它们看作缺失或无效?/p>
Z解决q个问题QValidator提供了两个附加的ActionForm子类Q它使你能够验证与操作相关联,而不是与Form Bean相关联。这样你可以根据哪个操作正在用Form Bean来指定把哪些验证用于该Form Bean了。对于特定的Form BeanQ你要像下面q样声明org.apache.struts.validator.ValidatorActionForm子类Q?/p>
public class AddressForm extends ValidatorActionForm { ... }
对于动态Form BeanQ在struts-config.xml文g中ؓForm Bean定义指定org.apache.struts.validator.DynaValidatorActionForm的类型,如下所C:
<form-bean name="addressForm" type="org.apache.struts .validator.DynaValidatorActionForm"> ... </form-bean>
在validation.xml文g中,把一l验证映到一个操作\径,而不是映到Form Bean名,因ؓ如果你定义了Create Address和Edit Address两个操作Q它们用同一个Form BeanQ,那么每个操作都会有一个惟一的操作名Q如下所C:
<action-mappings> <action path="/createAddress" type="com.jamesholmes .minihr.CreateAddressAction" name="addressForm"/> <action path="/editAddress" type="com.jamesholmes .minihr.EditAddressAction" name="addressForm"/> </action-mappings>
下面的validation.xml文g片断昄了两l验证,它们用于同一个Form BeanQ但却有不同的操作\径:
<formset> <form name="/createAddress"> <field property="city" depends="required"> <arg0 key="prompt.city"/> </field> </form> <form name="/editAddress"> <field property="state" depends="required"> <arg0 key="prompt.state"/> </field> </form> </formset>
因ؓForm Bean要么属于ValidatorActionForm子类Q要么属于DynaValidatorActionForm子类Q所以Validator知道用一个操作\径代替Form Bean的逻辑名称来找出用于Form Bean的验证?/p>
配置validation.xml文g
validation.xml文g用于声明应用到Form Beans的一l验证。要验证的每个Form Bean在这个文件中都有自己的定义。在q个定义中,指定要应用到该Form Bean的各域的验证。下面是一个validation.xml文g的例子,说明如何定义验证Q?/p>
<!DOCTYPE form-validation PUBLIC "-//Apache Software Foundation// DTD Commons Validator Rules Configuration 1.0//EN" "http://jakarta.apache.org/ commons/dtds/validator_1_0.dtd"> <form-validation> <formset> <form name="logonForm"> <field property="username" depends="required"> <arg0 key="prompt.username"/> </field> <field property="password" depends="required"> <arg0 key="prompt.password"/> </field> </form> </formset> </form-validation>
validation.xml文g的第一个元素是form-validation。这个元素是该文件的d素,而且只定义一ơ。在form-validation元素内定义form-set元素Q它包括多个表单元素。一般来_在文件中只定义一个form-set元素Q但是如果要验证国际化Q那p在每个地方单独用一个form-set元素?/span>
每个表单元素使用name属性将名称与其所包含的域验证集关联v来。Validator使用q个逻辑名称这些验证映到在struts-config.xml文g中定义的一个Form Bean。根据要验证的Form Bean的类型,Validator力求该名称与Form Bean的逻辑名称或操作\径相匚w。在表单元素内,field元素定义要应用到Form Bean的特定域的验证。field元素的property属性对应于特定Form Bean中的域名。depends属性利用validator-rules.xml文g指定验证例行E序的逻辑名称Q这些例行程序将应用到域验证中?/span>
配置ApplicationResources.properties
Validator使用Struts的资源绑定(Resource BundleQ机制将错误消息具体化。不用在框架中对错误消息q行编码,Validator使你能在ApplicationResources.properties文g中ؓ一个消息指定一个键|如果验证p|则将q回该键倹{validator-rules.xml文g中的每个验证例行E序都用validator标记的msg属性指定错误消息的键|如下所C:
<validator name="required" classname="org.apache .struts.validator.FieldChecks" method="validateRequired" methodParams="java.lang .Object, org.apache.commons.validator .ValidatorAction, org.apache.commons .validator.Field, org.apache.struts .action.ActionErrors, javax.servlet .http.HttpServletRequest" msg="errors.required">
如果在验证例行程序运行时验证p|Q则q回与msg属性指定的键值对应的消息?/p>
下面的片D|C来自ApplicationResources.properties文g的验证出错时的默认消息集Q它们由StrutsCZ应用E序提供。每个消息的键值对应于每个由validator-rules.xml文g中的验证例行E序所指定的消息,它们由StrutsCZ应用E序提供?/span>
# Error messages for Validator framework validations errors.required={0} is required. errors.minlength={0} cannot be less than {1} characters. errors.maxlength={0} cannot be greater than {2} characters. errors.invalid={0} is invalid. errors.byte={0} must be a byte. errors.short={0} must be a short. errors.integer={0} must be an integer. errors.long={0} must be a long.0. errors.float={0} must be a float. errors.double={0} must be a double. errors.date={0} is not a date. errors.range={0} is not in the range {1} through {2}. errors.creditcard={0} is not a valid credit card number. errors.email={0} is an invalid e-mail address.
h意,每条消息都有占位W,形式为{0}、{1}或{2}。在q行期间Q占位符被另一个g替,如所验证的域的名U。这一Ҏ特别有用,它你能够创建可被几个不同的域重复用的通用验证错误消息?
例如Q下面给出required验证的错误消息errors.requiredQ?
errors.required={0} is required.
当你使用validation.xml文g中的该required验证Ӟ必须定义用于替换该错误消息中的{0}的|如下所C:
<form name="auctionForm"> <field property="bid" depends="required"> <arg0 key="prompt.bid"/> </field> </form>
错误消息最多可以有4个占位符Q{0}和{3}。这些占位符分别UCؓarg0到arg3Q你可以通过使用arg0~arg3标记来指定它们。在上面的例子中Qarg0标记指定了用于替换{0}占位W的倹{该标记的key属性指定来自ApplicationResources.properties文g的一个消息键|它的值用于替换占位符Q如下所C:
阅读
关于Struts Console的更多文?/span>
|
prompt.bid=Auction Bid
使用消息键g替占位符的|q一Ҏ使你不必在validation.xml文g中对替换值反复硬~码。但是,如果你不想用Resource Bundle的键?值机制来指定占位W的|则可以用arg0标记的如下语法显式地指定占位W的|
<arg0 key="Auction Bid" resource="false"/>
在这个例子中Qresource属性的D为falseQ以侉K知Validator要把该key属性指定的g为占位符的|而不要作为ApplicationResources.properties文g中消息的一个键倹{?/p>
启用客户端验?
Validator除了提供了简化服务器端表单数据验证过E的框架外,它还提供了执行客L验证时易于用的Ҏ。在validator-rules.xml文g中定义的每一个验证例行程序都可以随意指定JavaScript代码Q这些代码可以在览器(客户端上的)中运行,从而执行与服务器端q行的验证相同的验证q程。在客户端进行验证时Q除非所有表单都通过验证Q否则这些表单不允许被提交?/span>
Z启用客户端验证,必须在每个需要验证的JSP中放上Struts HTML Tag LibraryQ标记库Q的javascript标记Q如下所C:
<html:javascript formName="logonForm"/>
javascript标记要求使用formName属性来为想要对其执行验证的表单指定validation.xml文g中给出的表单定义名,如下所C:
<form name="logonForm"> <field property="username" depends="required"> <arg0 key="prompt.username"/> </field> <field property="password" depends="required"> <arg0 key="prompt.password"/> </field> </form>
单定义指定的服务器端的所有验证都在客户端运行。由于客L验证用JavaScript执行Q所以可以有多种Ҏ不去执行它。要保验证q程L能运行,不论你是否选择启用了客L验证QValidator都在服务器端执行q些验证?/p>
l论
Validator框架针对表单数据的验证提供了可配|的pȝQ从而ؓ核心Struts框架d了很多有价值的功能。通过把Validator框架用于你的应用E序Q你可以节约旉q简化Struts应用E序的开发过E?
使用FileUploadlg实现文g上传
文g上传在web应用中非常普遍,要在servlet/jsp环境中实现文件上传功能非常容易,因ؓ|上已经有许多用java开发的lg用于文g上传Q本文以commons-fileuploadlgZQؓservlet/jsp应用d文g上传功能?/p>
common-fileuploadlg是apache的一个开源项目之一Q可以从http://jakarta.apache.org/commons/fileupload/下蝲。该lg单易用,可实Cơ上传一个或多个文gQƈ可限制文件大?/p>
下蝲后解压zip包,commons-fileupload-1.0.jar复制到tomcat的webapps\你的webapp\WEB-INF\lib\下,如果目录不存在请自徏目录?/p>
新徏一个servlet: Upload.java用于文g上传Q?/p>
import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;
import org.apache.commons.fileupload.*;
public class Upload extends HttpServlet {
private String uploadPath = "C:\\upload\\"; // 用于存放上传文g的目?br /> private String tempPath = "C:\\upload\\tmp\\"; // 用于存放临时文g的目?/p>
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
}
}
当servlet收到览器发出的Posth后,在doPost()Ҏ中实现文件上传。以下是CZ代码Q?/p>
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
try {
DiskFileUpload fu = new DiskFileUpload();
// 讄最大文件尺寸,q里?MB
fu.setSizeMax(4194304);
// 讄~冲区大,q里?kb
fu.setSizeThreshold(4096);
// 讄临时目录Q?br /> fu.setRepositoryPath(tempPath);
// 得到所有的文gQ?br /> List fileItems = fu.parseRequest(request);
Iterator i = fileItems.iterator();
// 依次处理每一个文Ӟ
while(i.hasNext()) {
FileItem fi = (FileItem)i.next();
// 获得文g名,q个文g名包括\径:
String fileName = fi.getName();
if(fileName!=null) {
// 在这里可以记录用户和文g信息
// ...
// 写入文ga.txtQ你也可以从fileName中提取文件名Q?br /> fi.write(new File(uploadPath + "a.txt"));
}
}
// 跌{C传成功提C页?br /> }
catch(Exception e) {
// 可以跌{出错面
}
}
如果要在配置文g中读取指定的上传文g夹,可以在init()Ҏ中执行:
public void init() throws ServletException {
uploadPath = ....
tempPath = ....
// 文g夹不存在p动创建:
if(!new File(uploadPath).isDirectory())
new File(uploadPath).mkdirs();
if(!new File(tempPath).isDirectory())
new File(tempPath).mkdirs();
}
~译该servletQ注意要指定classpathQ确保包含commons-upload-1.0.jar和tomcat\common\lib\servlet-api.jar?/p>
配置servletQ用C本打开tomcat\webapps\你的webapp\WEB-INF\web.xmlQ没有的话新Z个。典型配|如下:
<?xml version="1.0" encoding="ISO-8859-1"?>
<!DOCTYPE web-app
PUBLIC "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<servlet>
<servlet-name>Upload</servlet-name>
<servlet-class>Upload</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>Upload</servlet-name>
<url-pattern>/fileupload</url-pattern>
</servlet-mapping>
</web-app>
配置好servlet后,启动tomcatQ写一个简单的html试Q?/p>
<form action="fileupload" method="post"
enctype="multipart/form-data" name="form1">
<input type="file" name="file">
<input type="submit" name="Submit" value="upload">
</form>
注意action="fileupload"其中fileupload是配|servlet时指定的url-pattern?br />