第三章 使用Struts 2 Action
3.1 Struts 2 Action简介
Action 的作用:
- Action封装工作单元,或者至少是业务逻辑的入口点(如果业务逻辑很复杂,我们可能会把业务逻辑构建成一个业务组件,再把这个业务组件注入到Action中去)。
- Action为数据转移提供场所,Action只需把每一个期望承载的数据实现为JavaBean属性。除了这些简单的JavaBean属性,还有两种技术可以把Action作为数据转移对象使用(后续会讲到)。
- Action为结果路由选择返回控制字符串。
3.2 打包Action
1. Struts 2的包提供了一种继承机制,让你能够继承框架已经定义的组件。
<package name="packageName" namespace="/packageNamespace" extends="struts-default">
2. 可以为多个不同的包设置相同的命名空间。
3. 如果不设置namespace属性,你的Action就会进入默认命名空间。默认命名空间在所有其他的命名空间之下,用来解决不能与任何显示声明的命名空间匹配的请求。默认命名空间实际上是空字符串” ”。
4. 你也可以定义叫做”/”的根命名空间。根命名空间和其他显示声明的命名空间相同,必须被完全匹配。
5. 大部分智能默认值定义在系统内建的一个叫做struts-default的包中。虽然当你创建自定义的包时不是一定要扩展struts-default,但是省略了继承就等于拒绝了框架的核心功能。
3.3 实现Action
ActionSupport类。它是一个提供了Action接口和其他几个有用接口的默认实现的便利类,提供了诸如数据验证、错误消息本地化等功能。数据验证和文本本地化服务是通过拦截器和接口的协作实现的。拦截器控制服务的执行,Action实现接口提供被拦截器调用的方法。
public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable {
//....
}
1.基本验证
在默认的拦截器栈中我们可以看到params拦截器在workflow拦截器之前定义。params拦截器将请求数据转移到Action对象上。之后,workflow拦截器在这些数据被模型接受之前验证这些数据。
workflow拦截器称为工作流拦截器,因为如果验证出错,它会把请求工作流重新定向到输入页面。
注意:workflow拦截器必须在params拦截器将数据从请求转移到Action对象之后触发。
当workflow拦截器触发时,它首先会在Action上查找要调用的validate()方法。你会把验证逻辑放在validate()方法中。这个方法通过com.opensymphony.xwork2.Validateable公开出来。从技术上讲ActionSupport实现了validate()方法,你只需用特定的验证逻辑覆盖这个空方法。
public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable {
/**
* A default implementation that validates nothing.
* Subclasses should override this method to provide validations.
*/
public void validate() {
}
}
注意validate()方法没有返回值,秘密在于验证过程生成的错误消息。在调用完validate()方法之后,workflow拦截器检查验证逻辑是否生成了错误消息。如果找到了错误消息,workflow拦截器会改变请求的工作流。它会立即终止请求处理,将用户带回到输入表单,并在表单中显示错误消息。
那么错误消息保存到哪里?workflow如何检查是否有错误消息被创建?
com.opensymphony.xwork2.ValidationAware接口定义了存储和获取错误消息的方法。
public interface ValidationAware {
/**
* Set the Collection of Action-level String error messages.
*
* @param errorMessages Collection of String error messages
*/
void setActionErrors(Collection<String> errorMessages);
/**
* Get the Collection of Action-level error messages for this action. Error messages should not
* be added directly here, as implementations are free to return a new Collection or an
* Unmodifiable Collection.
*
* @return Collection of String error messages
*/
Collection<String> getActionErrors();
/**
* Set the Collection of Action-level String messages (not errors).
*
* @param messages Collection of String messages (not errors).
*/
void setActionMessages(Collection<String> messages);
/**
* Get the Collection of Action-level messages for this action. Messages should not be added
* directly here, as implementations are free to return a new Collection or an Unmodifiable
* Collection.
*
* @return Collection of String messages
*/
Collection<String> getActionMessages();
/**
* Set the field error map of fieldname (String) to Collection of String error messages.
*
* @param errorMap field error map
*/
void setFieldErrors(Map<String, List<String>> errorMap);
/**
* Get the field specific errors associated with this action. Error messages should not be added
* directly here, as implementations are free to return a new Collection or an Unmodifiable
* Collection.
*
* @return Map with errors mapped from fieldname (String) to Collection of String error messages
*/
Map<String, List<String>> getFieldErrors();
/**
* Add an Action-level error message to this Action.
*
* @param anErrorMessage the error message
*/
void addActionError(String anErrorMessage);
/**
* Add an Action-level message to this Action.
*
* @param aMessage the message
*/
void addActionMessage(String aMessage);
/**
* Add an error message for a given field.
*
* @param fieldName name of field
* @param errorMessage the error message
*/
void addFieldError(String fieldName, String errorMessage);
/**
* Check whether there are any Action-level error messages.
*
* @return true if any Action-level error messages have been registered
*/
boolean hasActionErrors();
/**
* Checks whether there are any Action-level messages.
*
* @return true if any Action-level messages have been registered
*/
boolean hasActionMessages();
/**
* Checks whether there are any action errors or field errors.
* <p/>
* <b>Note</b>: that this does not have the same meaning as in WW 1.x.
*
* @return <code>(hasActionErrors() || hasFieldErrors())</code>
*/
boolean hasErrors();
/**
* Check whether there are any field errors associated with this action.
*
* @return whether there are any field errors
*/
boolean hasFieldErrors();
}
实现这个重要接口的类必须为每一个可以验证的字段维护一系列错误消息和一系列与action整体相关的通用错误消息。幸运的是ActionSupport替我们做到了这一点。为了使用它们,我们只需调用下面两个方法。
public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable {
//....
public void addActionError(String anErrorMessage) {
validationAware.addActionError(anErrorMessage);
}
public void addFieldError(String fieldName, String errorMessage) {
validationAware.addFieldError(fieldName, errorMessage);
}
//....
}
2.使用资源包处理文本消息
最佳实践是将字符串消息集中放入外部可维护的资源包中。ActionSupport提供了内建的功能可以轻松地管理。ActionSupport实现了两个接口,它们协作提供了本地化消息文本功能。第一个接口com.opensymphony.xwork2.TextProvider提供了对这些消息的访问。这个接口提供了一系列灵活的方法,使用这些方法可以从资源包中取得消息。
public interface TextProvider {
/**
* Checks if a message key exists.
*
* @param key message key to check for
* @return boolean true if key exists, false otherwise.
*/
boolean hasKey(String key);
/**
* Gets a message based on a message key, or null if no message is found.
*
* @param key the resource bundle key that is to be searched for
* @return the message as found in the resource bundle, or null if none is found.
*/
String getText(String key);
/**
* Gets a message based on a key, or, if the message is not found, a supplied
* default value is returned.
*
* @param key the resource bundle key that is to be searched for
* @param defaultValue the default value which will be returned if no message is found
* @return the message as found in the resource bundle, or defaultValue if none is found
*/
String getText(String key, String defaultValue);
/**
* Gets a message based on a key using the supplied obj, as defined in
* {@link java.text.MessageFormat}, or, if the message is not found, a supplied
* default value is returned.
*
* @param key the resource bundle key that is to be searched for
* @param defaultValue the default value which will be returned if no message is found
* @param obj obj to be used in a {@link java.text.MessageFormat} message
* @return the message as found in the resource bundle, or defaultValue if none is found
*/
String getText(String key, String defaultValue, String obj);
/**
* Gets a message based on a key using the supplied args, as defined in
* {@link java.text.MessageFormat}, or null if no message is found.
*
* @param key the resource bundle key that is to be searched for
* @param args a list args to be used in a {@link java.text.MessageFormat} message
* @return the message as found in the resource bundle, or null if none is found.
*/
String getText(String key, List<?> args);
/**
* Gets a message based on a key using the supplied args, as defined in
* {@link java.text.MessageFormat}, or null if no message is found.
*
* @param key the resource bundle key that is to be searched for
* @param args an array args to be used in a {@link java.text.MessageFormat} message
* @return the message as found in the resource bundle, or null if none is found.
*/
String getText(String key, String[] args);
/**
* Gets a message based on a key using the supplied args, as defined in
* {@link java.text.MessageFormat}, or, if the message is not found, a supplied
* default value is returned.
*
* @param key the resource bundle key that is to be searched for
* @param defaultValue the default value which will be returned if no message is found
* @param args a list args to be used in a {@link java.text.MessageFormat} message
* @return the message as found in the resource bundle, or defaultValue if none is found
*/
String getText(String key, String defaultValue, List<?> args);
/**
* Gets a message based on a key using the supplied args, as defined in
* {@link java.text.MessageFormat}, or, if the message is not found, a supplied
* default value is returned.
*
* @param key the resource bundle key that is to be searched for
* @param defaultValue the default value which will be returned if no message is found
* @param args an array args to be used in a {@link java.text.MessageFormat} message
* @return the message as found in the resource bundle, or defaultValue if none is found
*/
String getText(String key, String defaultValue, String[] args);
/**
* Gets a message based on a key using the supplied args, as defined in
* {@link java.text.MessageFormat}, or, if the message is not found, a supplied
* default value is returned. Instead of using the value stack in the ActionContext
* this version of the getText() method uses the provided value stack.
*
* @param key the resource bundle key that is to be searched for
* @param defaultValue the default value which will be returned if no message is found
* @param args a list args to be used in a {@link java.text.MessageFormat} message
* @param stack the value stack to use for finding the text
* @return the message as found in the resource bundle, or defaultValue if none is found
*/
String getText(String key, String defaultValue, List<?> args, ValueStack stack);
/**
* Gets a message based on a key using the supplied args, as defined in
* {@link java.text.MessageFormat}, or, if the message is not found, a supplied
* default value is returned. Instead of using the value stack in the ActionContext
* this version of the getText() method uses the provided value stack.
*
* @param key the resource bundle key that is to be searched for
* @param defaultValue the default value which will be returned if no message is found
* @param args an array args to be used in a {@link java.text.MessageFormat} message
* @param stack the value stack to use for finding the text
* @return the message as found in the resource bundle, or defaultValue if none is found
*/
String getText(String key, String defaultValue, String[] args, ValueStack stack);
/**
* Get the named bundle, such as "com/acme/Foo".
*
* @param bundleName the name of the resource bundle, such as <code>"com/acme/Foo"</code>.
* @return the bundle
*/
ResourceBundle getTexts(String bundleName);
/**
* Get the resource bundle associated with the implementing class (usually an action).
*
* @return the bundle
*/
ResourceBundle getTexts();
}
ActionSupport实现了这些方法,使你可以通过关键字引用资源包中的对应的消息。
第二个接口com.opensymphony.xwork2.LocaleProvider只提供了一个方法getLocale()。
public interface LocaleProvider {
/**
* Gets the provided locale.
*
* @return the locale.
*/
Locale getLocale();
}
ActionSupport也为本地化消息文本提供了一个基本的国际化解决方案。ActionSupport实现了这个接口,根据浏览器发送来的地域设置取得用户所在的地域。
public class ActionSupport implements Action, Validateable, ValidationAware, TextProvider, LocaleProvider, Serializable {
public Locale getLocale() {
ActionContext ctx = ActionContext.getContext();
if (ctx != null) {
return ctx.getLocale();
} else {
if (LOG.isDebugEnabled()) {
LOG.debug("Action context not initialized");
}
return null;
}
}
}
你还是用以前的方式取得消息文本。甚至我们还没有使用它的时候,ActionSupport的TextProvider实现已经开始在每次为我们取得文本消息的时候通过调用LocalProvider接口的getLocale()方法检查地域。使用得到的地域,TextProvider(也就是ActionSupport)会尝试为这个地域定位属性文件。
3.4 向Action传递数据
1. 前面的内容,Action把从请求接收到的所有数据放在简单的JavaBean属性上。另外还有两种方式来传递数据。
- 第一种选择也是基于JavaBean。可以公开一个复杂对象作为JavaBean属性,让数据直接传输到这个对象上。
- 另一种选择是使用被称为ModelDriven的Action。
2. 使用域对象做数据转移存在危险。如果请求中的参数与域对象属性匹配,那么数据会被移动到这些属性上。但是域对象可能会包含一些敏感数据(如ID),我们不想把它们公开给自动数据转移机制。这个问题现在并没有很好地解决方案。
3.5 案例研究:文件上传
fileUpload拦截器创建了我们之前看到过的数据自动转移机制的一种特别版本。在默认拦截器栈defaultStack中可以看到fileUpload拦截器在params拦截器之前。当fileUpload拦截器执行时,它处理一个多重请求(multipart request)并且将文件与其他一些元数据一起转换到请求参数中(params拦截器会自动将数据转移到Action上)。在后加工阶段fileUpload拦截器再次出发时,用来消除上传文件的临时版本。