[size=x-large]struts2框架自带项目struts2-mailreader学习笔记[/size]
我们依照使用该项目的步骤来说。
[size=large]第一[/size]、一个网页请求转到welcome.do的网页,我们去查看web.xml中的配置,发现要调用StrutsPrepareAndExecuteFilte这个过滤器。而这个过滤器是干什么用的呢?
查看源代码我们发现:
1.StrutsPrepareAndExecuteFilte 实现了Filter接口。
服务器启动调用StrutsPrepareAndExecuteFilter .init()初始化来初始化几个重要的类,比如Dispatcher,FilterHostConfig。
2.当前台有请求发来,StrutsPrepareAndExecuteFilter 的doFilter()被调用,这个doFilter实现了封装request,查找 ActionMapper,以确定这个请求是否需要调用某个 Action。
那么,为什么使用StrutsPrepareAndExecuteFilter而不是用FilterDispatcher呢?可参见[url]http://wellfrog.iteye.com/blog/773482[/url]
在这里,还要注意,项目源码为:
而这个execute是ExecuteOperations的一个实例,这个execute调用的这个方法executeAction实际上是通过调用Dispatcher类的serviceAction方法来实现的:
再查看Dispatcher的源码,发现
这也就验证了struts2工作流程中的把控制权交给 ActionProxy,然后ActionProxy 依照框架的配置文件(struts.xml),找到需要调用的 Action 类。即
[size=large]第二[/size]、找到相应的welcome.java即这个继承ActionSupport的类后,其经过一些操作后返回SUCCESS,去struts.xml,在该项目中为mailreader-support.xml,因为struts.xml中为
这里的1.result的name默认值为SUCCESS,type的默认值为dispatcher。返回相应的视图。
2.interceptor-ref是指定某个action使用哪一个拦截器。可以是一个interceptor,也可以是一个interceptor-stack。再看mailreader-default.xml中
各拦截器的作用详见[url]http://blog.csdn.net/jadyer/article/details/5887529[/url]
[size=large]第三[/size]、我们查看返回的页面Welcome.jsp。[size=medium]1.[/size]如下国际化代码需要注意:
查看struts2参考手册的关于Generic Tags的内容,我们发现struts2的url标签的用法是这样的:[quote]This tag is used to create a URL.
You can use the <param> tag inside the body to provide additional request parameters. If the value of a param is an Array or an Iterable all the values will be added to the URL.[/quote]
可以理解为:这个标签用来创建一个请求,而param这个标签提供附加的请求参数值,注意请求参数为request_locale,请求参数值为en。这个en实际上就是国际化文件的语言码_国家码,因为这里的国际化文件名就为MailReaderSupport_en.properties,所以我们就写一个语言码即可。综上所述,这里相当于创建这样一个请求:
该locale是保存在以WW_TRANS_I18N_LOCALE所命名的session里面的。查看拦截器源码,发现:session.put(attributeName, locale)其中attributeName即为protected String attributeName = DEFAULT_SESSION_ATTRIBUTE
和public static final String DEFAULT_SESSION_ATTRIBUTE = "WW_TRANS_I18N_LOCALE"
于是便可通过这种方式实现一个选择页面所显示的语言环境的功能。
而这个
[size=medium]2.[/size]注意一下代码:
查看参考手册得知:
[quote]Render a I18n text message.
The message must be in a resource bundle with the same name as the action that it is associated with. In practice this means that you should create a properties file in the same package as your Java class with the same name as your class, but with .properties extension.
If the named message is not found in a property file, then the body of the tag will be used as default message. If no body is used, then the stack will be searched, and if a value is returned, it will written to the output. If no value is found on the stack, the key of the message will be written out.[/quote]
也就是说这个标签用于呈现国际化的文件,再查询有关本地化的参考文档,得知
[quote]Resource bundles are searched in the following order:
ActionClass.properties
Interface.properties (every interface and sub-interface)
BaseClass.properties (all the way to Object.properties)
ModelDriven's model (if implements ModelDriven), for the model object repeat from 1
package.properties (of the directory where class is located and every parent directory all the way to the root directory)
search up the i18n message key hierarchy itself
global resource properties[/quote]
也就是说我们这样查找:
Welcome这个Action的资源文件Welcome_xx_xx.properties(类级别)注意类级别的文件名定义:Action类名_语言码_国家码.properties,比如:LoginAction_en_US.properties,LoginAction_zh_CN.properties;;
找不到,则查找与它所实现的接口同名的资源文件MyInterface.properties;
再查找其父文件ParentAction.properties;
判断当前ChildAction是否实现接口ModelDriven。如果是,调用getModel()获得对象,查找与其同名的资源文件;
查找当前包下的package.properties文件(包级别);
查找当前包的父包,直到最顶层包;
查找i8n资源文件?;
查找全局资源文件,如果是在全局中定义资源文件,则需要在struts.xml里配置,例子:<constant name="struts.custom.i18n.resources" value="message" />,其中message为资源文件的起始名;(全局级别)
如果还找不到,则在值栈中搜索,参考[url]http://txyly998.iteye.com/blog/336357[/url]
所以我们这里是查找相应的父类MailReaderSupport的资源文件MailReaderSupport.properties.
[size=medium]3.[/size]我们再看如下代码
这里就要注意我们上面提到的url的用法,即[quote]This tag is used to create a URL.[/quote]我们查询相应的mailreader-support配置文件得到:
这里要注意这个{1}的用法即代表这个*号,在这里就为调用Registration中的input方法,我们查看代码发现经过一系列操作后,其返回input视图,对应Registration这个页面.
[size=large]第四[/size]、我们查看这个Registration页面,发现如下值得关注
这里的<s:if test="">是struts2的控制标签,其中的task为Registration这个Action的属性。这里是使用OGNL表达式从值栈中取出的。这个Task属性的值如果是Create就代表在session中没有这个用户名,所以就在该注册页面中显示注册信息,而不显示注册以后显示的信息,而这个注册以后的信息是否显示则是通过判断task是否为Edit来得到。
再往下看,
<s:form>是表单用法,<s:token/>用法如下[quote]The token tag is used to help with the "double click" submission problem. It is needed if you are using the TokenInterceptor or the TokenSessionInterceptor. The s:token tag merely places a hidden element that contains the unique token.[/quote]
此外,这些标签的name属性代表这些元素的名称,而key属性则用于访问国际化文件内容,
这里我们要介绍下几种调用国际化资源的方式:
[size=medium]1[/size]、jsp页面的国际化,[size=small]a.[/size]使用标签
[size=medium]2[/size]、getText(String key)可在所有需要使用的地方使用,如[size=small]a.[/size]
[size=small]b.[/size]action中的validate方法中增加错误信息的国际化
[size=small]c.[/size]xml验证框架中的错误信息国际化
<message key = "name.invalid"></message>
或<message>${getText("name.invalid")}</message>
而<s:hidden>是[quote]Renders an HTML input element of type hidden, populated by the specified property from the ValueStack.[/quote] 对应用户名不符合要求时显示的文字,如下面的Password is not in the range 4 through 10.
我们可以通过看运行界面来理解这些标签:
Username: 123
Password is not in the range 4 through 10.
Password: ***
(Repeat) Password:***
Full Name: 123
From Address is an invalid e-mail address.
From Address: 123
Reply To Address is an invalid e-mail address.
Reply To Address: 123
继续查看代码,发现这个页面的输入结果提交给Registration_save这个action,同样是调用Registration这个Action的save方法。查看save方法,发现如下需要注意
[size=large]第五[/size]、我们回到welcome.jsp,看他的第二个链接
[quote]Log on to the MailReader Demonstration Application [/quote]
这个链接是调用Login的input方法,
这里虽然Login没有定义input方法,但是他的父类ActionSupport定义了input方法,它直接返回input字符串。这个结果集就要我们返回login.jsp页面。再来看这个Login.jsp页面。它的action就为Login,那么我们对于这个action映射可以看到它的method实际上应该调用的是LoginAction的execute(),我们可以理解其为:不写method,其默认值就是execute()。完成相应的execute()中的操作后,其返回SUCCESS,这个对应的result为MainMenu,因为
[size=medium]1.[/size]一个是编辑自己的信息,这个返回到刚刚我们提到的Registration页面,和上面的不同在于,由于
[size=medium]2.[/size]另一个就是注销当前用户,返回到welcome.jsp。由于代码差不多,这里就不在赘述了。
[size=large]第六[/size]、我们看客户端的校验框架,
这里还要注意一点,被校验的Action类要继承ActionSupport类,并要在action配置中指定名为input的jsp,因为struts2在校验失败后会自动返回input页面
具体的项目文件见附件,也可去官网下载。
我们依照使用该项目的步骤来说。
[size=large]第一[/size]、一个网页请求转到welcome.do的网页,我们去查看web.xml中的配置,发现要调用StrutsPrepareAndExecuteFilte这个过滤器。而这个过滤器是干什么用的呢?
查看源代码我们发现:
1.StrutsPrepareAndExecuteFilte 实现了Filter接口。
服务器启动调用StrutsPrepareAndExecuteFilter .init()初始化来初始化几个重要的类,比如Dispatcher,FilterHostConfig。
2.当前台有请求发来,StrutsPrepareAndExecuteFilter 的doFilter()被调用,这个doFilter实现了封装request,查找 ActionMapper,以确定这个请求是否需要调用某个 Action。
那么,为什么使用StrutsPrepareAndExecuteFilter而不是用FilterDispatcher呢?可参见[url]http://wellfrog.iteye.com/blog/773482[/url]
在这里,还要注意,项目源码为:
execute.executeAction(request, response, mapping);
而这个execute是ExecuteOperations的一个实例,这个execute调用的这个方法executeAction实际上是通过调用Dispatcher类的serviceAction方法来实现的:
public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
dispatcher.serviceAction(request, response, servletContext, mapping);
}
再查看Dispatcher的源码,发现
:Configuration config = configurationManager.getConfiguration();
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);
request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());
// if the ActionMapping says to go straight to a result, do it!
if (mapping.getResult() != null) {
Result result = mapping.getResult();
result.execute(proxy.getInvocation());
} else {
proxy.execute();
}
这也就验证了struts2工作流程中的把控制权交给 ActionProxy,然后ActionProxy 依照框架的配置文件(struts.xml),找到需要调用的 Action 类。即
<action name="Welcome" class="mailreader2.Welcome">
<result>/Welcome.jsp</result>
<interceptor-ref name="guest"/>
</action>
ActionProxy 创建一个 ActionInvocation 的实例。ActionInvocation 先调用相关的拦截器 (Action 调用之前的部分),最后调用 Action。 具体的拦截器工作流程请参见[url]http://wlzjdm.iteye.com/blog/1525899[/url]
[size=large]第二[/size]、找到相应的welcome.java即这个继承ActionSupport的类后,其经过一些操作后返回SUCCESS,去struts.xml,在该项目中为mailreader-support.xml,因为struts.xml中为
<include file="mailreader-default.xml"/>
<include file="mailreader-support.xml"/>
<action name="Welcome" class="mailreader2.Welcome">
<result>/Welcome.jsp</result>
<interceptor-ref name="guest"/>
</action>
这里的1.result的name默认值为SUCCESS,type的默认值为dispatcher。返回相应的视图。
2.interceptor-ref是指定某个action使用哪一个拦截器。可以是一个interceptor,也可以是一个interceptor-stack。再看mailreader-default.xml中
<interceptor-stack name="guest" >
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
代表这个guest拦截器是一个拦截器栈,使用的是defaultStack这个拦截器栈,在struts2-core-xxx.jar包下找到struts-default.xml,再在其中找到如下代码:
<interceptor-stack name="defaultStack">
<interceptor-ref name="exception"/>
<interceptor-ref name="alias"/>
<interceptor-ref name="servletConfig"/>
<interceptor-ref name="i18n"/>
<interceptor-ref name="prepare"/>
<interceptor-ref name="chain"/>
<interceptor-ref name="scopedModelDriven"/>
<interceptor-ref name="modelDriven"/>
<interceptor-ref name="fileUpload"/>
<interceptor-ref name="checkbox"/>
<interceptor-ref name="multiselect"/>
<interceptor-ref name="staticParams"/>
<interceptor-ref name="actionMappingParams"/>
<interceptor-ref name="params">
<param name="excludeParams">dojo\..*,^struts\..*,^session\..*,^request\..*,^application\..*,^servlet(Request|Response)\..*,parameters\...*</param>
</interceptor-ref>
<interceptor-ref name="conversionError"/>
<interceptor-ref name="validation">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="workflow">
<param name="excludeMethods">input,back,cancel,browse</param>
</interceptor-ref>
<interceptor-ref name="debugging"/>
</interceptor-stack>
<!-- The completeStack is here for backwards compatibility for
applications that still refer to the defaultStack by the
old name -->
<interceptor-stack name="completeStack">
<interceptor-ref name="defaultStack"/>
</interceptor-stack>
各拦截器的作用详见[url]http://blog.csdn.net/jadyer/article/details/5887529[/url]
[size=large]第三[/size]、我们查看返回的页面Welcome.jsp。[size=medium]1.[/size]如下国际化代码需要注意:
<s:url id="en" action="Welcome">
<s:param name="request_locale">en</s:param>
</s:url>
<s:a href="%{en}">English</s:a>
查看struts2参考手册的关于Generic Tags的内容,我们发现struts2的url标签的用法是这样的:[quote]This tag is used to create a URL.
You can use the <param> tag inside the body to provide additional request parameters. If the value of a param is an Array or an Iterable all the values will be added to the URL.[/quote]
可以理解为:这个标签用来创建一个请求,而param这个标签提供附加的请求参数值,注意请求参数为request_locale,请求参数值为en。这个en实际上就是国际化文件的语言码_国家码,因为这里的国际化文件名就为MailReaderSupport_en.properties,所以我们就写一个语言码即可。综上所述,这里相当于创建这样一个请求:
http://localhost:8080/MyStrutsTest3/Welcome.do?request_locale=en
这里就要说下国际化的实现过程:该国际化是通过Struts2的I18nInterceptor拦截器会拦截所有的Action而实现的,它主要做的事情为:1.从客户端发送过来的请求参数中寻找是否存在名为request_locale的参数。2.若有,则将request_locale的value转化为locale保存起来
该locale是保存在以WW_TRANS_I18N_LOCALE所命名的session里面的。查看拦截器源码,发现:session.put(attributeName, locale)其中attributeName即为protected String attributeName = DEFAULT_SESSION_ATTRIBUTE
和public static final String DEFAULT_SESSION_ATTRIBUTE = "WW_TRANS_I18N_LOCALE"
于是便可通过这种方式实现一个选择页面所显示的语言环境的功能。
而这个
<s:url id="en" action="Welcome">
的id,现在应该使用var属性取代,它的作用是:[quote]Name used to reference the value pushed into the Value Stack[/quote],也就是用这个id属性的值去命名它,以便我们可以用它去引用值栈中的值。
[size=medium]2.[/size]注意一下代码:
<s:text name="index.title"/>
查看参考手册得知:
[quote]Render a I18n text message.
The message must be in a resource bundle with the same name as the action that it is associated with. In practice this means that you should create a properties file in the same package as your Java class with the same name as your class, but with .properties extension.
If the named message is not found in a property file, then the body of the tag will be used as default message. If no body is used, then the stack will be searched, and if a value is returned, it will written to the output. If no value is found on the stack, the key of the message will be written out.[/quote]
也就是说这个标签用于呈现国际化的文件,再查询有关本地化的参考文档,得知
[quote]Resource bundles are searched in the following order:
ActionClass.properties
Interface.properties (every interface and sub-interface)
BaseClass.properties (all the way to Object.properties)
ModelDriven's model (if implements ModelDriven), for the model object repeat from 1
package.properties (of the directory where class is located and every parent directory all the way to the root directory)
search up the i18n message key hierarchy itself
global resource properties[/quote]
也就是说我们这样查找:
Welcome这个Action的资源文件Welcome_xx_xx.properties(类级别)注意类级别的文件名定义:Action类名_语言码_国家码.properties,比如:LoginAction_en_US.properties,LoginAction_zh_CN.properties;;
找不到,则查找与它所实现的接口同名的资源文件MyInterface.properties;
再查找其父文件ParentAction.properties;
判断当前ChildAction是否实现接口ModelDriven。如果是,调用getModel()获得对象,查找与其同名的资源文件;
查找当前包下的package.properties文件(包级别);
查找当前包的父包,直到最顶层包;
查找i8n资源文件?;
查找全局资源文件,如果是在全局中定义资源文件,则需要在struts.xml里配置,例子:<constant name="struts.custom.i18n.resources" value="message" />,其中message为资源文件的起始名;(全局级别)
如果还找不到,则在值栈中搜索,参考[url]http://txyly998.iteye.com/blog/336357[/url]
所以我们这里是查找相应的父类MailReaderSupport的资源文件MailReaderSupport.properties.
[size=medium]3.[/size]我们再看如下代码
<li><a href="<s:url action="Registration_input"/>"><s:text
name="index.registration"/></a></li>
这里就要注意我们上面提到的url的用法,即[quote]This tag is used to create a URL.[/quote]我们查询相应的mailreader-support配置文件得到:
<action name="Registration_*" method="{1}" class="mailreader2.Registration">
<result name="input">/Registration.jsp</result>
<result type="redirectAction">MainMenu</result>
<interceptor-ref name="guest"/>
</action>
这里要注意这个{1}的用法即代表这个*号,在这里就为调用Registration中的input方法,我们查看代码发现经过一系列操作后,其返回input视图,对应Registration这个页面.
[size=large]第四[/size]、我们查看这个Registration页面,发现如下值得关注
<s:if test="task=='Create'">
<title><s:text name="registration.title.create"/></title>
</s:if>
<s:if test="task=='Edit'">
<title><s:text name="registration.title.edit"/></title>
</s:if>
这里的<s:if test="">是struts2的控制标签,其中的task为Registration这个Action的属性。这里是使用OGNL表达式从值栈中取出的。这个Task属性的值如果是Create就代表在session中没有这个用户名,所以就在该注册页面中显示注册信息,而不显示注册以后显示的信息,而这个注册以后的信息是否显示则是通过判断task是否为Edit来得到。
再往下看,
<s:actionerror/>
<s:form action="Registration_save" validate="false">
<s:token />
<s:hidden name="task"/>
<s:if test="task == 'Create'">
<s:textfield key="username"/>
</s:if>
<s:else>
<s:label key="username"/>
<s:hidden name="username"/>
</s:else>
<s:password key="password" showPassword="true"/>
<s:password key="password2"/>
<s:textfield key="user.fullName"/>
<s:textfield key="user.fromAddress"/>
<s:textfield key="user.replyToAddress"/>
<s:if test="task == 'Create'">
<s:submit key="button.save" action="Registration_save"/>
<s:reset key="button.reset"/>
<s:submit action="Welcome" key="button.cancel"
onclick="form.onsubmit=null"/>
</s:if>
<s:else>
<s:submit key="button.save" action="Registration"/>
<s:reset key="button.reset"/>
<s:submit action="MainMenu" key="button.cancel"
onclick="form.onsubmit=null"/>
</s:else>
</s:form>
<s:form>是表单用法,<s:token/>用法如下[quote]The token tag is used to help with the "double click" submission problem. It is needed if you are using the TokenInterceptor or the TokenSessionInterceptor. The s:token tag merely places a hidden element that contains the unique token.[/quote]
此外,这些标签的name属性代表这些元素的名称,而key属性则用于访问国际化文件内容,
这里我们要介绍下几种调用国际化资源的方式:
[size=medium]1[/size]、jsp页面的国际化,[size=small]a.[/size]使用标签
<s:text name ="资源文件中中定义的key">
输出国际化[size=small]b.[/size]使用struts2标签的key属性,此时一定要注意theme不能设置为simple,否则不能成功引用国家化文件,如<s:textfield name =“name”key ="资源化文件中的key">。[size=small]c[/size].使用<s:i18n></s:i18n>在对应资源文件中查找如
<s:i18n name = "globalMessages">
<s:text name ="username"></s:text>
</s:i18n>
[size=medium]2[/size]、getText(String key)可在所有需要使用的地方使用,如[size=small]a.[/size]
<s:textfield label ="%{getText("username")}" name = "username">
其中%{}是取出值栈中的Action对象 然后我们直接调用它的相应方法取出这个username属性。或者说我们对这个值栈中的action对象我们调用它相应的方法。
[size=small]b.[/size]action中的validate方法中增加错误信息的国际化
this.addFieldError(username,this.getText("username.invalid"));
this.addActionError(this.getText("username.invalid"));
[size=small]c.[/size]xml验证框架中的错误信息国际化
<message key = "name.invalid"></message>
或<message>${getText("name.invalid")}</message>
而<s:hidden>是[quote]Renders an HTML input element of type hidden, populated by the specified property from the ValueStack.[/quote] 对应用户名不符合要求时显示的文字,如下面的Password is not in the range 4 through 10.
我们可以通过看运行界面来理解这些标签:
Username: 123
Password is not in the range 4 through 10.
Password: ***
(Repeat) Password:***
Full Name: 123
From Address is an invalid e-mail address.
From Address: 123
Reply To Address is an invalid e-mail address.
Reply To Address: 123
继续查看代码,发现这个页面的输入结果提交给Registration_save这个action,同样是调用Registration这个Action的save方法。查看save方法,发现如下需要注意
addActionError(getText("error.username.unique"));
这个代码意思是出错后就在相应jsp页面的<s:actionerror>中显示信息,而这个信息通过getText这个方法从国际化文件中得到。
[size=large]第五[/size]、我们回到welcome.jsp,看他的第二个链接
[quote]Log on to the MailReader Demonstration Application [/quote]
这个链接是调用Login的input方法,
<action name="Login_*" method="{1}" class="mailreader2.Login">
<result name="input">/Login.jsp</result>
<result name="cancel" type="redirectAction">Welcome</result>
<result type="redirectAction">MainMenu</result>
<result name="expired" type="chain">ChangePassword</result>
<exception-mapping
exception="org.apache.struts.apps.mailreader.dao.ExpiredPasswordException"
result="expired"/>
<interceptor-ref name="guest"/>
</action>
这里虽然Login没有定义input方法,但是他的父类ActionSupport定义了input方法,它直接返回input字符串。这个结果集就要我们返回login.jsp页面。再来看这个Login.jsp页面。它的action就为Login,那么我们对于这个action映射可以看到它的method实际上应该调用的是LoginAction的execute(),我们可以理解其为:不写method,其默认值就是execute()。完成相应的execute()中的操作后,其返回SUCCESS,这个对应的result为MainMenu,因为
<result type="redirectAction">MainMenu</result>
默认的name为SUCCESS。得到这个主页面,然后也有两个链接,
[size=medium]1.[/size]一个是编辑自己的信息,这个返回到刚刚我们提到的Registration页面,和上面的不同在于,由于
<s:if test ="task=='Edit'">
这里就显示相应的编辑信息。
[size=medium]2.[/size]另一个就是注销当前用户,返回到welcome.jsp。由于代码差不多,这里就不在赘述了。
[size=large]第六[/size]、我们看客户端的校验框架,
<validators>
<field name="username">
<field-validator type="requiredstring">
<message key="error.username.required"/>
</field-validator>
</field>
<field name="user.fullName">
<field-validator type="requiredstring">
<message key="error.fullName.required"/>
</field-validator>
</field>
<field name="user.fromAddress">
<field-validator type="requiredstring">
<message key="error.fromAddress.required"/>
</field-validator>
<field-validator type="email">
<message key="errors.email"/>
</field-validator>
</field>
<field name="user.replyToAddress">
<field-validator type="email">
<message key="errors.email"/>
</field-validator>
</field>
</validators>
如果我们要使用上述客户端校验框架,我们必须在所检验的字段的这个页面的form标签中将<s:form validate="true">的validate属性设置为TRUE,并且不能将<s:form theme="">的theme属性设置为simple,我的理解是,我们使用simple主题,排版中就不会有错误信息显示的地方了。设置<s:form validate = "true">属性之后,表单的οnsubmit="return validateForm_register();"即在源码中多出了一段函数名为validateForm_register()的JavaScript代码,调用完成后再提交到相关页面。
这里还要注意一点,被校验的Action类要继承ActionSupport类,并要在action配置中指定名为input的jsp,因为struts2在校验失败后会自动返回input页面
具体的项目文件见附件,也可去官网下载。