前面說道實現Action一般選擇繼承ActionSupport的方式,因爲它提供了一些額外的功能,比如基本的數據驗證和訪問本地信息。
基本數據驗證
由於ActionSupport類實現了Validateable接口,那麼在該動作被觸發的時候會在執行動作方法之前先執行validate方法,如果驗證沒有通過,那麼就會返回信息輸入結果頁面。因此我們只需要在Action中重寫validate方法就可以實現數據的驗證了。
- public class HelloWorld extends ActionSupport {
- private String userName;
- public String getUserName() {
- return userName;
- }
- public void setUserName(String userName) {
- this.userName = userName;
- }
- public String execute1() throws Exception {
- return "success";
- }
- @Override
- public void validate() {
- if(userName == null){
- addFieldError("userName","未獲取到參數!");
- }else if(userName.length()>5){
- addFieldError("userName","用戶名太長");
- }
- }
- }
input.jsp
<body>
<s:if test="hasFieldErrors()">
<s:iterator value="fieldErrors">
<font color="#FF0000"><s:property value="value[0]"/></font><br>
</s:iterator>
</s:if>
<form action="login.action" method="post">
username : <input type="text" name="userName"/><br/>
password :<input type="password" name="password"><br/>
<input type="submit" value="submit"/>
</form>
</body>
執行:
結果:
這裏有幾點需要注意:
(1) 每次執行動作方法之前,都會執行validate方法,如果我們的Action中有多個動作方法的話,那麼每個動作方法執行之前都會執行validate方法,因此validate方法中一般執行一些通用的檢查。
(2) 如果validate中沒有通過,即產生了錯誤消息,那麼不會執行動作方法,會直接返回”input”。
下面大致瞭解一下validate方法執行的原理。首先我們需要知道,這個方法是被攔截器調用的,攔截器放在動作執行之前,攔截每個訪問該Action的請求。這個攔截器叫做Workflow攔截器,查看文檔可以知道,該攔截器的實現類爲com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor。文檔中說這個攔截器要做的事情:
(1)如果Action中有validate{MethodName}()方法,那麼執行它
(2)如果(1)不成立,但是Action中有validateDo{MethodName}()方法,那麼執行它
(3)不管(1)或(2)是否執行,只要這個攔截器的alwaysInvokeValidate屬性爲true,那麼總是會執行validate方法。
查看DefaultWorkflowInterceptor的源碼:
- public class DefaultWorkflowInterceptor extends MethodFilterInterceptor {
- private static final long serialVersionUID = 7563014655616490865L;
- private static final Logger LOG = LoggerFactory.getLogger(DefaultWorkflowInterceptor.class);
- private static final Class[] EMPTY_CLASS_ARRAY = new Class[0];
- private String inputResultName = Action.INPUT;
- public void setInputResultName(String inputResultName) {
- this.inputResultName = inputResultName;
- }
- @Override
- protected String doIntercept(ActionInvocation invocation) throws Exception {
- Object action = invocation.getAction();
- if (action instanceof ValidationAware) {
- ValidationAware validationAwareAction = (ValidationAware) action;
- if (validationAwareAction.hasErrors()) {
- if (LOG.isDebugEnabled()) {
- LOG.debug("Errors on action " + validationAwareAction + ", returning result name 'input'");
- }
- String resultName = inputResultName;
- if (action instanceof ValidationWorkflowAware) {
- resultName = ((ValidationWorkflowAware) action).getInputResultName();
- }
- InputConfig annotation = action.getClass().getMethod(invocation.getProxy().getMethod(), EMPTY_CLASS_ARRAY).getAnnotation(InputConfig.class);
- if (annotation != null) {
- if (!annotation.methodName().equals("")) {
- Method method = action.getClass().getMethod(annotation.methodName());
- resultName = (String) method.invoke(action);
- } else {
- resultName = annotation.resultName();
- }
- }
- return resultName;
- }
- }
- return invocation.invoke();
- }
- }
發現在這個攔截器中根本就沒有調用validate方法,而只是對是否產生的錯誤信息進行了檢測。並且看以看到,如果存在錯誤信息,默認返回的Result是Action.input(”input”)。
那麼既然workflow攔截器沒有執行validate方法,由於我們的Action使用的默認的攔截器棧,那麼就去看看在workflow攔截器前面的攔截器validation攔截器。
查看文檔可以知道這個攔截器的實現類爲:org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor。這個類繼承了com.opensymphony.xwork2.validator.ValidationInterceptor這個類查看這個類的源碼,裏面有一個doBeforInvocation方法:
- protected void doBeforeInvocation(ActionInvocation invocation) throws Exception {
- Object action = invocation.getAction();
- ActionProxy proxy = invocation.getProxy();
- //the action name has to be from the url, otherwise validators that use aliases, like
- //MyActio-someaction-validator.xml will not be found, see WW-3194
- String context = proxy.getActionName();
- String method = proxy.getMethod();
- if (log.isDebugEnabled()) {
- log.debug("Validating "
- + invocation.getProxy().getNamespace() + "/" + invocation.getProxy().getActionName() + " with method "+ method +".");
- }
- if (declarative) {
- if (validateAnnotatedMethodOnly) {
- actionValidatorManager.validate(action, context, method);
- } else {
- actionValidatorManager.validate(action, context);
- }
- }
- if (action instanceof Validateable && programmatic) {
- // keep exception that might occured in validateXXX or validateDoXXX
- Exception exception = null;
- Validateable validateable = (Validateable) action;
- if (LOG.isDebugEnabled()) {
- LOG.debug("Invoking validate() on action "+validateable);
- }
- try {
- PrefixMethodInvocationUtil.invokePrefixMethod(
- invocation,
- new String[] { VALIDATE_PREFIX, ALT_VALIDATE_PREFIX });
- }
- catch(Exception e) {
- // If any exception occurred while doing reflection, we want
- // validate() to be executed
- if (LOG.isWarnEnabled()) {
- LOG.warn("an exception occured while executing the prefix method", e);
- }
- exception = e;
- }
- if (alwaysInvokeValidate) {
- validateable.validate();
- }
- if (exception != null) {
- // rethrow if something is wrong while doing validateXXX / validateDoXXX
- throw exception;
- }
- }
- }
可以看到,正是在這個方法中對Action中的validate方法進行了調用。爲了驗證到底validate方法是在validation攔截器中被調用的還是在workflow攔截器中被調用的,我們寫個小實例,不使用defaultStack,我們手動爲Action配置攔截器棧(這個攔截器棧基本和defaultStack相同):
- <package name="default" namespace="/" extends="struts-default">
- <interceptors>
- <interceptor-stack name="luo">
- <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\..*</param>
- </interceptor-ref>
- <interceptor-ref name="conversionError"/>
- <interceptor-ref name="workflow">
- <param name="excludeMethods">input,back,cancel,browse</param>
- </interceptor-ref>
- <interceptor-ref name="debugging"/>
- </interceptor-stack>
- </interceptors>
- <default-interceptor-ref name="luo"></default-action-ref>
- <action name="hello" class="action.HelloWorld">
- <result name="success">/success.jsp</result>
- <result name="input">/input.jsp</result>
- </action>
- </package>
我們僅僅是將validation攔截器移出了Action的先前默認的defaultStack攔截器棧中,我們再來執行前面執行過的測試:
結果:
success結果被顯示了,說明validate方法沒有被調用了。那麼就說明了validate方法是在validattion攔截器中被調用的。
workflow攔截器和validation攔截器可以傳遞一些參數:
- <interceptor-ref name="validation">
- <paramnameparamname="excludeMethods">input,back,cancel,browse</param>
- </interceptor-ref>
當執行到與excludeMethods參數中指定的方法同名的方法時,攔截器不會做出反映。還可以通過這種形式來爲攔截器指定其他屬性,比如爲workflow指定默認返回的Result等。
- <interceptor-ref name="workflow">
- <param name="inputResultName">error</param>
- <param name="excludeMethods">*</param>
- <param name="includeMethods">myWorkflowMethod</param>
- </interceptor-ref>
訪問本地信息
在上面的例子程序中,我們將錯誤信息硬編碼在代碼中,這樣有一些弊端:
(1) 一是不容易修改,如果我們想改變消息的內容,還得重新修改代碼和重新編譯類
(2) 而是不利於國際化,如果要將其中的中文換成英文,那麼還得到代碼中來修改和重新編譯類
通過訪問本地信息,我們將一些消息按照鍵值對的形式保存在類文件外部的文件,通過key來獲取文件中對應的消息,那麼一旦我們需要修改消息內容,那麼就只需要修改這個外部文件而不需要重新編譯類了。基本步驟:
(1) 首先建立消息文件,在Action類的類路徑下建立一個與Action同名的properties文件,例如HelloWorld.properties,然後在文件裏按照 key= value的形式添加錯誤消息。
nameIsNull =\u672A\u83B7\u53D6\u5230\u53C2\u6570
nameIsTooLong=\u7528\u6237\u540D\u592A\u957F
需要注意的是Value的值必須是unicode編碼,這樣在程序充才能正確獲取,在eclipse中可以使用可視化工具編輯properties文件,這樣生成的代碼會將中文轉換爲unicode編碼。JDK中也提供了native2ascii命令來實現這種轉換,具體方法google一下就知道了。
(2)修改原來在Actin中硬編碼的錯誤消息,改成從配置文件中讀取信息。
public void validate() {
if(userName ==null){
addFieldError("userName",getText("nameIsNull"));
}else if(userName.length()>5){
addFieldError("userName",getText("nameIsTooLong"));
}
}
重新運行程序,結果和前面一樣。至於實現的原理,最終還是藉助JDK提供的ResourceBoundle來實現的。ActionSupport類實現了TextProvider接口和LocalProvider接口。當我們調用ActionSupport的getText方法時,其內部實際上是調用了TextProvider一個實現類(TextProviderSupport)的對象上的getText方法。
public String getText(String aTextName) {
return getTextProvider().getText(aTextName);
}
private TextProvider getTextProvider() {
if (textProvider ==null) {
TextProviderFactory tpf = new TextProviderFactory();
if (container !=null) {
container.inject(tpf);
}
textProvider = tpf.createInstance(getClass(), this);
}
return textProvider;
}
由於實現了LocalProvider,因此ActionSupport也是LocalProvider類型,其getLocal從ActionContext中獲取Local對象。使用系統默認的Local對象時,ResourceBoundle會在Class所在路徑下尋找與當前類同名的properties文件,如果使用指定的Local對象,那麼我們的資源文件還需要在名字中添加相應的國家代碼,例如:HelloWorld_zh_CN.properties