透过实例学习Struts2 (2)

通过实例学习Struts2 (2)

接上一篇 《通过实例学习Struts2 (1) 》

在Action中支持通配符

在example.xml 中,我们能看到这样的定义:

<action name="Login_*" method="{1}" class="example.Login">
            <result name="input">/example/Login.jsp</result>
            <result type="redirectAction">Menu</result>

 </action>

大胆的猜测一下,这个定义应该支持Login_execute, Login_test 等Action名称,  而method="{1}" 中的{1} 应该是 第一个通配符的值, 例如execute, test, 

也就是说要执行java 类Login 中的execute,input方法。

实验一下:http://localhost:8080/struts2-blank/example/Login_excute,   注意namespace 是/example

果然登录界面出现了:

透过实例学习Struts2 (2)


再尝试一下http://localhost:8080/struts2-blank/example/Login_test , 报错了:

Exception:

java.lang.NoSuchMethodException: example.Login.test()

Stack trace:

        java.lang.NoSuchMethodException: example.Login.test()
	at java.lang.Class.throwNoSuchMethodException(Class.java:286)
	at java.lang.Class.getMethod(Class.java:845)
	at org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor.getActionMethod(AnnotationValidationInterceptor.java:75)
	at org.apache.struts2.interceptor.validation.AnnotationValidationInterceptor.doIntercept(AnnotationValidationInterceptor.java:47)
	at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246)
	at com.opensymphony.xwork2.interceptor.ConversionErrorInterceptor.intercept(ConversionErrorInterceptor.java:138)
	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246)
	at com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:239)
	at com.opensymphony.xwork2.interceptor.MethodFilterInterceptor.intercept(MethodFilterInterceptor.java:98)
	at com.opensymphony.xwork2.DefaultActionInvocation.invoke(DefaultActionInvocation.java:246)
	at com.opensymphony.xwork2.interceptor.ParametersInterceptor.doIntercept(ParametersInterceptor.java:239)

还记得之前定义Global error page 吗, 很明显上面的界面是由error.jsp 渲染出来的,使用struts2提供的tag library ,很方便

<html>
<head><title>Simple jsp page</title></head>
<body>
    <h3>Exception:</h3>
    <s:property value="exception"/>

    <h3>Stack trace:</h3>
    <pre>
        <s:property value="exceptionStack"/>
    </pre>
</body>
</html>


ActionForm在哪儿?

在Struts1 中,是使用ActionForm 来封装从浏览器中来的数据,每个Action 都需要写一个ActionForm,, 用起来虽然比较麻烦,但是比较清晰。

在Struts2中,我们似乎并没有看到类似的概念,只看到在Action中的一些getter和setter方法,例如class Login 中的username,password

    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; }

而在负责登陆的jsp中是这样的:

<s:form action="Login">
    <s:textfield key="username"/>  <--- 肯定是和class Login 中的username 绑定
    <s:password key="password" />  <--- 肯定是和class Login 中的password 绑定
    <s:submit/>
</s:form>

各位同学不妨试验一下,在setUsername,和setPassword 中加入一些System.out ,在控制台输出用户名和密码,看看(1) 什么时候调用 (2) 和界面是用户输入的关系


试验的结果会表明: 在Struts2中, Form 和 Action是混到一起了,Action中可以有实例变量,并且可以被框架自动赋值(当然是通过setter方法),至于通过getter暴露出的属性值,在jsp中可以引用。

既然Action中有实例变量,那就意味着Struts2会为每一个http request 创建一个新的Action实例(Struts1不是这样的),所以你不用担心线程安全问题


扩展思考: 为什么要混合ActionForm和Action?

 根据我在Struts1中的开发体验,为每个Action开发一个ActionForm确实是一件不那么Happy的事情,需要额外的工作建立一个额外ActionForm的类, 并且这个类只能用在相关的Action上。

Struts2 则把Action和ActionForm合并为一了, 咋一看感觉比较乱,但用起来就会发现,确实是极大的减少了开发量,通过简单的getter/setter就能获取界面上的数据。

虽然在设计上看起来不够优雅和完美,但实用性十足,这就够了。

对于那些追求完美的同学, 我相信struts2 还是能支持ActionForm 的, 我们慢慢看。


如何做输入验证?

输入验证可能是Web application最基本的功能了, struts2 当然需要完美支持, 观察一下项目中的Login_validator.xml, 你能猜到什么?

<validators>
    <field name="username">
        <field-validator type="requiredstring">
            <message key="requiredstring"/>
        </field-validator>
    </field>
    <field name="password">
        <field-validator type="requiredstring">
            <message key="requiredstring"/>
        </field-validator>
    </field>
</validators>
(1) 这是对两个field做验证,分别对应Login 中的username和password

(2) 验证器是requiredstring , 从字面意思来猜应该是这个field 是字符串类型,并且不能为空

(3) 如果验证失败,显示的出错消息是应该在resource文件中定义, 消息的key 是requriedstring

(4) 输入验证的配置文件,格式应该是<ActionName>_validator.xml ,  很明显,这个文件应该和Action 类是紧紧绑定的,应该放在同一个package下面。

我们来看一下前面提到的package.properties

requiredstring = ${getText(fieldName)} is required.
password = Password
username = User Name

其中的requiredstring 定义看起来比较奇怪 : ${getText(fieldName)} is required.

fieldName 应该就是当前要验证的filed的名称,例如"username", "password"

而getText() 应该是struts2提供的方法,估计是为了多语言处理的,如果当前的field 是"username" , 那么 getText(“username”) 在这个例子中就是“User Name”.

错误信息怎么显示?

验证失败,肯定要在jsp 中输入错误信息,这一点Struts2的tag library已经给我们封装好了, 当然是高度封装:

<s:form action="Login">
    <s:textfield key="username"/>
    <s:password key="password" />
    <s:submit/>
</s:form>

如果username 或者password没有输入, struts2就会在每一个输入域的上方显示错误消息。

注意  <s:textfield key="username"/>  用的是key = xxx , 你可以试一下 name=xxxx, 就可以发现 其中的奥秘:

使用key =xxx , 最终的html中既有label ,又有input field,  使用name=xxx,只会输出 input filed ,需要自己来写label 了。

扩展思考: 我只能通过xml文件类配置输入验证吗?

有时候确实需要复杂的基于业务逻辑的验证, 仅仅依靠xml是不够的

观察一下Login这个类, 它最终扩展了ActionSupport ,这个类有很多和验证相关的方法,例如addFieldError (),validate()等等,我们尝试一下在class Login中覆盖validate方法:

public void validate(){
    	if(!"andy".equals(getUsername())){
    		addFieldError("username",getText("user.incorrect"));
    	}
    	if(!"pass4andy".equals(getPassword())){
    		addFieldError("password",getText("password.incorrect"));
    	}
    }

当然要记得在package.properties 新添加两个message

user.incorrect=User name is not correct

password.incorrect=Password is not correct

试着登陆一下,你就会发现我们的validate开始工作了,是不是很easy ?


今天到此为止,明天来看看国际化怎么做