上面的JSP页面与前面的JSP页面没有太大改变,除了使用了JSP 2.0语法来输出Session中的user属性。关于JSP 2.0表达式的知识,请参看笔者所著的《轻量级J2EE企业应用实战》一书的第2章。 在如图2.1所示页面的“用户名”输入框中输入scott,在“密码”输入框中输入tiger,然后单击“登录”按钮,将看到如图2.4所示的页面。 在上面登录成功的页面中,已经输出登录所用的用户名:scott,可见在Action通过ActionContext设置Session是成功的。 2.5.3 添加处理信息 到目前为止,Action仅仅控制转发用户请求,JSP页面并未获得Action的处理结果。对于大部分Web应用而言,用户需要获得请求Action的处理结果,例如,在线购物系统需要查询某个种类下的商品,则Action调用业务逻辑组件的业务逻辑方法得到该种类下的全部商品,而JSP页面则获取该Action的处理结果,并将全部结果迭代输出。 下面将为应用增加一个Action,该Action负责获取某个系列的全部书籍。为了让该Action可以获取这系列的书籍,我们增加一个业务逻辑组件,它包含一个业务逻辑方法,该方法可以获取某个系列的全部书籍。 下面是系统所用的业务逻辑组件的代码: public class BookService { //以一个常量数组模拟了从持久存储设备(数据库)中取出的数据 private String[] books = new String[]{ \宝典\, \轻量级J2EE企业应用实战\\基于J2EE的Ajax宝典\\整合开发\}; //业务逻辑方法,该方法返回全部图书 public String[] getLeeBooks() { return books; } }
上面的业务逻辑组件实际上就是MVC模式中的Model,它负责实现系统业务逻辑方法。理论上,业务逻辑组件实现业务逻辑方法时,必须依赖于底层的持久层组件,但此处的业务逻辑组件则只是返回一个静态的字符串数组——因为这只是一种模拟。
注意 此处的业务逻辑组件只是模拟实现业务逻辑方法,并未真正调用持久层组件来获取数据库信息。 在系统中增加如下Action类,该Action类先判断Session中user属性是否存在,并且等于scott字符串——这要求查看图书之前,用户必须已经登录本系统。如果用户已经登录本系统,则获取系统中全部书籍,否则返回登录页面。
新增的Action类的代码如下:
public class GetBooksAction implements Action {
//该属性并不用于封装用户请求参数,而用于封装Action需要输出到JSP页面信息 private String[] books; //books属性的setter方法
public void setBooks(String[] books) {
this.books = books; }
//books属性的getter方法 public String[] getBooks() {
return books; }
//处理用户请求的execute方法
public String execute() throws Exception {
//获取Session中的user属性
String user = (String)ActionContext.getContext().getSession(). get(\
//如果user属性不为空,且该属性值为scott if (user != null && user.equals(\{
//创建BookService实例
BookService bs = new BookService();
//将业务逻辑组件的返回值设置成该Action的属性 setBooks(bs.getLeeBooks()); return SUCCESS; } else {
return LOGIN; }
} }
通过上面的Action类,我们发现Action类中的成员属性,并不一定用于封装用户的请求参数,也可能是封装了Action需要传入下一个JSP页面中显示的属性。
提示 Action中的成员属性,并一定用于封装用户的请求参数,也可能是封装了Action需要传入下一个页面显示的值。实际上,这些值将被封装在ValueStack对象中。 当我们的控制器需要调用业务逻辑方法时,我们直接创建了一个业务逻辑组件的实例,这并不是一种好的做法,因为控制器不应该关心业务逻辑组件的实例化过程。比较成熟的做法可以利用工厂模式来管理业务逻辑组件;当然,目前最流行的方式是利用依赖注入——这将在后面章节里介绍。
注意 实际项目中不会在控制器中直接创建业务逻辑组件的实例,而是通过工厂模式管理业务逻辑组件实例,或者通过依赖注入将业务逻辑组件实例注入控制器组件。 该Action处理用户请求时,无需获得用户的任何请求参数。将该Action配置在struts.xml文件中,配置该Action的配置片段如下:
当用户向getBooks.action发送请求时,该请求将被转发给lee.GetBooksAction处理。 2.5.4 输出处理信息 如果用户没有登录,直接向getBooks.action发送请求,该请求将被转发到login.jsp页面。如果用户已经登录,getBooks.action将从系统中加载到系统中的所有图书,并将请求转发给showBook.jsp页面,因此showBook.jsp页面必须负责输出全部图书。 下面笔者将以最原始的方式:JSP脚本来输出全部图书。 注意 在实际应用中,几乎绝对不会使用笔者这种方式来输出Action转发给JSP输出的信息,但笔者为了让读者更清楚Struts 2标签库在底层所完成的动作,故此处使用JSP脚本来输出全部图书信息。 当Action设置了某个属性值后,Struts 2将这些属性值全部封装在一个叫做struts.valueStack的请求属性里。 提示 读者可能感到奇怪:笔者是如何知道Struts 2将这些属性值封装在struts.valueStack请求属性里的?这一方面与编程经验有关,另一方面可以通过查看Struts 2的各种文档,最重要的一点是可以在showBook.jsp页面中通过getAttributeNames方法分析请求中的全部属性。 为了在JSP页面中输出需要输出的图书信息,我们可以通过如下代码来获取包含全部输出信息的ValueStack对象。 //获取封装输出信息的ValueStack对象 request.getAttribute(\ 上面代码返回一个ValueStack对象,该对象封装了全部的输出信息。该对象是 Struts 2使用的一个ValueStack对象,可以通过OGNL表达式非常方便地访问该对象封装的信息。 从数据结构上来看,ValueStack有点类似于Map结构,但它比Map结构更加强大(因为它可以根据表达式来查询值)。Action所有的属性都被封装到了ValueStack对象中,Action中的属性名可以理解为ValueStack中value的名字。 大致理解了ValueStack对象的结构后,我们可以通过如下代码来获取Action中设置的全部图书信息。 //调用ValueStack的fineValue方法查看某个表达式的值 vs.findValue(\ 理解了上面关键的两步,整个JSP页面的代码就比较容易了解了。下面是showBook.jsp页面的代码: <%@ page language=\contentType=\charset=GBK\<% @page import=\
作者李刚的图书 <%
//获取封装输出信息的ValueStack对象
ValueStack vs = (ValueStack)request.getAttribute(\//调用ValueStack的fineValue方法获取Action中的books属性值 String[] books = (String[])vs.findValue(\//迭代输出全部图书信息 for (String book : books) { %>
书名: <%=book%> <%}%>
不可否认,上面JSP页面的代码是丑陋的,而且难以维护,因为里面镶嵌了大量的Java脚本。但它对于读者理解Struts 2如何处理封装在Action的ValueStack却很有帮助。
在浏览器中向getBooks.action发送请求,将看到如图2.5所示的页面。 通过上面页面,我们看到JSP页面已经输出了Struts 2控制器的返回信息。上面整个过程,已经完全包括了Struts 2框架的3个部分:视图、控制器和模型。