convention-plugin 可以用来实现 struts2 的零配置。
零配置的意思并不是说没有配置,而是通过约定大于配置的方式,大量通过约定来调度页面的跳转而使得配置大大减少。
考虑到某种因素,这里采用 myeclipse 作为示例 IDE,环境 :
JDK 1.6
myeclipse 8.6.1
struts 2.1.8
web.xml
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
struts.xml
<struts>
<constant name="struts.devMode" value="true"/> <!-- 开发模式 -->
<constant name="struts.i18n.encoding" value="UTF-8"/> <!-- Web运用编码 -->
<constant name="struts.convention.result.path" value="/view/" /> <!-- 搜索视图资源的路径 -->
<constant name="struts.convention.action.name.separator" value="_" /> <!-- Action类名分隔符 -->
<constant name="struts.convention.classes.reload" value="true" /> <!-- convention类重加载 -->
<constant name="struts.convention.action.suffix" value="Action" /> <!-- Action后缀名 -->
<constant name="struts.action.extension" value="action,do,html,htm,php,aspx" /> <!-- Action扩展名 -->
<constant name="struts.convention.package.locators" value="action,actions" /> <!-- 搜索Action资源的包路径 -->
</struts>
convention 几项重要配置 :
Action 类后缀名 : <constant name="struts.convention.action.suffix" value="Action" />
继承了 struts2 的 ActionSupport 类的类不是一个普通的类,它具有能够处理请求和对请求作出响应的能力,
通常,开发者常常为这种特殊的类起一个后缀名,以区分一般普通的类。例如,xxAction,xxController,
这里的类名的后缀 Action,Controller 就是对应上面配置中的 value 的值,这个值会在发起 URL 请求的时候被截去。例如 : TestAction ---> test
Action类扩展名 [多项以逗号隔开] : <constant name="struts.action.extension" value="action,do,html,htm,php,aspx" />
与文件的扩展名相似的,例如 : TestAction ---> test.action 或 test.do 或 test.html 或 test.php 或 test.aspx 或 ...
注意 : 若该项被配置,则,访问所有的 Action 都需带上 Action 的扩展名,否则客户端将抛出 404 ERROR
Action类名分隔符 : <constant name="struts.convention.action.name.separator" value="_" />
若 Action 类名中出现驼峰标识的串,则用分隔符来切割串,如 HelloWorldAction ---> hello_world
搜索 Action 资源的包路径 [多项以逗号隔开] : <constant name="struts.convention.package.locators" value="action,actions" />
只有当包名含有以上配置中 value 值中至少一项时,convention plugin 才能搜索找得到该 Action,
否则就算访问的 Action 是存在的,convention plugin 也无法找得到该 Action
注意 : 若包名不是以上列出现过的串结束,则后面的串相当于该包下所有 Action 访问的命名空间 ( 以下面的 LoginAction 作为示例 )。
搜索视图资源(JSP,freemarker,等)的路径 : <constant name="struts.convention.result.path" value="/view/" />
所有的视图资源都需放在配置指定的文件夹路径下,否则,就算结果视图是真实存在的,convention plugin 也无法找得到该视图。默认值是 /WEB-INF/content/
demo 结构图 :
convention 约定 :
1. [ Action 不存在的情况 ] 若 convention plugin 在包搜索路径中搜索不到与请求的 URL 相匹配的 Action,则会到视图的搜索路径下搜索
与请求的 URL 相匹配的视图资源,若还搜索不到,则抛出 no Action mapped Exception
示例 :
view/hello_world.jsp [ 没有与之匹配的 Action 类 ]
<html>
<body>
<h2>Hello World!!</h2>
</body>
</html>
2. [ Action 的 execute 方法或动态方法调用的情况 ] 如请求 name!method.action,若 NameAction 存在,且 NameAction 中存在 method 这样一个方法,
则根据 method 方法返回的字符串,结果的视图资源名称规则 ( 视图以 JSP 文件为例 ) :
① success -----> name.jsp 或 name_success.jsp ; 若这两个视图同时存在,则 convention plugin 会选择 name_success.jsp 作为视图来展示
② 非 success 的任意串 string -----> name_string.jsp
示例 :
package net.yeah.fancydeepin.action;
import com.opensymphony.xwork2.ActionSupport;
/**
* -----------------------------------------
* @描述 TODO
* @作者 fancy
* @邮箱 fancydeepin@yeah.net
* @日期 2012-10-26 <BR>
* -----------------------------------------
*/
public class SayHelloAction extends ActionSupport{
private static final long serialVersionUID = 1L;
private String message;
public String execute(){
message = "Hello struts2 convention plugin!";
return SUCCESS;
}
public String say(){
message = "SayHelloAction, say";
return "world";
}
public String sayHello(){
message = "SayHelloAction, sayHello";
return "conventionPlugin";
}
public String getMessage() {
return message;
}
}
view/say_hello.jsp
<html>
<body>
<h2>say_hello.jsp → Message : ${message}</h2>
</body>
</html>
view/say_hello_world.jsp
<html>
<body>
<h2>say_hello_world.jsp → Message : ${message}</h2>
</body>
</html>
view/say_hello_conventionPlugin.jsp
<html>
<body>
<h2>say_hello_conventionPlugin.jsp → Message : ${message}</h2>
</body>
</html>
convention 注解 :
@ParentPackage : 对应于 struts.xml 常量配置项的 <constant name="struts.convention.default.parent.package" value="xx"/> 默认值是 convention-default
@ResultPath : 对应于 struts.xml 常量配置项的 <constant name="struts.convention.result.path" value="xx" /> 默认值是 /WEB-INF/content/
@Namespace : Action 访问的命名空间,该注解一旦声明,结果的视图资源需放在 : 视图搜索目录/命名空间 ( 如 : view/simple/demo.jsp )
package net.yeah.fancydeepin.action;
import org.apache.struts2.convention.annotation.Namespace;
import org.apache.struts2.convention.annotation.ParentPackage;
import org.apache.struts2.convention.annotation.ResultPath;
import com.opensymphony.xwork2.ActionSupport;
/**
* -----------------------------------------
* @描述 TODO
* @作者 fancy
* @邮箱 fancydeepin@yeah.net
* @日期 2012-10-26 <BR>
* -----------------------------------------
*/
@ParentPackage("convention-default")
@Namespace("/simple")
@ResultPath("/view/")
public class DemoAction extends ActionSupport{
private static final long serialVersionUID = 1L;
private String message;
public String execute(){
message = "DemoAction";
return SUCCESS;
}
public String getMessage() {
return message;
}
}
view/simple/demo.jsp
<html>
<body>
<h2>demo.jsp → Hello World ${message}!</h2>
</body>
</html>
@Action
package net.yeah.fancydeepin.action;
import org.apache.struts2.convention.annotation.Action;
import com.opensymphony.xwork2.ActionSupport;
/**
* -----------------------------------------
* @描述 TODO
* @作者 fancy
* @邮箱 fancydeepin@yeah.net
* @日期 2012-10-26 <BR>
* -----------------------------------------
*/
public class ActionannotationAction extends ActionSupport{
private static final long serialVersionUID = 1L;
private String message;
@Action("invoke")
public String invokeMethod(){
message = "ActionannotationAction, invokeMethod";
return SUCCESS;
}
public String getMessage() {
return message;
}
}
view/invoke.jsp
<html>
<body>
<h2>invoke.jsp → Message : ${message}</h2>
</body>
</html>
@Result,@Results
package net.yeah.fancydeepin.action;
import java.util.Random;
import org.apache.struts2.convention.annotation.Action;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;
import com.opensymphony.xwork2.ActionSupport;
/**
* -----------------------------------------
* @描述 TODO
* @作者 fancy
* @邮箱 fancydeepin@yeah.net
* @日期 2012-10-26 <BR>
* -----------------------------------------
*/
@Results({@Result(name = "success", location = "result.jsp")})
public class ResultAction extends ActionSupport{
private static final long serialVersionUID = 1L;
private String message;
public String execute(){
message = "ResultAction, execute";
return SUCCESS;
}
@Action(value = "doIt",
results = {
@Result(name = "isTrue", location = "result_true.jsp"),
@Result(name = "isFalse", location = "result_false.jsp")
}
)
public String doExecute(){
message = "doExecute isFalse.";
if(new Random().nextBoolean()){
message = "doExecute isTrue.";
return "isTrue";
}
return "isFalse";
}
public String getMessage() {
return message;
}
}
view/result.jsp
<html>
<body>
<h2>result.jsp → Message : ${message}</h2>
</body>
</html>
view/result_true.jsp
<html>
<body>
<h2>result_true.jsp → Message : ${message}</h2>
</body>
</html>
view/result_false.jsp
<html>
<body>
<h2>result_false.jsp → Message : ${message}</h2>
</body>
</html>
The last example
package net.yeah.fancydeepin.action.admin;
import com.opensymphony.xwork2.ActionSupport;
/**
* -----------------------------------------
* @描述 TODO
* @作者 fancy
* @邮箱 fancydeepin@yeah.net
* @日期 2012-10-26 <BR>
* -----------------------------------------
*/
public class LoginAction extends ActionSupport{
private static final long serialVersionUID = 1L;
private String username;
private String password;
public String log(){
username = username.intern();
password = password.intern();
if(username == "admin" && password == "fancy"){
return SUCCESS;
}
return ERROR;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
}
view/admin/login_success.jsp
<html>
<body>
<h2>Welcome!!</h2>
</body>
</html>
view/admin/login_error.jsp
<html>
<body>
<h2>Login Failed!!</h2>
</body>
</html>