该文章通过讲述一个简单但功能齐全的应用开发过程来指导Struts2的初学者. 文章中包含了使用到的代码段, 但你最好在自己搭建一个服务器来运行MailReader应用.
该教程默认读者有一定的Java,JavaBeans,JSP,web应用开发的基础知识. 想了解底层的实现技术,请浏览 Key Technologies Primer.
需要的知道的是,该MailReaer只是该应用的第一次迭代开发. 该版本提供的功能是让用户登录并维护多个不同邮箱服务器的账号. 完全完成后, 该应用能让用户从他们的账号中读取邮件.
该MailReader应用演示了注册,登录,维护一些主记录和子记录。文章概述了开发过程中需要做的事,包括jsp,java类,配置文件的编写。
JAAS - Note that for compatibility and ease of deployment, the MailReader uses "application-based" authorization. However, use of the standard Java Authentication and Authorization Service (JAAS) is recommended for most applications. (See the Key Technologies Primer for more about authentication technologies.)
首先来了解一个初始的欢迎页面是如何呈现的, 然后如何登录应用和修改订阅信息. 请注意该文章不是教你如何写一个简单的hello wrold程序,而是通过丰富的实践经验和“最佳实践”来开发一个应用。 你应该调整状态来仔细的阅读这29页长的文章。
一个通常的web应用, 可以指定一些主页面. 当你使用这个web应用没有指定一个特殊的页面时候,服务器会返回一个默认的主页。
当一个web应用被载入时, 容器会读取和解析 "Web应用部署文件"或 "web.xml" 文件. Struts2通入一个过滤器来嵌入一个web应用中. 跟其他过滤器一样, "struts2" 的过滤器filter部署在"web.xml"中.
Struts 2 MailReader
struts2
org.apache.struts2.dispatcher.FilterDispatcher
struts2
/*
org.springframework.web.context.ContextLoaderListener
mailreader2.ApplicationListener
index.html
注意在web.xml中没有指定actions的后缀名. Struts 2 默认的后缀名是 ".action", 但可以在 struts.properties 文件中替换. 为了与之前的版本相容, MailReader使用.do作为后缀名
struts.action.extension = do
web.xml中为应用指定了一个主页面.当一个请求不是指定具体页面而是一个目录, 容器会使用默认的主页作为返回.
但是,大多数Struts2应用不会指向一个具体的真实的页面, 而是一个虚拟的资源 - actions. Actions 指定了一段你需要在返回一个页面或者其他资源之前执行的代码. 一个被普遍认可的做法是不直接链接到服务页面, 而是仅仅通过action的映射. By linking to actions, developers can often "rewire" an application without editing the server pages.
"Link actions not pages."
actions 被列在一个或多个 XML配置文件中, 默认的配置文件是 "struts.xml". 在载入一个web应用时, struts.xml和其他包含在内的配置文件会被解析, 然后框架会创建出一些配置对象. 主要的工作是将一个请求和某些action,某些页面进行关联。
可以在web.xml中设置0或多个"Welcome" 页面.除非你在使用JAVA1.5,actions不能被指定为一个 Welcome page. 所以在这种情况下,如何履行"Link actions not pages."的最佳实践呢?
一种解决方案是用一个页面来引导actions. 我们可以用一个"index.html" 作为主页面然后将它重定向至 "Welcome" action.
Loading ...
我们也可以使用jsp页面通过struts标签进行重定向,但是一个简单的HTML就足够解决了。
当客户端请求"Welcome.do", 这个请求将进入 "struts2" 通过 FilterDispatcher (之前在web.xml中配置的过滤器).FilterDispatcher 从配置中寻找合适的action映射. 如果我们只需要跳转至Welcome页面, 只需要进行简单的配置.
/pages/Welcome.jsp
如果请求 Welcome action ("Welcome.do"), "/page/Welcome.jsp" 页面会作为响应返回.客户端不知道也不需要知道返回资源的路径其实是"/pages/Welcome.jsp",客户端仅知道自己请求的资源是"Welcome.do".
如果你看了MailReader的配置文件,会发现Welcome action的配置其实有更多内容.
class="mailreader2.Welcome">
/pages/Welcome.jsp
任何时候请求Welcome acton,Welcome 这个JAVA类都会被执行. 当他执行完,会选择一个"result" . 默认的result 名是"success".另外一个有效的result"error", 定义在一个全局域中.
Action不需要知道结果 "success" 或 "error"的返回类型,只需要返回一个result的名字, 不必了解它的具体实现.
所以所有的result的细节,包括返回页面的路径,都只需要在配置文件中定义一次. 将实现细节耦合在一起而不是在应用中到处分散。
Struts配置文件让我们可以分离关注点,并且只作一次定义,帮助我们规范地开发应用。
为什么welcome action需要选择 "success" 或 "error"?
MailReader应用保存了一些用户和他们的邮箱账号在数据库中. 如果无法连接到数据库, 应用则无法进行工作. 所以在显示welcome页面之前, welcome类 会检查数据是否可用.
MailReader也是一个国际化的应用. 所以Welcome类也会先检查信息资源的有效性. 如果两个资源都是有效的,class 才会返回结果 "success" . 此外, class会返回结果"error",则页面无法正常显示。
package mailreader2;
public class Welcome extends MailreaderSupport {
public String execute() {
// Confirm message resources loaded
String message = getText(Constants.ERROR_DATABASE_MISSING);
if (Constants.ERROR_DATABASE_MISSING.equals(message)) {
addActionError(Constants.ERROR_MESSAGES_NOT_LOADED);
}
// Confirm database loaded
if (null==getDatabase()) {
addActionError(Constants.ERROR_DATABASE_NOT_LOADED);
}
if (hasErrors()) {
return ERROR;
}
else {
return SUCCESS;
}
}
}
几个常用的result名字被预先定义好了, 包括 ERROR, SUCCESS, LOGIN, NONE, 和 INPUT, 可以在 Struts2中任意使用.
之前提到, "error" 定义在全局范围. 其他的action可能也会碰上数据库无法连接的问题, 或者其他错误发生. MailReader 定义 "error" result 为 Global Result, 所以任意的action可以使用它.
"error">/pages/Error.jsp
/pages/Error.jsp
Login_input
当然, 如果一个自定义的action mapping 包含了自己的 "error" result, 则局部的result会被使用。
数据库被当作一个对象保存在应用中. 数据库对象是实现于一个接口的.应用不需要重启就可以载入不用的数据库接口的实现类. But how is the database object loaded in the first place?
我们在"web.xml"配置了一个自定义的监听器用于创建数据库对象.
mailreader2.ApplicationListener
默认的, 我们的ApplicationListener 会载入一个 MemoryDatabase, UserDatabase的实现类. MemoryDatabase stores the database content as a XML document, which is parsed and loaded as a set of nested hashtables. The outer table is the list of user objects, each of which has its own inner hashtable of subscriptions. When you register, a user object is stored in this hashtable. When you login, the user object is stored within the session context.
数据库已经被创建好并有一个示例用户. 如果你检查"/src/main/resources"路径下的"database.xml" 文件 , 你可以看见实例用户在其中的描述.
autoConnect="false"
password="bar" type="pop3" username="user1234">
The "seed" user element creates a registration record for "John Q. User", with the subscription detail for his hotmail and yahoo accounts.
MailReader是一个国际化的应用. 在Struts 2中, 在Action类在进行处理时,信息资源是关联在其中的. 检查源码,会看见一个语言资源包MailreaderSupport. MailreaderSupport 是MailReader应用中的所有action的基本类. Since 因为所有的Action都继承自MailreaderSupport, 则共用着相同的语言资源包.
index.heading=MailReader Application Options
index.login=Log on to the MailReader Application
index.registration=Register with the MailReader Application
index.title=MailReader Demonstration Application
index.tour=A Walking Tour of the MailReader Demonstration Application
如果你在资源中更改一条信息,然后重载应用,这个更改就生效了. 如果你为其他地域提供信息资源包,则可以本地化你的应用. MailReader提供了 English, Russian, 和Japanese的资源.
确定完必要的资源存在后,Welcome action 将跳转至 Welcome page.
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="http://struts.apache.org/tags" %>
" rel="stylesheet"
type="text/css"/>
Language Options
-
en
English
-
ja
Japanese
-
ru
Russian
"
alt=" "/>
在上面的Welcome page中, 使用了 Struts 2 标签库,它们用红色来标记了,分别使用了Struts JSP标签的 "text", "url", and "i18n".
(在Struts 2 MailReader 应用中使用了"s:"前缀, 你也可以使用任何你想使用的前缀在你的应用中.)
text 标签中插入了一条应用默认的信息资源中的信息.如果用户的地区设置被更改了, text标签会使用新的本地的资源来替代.
url 标签可以指向一个action或者其他web资源, applying "URL encoding" to the hyperlinks as needed. Java's URL encoding feature lets your application maintain client state without requiring cookies.
Cookies - If you turn cookies off in your browser, and then reload your browser and this page, you will see the links with the Java session id information attached. (If you are using Internet Explorer and try this, be sure you reset cookies for the appropriate security zone, and that you disallow "per-session" cookies.)
i18n 标签提供访问多个资源包. MailReader application uses a second set of message resources for non-text elements. When these are needed, we use the "i18n" tag to specify a different bundle.
The alternate bundle is stored in the {{/src/main/resources}} folder, so that it ends up under "classes", which is on the application's class path.
In the span of a single request for the Welcome page, the framework has done quite a bit already:
成功跳转后, Welcome页面会有两个选项: 登录和注册. 下面先介绍登录的实现。
如果选择了登录, 且一切正常, Login action 会跳转至 Login 页面.
Login 页面显示了一个输入用户名和密码的表单.你可以使用默认的用户名和密码登录.试试用不同错误的方式来登录,看看应用会给出什么响应. 注意用户名和密码都是大小写敏感的.
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="http://struts.apache.org/tags" %>
" rel="stylesheet"
type="text/css"/>
action="Login_cancel" onclick="form.onsubmit=null"
key="button.cancel"/>
在welcome页面上我们已经看过一些struts2标签,现在再来看几个新的标签.
login.jsp中第一遇见的新标签是actionerrors. 每一个属性都可能发生各种验证错误. 如果你没有输入用户名, struts会在该标记处显示一个错误信息来提示你. 但也有跟属性无关的错误,如数据库可能无法连接了. 如果action返回一个"Action Error", 和 "Field Error"不同的是, 错误会显示在 "actionerror" 标签.无论是Action Errors还会是Field Errors错误的提示文本, 都应该指向资源包,这样便于管理和实现国际化。
第二个新标签是form. 对应HTML中的form标签. "validate=true" 设置开启了客户端检验, 所以form 在发送至服务前会通过Javascript来进行验证. 为了确保安全,struts还是会进行验证, 但是开启客户端验证会为服务器减少开销。
在form标签中, 可以看见更多的新标签: "textfield", "password", "submit" 和 "reset". 还可以看到 "submit" 利用action属性完成两个不同的功能.
当我们在form中添加一个控件的时候,我们还需要使用HTML来完成需求. 通常, 我们只是需要一个简单的 "input type=text" 标签. 我们还会为它添加一个label,可能还会想要一个tooltip.当然, 应该还要有用来显示验证结果的.
Struts标签提供了模板和样式,则使用一个简单的Struts标签就可以完成一系列HTML的编写.例如, 这一个标签:
textfield key="username"/>
会生成如下HTML标记.
如果你不喜欢由Struts标签生成的标记, 它们都可以被替换. 每个标签都是由一个可以被修改的模板控制的. 例如,这是ActionErrors标签默认的生成HTML代码的模板:
<#if (actionErrors?exists && actionErrors?size > 0)>
<#list actionErrors as error>
- ${error}
#list>
#if>
如果你想要ActionErrors用表格替换列表的方式显示, 你可以复制下面的文件,将它保存在"template/simple/actionerror.ftl", 且将它放在你的应用的classpath之下.
<#if (actionErrors?exists && actionErrors?size > 0)>
<#list actionErrors as error>
${error}
#list>
#if>
Under the covers, Struts 使用Freemarker作为它的标准的模板语言. FreeMarker类似于Velocity,但它提供了更好的错误反馈和其他额外的特性. 如果你愿意, Velocity和JSP模板可应用来创建你自己的标签.
password 标签对应了一个"input type=password" 标签, along with the usual template/theme markup. 默认的, password 不会保留出入的信息在提交失败时. 如果用户名是错的, 客户端会要求再次输入密码. (如果你希望在验证失败时保留密码, 你可以将标签的"showPassword" 属性为true.)
显然, submit和reset标签对应的是它们对应的类型的button.
The second submit button is more interesting.
action="Login_cancel" onclick="form.onsubmit=null"
key="button.cancel"/>
这里我们在form中创建了一个 Cancel button. 这个button 的属性 action="Login_cancel"告诉框架去使用Login的"cancel" 方法替代"execute"方法.οnclick="form.οnsubmit=null" 脚本使客户端验证失效. 在服务器端, "cancel" 是一个用来跳过验证的方法, 所以请求会直接调用 Action 的cancel方法. Another entry on the special-case list is the "input" method.
The Struts Tags have options and capabilities beyond what we have shown here. For more see, the Struts Tag documentation.
但是标签为何知道两个属性都是必要的呢?怎么知道当属性为空的时候显示什么错误信息?
为了得到答案,我们需要看下另一种配置文件 : "validation" 文件.
当然可以很容易的在Action类中编写一段验证数据的代码, 但Struts提供了一种更为简单的方式.
这种验证通过XML文件来配置, Login-validation.xml.
需要注意 DTD引用的是 "XWork"中的. Open Symphony XWork 脱离web容器的通用的基于命令模式的框架.本质上, Struts 2 是将XWork拓展成一个web框架.
The field elements correspond to the ActionForm properties. The username and password field elements say that each field depends on the "requiredstring" validator. If the username is blank or absent, validation will fail and an error message is generated. The messages would be based on the "error.username.required" or "error.password.required" message templates from the resource bundle.
如果验证通过了, 框架将调用Login Action的"execute"方法. 实际上 Login Action 非常简短, 因为大部分都继承自一个基本类,MailreaderSupport.
package mailreader2;
import org.apache.struts.apps.mailreader.dao.User;
public final class Login extends MailreaderSupport {
public String execute() throws ExpiredPasswordException {
User user = findUser(getUsername(), getPassword());
if (user != null) {
setUser(user);
}
if (hasErrors()) {
return INPUT;
}
return SUCCESS;
}
}
Login 实现了如何认证一个user.试用提供的信息来寻到对应的user.如果user找到了,则将其缓存.如果没找到,则返回 "input" 让客户端重新尝试.否则,返回 "success",则客户端可以访问应用上更多的内容.
我们来看看MailreaderSupport 和另一个基本类ActionSupport的成员变量和方法, "getUsername", "getPassword", "findUser", "setUser", 和 "hasErrors".
Struts希望你直接在Action中定义JavaBean properties . 任何JavaBean 属性都可以被使用. 当一个请求到来, 任何Action 类上的共有属性会与请求参数匹配.如果名字相匹配, 请求参数的值会被写进 JavaBean 的属性. Struts会尽力将数据进行转换,如果需要还会报告发生的错误.
Username和Password属性并没有什么特别之处, 仅仅是标准的JavaBean属性.
private String username = null;
public String getUsername() {
return this.username;
}
public void setUsername(String username) {
this.username = username;
}
private String password = null;
public String getPassword() {
return this.password;
}
public void setPassword(String password) {
this.password = password;
}
我们用这些属性来保存客户端传来的值, 通过它们来执行 findUser 方法.
public User findUser(String username, String password)
throws ExpiredPasswordException {
User user = getDatabase().findUser(username);
if ((user != null) && !user.getPassword().equals(password)) {
user = null;
}
if (user == null) {
this.addFieldError("password", getText("error.password.mismatch"));
}
return user;
}
"findUser" 方法进入了MailReader的 Data Access Object 层, which is represented by the Database property. DAO 层的代码被分离成一个组件. MailReader应用 导入了DAO JAR,但是没有管理任何DAO源码的职责. Keeping the data access layer at "arms-length" is a very good habit. It encourages a style of development where the data access layer can be tested and developed independently of a specific end-user application.实际上, 我们有几个不同版本的MailReader应用, 而它们都共用了一个MailReader DAO JAR!
"Strongly separate data access and business logic from the rest of the application."
当"findUser" 方法返回, Login Action查看是否返回了一个有效的user. 一个有效的user会被放在User property. 尽管它还是一个 JavaBean 属性, the User property is not implemented in quite the same way as Username and Password.
public User getUser() {
return (User) getSession().get(Constants.USER_KEY);
}
public void setUser(User user) {
getSession().put(Constants.USER_KEY, user);
}
用于替代使用一个属性来存放值, "setUser" 通过Session.
private Map session;
public Map getSession() {
return session;
public void setSession(Map value) {
session = value;
}
查看 MailreaderSupport 类, 你也许会认为 Session 属性是一个简单古老的 Map. 实际上, Session属性是在运行期用来支持servlet session对象的配适器. MailreaderSupport类不需要具体了解过程. 可以随时使用Session就像一个Map. 我们还可以通过一些MAP的其他实现类来测试MailreaderSupport类。 进行测试来看看 MailreaderSupport 会因为一个“假”的 Session对象产生什么变化.
但是, 当MailreaderSupport运行在一个web应用中, 它如何获取一个servlet session?
如何仔细查看 MailreaderSupport 类, 你会发现没有一行代码用来设置 session property. 是的,当我们运行这个类时, session 属性是空的.
这种在运行期为Session属性提供一个值的方式称为 "dependency injection"(依赖注入). MailreaderSupport 类实现了SessionAware接口.SessionAware 是在Struts内部的, 它定义了一个为 Session 属性设值的方法.
public void setSession(Map session);
在Struts内部还有一个对象 ServletConfigInterceptor. 当ServletConfigInterceptor 看见一个实现了SessionAware接口的 Action ,它会自动地在session属性里传值.
if (action instanceof SessionAware
) {
((SessionAware) action).setSession
(context.getSession());
}
Struts使用一些 "Interceptor" 类为每一个在应用中定义的action创建一个front controller. 每个Interceptor 会被调用来在Action执行之前处理请求, 在action执行完后再次调用. (如果你了解 Servlet Filters, 你会理解这个模式.但是不同于Filters, Interceptors 不依赖于HTTP. Interceptors 可以在web容器之外测试、开发.)
你可以为你的action设置相同的interceptor, 或者为指定的action定义一些自己的 Interceptors, 还可以为不用action类型定义不同的Interceptors. Struts带有一些默认的 interceptor, 在没有指定interceptor时使用, 你也可以在配置文件中指定一个自己的默认的interceptor.
许多Interceptor提供了一些实用的功能,如设置session属性,如 ValidationInterceptor, 可以改变一个action的流程。 Interceptor 是Struts的核心特性.
如果没有发现有效的 User,或者密码不匹配,"findUser" 方法会调用addFieldError方法来记录这个错误. 当 "findUser" 返回时,Login Action 检查有没有错误, 然后返回结果 INPUT 或者 SUCCESS.
"addFieldError" 方法来自Struts自带的ActionSupport类. INPUT 和 SUCCESS也是在 ActionSupport类中. ActionSupport类提供了许多有用的方法,但不需要一定得选择它作为基类. 任何Java类都可以当作一个action.
良好的做法是为你的应用中的action提供一个带有实用功能的基类. Struts为我们提供了 ActionSupport, 而在 MailReader 应用则使用了MailreaderSupport 类.
"Use a base class to define common functionality."
当Login 返回一个INPUT而不是SUCCESS. Struts如何确定下一步该怎么做?
我们需要在配置文件中查看Login的配置。
Login action 配置描述了流程的操作, 包括了返回"input"时下一步该怎么做, 或者默认的"success".
/pages/Login.jsp
Welcome
MainMenu
ChangePassword
<exception-mapping
exception="org.apache.struts.apps.mailreader.dao.ExpiredPasswordException"
result="expired"/>
你会注意到这个action配置的名字不是 "Login" 而是 "Login_*". 这个星号是一个通配符用来匹配任意的字符. 在方法属性中, "{1}" 代表了前面*中代表的内容. When we cite actions like "Login_cancel" or "Login_input", the framework matches "cancel" or "input" with the wildcard and fills in the blanks.
The "trailing bang" notation was hardwired into WebWork 2. To provide backward compatibility, the notation is supported by Struts 2.0. If you prefer to use wildcards to emulate the same notation, as the Mailreader does, you should disable the old notation in the Struts properties file.
struts.enable.DynamicMethodInvocation = false
Using wildcards with a exclamation point (or "bang") is not the only way we can use wilcards to invoke methods. If we wanted to use actions like "inputLogin", we could move the asterisk and use an action name like "*Login".
Within the Login action element, the first result element is named "input". If validation or authentification fail, the Action class will return "input" and the framework will transfer control to the "Login.jsp" page.
The second result element is named cancel. If someone presses the cancel button on the Login page, the Action class will return "cancel", this result will be selected, and the framework will issue a redirect to the Welcome action.
The third result has no name, so it will be called if the default success token is returned. So, if the Login succeeds, control will transfer to the MainMenu action.
The MailReader DAO exposes a "ExpiredPasswordException". If the DAO throws this exception when the User logs in, the framework will process the exception-mapping and transfer control to the "ChangePassword" action.
Just in case any other Exceptions are thrown, the MailReader application also defines a global handler.
如果一个没有预料到的异常被抛出, exception-mapping 会将控制传给 action的 "error" result, 或者全局的 "error" result. MailReader 定义了一个全局 "error" result 将其跳转至 "Error.jsp" 页面来显示错误信息.
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="http://struts.apache.org/tags" %>
Unexpected Error
An unexpected error has occured
Please report this error to your system administrator
or appropriate technical support personnel.
Thank you for your cooperation.
Error Message
Technical Details
Error 页面使用了property标签来显示异常信息.
在最后, Login action 指定了一个 InterceptorStack 为 defaultStack. 如果之前使用过 Struts 2 或 WebWork 2 , 你会感觉奇怪, 因为 "defaultStack" 是初始的默认值.
在 MailReader应用中,大多数action只对通过身份验证的用户提供服务,出了 Welcome, Login, 和 Register action是向任何人提供的。 为了验证客户端请求的合法性, MailReader 使用了一个自定义的Interceptor 和 Interceptor stack.
package mailreader2;
import com.opensymphony.xwork2.interceptor.Interceptor;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.Action;
import java.util.Map;
import org.apache.struts.apps.mailreader.dao.User;
public class AuthenticationInterceptor implements Interceptor {
public void destroy () {}
public void init() {}
public String intercept(ActionInvocation actionInvocation) throws Exception {
Map session = actionInvocation.getInvocationContext().getSession();
User user = (User) session.get(Constants.USER_KEY);
boolean isAuthenticated = (null!=user) && (null!=user.getDatabase());
if (isAuthenticated) {
return actionInvocation.invoke();
}
else {
return Action.LOGIN;
}
}
}
AuthenticationInterceptor 查找user对象是否被保存在客户端对应的session中. 如果是,则正常返回,下一个interceptor会被调用.如果user对象不存在,则返回 "login". Struts会在全局的result中匹配"login" 然后跳转至 Login action.
MailReader 定义了三个自定义的Interceptor stacks: "user", "user-submit", 和 "guest".
<default-interceptor-ref name="user"/>
user stacks 要求客户端通过了认证. 就是说, User 对象应该在session中.action使用了guest stack 则可以被任意客户端访问. The -submit versions of each can be used with actions with forms, to guard against double submits.
一个常见的web应用中的问题是用户时常没有耐心.有时候, 用户会多按一次提交按钮,则浏览器会再一次提交请求,以至于提交了两次做同样事情的请求. 比如在注册用户时,如果多点了一次提交按钮, 又赶上巧合,则会显示该用户已经被注册了. (在第一次提交时.) 实际中这可能不会发生,但是在一个长时间的处理过程中, 如检查购物车,就容易发生二次提交.
为了防止二次提交,和后退按钮导致的重新提交, Struts会生成一个嵌入在form里面的token保存在session里.如果token的值不一样, 则表明出现了异常, 即一个form被提交了多次.
Token Session Interceptor 也会尝试提供智能的fail-over 在多次使用相同session的请求中。它会阻挡后来的请求直到之前的请求处理完毕, 然后返回 "invalid.token" , 它会尝试显示相同的原始的有效的action响应.
因为默认的 interceptor stack 会认证客户端, 我们需要指定一个标准的 "defaultStack" 为那些 "guest actions", Welcome, Login, 和Register.要求通过默认的验证是好的做法,因为它意味着我们不会忘记为一个新的action进行拦截. 同时,那些讨厌的用户不会被拒绝使用一些 "guest" 服务.
成功登录后,将会显示Main Menu页面. 如果你使用了默认的账号登录,页面的标题应该是 "Main Menu Options for John Q. User". 下面会有两个连接:
我们来看下 "MainMenu" action 映射和 "MainMenu.jsp" 的源码.
/pages/MainMenu.jsp
<%@ page contentType="text/html; charset=UTF-8" %>
<%@ taglib prefix="s" uri="http://struts.apache.org/tags" %>
" rel="stylesheet"
type="text/css"/>
"MainMenu.jsp" 中包含了一个新的标签 property, 用来为用户生成不同的页面,根据user中的"fullName" 属性.
MainMenu action能够显示user的fullname是因为继承自MailreaderSupport类. MailreaderSupport 类有一个User属性让text标签来访问. 如果没有MailreaderSupport, property 标签无法找到User对象来读取fullname.
MainMenu 页面中有两个连接.一个是 "Edit your user registration profile". 另一个是"Logout the MailReader Demonstration Application".
如果你点击了 "Edit your user registration profile" , 我们最终会来到 MailReader 应用的关键功能: Registration 或者 "Profile" 页面. 这个页面显示了MailReader中你的资料, 其中利用了一些有趣的技术.
为了完成两个任务 "Create" 和 "Edit" , "Registration.jsp"对test标签进行拓展,让它看起来是两个不同的页面.
" rel="stylesheet"
type="text/css"/>
例如, 如果客户端要是要进行编辑(task == 'Edit'), 页面会根据user对象插入username. 如果是注册 (task == 'Create'), 页面会有一个空的输入框.
Presention Logic - "test" 标签是一个在你页面中加入逻辑表达式的便捷方式. Customized pages帮助防止用户误操作,动态生成页面减少服务器需要维护的页面,以及其他好处。
页面中也使用了一些逻辑标签来显示订阅信息给用户. 如果RegistrationForm 的task是 "Edit", 那么页面下方将会显示订阅列表.
"task == 'Edit'">