Struts是特别为基于Web的应用而设计的,因此它自然采用了流行的Model-2 MVC策略,或者说架构。

插图1.5 Struts Model-2
Struts框架对Model、View和Controller有非常明确的定义。当我们进一步考察Struts如何实现MVC的这三个支柱时,你还将看到它们是如何被很好分离的(这是MVC模式一个主要的好处)。你可能已经注意到,Struts更多的是一个应用框架,而不是严谨的UI组件框架。但是它的主要目的,仍然是使用可重用的组件(客户端可能通过它们操作底层的信息或数据)来建立用户界面。
1.3.2.1 Controller
Struts controller的核心是ActionServlet——一个Front Controller servlet。Action- Servlet是对Web应用以及后台控制中心(决定各请求如何处理)访问的唯一接口。它负责接收来自客户端(例如Web浏览器)的HTTP请求,并将它们直接派发到另一个Web页面,或者
一个合适的处理器(handler),由后者最终提供响应。
Struts中的处理器就是Action,其本质是一个Command模式的JavaBean。它负责检查请求中的信息,执行一些操作,也可能填装一些表示组件将要使用的数据,然后同ActionServlet通信,决定下一步将控制转发到何处。一个常见Action的例子就是LoginAction,通过调用在Model层中对业务对象的操作,尝试让用户登录系统。如果尝试成功,控制将可能被转到JavaServer页面(JSP)来显示该用户的主页;否则,控制可能被转发回原先的登录JSP,并提供进一步的指导。
此时你可能奇怪,ActionServlet是如何判断将请求派发往何处呢?有个解决方法是在ActionServlet本身内部硬性编写判断点,不过这差不多立即就会引发维护问题。Struts通过提供一个外部的XML(Extensible Markup Language,可扩展标记语言)配置文件,通常名为struts-config.xml来解决这个设计问题。该文件定义了请求的URL到Action类之间的映射关系,以及其它一些内容。这种映射在Struts中叫作ActionMapping。当Web应用启动之后,ActionServlet读取该配置文件,将它的内容保存在内存中,之后当客户端的每个请求到达时,访问该信息。清单1.1是这个文件的简单示例。
<?xml version="1.0"?>
<!DOCTYPE struts-config PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 1.1//EN"
"http://jakarta.apache.org/struts/dtds/struts-config_1_1.dtd" >
<struts-config>
<form-beans>
<form-bean name="loginForm" type="login.LoginForm"/>
</form-beans>
<action-mappings>
<action name="loginForm"
path="/login"
scope="request"
type="login.LoginAction">
<forward name="success" path="/success.jsp"/>
<forward name="failure" path="/failure.jsp"/>
</action>
</action-mappings>
</struts-config>
清单1.1 Struts配置文件示例
这个特例配置文件为用户登录定义了一个映射。不管何时,只要接收到的请求以/login结尾,ActionServlet将会把控制转发到LoginAction。
此时你可能还想知道,在Action完成任务后,它是如何指示ActionServlet将控制派发到哪儿呢?你可以在action中硬性编码,不过这将使它们很难重用与维护。注意到在配置文件中有两个转发标记,分别叫作success和failure。LoginAction在运行时可以访问ActionMapping,并可以在将控制交还给ActionServlet时,使用这些逻辑名而非物理路径。改变路径只需要在配置文件中进行一处改动,而不需要改动action。
清单1.2提供了这个简单Action——LoginAction——的源代码,来一探究竟。为简单起见,这个Action并不包括国际化以及日志的代码。
public final class LoginAction extends Action {
public ActionForward execute(ActionMapping mapping,
ActionForm form,
HttpServletRequest request,
HttpServletResponse response)
throws Exception {
User user = null;
// Extract user information from request
String userName = (String)
PropertyUtils.getSimpleProperty(form, "username");
String password = (String)
PropertyUtils.getSimpleProperty(form, "password");
// Access the Login business object.
UserSecurityService security = new UserSecurityService();
// Attempt to log in.
user = service.login(userName, password);
// If no such user exists, display error page
if(user == null) {
return mapping.findForward("failure");
}
// Create a new session for this user.
HttpSession session = request.getSession();
session.setAttribute(Constants.USER_KEY, user);
// The user exists and has successfully logged in.
清单1.2 简单的登录Action(待续)
// Display the success page.
return mapping.findForward(“success”);
}
}
清单1.2 (续)
从这个示例中,你可以看到如何访问业务对象,以及Action如何告诉ActionServlet下一步将控制转发到哪里。这里是到Model层的一个清晰界限。甚至在Controller层的页面流(page flow)也被解耦——因为使用了逻辑名转发,而不是硬性编码的路径。
1.3.2.2 Model
Model包括了应用的业务对象所描绘的领域模型(domain model),它并不是由Struts框架明确要求或强制的。这些业务对象通常要么表现为企业级JavaBean(EJB)、Java数据对象(JDO或及其它类似Hibernate的ORM框架),要么表现为像DAO模式所描述的那样,通过JDBC访问数据库的朴素JavaBean。Struts的Controller在Struts的Action对象内同Model发生联系。如前所述,Action通常会调用对业务对象的操作来达到目的。
注意:
对于许多刚刚接触Struts、经验缺乏的开发者来说,常犯的一个错误是把业务逻辑放在他们的Action对象中。这么做的缺陷是,抹杀了Struts的Controller同Model之间的界限,应该避免。清单1.2给出了一个很好的例子,说明了Action应该如何同业务层交互。
1.3.2.3 View
Struts中的View层一般由包含自定义标记的JSP页面构成。Struts框架提供了许多框架可识别的自定义标记库。这些标记库中有一个叫Tiles,它允许你通过可重用的JSP模板来组合View。这个标记库将在下一节讨论。
继续我们的登录示例,登录的JSP页面如清单1.3所示。
<%@ page contentType=”text/html;charset=UTF-8” language=”java” %>
<%@ taglib uri=”/WEB-INF/struts-html.tld” prefix=”html” %>
<%@ taglib uri=”/WEB-INF/struts-bean.tld” prefix=”bean” %>
<html:html>
<head>
<title><bean:message key="app.title"/></title>
</head>
<body>
Log on with your username and password.<br/><br/>
<html:errors/>
<html:form action="/login">
Username: <html:text property="username"/><br/>
Password: <html:password property="password"/><br/>
<html:submit value="Login"/>
</html:form>
</body>
</html:html>
清单1.3 登录的JSP页面示例
迄今为止,我们还没有涉及数据如何来往于JSP之类的表示组件。Struts通过ActionForm来完成传递,后者本质上就是一个JavaBean,有若干合适的property。你可以将它看作是View数据对象。作为登录示例的一部分,清单1.4给出了LoginForm的代码。
public final class LoginForm extends ActionForm {
private String password;
private String username;
/**
* Provides the password
*/
public String getPassword() {
return (this.password);
}
/**
* Sets the desired password
*/
public void setPassword(String password) {
this.password = password;
清单1.4 登录示例的LoginForm(待续)
}
/**
* Provides the username
*/
public String getUsername() {
return (this.username);
}
/**
* Sets the desired username
*/
public void setUsername(String username) {
this.username = username;
}
/**
* Resets all properties to their default values
*/
public void reset(ActionMapping mapping,
HttpServletRequest request) {
password = null;
username = null;
}
/**
* Validate the properties that have been set.
*/
public ActionErrors validate(ActionMapping mapping,
HttpServletRequest request) {
ActionErrors errors = new ActionErrors();
if ((username == null) || (username.length() < 1))
errors.add(
“username”,
new ActionError(“Username Is required.”)
);
if ((password == null) || (password.length() < 1))
errors.add(
“password”,
new ActionError(“Password Is required.”)
);
return errors;
}
}
清单1.4 (续)
如清单1.3展示的那样,我们使用Struts的HTML标记库来为登录提供输入参数。当ActionServlet从该页面收到请求时,会尝试把这些参数装填入一个LoginForm对象中。我们已经提供的validate()方法将被ActionServlet调用。如果报告有错误,页面会被重新载入并显示错误信息;如果一切正常,ActionServlet将会把控制转到LoginAction(正如清单1.1中的action映射部分所指定的那样)。LoginAction将访问LoginForm,如清单1.2所示。而且在清单1.1的form-bean中作了指定。
注意:
如果从应用的立场考察Struts框架,ActionForm不是Model层的一部分。它实际上是Model的一个快照,稍后用来在View层中渲染响应。因此,ActionForm在Struts中可以认为是View层的一部分,如插图1.5演示的那样。
回答了如何从请求中取得数据给Action,但是如何从Action得到数据,提供给最终的表示组件渲染响应呢?因为每个Action都可以访问ActionServlet的响应对象,它可以随意在请求或会话范围内放置任何信息,之后被某个JSP页面所使用。JSP不知道或者不关心数据从何而来,这为Controller和View层之间提供了很好的分隔。