Struts2系统学习笔记二

来源:互联网 发布:眠趣 知乎 编辑:程序博客网 时间:2024/05/16 08:55

核心概念:动作、拦截器和类型转换

使用Struts 2动作

内容:
- 打包动作
- 实现动作
- 介绍对象属性和ModelDriven动作
- 上传文件

动作完成每一个请求的核心处理。它们包含业务逻辑、承载数据,以及呈现结果页面。
Struts 2是一个面向动作的框架,动作是这个框架的核心。

1. Struts 2动作简介

就框架而言,动作最大的作用是封装需要做的实际工作。第二个主要作用是,作为请求与视图之间的数据携带者。

动作封装工作单元
在动作中使用execute( )方法来实现这个功能。如:

public String execute() {    setCustomGreeting( GREETING + getName() );    return "SUCCESS";}

如果业务逻辑复杂,可能需要把业务逻辑构建成一个业务组件,再将组件注入到动作中。struts 2支持依赖注入。

动作为数据转移提供场所

private String name;public String getName() {    return name;}public void setName(String name) {    this.name = name;}private String customGreeting;public String getCustomGreeting(){    return customGreeting;}public void setCustomGreeting( String customGreeting ){    this.customGreeting = customGreeting;}

动作只需要把承载的数据实现为JavaBean属性,框架就能自动完成这些工作。

生成呈现页面

<action name="HelloWorld" class="manning.chapterOne.HelloWorld">    <result name="SUCCESS">/chapterTwo/HelloWorld.jsp</result>    <result name="ERROR">/chapterTwo/Error.jsp</result></action>

返回控制字符串。

2. 打包动作

当框架生成应用架构时,他会组织action和其他的组件到一个合乎逻辑的容器内,叫做包。
Struts 2的包很像Java的包,它提供了一种基于功能或者领域的共性将动作组件分组的机制。一些重要的操作属性,如用来映射到动作的URL命名空间,都是在包级别定义的。并且提供了继承机制。

2.1 具体

包声明:

<package name="chapterThreeSecure" namespace="/chapterThree/secure" extends="struts-default">    <action name="AdminPortfolio" >        <result>/chapterThree/AdminPortfolio.jsp</result>    </action>    <action name="AddImage" >        <result>/chapterThree/ImageAdded.jsp</result>    </action>    <action name="RemoveImage" >        <result>/chapterThree/ImageRemoved.jsp</result>    </action></package>

2.2 struts-default包中的组件

struts-default包中包含了很多常用的架构组件,如果不继承自他,那么用struts2也没什么意义了。

3. 实现动作

Struts 2动作不必实现Action接口。任何对象都可以通过实现一个返回控制字符串的execute()犯法来非正式地实现框架与动作之间的契约。

3.1 可选的Action接口

xwork2.Action接口之定义了一个方法:

String execute() throws Exception

定义了常量:

public static final String ERROR "error"public static final String INPUT "input"public static final String LOGIN "login"public static final String NONE "none"public static final String SUCCESS "success"

3.2 ActionSupport类

ActionSupport类提供了Action接口和其他几个有用接口的默认实现的便利类,提供了如数据验证、错误信息本地化等功能。
基本验证

public class Register extends ActionSupport {    public String execute(){        User user = new User();        user.setPassword( getPassword() );        user.setPortfolioName( getPortfolioName() );        user.setUsername( getUsername() );        getPortfolioService().createAccount( user );        return SUCCESS;    }    private String username;    private String password;    private String portfolioName;    public String getPortfolioName() {        return portfolioName;    }    public void setPortfolioName(String portfolioName) {        this.portfolioName = portfolioName;    }    public String getPassword() {        return password;    }    public void setPassword(String password) {        this.password = password;    }    public String getUsername() {        return username;    }    public void setUsername(String username) {        this.username = username;    }    public void validate(){        PortfolioService ps = getPortfolioService();        if ( getPassword().length() == 0 ){            addFieldError( "password", "Password is required.") );        }        if ( getUsername().length() == 0 ){            addFieldError( "username", "Username is required." );        }        if ( getPortfolioName().length() == 0 ){            addFieldError( "portfolioName", "Portfolio name is required.");        }        if ( ps.userExists(getUsername() ) ){            addFieldError("username", "This user already exists.");        }    }    public PortfolioService getPortfolioService( ) {        return new PortfolioService();    }}

如这个例子,动作扩展自ActionSupport类。提供execute( )方法,该方法包含注册用户的业务逻辑,并且包含JavaBean类型的自动传输数据。
动作提供了validate( )方法,这样就能让execute方法只关注业务逻辑。然后通过ActionSupport提供的方法来创建和存储错误消息(如addFieldError( ))。

使用资源包处理文本消息
先准备Register.properties:

user.exists=This user already exists.username.required=Username is required.password.required=Password is required.portfolioName.required=Portfolio Name is required.
public void validate(){    PortfolioService ps = getPortfolioService();    if ( getPassword().length() == 0 ){        addFieldError( "password", getText("password.required") );    }    if ( getUsername().length() == 0 ){        addFieldError( "username", getText("username.required") );    }    if ( getPortfolioName().length() == 0 ){        addFieldError( "portfolioName", getText( "portfolioName.required" ));    }    if ( ps.userExists(getUsername() ) ){        addFieldError("username", getText( "user.exists"));    }}

使用TextProvider的getText()来获取字段。

4 向对象传递数据

在Struts2中我们可以选择JavaBean来进行数据转移,也可以公开一个复杂对象作为JavaBean,让数据直接传输到这个对象上,这被称为ModelDriven。

4.1 使用对象直接进行数据传输

如之前手动的收集数据:

public String execute(){    User user = new User();    user.setPassword( getPassword() );    user.setPortfolioName( getPortfolioName() );    user.setUsername( getUsername() );    getPortfolioService().createAccount( user );    return SUCCESS;}

封装到对象内:

public String execute(){    getPortfolioService().createAccount( user );    return SUCCESS;}private User user;public User getUser() {    return user;}public void setUser(User user) {    this.user = user;}public void validate(){    if ( getUser().getPassword().length() == 0 ){        addFieldError( "user.password", getText("password.required") );    }}

并且在JSP中也需要相应的改变:

<s:textfield name="user.username" label="Username"/><h5>Congratulations! You have created </h5><h3>The <s:property value="user.portfolioName" /> Portfolio</h3>

4.2 ModelDriven动作

ModelDriven通过getModel( )方法公开应用程序的域对象。

public class ModelDrivenRegister extends ActionSupport implements ModelDriven {    public String execute(){        getPortfolioService().createAccount( user );        return SUCCESS;    }    private User user = new User();    public Object getModel() {        return user;    }    public void validate(){        . . .        if ( user.getPassword().length() == 0 ){        addFieldError( "password", getText("password.required") );        }        . . .    }    . . .}

注意user被初始化了,并且该引用只能指向一个对象。

<s:textfield name="username" label="Username"/><h5>Congratulations! You have created </h5><h3>The <s:property value="portfolioName" /> Portfolio</h3>

5 上传文件例子

上传例子:
https://github.com/xu509/Alan/tree/master/src/main/java/com/alan/web/uploadphoto


使用拦截器

1 为什么拦截请求

在使得框架能够高水平的关注分离上,拦截器起到了至关重要的作用。

1.1 清理MVC

框架不直接调用动作的execute( )方法,而是创建一个叫做ActionInvocation的对象,它封装了动作和一系列的preprocess和postprocess的拦截器。
这里写图片描述

1.2 从拦截器受益

通过分层使得软件整洁,增加了可读性和可测试性以及灵活性。而灵活性主要体现在可重用和可配置。

2 拦截器工作原理

理解ActionInvocation非常重要。

2.1 总指挥ActionInvocation

当框架收到一个请求时,他首先找到URL映射的动作,将一个动作的实例加入到新创建的ActionInvocation实例中。接着咨询架构,发现那些拦截器应该以什么样的顺序被触发,将拦截器的引用加入到ActionInvocation中。除了这些ActionInvocation也拥有其他重要信息的引用,比如servlet请求对象和当前动作可用的结果组件的映射。

2.2 如何触发拦截器

ActionInvocation公开了invoke( )方法,框架通过调用这个方法开始动作的执行。然后ActionInvocation通过拦截器栈的拦截器调用过程,通过调用拦截器的intercept( )方法。

拿TimerInterceptor举例:

public String intercept(ActionInvocation invocation) throws Exception{    long startTime = System.currentTimeMillis();    String result = invocation.invoke();    long executionTime = System.currentTimeMillis() - startTime;    ... log the time ...    return result;}

调用拦截器可分为三个阶段:
- 预处理阶段(preprocessing):拦截器能用来准备、过滤、改变或者操作任何可以访问的重要数据。这些数据包括与请求相关的关键对象和数据,也包括动作。
- 调用invoke( )或转移工作流。
- 当invoke( )方法返回控制字符串之后,任何一个返回的拦截器都可以随意决定改变任何可以访问的对象和数据。

3 研究内建的Struts 2拦截器

3.1 工具拦截器

timer拦截器
记录执行花费时间
INFO: Executed action [/chapterFour/secure/ImageUpload!execute] took 123 ms.

logger拦截器
提供了简单的日志记录机制
INFO: Starting execution stack for action /chapterFour/secure/ImageUpload
INFO: Finishing execution stack for action /chapterFour/secure/ImageUpload

3.2 数据转移拦截器

params拦截器(defaultStack)
它将请求参数转移到通过ValueStack公开的属性上。param只负责将数据转移到ValueStack上发现的第一个匹配的属性上。

static-params拦截器(defaultStack)
这个参数也将参数转移到ValueStack公开的属性上,不同的是参数的来源。

<action name="exampleAction" class="example.ExampleAction">    <param name="firstName">John</param>    <param name="lastName">Doe</param></action>

static-params拦截器被调用时携带上述定义的两个名值对。而根据params拦截器和static-params拦截器的顺序来决定谁覆盖谁的值。

autowiring拦截器
为使用Spring管理应用程序资源提供了一个集成点。

servlet-config拦截器(defaultStack)
该拦截器提供了将来源于Servlet API的各种对象注入到动作的简洁方法。
该拦截器通过将各种对象设置到动作必须实现的接口公开的设置方法的方式工作。
- ServletContextAware设置ServletContext。
- ServletRequestAware设置HttpServletRequest。
- ServletResponseAware设置HttpServletResponse。
- ParameterAware设置Map类型的请求参数。
- RequestAware设置Map类型的请求属性。
- SessionAware设置Map类型的会话属性。
- ApplicationAware设置Map类型的应用程序领域属性。
- PrincipalAware设置Principal对象。

fileUpload拦截器(defaultStack)
fileUpload拦截器将文件和元数据从多重请求转换为常规的请求参数。

3.3 工作流拦截器

工作流拦截器提供改变请求处理的工作流的机会。工作流指贯穿拦截器、动作、结果最后又回到拦截器的处理路径。
workflow拦截器(defaultStack)
workflow参数:
- alwaysInvokeValidate:true或false,意味着validate( )方法将会被调用。
- inputResultName:验证失败时选择的结果,默认值是Action.INPUT。
- excludeMethods:workflow拦截器不应该执行的方法名。

比如:

<interceptor-ref name="workflow">    <param name="excludeMethods">input,back,cancel,browse</param></interceptor-ref>

validation拦截器(defaultStack)
提供简单形式的验证。

prepare拦截器(defaultStack)
prepare拦截器提供了一种向动作追加额外工作流处理的通用入口点。

modelDriven(defaultStack)
通过将模型放在ValueStack上而改变工作流。

3.4 其他拦截器

exception拦截器(defaultStack)
位于拦截器栈的顶端。

<global-results>    <result name="error">/chapterFour/Error.jsp</result></global-results><global-exception-mappings>    <exception-mapping exception="java.lang.Exception" result="error"/></global-exception-mappings>

在页面上可已JavaBean属性公开出来:

<p><h4>Exception Name: </h4><s:property value="exception" /></p><p><h4>What you did wrong:</h4> <s:property value="exceptionStack" /></p><p><h5>Also, please confirm that your Internet is working before actuallycontacting us.</h4></p>

token拦截器和token-session拦截器
该拦截器作为避免表单重复提交系统的一部分。

scoped-modelDriven拦截器(defaultStack)
实现跨请求的数据对象的向导应用。

execAndWait拦截器
当请求需要执行很长时间,给与的用户反馈避免重复提交。

3.5 内建的拦截器栈

继承struts-default包来继承defaultStack在内的所有内建的栈。

4 声明拦截器

4.1 声明拦截器和拦截器栈

已struts-default.xml为例:

<package name = "struts-default">    ...    <interceptors>        <interceptor name="execAndWait" class="ExecuteAndWaitInterceptor"/>        <interceptor name="exception" class="ExceptionMappingInterceptor"/>        <interceptor name="fileUpload" class="FileUploadInterceptor"/>        <interceptor-stack name="defaultStack">            <interceptor-ref name="exception"/>            <interceptor-ref name="fileUpload"/>        </interceptor-stack>    </interceptors>    <default-interceptor-ref name="defaultStack"/></package>

4.2 将拦截器映射到动作组件

<action name="MyAction" class="org.actions.myactions.MyAction">    <interceptor-ref name="timer"/>    <interceptor-ref name="logger"/>    <result>Success.jsp</result></action>
<action name="MyAction" class="org.actions.myactions.MyAction">    <interceptor-ref name="timer"/>    <interceptor-ref name="logger"/>    <interceptor-ref name="defaultStack"/>    <result>Success.jsp</result></action>

4.3 设置、覆盖拦截器参数

比如在默认包中,workflow有这样的参数:

<interceptor-ref name="workflow">    <param name="excludeMethods">input,back,cancel,browse</param></interceptor-ref>

如果我们想改变excludeMethods参数:

<action name="YourAction" class="org.actions.youractions.YourAction">    <interceptor-ref name="defaultStack">        <param name="workflow.excludeMethods">doSomething</param>    </interceptor-ref>    <result>Success.jsp</result></action>

5 构建自定义拦截器

5.1 实现Interceptor接口

编写拦截器需要实现interceptor.Interceptor接口。

public interface Interceptor extends Serializable {    void destroy();    void init();    String intercept(ActionInvocation invocation) throws Exception;}

6 小结

具体实现:
https://github.com/xu509/Alan/tree/master/src/main/java/com/alan/web/checkuser


数据转移:OGNL和类型转换

内容:
- 数据转移
- 使用OGNL
- 使用内建的类型转换器
- 自定义类型转换器

1 Web应用程序领域常见任务

2 OGNL和Struts 2

2.1 OGNL是什么

OGNL代表Object-Graph Navigation Language(对象图导航语言)。
从开发人员基于Struts2框架构建应用程序的角度看,OGNL包含两件事:表达式语言和类型转换器。

表达式语言

<h5>Congratulations! You have created </h5><h3>The <s:property value="portfolioName" /> Portfolio</h3>

value属性双引号之间的片段称为表达式语言。

类型转换

2.2 OGNL如何融入框架

这里写图片描述

3 内建类型转换器

3.1 立即使用的类型转换器

  • String
  • boolean/Boolean,转换“true”和“false”。
  • char/Charactor
  • int/Integer等原始类型
  • Date,当前Locale的SHORT格式的字符串版本
  • array,每一个字符串元素必须能够转换成数组的类型
  • list,默认使用String填充
  • Map,默认使用String填充

3.2 使用OGNL表达式从表单字段名映射到属性

将Java属性和表单字段名关联起来以利于请求参数的自动数据转移和类型转换是一个两阶段的过程。
首先,需要为表单字段的name属性编写OGNL表达式。接着,需要在Java端创建接收数据的属性。

1. 原始类型和包装类

<s:form action="Register">    <s:textfield name="user.username" label="Username"/>    <s:password name="user.password" label="Password"/>    <s:textfield name="user.portfolioName" label="Enter a name for your portfolio"/>        <s:textfield name="user.age" label="Enter your age, with double precision!"/>       <s:textfield name="user.birthday" label="Enter your birthday. (mm/dd/yy)"/>         <s:submit/></s:form>   

每一个输入字段的名字实际上是一个OGNL表达式。
如果输入的字符串值不合法,就会抛出一个转换异常。

处理多值请求参数
Struts2支持多种请求参数转移到Java端各种面向集合的数据类型,无论是数组还是Collection。

数组

<s:form action="ArraysDataTransferTest">  <s:textfield name="ages" label="Ages"/>  <s:textfield name="ages" label="Ages"/>  <s:textfield name="ages" label="Ages"/>   <s:textfield name="names[0]" label="names"/>  <s:textfield name="names[1]" label="names"/>  <s:textfield name="names[2]" label="names"/>          <s:submit/></s:form>   

List

<s:form action="ListsDataTransferTest">    <s:textfield name="middleNames[0]" label="middleNames"/>    <s:textfield name="middleNames[1]" label="middleNames"/>    <s:textfield name="middleNames[2]" label="middleNames"/>    <s:textfield name="lastNames" label="lastNames"/>    <s:textfield name="lastNames" label="lastNames"/>    <s:textfield name="lastNames" label="lastNames"/>    <s:textfield name="weights[0]" label="weights"/>    <s:textfield name="weights[1]" label="weights"/>    <s:textfield name="weights[2]" label="weights"/>    <s:textfield name="users[0].username" label="Usernames"/>    <s:textfield name="users[1].username" label="Usernames"/>    <s:textfield name="users[2].username" label="Usernames"/>    <s:submit/></s:form>   

使用方式和数组一样,但是由于list可能没有指定类型(即没有使用泛型),所以使用list的时候有两种情况。
1、默认情况下,将请求的参数转换为String。
2、使用属性文件来告知OGNL如何转换
这里写图片描述
比如DataTransferTest的转换文件:
DataTransferTest-conversion.properties

Element_users=manning.chapterFive.utils.UserElement_weights=java.lang.DoubleElement_myUsers=manning.chapterFive.utils.UserKey_myOrderedUsers=java.lang.IntegerElement_myOrderedUsers=manning.chapterFive.utils.User

使用泛型的话就不需要这些配置。
使用上述配置时注意不要初始化list,否则会报错。

完整例子:
jsp文件:

<s:textfield name="users[0].username" label="Usernames"/><s:textfield name="users[1].username" label="Usernames"/><s:textfield name="users[2].username" label="Usernames"/>

action:

private List users ;public List getUsers(){    return users;}public void setUsers ( List users ) {    this.users=users;}

properties:

Element_users=manning.utils.User

map
OGNL需要标明关键字以及Map属性指定的类型。

 <s:textfield name="maidenNames.mary" label="Maiden Name"/> <s:textfield name="maidenNames.jane" label="Maiden Name"/> <s:textfield name="maidenNames.hellen" label="Maiden Name"/> <s:textfield name="maidenNames['beth']" label="Maiden Name"/> <s:textfield name="maidenNames['sharon']" label="Maiden Name"/> <s:textfield name="maidenNames['martha']" label="Maiden Name"/>

可用两种语法确定关键字。

可用properties来确定类型:

Element_myUsers=com.alan.web.datatransfer.utils.User
<s:textfield name="myUsers['chad'].username" label="Usernames"/><s:textfield name="myUsers['jimmy'].username" label="Usernames"/><s:textfield name="myUsers['elephant'].username" label="Usernames"/><s:textfield name="myUsers.chad.birthday" label="birthday"/><s:textfield name="myUsers.jimmy.birthday" label="birthday"/><s:textfield name="myUsers.elephant.birthday" label="birthday"/>

指定关键字:

Key_myOrderedUsers=java.lang.IntegerElement_myOrderedUsers=com.alan.web.datatransfer.utils.User
<s:textfield name="myOrderedUsers['1'].birthday" label="birthday"/><s:textfield name="myOrderedUsers['2'].birthday" label="birthday"/><s:textfield name="myOrderedUsers['3'].birthday" label="birthday"/>

4 自定义类型转换

4.1 实现类型转换器

所有的类型转换器都必须实现 ognl.TypeConverter接口。在Struts2 中,我们可以通过继承StrutsTypeConverter这个基类来实现自定义类型转换,以下2个抽象方法需要实现:

public abstract Object convertFromString(Map context, String[] values,Class toClass);public abstract String convertToString(Map context, Object o);

实现类: https://github.com/xu509/Alan/blob/master/src/main/java/com/alan/web/datatransfer/utils/CircleTypeConverter.java

4.2 在框架中配置自定义转换器

在properties文件中配置,命名约定为ActionName-conversion.properties,保证action中的成员类型为javabean,properties中将这个成员变量指向转换器:

circle=manning.utils.CircleTypeConverter

4.3 全局转换器

不使用ActionClassName-conversion.properties文件配置,而是使用xwork-conversion.properties。并且将这个文件放入类路径中。

0 0