什么是框架
框架是一种结构化的软件,框架尽量将特定领域的日常任务和具体问题的处理流程抽象化,然后提供一个平台,基于这个平台更快的构建web应用程序。
两个方面:尽量自动化web应用程序开发过程中的常见问题;尽量提供优秀的架构解决方案来优化web应用程序中常见的工作流。
struts2工作原理
使用xml和注解两种方式声明一个action
<action name=’login’>
<resultname=”success”>/myPg.jsp</result>
<resultname=”error”>/error.jsp</result>
</action>
@Results({@Result(name=”success”,value=”/myPg.jsp”),
@Result(name=”error”,value=”/error.jsp”)
})
public class Login
{
… …
}
web.xml中配置struts2
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.FilterDispatcher</filter-class>
actionPackages用于启用注解 告诉struts2在哪里查找注解 |
<init-param>
<param-name>actionPackages</param-name>
<param-value>com.aowin</param-value>
</init-param>
</filter>
<filter-mapping>
struts2拦截的url请求 |
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
一个典型的struts.xml文件
<?xml version="1.0"encoding="UTF-8" ?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration2.1.7//EN"
"http://struts.apache.org/dtds/struts-2.1.7.dtd">
<struts>
<!—常量调整struts属性,开发者模式 -->
<constantname=”struts.devMode” value=”true”/>
<!--action由包来管理的 -->
<packagename="deafult" namespace="/"extends="struts-default">
<!-- action的name与请求的名字是一致,action的class与处理请求的action的类一致的默认执行action中execute方法-->
<actionname="login" class="com.aowin.action.LoginAction">
<!--result表示方法的处理结果,name与逻辑视图对应
result的内容部分表示物理视图-->
<resultname="success">hello.jsp</result>
</action>
</package>
<!—将其他的xml文档包含进此根文档-->
<includefile=”com/aowin/xxx.xml”/>
</struts>
struts.xml是在默认包内定义全局动作的好地方。
使用包的命名空间来决定映射到动作的url:
http:// +localhost:8080 + /myApplication + /namespace + /HelloWorld.action
协议 主机名:端口 应用根目录/servlet上下文 包命名空间 动作名.action
从表单页面通过action再到结果页面数据的传递
提交表单的页面:
<%--引入struts标签--%>
<%@ taglib prefix=”s” uri=”/struts-tags” %>
<body>
<p>
<%--用struts标签来表示表单元素--%>
<s:formaction=”HelloWorld” /><%--HelloWorld是对应的action名称没有后缀--%>
<s:textfiledname=”name” label=”Your name”/><name对应javaBean属性>
<s:submit/>
</s:form>
</p>
</body>
提交页面表单时,框架会将传入的数据设置到javaBean属性上,通过execute()方法可以访问、操作这些数据,并返回一个结果字符串来转发到另一个jsp页面。
action的动作java类
public class HelloWorld
{
privatestatic final String GREETING=”Hello”;
private Sringname; //保存数据的JavaBean属性
private StringcustromGreeting; //保存数据的JavaBean属性
//要执行的动作的业务逻辑,action默认调用此方法
public Stringexecute()
{
setCustomGreeting(GREETING+getName());
return“success”;
}
//省略getter和setter方法
…
…
}
呈现结果的jsp页面代码片段获取传输的数据
<%--引入struts标签--%>
<%@ taglib prefix=”s” uri=”/struts-tags” %>
<body>
<p>
<%--用struts标签来接受javaBean属性中的数据--%>
<s:propertyvalue=”customGreeting”/>
</p>
</body>
使用注解的方式来配置动作action:
1. 首先在web.xml配置初始化参数actionsPackages,参数指定了一个扫描包,框架会用此包名之后的包名称作为action的命名空间。比如指定了在com.aowin中扫描,而动作类在com.aowin.myAction包中。则命名空间就是myAction。
2. Java类可以通过命名约定类名以Action结尾。来表明这是一个action动作类。或者通过实现Action接口(直接继承实现action接口的ActionSupport类亦可)。
事例如下:
@Result(name=”success”,value=”/helloWorld.jsp”) //指示动作结果页面
public class NameCollector extends ActionSupport
{
。。。。。。
}
或者是
@Result(name=”success”,value=”/helloWorld.jsp”) //指示动作结果页面
public class NameCollectorAction //在动作url请求中Action部分会被去除
{
。。。。。。
}
struts.xml中包(package)的声明
<package></package>元素的属性:
属性 | 描述 |
name(必须) | 包的名字 |
namespace | 包内所用动作的命名空间 |
extends | 被继承的父包 |
abstract | 如果为true,这个包只能用了定义可继承的组件,不能定义动作 |
namespace空命名空间””和根命名空间”/”的区别
默认的命名空间namespace="",根命名空间namespace="/"。 <package name="test" extends="struts-default"> ,如果未指定命名空间,则命名空间默认为namespace=""。 默认命名空间里的Action可以处理任何命名空间下的Action请求。例如,如果存在URL为/barspace/bar.action的请求,并且/barspace的命名空间下没有名为bar的Action,则默认命名空间下名为bar的Action也会处理用户请求。但根命名空间下的Action只处理根命名空间下的Action的请求,这是根命名空间和默认命名空间的区别。 命名空间只有一个级别。如果请求的URL是/bookservice/search/get.action,系统将先在/bookservice/search的命名空间下查找名为get的Action,如果在该命名空间内找到名为get的Action,则由该Action处理用户的请求;如果未找到,系统将直接进入默认的命名空间中查找名为get的Action,而不会在/bookservice的命名空间下查找名为get的Action.
Action接口
public interface Action {
public static final String SUCCESS ="success";
public static final String NONE ="none";
public static final String ERROR ="error";
public static final String INPUT ="input";
public static final String LOGIN ="login";
public String execute()throws Exception;
}
数据验证拦截器
ActionSupport类的声明如下:
publicclassActionSupportimplements Action, Validateable, ValidationAware,TextProvider, LocaleProvider, Serializable{}
action动作类扩展ActionSupport类,ActionSupport实现了validate()方法,我们用特定的验证逻辑覆盖这个空方法,当执行动作时会触发默认的拦截器workflow拦截器,它会调用找到的validate()方法。当验证不通过时会把请求工作流重新定向到输入页面。
workflow拦截器的定义如下:
<interceptor name=”workflow”class=”com.opensymphony.xwork2.interceptor.DefaultWorkflowInterceptor”/>
事例如下:register的动作类
public class Register extends ActionSupport
{
privateString username;
privateString password;
privateString bagName;
privateUserService userService;
publicString execute()
{
Useruser=new User();
user.setPassword(getPassword() );
user.setBagName(getBagName() );
user.setUsername(getUsername() );
userService.insertUser(user);
returnSUCCESS;
}
//数据验证业务逻辑,没有返回值
public void validate()
{
if(getPassword().length()== 0)
{
/*addFieldError()用来创建和存储错误消息,拦截器会检查是否存在错误消息,若存在则表示验证失败,终止请求,选择为”input”的结果视图*/
addFieldError(“password”,”Password isrequired.”);
}
if(getUsername().length()== 0)
{
addFieldError(“username”,”usernameis required.”);
}
if(getBagName().length()==0)
{
addFiledError(“bagName”,”Bag name isrequired.”);
}
if(userService.userExists(getUsername()))
{
addFieldError(“username”,”This useralready exists.”);
}
}
… …//省略其他settter和gettter方法
}
使用资源包处理文本消息
需要创建属性文件,并且名字和动作类名相同,比如对应Register.java的属性文件可以命名为Register.properties.将属性文件和类放在一个包中。ActionSupport实现了方法来从属性文件中取得消息。
Register.properties内容如下:
user.exists=This user already exists.
username.required=Username is required.
password.required=Password is required.
bagName.required=Bag name is required.
上面的验证错误消息可以更改如下:
//数据验证业务逻辑,没有返回值
public void validate()
{
if(getPassword().length()== 0)
{
/*addFieldError()用来创建和存储错误消息,拦截器会检查是否存在错误消息,若存在则表示验证失败,终止请求,选择为”input”的结果视图*/
addFieldError(“password”,getText(“password.required”));
}
if(getUsername().length()== 0)
{
addFieldError(“username”,getText(“username.required”));
}
if(getBagName().length()==0)
{
addFiledError(“bagName”,getText(“bagName.required”));
}
if(userService.userExists(getUsername()))
{
addFieldError(“username”,getText(”user.exists”));
}
}
接口com.opensymphony.xwork2.LocalProvider提供了一个方法getLocale(),可以根据地域设置本地化的消息字符串。ActionSupport实现了这个接口,并且会自动判断得到本地的属性文件。需要为当前地域提供属性文件。
属性文件命名为:类名_地域代码.properties。比如对应中国的消息文件可以设置为
Register_zh.properties.内容如下:
user.exists=此用户已存在。
username.required=用户名不能为空。
password.required=密码不能为空。
bagName.required=公文包名不能为空。
向对象传递数据
公开一个复杂对象作为javaBean属性,让数据传输到这个对象上。还有另一种选择就是使用ModelDriven动作(涉及一个接口和一个默认拦截器)。
对象支持的JavaBean属性
public class Register extends ActionSupport
{
privateUser user; //User类包含username,password,bagName三个字段
privateUserService userService;
publicString execute()
{
userService.insertUser(user);
returnSUCCESS;
}
//数据验证业务逻辑,没有返回值
public void validate()
{
if(getUser().getPassword().length()== 0)
{
/*addFieldError()用来创建和存储错误消息,拦截器会检查是否存在错误消息,若存在则表示验证失败,终止请求,选择为”input”的结果视图*/
addFieldError(“password”, getText(“password.required”));
}
if(getUser().getUsername().length()== 0)
{
addFieldError(“username”, getText(“username.required”));
}
if(getUser().getBagName().length()==0)
{
addFiledError(“bagName”, getText(“bagName.required”));
}
if(userService.userExists(getUser().getUsername()))
{
addFieldError(“username”, getText(“user.exists”));
}
}
… …//省略其他settter和gettter方法
}
再提交动作的表单中需要修改字段域的name属性:
<s:textfield name=”user.username” label=”Username”/>
结果页面也要用深层属性标记来进行访问数据:
<h3> hello,<s:property value=”user.username”/> </h3>
使用ModelDriven动作来实现对象数据传输
实现com.opensymphony.xwork2.ModelDriven接口,通过getModel()方法公开应用域对象。数据转移依然是自动化的。
public class Register extends ActionSupport implementsModelDriven
{
//必须对User进行对象初始化
privateUser user=new User(); //User类包含username,password,bagName三个字段
privateUserService userService;
publicString execute()
{
userService.insertUser(user);
returnSUCCESS;
}
publicObject getModel()
{
return user;
}
… …
}
陷阱:若在动作内部execute()方法中改变模型字段,框架不会察觉,将导致数据不一致的问题。jsp结果页面会根据原始的引用对象求值,在方法内部新设置的对象值将无法访问。
在表单页面和结果页面不需要进行深层引用:
<s:textfield name=” username”label=”Username”/>
<h3> hello,<s:property value=” username”/></h3>
从页面表单数据到javaBean属性的传输与类型转换
内建的类型转换器:
String boolean/Boolean char/Character int/Integer float/Float long/Long
double/Double Date(当前Locale的SHORT格式的字符串版本如12/10/97)
array List Map
当框架定位一个OGNL 表达式指向上述Java属性时,会自动完成类型的转换。
1.数组
指向javaBean数组字段的jsp表单字段:
<s:form action=”xx”>
这些字段指向ages属性 |
<s:textfieldname=”ages” label=”Ages”/>
<s:textfieldname=”ages” label=”Ages”/>
<s:textfieldname=”ages” label=”Ages”/>
这些字段指向 names属性带索引 |
<s:textfieldname=”names[0]” label=”names”/>
<s:textfieldname=”names[0]” label=”names”/>
<s:textfieldname=”names[0]” label=”names”/>
<s:submit/>
</s:form>
请求进入框架时,共存在4个请求参数:
参数名 | 参数值 |
ages | 12,33,102 |
names[0] | Chad |
names[1] | Don |
names[2] | Beth |
执行的javaBean数组属性
private Double[] ages;
private String[] names=new String[10];
注:表单中name属性带索引的数据参数,这种方法必须对数组进行初始化。
2.List
默认情况下,框架会把参数转换为String,并且填充到List属性中。
<s:textfieldname=”middleNames[0]” label=”middleNames”/>
<s:textfieldname=” middleNames[0]” label=”middleNames”/>
<s:textfieldname=” middleNames[0]” label=”middleNames”/>
<s:textfieldname=”lastNames” label=”lastNames”/>
<s:textfieldname=”lastNames” label=”lastNames”/>
<s:textfieldname=”lastNames” label=”lastNames”/>
来自于action动作类的List属性
private List lastNames;
private List middleNames;
注意:不需要初始化任何一个List。没有类型说明,List中的元素都是String。
为List中元素指定类型,可以使用泛型集合List<>.还可以通过一个简单的属性文件进行指定。属性文件命名约定:
ClassName-conversion.properties
ClassName:表示动作类名;-conversion:为类型转换属性文件的固定后缀。
属性文件放在和类同一个包中。
内容如下所示:
Element-middleNames=java.lang.String
Elementt-:为固定前缀;middleNames:为List类型属性的名字。
指定List属性的元素类型为一个User类。
<s:textfieldname=”users[0].username” label=”Usernames”/>
<s:textfieldname=”users[1].username” label=”Usernames”/>
<s:textfieldname=”users[2].username” label=”Usernames”/>
动作类的属性:
private List<User> users;
3.Map
Map对象必须提供一个键值,也可以为键值指定一个类型,否则默认为String.
<s:textfieldname=”maidenNames.mary.username” label=”username”/>
<s:textfieldname=”maidenNames.jane.username” label=”username”/>
<s:textfieldname= “maidenNames.hellen.username” label=”username”/>
<s:textfieldname=”maidenNames[‘beth’],birthday” label=”birthday”/>
<s:textfieldname=”maidenNames[‘sharon’].birthday” label=”birthday”/>
<s:textfieldname= “maidenNames[‘martha’].birthday” label=”birthday”/>
为键和值指定类型:
Key_maidenNames=java.lang.String
Element_maidenNames=com.aowin.User
javaBean属性:
privateMap maidenNames;
强烈建议使用泛型集合。
自定义类型转换
我们可以自定义一个类型转换器来实现我们想要转换的任何java类型
比如将一个表单里的数据字符串:C:r15转换成Circle类对象其中15是半径
Circle类:
public class Circle
{
privatedouble radius;
… …
}
自定义类型转换器,需要实现StrutsTypeConverter接口。
public class CircleTypeConverter extendsStrutsTypeConverter
{
publicObject convertFromString(Map context,String[] values),Class toClass)
{
StringuserString=values[0];
CirclenewCircle=parseCircle(userString);
returnnewCircle;
}
publicString convertToString(Map context,Object o)
{
Circlecircle=(Circle)o;
StringuserString=”C:r”+circle.getRadius();
returnuserString;
}
privateCircle parseCircle(String userString) throws TypeConversionException
{
Circlecircle=null;
intradiusIndex=userString.indexOf(‘r’)+1;
if(!useString.startWith(“C:r”))
thrownew TypeConversionException(“Invalid Syntax”);
intradius;
try
{
radius=Integer.parseInt(userString.substring(radiusIndex));
}
catch(NumberFormatExceptione)
{
thrownew TypeConversionException(“Invalid Value for Radius”);
}
circle= new Circle();
circle.setRadius(radius);
returncircle;
}
}
配置框架使用自定义转换器
1. 属性专用
属性文件ActionName-conversion.properties与动作类在同一个包中。
circle=com.aowin.utils.CircleTypeConverter
其中circle是动作类中的属性名,Circle类。
2. 全局类型转换
指定所有相应类型都使用自定义的转换器。
属性文件:xwork-conversion.properties文件,并放在src目录中。
文件内容:
com.aowin.model.Circle=com.aowin.utils.CircleTypeConverter.
类名=对应类型转换器
文件上传
默认拦截器栈包含了拦截器:FileUploadInterceptor
<interceptorname=”fileUpload”
class=”org.apache.struts2.interceptor.FileUploadInterceptor”/>
由fileUpload拦截器添加的请求参数,在动作类中追加与表中名字匹配的JavaBean属性。
参数名字 | 参数类型和值 |
myFile(来源于表单文件上传域的name属性值) | File——被上传的文件 |
myFileContentType | String——文件的内容类型 |
myFileFileName | String——存储在服务器上的上传文件名 |
文件上传表单的jsp代码片段:
<s:form action=”ImageUpload”method=”post” enctype=”multipart/form-data”>
<!—enctype属性告诉框架这个请求被当作上传处理,否则无法工作-->
<!—name 属性提供的值用来构建接收上传数据的JavaBean属性-->
<s:filename=”pic” label=”Picture”/>
<s:submit/>
</s:form>
文件上传处理的动作(xml文件)
<package name=”secure” namespace=”/secure”extends=”struts-default”>
… …
<actionname=”ImageUpload” class=”com.awoin.action.ImageUpload”>
<!—Param元素的name属性值对应与相应的动作类中的javaBean属性,元素文本内容会传递给此属性在此是设置文件保存路径,./表示了eclipse安装根目录-->
<param name="fileSystemPath">./portfolioFS</param>
<result>/ImageAdded.jsp</result>
<resultname=”input”>/ImageUploadForm.jsp</result>
</action>
… …
</package>
对应的动作类
public class ImageUpload extends ActionSupport
{
//不需要实现所有的参数javaBean属性。
private Filepic;
private StringpicContentType;
private StringpicFileName;
private String fileSystemPath;
private PortfolioService portfolioService;
public String execute()
{
try
{
PortfolioService.addImage(getPic(),getPicFileName(), getFileSystemPath());
}catch(IOExceptione)
{
e.printStackTrace();
}
return SUCCESS;
}
… …
}
框架支持相同参数名上传多个文件,只需把动作类的javaBean属性改为数组。
与Servlet相关的各种接口
动作可以实现任意数量的接口,每个接口包含一个方法——当前资源的设置方法
l ServletContextAware 设置ServletContext
l ServletRequestAware 设置HttpServletRequest
l ServletResponseAware 设置HttpServletResonse
l ParameterAware 设置Map类型的请求参数
l RequestAware 设置Map类型的请求属性
l SessionAware 设置Map类型的会话属性
l ApplicationAware 设置Map类型的应用程序领域属性
l PrincipalAware 设置Principal对象(安全相关)
通过exception拦截器建立异常处理
xml文件中的代码片段:
<global-results>
<!—全局结果,对包内所有动作可见-->
<resultname=”error”>/error.jsp</result>
<global-results>
<global-exception-mappings>
<!—告诉exception拦截器一个给定异常呈现哪个结果-->
<exception-mappingexception=”java.lang.Exception” result=”error”/>
</global-exception-mappings>
exception拦截器会创建一个ExceptionHolder对象,放在ValueStack最顶端,它把跟踪栈和异常作为javaBean属性公开处理,可以在错误页面访问这些属性。
<p><Exception Name:<s:property value=”exception”/></p>
<p>What you did wrong:<s:property value=”exceptionStack”/></p>
声明拦截器
每一个package元素只能包含一个interceptors元素,并且这个元素必须出现在xml文档中一个特定位置。
<!ELEMENT struts (package|include|bean|constant)*>
<!ELEMENTpackage (result-types?, interceptors?,default-interceptor-ref?,
default-action-ref?,global-results?,global-exception-mappings?,action*)>
将一系列拦截器和特定动作关联起来
<actionname=”MyAction” class=”com.aowin.myactions.MyAction”>
<interceptor-ref name=”timer”/>
<interceptor-ref name=”logger”/>
<!--只要声明了自己的拦截器默认值失效,默认拦截器必须显式指出-->
<interceptor-ref name=”defaultStack”/>
<result>Success.jsp</result>
</action>
设置、覆盖拦截器参数
interceptor-ref是传入参数的地方
<interceptor-refname=”workflow”>
<param name=”excludeMethods”>input,back,cancel,browse</param>
</interceptor-ref>
若想重用包含这个引用的defaultStack,并想改变exclude-Methods参数值
如下所示:
<action name=”MyAction”class=”com.aowin.myactions.MyAction”>
<interceptor-ref name=”defaultStack”>
<paramname=”workflow.excludeMethods”>doSomething</param>
</interceptor-ref>
<result>Success.jsp</result>
</action>
构建自定义拦截器(身份验证)
注意:拦截器实例在动作之间共享,每一个请求都会创建动作的一个新实例,但是拦截器会重用。拦截器是无状态的,不要在拦截器中存储与当前正在处理的请求相关的数据。
1. 实现Interceptor接口
编写一个拦截器,需实现com.opensymphony.xwork2.interceptor.Interceptor接口。
public interfaceInterceptor extends Serializable
{
void destroy();
void init();
Stringintercept(ActionInvocation invocation) throws Exception;
}
2. 构建AuthenticationInterceptor拦截器
当AuthenticationInterceptor触发时,它检查用户对象是否出现在会话中。若出现在会话中,它就让动作像往常一样触发。否则重定向到登录页面,终止工作流。
首先查看login动作类,验证用户身份并将其存储在会话作用域中
//实现SessionAware接口用来存储会话域对象
public classLogin extends ActionSupport implementsSessionAware
{
private User user;
private Map session;
private UserService userService;
public String execute()
{
//验证用户在数据库中是否存在
User logUser=userService.authenticateUser(user.getUsername(),
user.getPassword());
if(logUser == null)
{
return INPUT;
}
else
{
session.put(“user”,logUser);
}
retrun SUCCESS;
}
… …//省略其他getter和setter方法
}
AuthenticationInterceptor拦截器的代码
public classAuthenticationInterceptor implements Interceptor
{
public void destroy(){}
public void init(){}
public String intercept(ActionInvocationactionInvocation)throws Exception
{
//取得会话映射
Mapsession=actionInvocation.getInvocationContext().getSession();
Useruser=(User)session.get(“user”);
if(user == null)
{
//指向登录页面,此登录结果定义为了全局结果
return Action.LOGIN;
}
else
{
Actionaction=(Action)actionInvocation.getAction();
//我们自定义的UserAware接口允许动作让用户对象
//自动注入到设置方法
if(action instanceofUserAware)
{
(UserAware)action).setUser(user);
}
//验证完成后调用ActionInvocation对象的invoke()方法,
//将控制权交给剩余的拦截器以及动作
returnactionInvocation.invoke();
}
}
}
3.xml文件中声明自定义的拦截器并构建新的默认栈
<packagename=”xxx” namespace=”/xxx/secure” extends=”struts-default”>
<interceptors>
<interceptor name=”authenticationInterceptor”
class=”com.aowin.utils.AuthenticationInterceptor”/>
<interceptor-stackname=”secureStack”>
<interceptor-refname=”authenticationInterceptor”/>
<interceptor-refname=”defaultStack”/>
</interceptor-stack>
</interceptors>
<default-interceptor-refname=”secureStack”/>
. . . . . .
</package>
struts2视图标签
数据存储的场所:ActionContext
使用OGNL表达式将表单字段名绑定到对象中的具体属性,动作对象被放在ValueStack对象上,并且OGNL表达式指向这个栈上的属性。OGNL表达式可以根据任何一系列对象求值,ValueStack只是这些对象中的一个,默认的。这些对象全部存储在ActionContext中。
ActionnContext包含框架请求处理过程可以访问的所有数据,包含内容从应用程序数据到会话作用域或者应用程序作用域的映射。所有特定应用程序的数据,都存储在ValueStack上,它是ActionContext的一个对象。
所有OGNL表达式都必须根据ActionContext包含的某一对象求值,默认情况会选择ValueStack。
OGNL表达式指向特定对象上的属性,每一个OGNL表达式解析都需要一个根对象(rootobject)。例如:
user.account.balance
指向user对象的account对象的balance属性,user对象会默认在ValueStack中查找。
ActionContext:
ValueStack |
parameters |
application |
request |
attr |
session |
名字 | 描述 |
parmeters | 当前请求中请求参数的映射 |
request | 请求作用域的属性的映射 |
session | 会话作用域的属性的映射 |
application | 应用程序作用域的映射 |
attr | 按照页面、请求、会话、应用程序作用域的顺序,返回第一个出现的属性 |
ValueStack | 包含当前请求的应用程序特定领域的所有数据 |
OGNL选择一个根对象解析的表达式:使用#操作符。例
#session[‘user’] #application[‘count’] #request[‘user’]
ognl表达式动态创建Ma
p
过滤和投影集合
过滤:允许根据某种规则过滤一系列对象。接收一个包含N个对象的Collection,生成一个包含这些元素的子集的新集合。
投影:根据某个规则转换一系列对象。产生一个与原来输入集合元素数目相等的Collection,投影产生一对一的结果集合。
过滤的语法:collectionName.{?expression}
可以使用#this操作符来引用集合中正在被评估的元素。
投影的语法:collectionName.{expression}
实例代码:
OGNL表达式 | 描述 |
users.(?#this.age>30) | 过滤,返回一个30岁以上用户的新集合 |
users.{username} | 投影,返回一个用户名字符串的新集合 |
users.{firstName+’ ’+lastName} | 投影,返回每一个用户全名字符串的新集合 |
users.{?#this.age>30}.{username} | 先过滤再投影,返回年龄在30岁以上的用户名字符串的新集合 |
表达式语言的高级特性
1. 字面值和操作符
OGNL表达式语言的字面值
| 字面值类型 |
| 示例 |
|
| Char |
| ‘a’ |
|
| String |
| ‘hello’ “hello” |
|
| Boolean |
| true false |
|
| int |
| 123 |
|
| double |
| 123.5 |
|
| BigDecimal |
| 123b |
|
| BigInteger |
| 123h |
|
OGNL表达式语言的操作符
| 操作符 |
| 示例 |
|
| add(+) |
| 2+4 ‘hello’+’world’ |
|
| subtract(-) |
| 5-3 |
|
| multiply(*) |
| 8*2 |
|
| divide(/) |
| 9/3 |
|
| modulus(%) |
| 9%3 |
|
| increment(++) |
| ++foo,foo++ |
|
| decrement(--) |
| bar--,--bar |
|
| equality(==) |
| foo==bar |
|
| less than(<) |
| 1<2 |
|
| greater than(>) |
| 2>1 |
|
OGNL表达式支持逗号表达式,每一个子表达式执行完毕后将控制权转交给下一个子表达式,最后返回最后一个子表达式的值,就是整个表达式的返回值。
逗号表达式:user.age=10,user.name=”chad”,user.username
2. 调用方法
从OGNL表达式语言调用方法
| Java代码 |
| OGNL表达式 |
|
| utilityBean.makeRandomNumber() |
| makeRandomNumber |
|
| utilityBean.getRandomNumberSeed() |
| getRandomNumberSeed() 或者RandomNumberSeed |
|
3. 访问静态方法和字段
有两种访问方式:
1.指定完全限定的类名。
语法:@fullClassName@property 或 methodCall
实例:@com.aowin.utils.Struts2Constants@USER
@com.aowin.utils.myBean@startImageWrapper()
2.根据ValueStack解析。类似第一种,使用vs记号代替全类名,vs表示ValueStack
@vs@USER
@vs@startImageWrapper()
Struts2标签API语法
struts2标签可以分为:数据标签(datatag)、流程控制标签(control-flow tag)、UI标签(UI tag)和其他标签(miscellaneous tag)。
1.jsp语法
声明标签:
<%@ taglib prefix=”s” uri=”/struts-tags”%>
简单语法举例:
<s:property value=”name”/>
2.Velocity语法
Struts2标签API使用Velocity宏实现。
#sform(“action=Register”)
#stextfield(“label=Username” “name=username”)
#spassword(“label=Password” “name=password”)
#stextfield(“label=Enter a name” “name=protfolioName”)
#ssubmit(“value=Submit”)
#end
3. FreeMarker语法
<@s.property value=”name”/>
OGNL设置标签属性
若一个属性类型是String,,写入的属性值会被作为字符串字面值解析。
若一个属性不是String类型,写入的值被作为OGNL表达式解析。例:
<s:propertyvalue=”theProperty”/>
<s:propertyvalue=”theProperty” default=”doesNotExist”/>
注:value属性为Object类型,会从ValueStack上取得同名属性值,并写到页面上。
由于未使用#操作符来指定ActionContext中的对象,会默认在ValueStack上查找。
若不存在,则一个空值会被转为空串。第二个标签指定了default属性,在value指定的属性不存在的情况下,会显示此default的值,是string类型,直接显示字面值。使用%{expression}可强制将字符串当作OGNL表达式解析。
数据标签
1. property标签
property标签的属性
属性 | 是否必须 | 默认值 | 类型 | 描述 |
value | 否 | 栈最顶端 | Object | 被显示的值 |
default | 否 | 空(null) | String | 值为空时使用的默认值 |
escape | 否 | true | Boolean | 是否转义HTML |
例:<s:property value=”user.username”/>
1,访问Action值栈中的普通属性:
<s:propertyvalue="attrName"/>
2,访问Action值栈中的对象属性(要有get set方法):
<s:propertyvalue="obj.attrName"/>
<s:propertyvalue="obj1.obj2.attrName"/>
3,访问值栈中对象属性的方法
<s:propertyvalue="obj.methodName()"/>
4,访问值栈中action的普通方法:
<s:propertyvalue="methodName()"/>
5,访问静态方法:
<s:propertyvalue="@com.softeem.LoginAction@methodName()"/>
6,访问静态属性:
配置属性文件,允许ognl访问静态方法struts.ognl.allow...=true
<s:propertyvalue="@com.softeem.LoginAction@attrName"/>
7,访问Math类的静态方法:
<s:propertyvalue="@@min(9,7)"/>
8,访问普通类的构造方法:
<s:propertyvalue="new com.softeem.User(2)"/>
9,访问集合:
①list集合对象
<s:propertyvalue="listName"/>
②list集合中的某个元素
<s:propertyvalue="listName[1]"/>
③list中某个属性的集合
<s:propertyvalue="listName.{field}"/>
④list中某个属性集合的特定值
<s:propertyvalue="listName.{field}[0]"/>
⑤访问set
<s:propertyvalue="setName"/>
⑥访问set中某个元素
<s:propertyvalue="setName[0]"/>
⑦访问map
<s:propertyvalue="mapName"/>
⑧根据key访问Map中的元素
<s:propertyvalue="mapName.username"/>
<s:propertyvalue="mapName['username']"/>
<s:propertyvalue="mapName[/"username/"]"/>
⑨访问map中所有的key
<s:propertyvalue="mapName.keys"/>
10,访问map中所有的values
<s:propertyvalue="mapName.values"/>
11,访问map的大小
<s:propertyvalue="mapName.size()"/>
12,投影
<s:propertyvalue="listName.{?#this.age==1}"/>
<s:propertyvalue="listName.{^#this.age>1}"/>
<s:propertyvalue="listName.{$#this.age==1}"/>
<s:propertyvalue="listName.{$#this.age==1}.{age}==null"/>
[]:<s:propertyvalue="[0]"/>值栈中的对象
2. set标签
set标签的属性
属性 | 是否必须 | 类型 | 描述 |
name | 是 | String | 被设置在指定作用域内的变量的引用名 |
scope | 否 | String | Application、session、request、page或者action 默认值为action(ActionContext) |
value | 否 | Object | 设置值的表达式 |
例:
<s:setname=”username” value=”user.username”/>
hello,<s:propertyvalue=”#username”/>.How are you?
注解:set把user.username表达式的值设置到标签name属性指定的引用上,由于为指定作用域。默认放在了ActionContext中,直接用#来进行引用
设置在application作用域中
<s:setname=”username” scope=”application” value=”user.username”/>
hello,<s:propertyvalue=”#application[‘username’]”/>.How are you?
3. push标签
把属性放到ValueStack的顶端,这样可以直接引用此属性所包含的对象
push标签的属性
属性 | 是否必须 | 类型 | 描述 |
value | 是 | Object | 放到ValueStack中的值 |
例:
<s:push value=”user”>
<s:propertyvalue=”portfolioName”/>
<s:propertyvalue=”username”/>
</s:push>
结束标签会将user从ValueStack顶端删除。
4. bean标签
bean标签像是set标签和push标签的混合标签。可以自己创建一个对象的实例,并放到ValueStack上或者设置为ActionContext的顶级引用,默认放到ValueStack上,并且标签存在期间一直保留在ValueStack上。若想在标签之外继续引用创建的对象,可以使用var属性指定一个引用名,此引用会作为一个命名参数存在于ActionContext中,在请求周期内可以使用#操作符访问这个对象。
bean标签的属性
属性 | 是否必须 | 类型 | 描述 |
name | 是 | String | 被创建bean的包名和类名 |
var | 否 | String | 在结束标签作用域之外引用这个bean时使用的变量名 |
事例展示:
<s:beanname=”org.apache.struts2.util.Counter” var=”counter”>
<s:param name=”last” value=”7”/>
</s:bean>
<s:iteratorvalue=”#counter”>
<li><s:property/></li>
</s:iterator>
bean在ActionContext中,要用#操作符来命名它,若不使用var属性,则bean放在ValueStack上。
<s:property/>没有指定任何属性,只输出ValueStack最顶层的属性。
另一个事例:将bean放在ValueStack上
<s:beanname=”com.aowin.utils.JokeBean”>
<s:paramname=”jokeType”>knockknock</s:param>
<s:property value=”statrAJoke()”/>
</s:bean>
对象放在了ValueStack中,使用OGNL方法调用了对象的方法startAJoke();
5. action标签
这个标签允许我们从当前视图层调用其他的动作。
action标签的属性
属性 | 是否必须 | 类型 | 描述 |
name | 是 | String | 动作名 |
namespace | 否 | String | 动作的命名空间,默认使用当前页面的命名空间 |
var | 否 | String | 在页面后续代码中使用的动作对象的引用名 |
executeresult | 否 | Boolean | 设置为true时执行动作的结果(默认为false) |
flush | 否 | Boolean | 设置为true时在action标签结尾会刷新写出缓冲(默认为true) |
ignoreContextParams | 否 | Boolean | 设置为true时动作被调用时不包括请求参数(默认为false) |
<s:action name=”TargetAction”executeResult=”true”/>辅助动作的结果视图应该是一个html片段。
主动作页面可以访问辅助动作的数据。
<s:action name=”TatgetAction”/>
<s:propertyvalue=”#request.dataFromSecondAction”/>访问辅助动作action中放到请求中的数据。
控制标签
1. iterator标签
遍历集合对象。
action标签的属性
属性 | 是否必须 | 类型 | 描述 |
value | 是 | Object | 被遍历的对象 |
status | 否 | String | 若指定,这个属性指定的名字会引用IteratorStatus对象,并放在ActionContext中,主要用于计数 |
实例如下:
<s:iteratorvalue=”users” status=”itStatus”>
<li>
<s:property value=”#itStatus.count”/>
<s:propertyvalue=”portfolioName”/>
</li>
</s:iterator>
在标签内部每个对象轮流放在ValueStack顶端。itStatus引用IteratorStatus对象,然后进行遍历状态。
IteratorStatus对象的公有方法:
方法名 | 返回值 |
getCount | int |
getIndex | int |
isEven | boolean |
isFirst | boolean |
isLast | boolean |
isOdd | boolean |
modulus(int operand) | int |
2. if和else标签
if和elseif标签属性
属性 | 是否必须 | 类型 | 描述 |
test | 是 | Boolean | 对被求值进行测试的布尔表达式 |
实例如下:
<s:iftest=”user.age > 35”>This user is too old.</s:if>
<s:elseiftest=”user.age<20”>This user is too young</s:elseif>
<s:else>Thisuser is just right</s:else>
其他标签
1. include标签
在当前呈现页面中包含其他Web资源的输出。可以向被包含的资源传递请求参数。
include标签可以引用任何的Servlet资源,而action标签只能包含动作。
include标签属性
属性 | 是否必须 | 类型 | 描述 |
value | 是 | String | 页面、动作、Servlet及其他可以被引用的url名字 |
2. URL标签
URL标签属性
属性 | 是否必须 | 类型 | 描述 |
value | 否 | String | 基础URL,默认为呈现当前页面的URL |
action | 否 | String | 生成的URL执行的动作名,不需要.action扩展名 |
var | 否 | String | 若指定,url不会被写出,会存在ActionContext中留待后用 |
includeParams | 否 | String | 从all、get、none中选择参数,默认值为get |
includeContext | 否 | Boolean | 若为true,则生成的URL会使用程序的Context作为前缀,默认值为true |
encode | 否 | Boolean | 若不支持Cookie,会将Session ID追加在生成的URL中 |
scheme | 否 | String | 指定协议,默认使用当前协议(HTTP/HTTPS) |
举例:
<s:url action=”IteratorTag”var=”myUrl”>
<s:param name=”id” value=”2”/>
</s:url>
<a href=’<s:propertyvalue=”#myUrl”/>’>Click Me</a>
生成如下url标记:<a href=’/应用程序名/namespace/IteratorTag.action?id=2’>
<a href=’<s:urlvalue=”IteratorTag.action”/>’>Click Me</a>
3. i18n和text标签
用于国际化支持的标签。
text标签用来显示与具体语言相关的文本,这个标签使用一个关键字在一系列文本资源中查找,这个标签从属性文件ResourseBundle中取得消息值。框架默认的Locale判断机制判断在哪个Locale对应的文本资源中解析这个关键字。
text标签属性
属性 | 是否必须 | 类型 | 描述 |
name | 是 | String | 在ResourceBundle中查找用的关键字 |
var | 否 | String | 若找到,文本会使用这个名字保存在ActionContext |
可以命名一个特别的ResourceBundle来解析text标签,若手动指定ResourceBundle,可以使用i18n标签
i8n标签属性
属性 | 是否必须 | 类型 | 描述 |
name | 是 | String | ResourceBundle的名字 |
举例如下:
<s:i18n name=”com.aowin.myResourceBundle_tr”>
In<s:text name=”language”/>
<s:textname=”girl” var=”foreignWord”/>
</s:i18n>
<s:property value=”#foreignWord”/>means girl.
4. param标签
param标签属性
属性 | 是否必须 | 类型 | 描述 |
name | 否 | String | 参数名 |
value | 否 | Object | 参数值 |
UI组件标签(表单)
所有UI标签的通用属性
属性 | 主题 | 数据类型 | 描述 |
name | simple | String | 设置表单输入元素的name属性,name属性指向ValueStack上的属性作为提交的请求参数值的目标 |
value | simple | Object | 指向ValueStack上的属性的OGNL表达式,用来为预填充设置表单输入元素的值。默认为name属性设定的值 |
key | simple | String | 从ResourceBundle取得本地化的标签(label),可以传播到name属性,也可传播到value属性 |
label | XHTML | String | 为组件创建一个HTML标签(label),若设定使用key属性和本地化的文本就不需要指定这个属性 |
labelPosition | XHTML | String | 元素标签(label)的位置,可选的值是left或者top |
required | XHTML | Boolean | 若为true,则标签(label)旁边会出现一个星号来暗示这是必需字段。默认情况下,若一个字段级别的数据验证器通过name属性与这个输入字段关联时,这个值为true |
id | simple | String | HTML的id属性,若id属性未指定,则组件会创建一个唯一标识(由动作名和表单name属性值组成,‘_’分隔) |
cssClass | simple | String | HTML的class属性,css使用 |
cssStyle | simple | String | HTML的style属性,CSS使用 |
disabled | simple | String | HTML的disabled属性 |
tabindex | simple | String | HTML的tabindex属性 |
theme | N/A | String | 在哪个主题下呈现这个组件,例xhtml、css_xhtml、ajax或simple。默认值是xhtml,在default.properties中设置 |
templateDir | N/A | String | 用来覆盖从中取出模板的默认目录名 |
template | N/A | String | 用来呈现UI标签的模板,所有的UI标签都有默认的模板(除了component标签),但是模板可以被覆盖 |
head标签
必须放在HTML的head元素内部,head标签会生成信息。这些信息包括css样式表和脚本元素。若某个标签依赖head标签引入的资源,则不可省去head标签。
例:
<head>
<title>the title</title>
<s:head/>
</head>
以下是标签创建的内容
<link rel=”stylesheet”href=”…styles.css” type=”text/css”/>
<script type=”text/javascript”src=”…dojo.js”/>
<scripttype=”text/javascript” src=”dojoRequire.js”/>
form标签
常用的form标签属性
属性 | 数据类型 | 描述 |
action | String | 表单提交的动作目标,可以是动作名字(无.action扩展名)或URL |
namespace | String | Struts2命名空间,在这个命名空间中查找命名动作(action属性)或者从这里开始构建URL,默认为当前命名空间 |
method | String | 与HTML form属性相同,默认是POST |
target | String | 与HTML form属性相同,请求的资源打开的位置 |
enctype | String | 上传文件时设置为multipart/form-data |
validate | Boolean | 与验证框架一起使用,打开客户端JavaScript验证 |
(1)若没有指定action属性,则重新执行生成当前页面的动作。
(2)若action中指定的值不是声明性架构中的Struts2动作,则此值会被用来直接构建URL。若指定的字符串以一个斜线(/)开头,则被假定为与ServletContext相关,框架会在它之前加上ServletContext的路径构建一个URL,若没有已斜线开头,则此值被直接当作URL(如http://www.baidu.com)。若指定的既不是动作,也不是以斜线开头,也不是完整的URL,则会将当前表单的视图路径去掉最后的文件/动作名然后与action的值进行拼接构造一个请求URL。举例:
<s:form action=”MyResource”>
若此表单的地址是:http://localhost/theApp/my/LoginForm.action
则构造的请求地址是:http://localhost/theApp/my/MyResource
textfield标签
重要的textfield标签属性
属性 | 数据类型 | 描述 |
maxlength | String | 字段数据的最大长度 |
readonly | Boolean | 若为true,字段不可编辑 |
size | String | textfield的可视长度 |
示例如下:
<s:formaction=”Login”>
<!—key属性会从ResourceBundle中取得本地化的标签,并且传播到name属性值为username.属性文件在类路径下为global-messages.properties -->
<s:textfield key=”username”/>
<s:password name=”password” label=”Password” />
<s:submit/>
</s:form>
global-messages.properties内容如下:
username=Username
为了使用全局属性文件我们必须创建struts.properties文件并指定以下属性:
struts.custom.i18n.resources=global-messages
password标签
重要的password标签属性
属性 | 数据类型 | 描述 |
maxlength | String | 字段数据的最大长度 |
readonly | Boolean | 若为true,字段不可编辑 |
size | String | 文本字段的可视长度 |
showPassword | Boolean | 若为true,且ValueStack中对应属性有值,则密码会被预填充。默认为false. |
textarea标签
重要的textarea标签属性
属性 | 数据类型 | 描述 |
cols | Integer | 列数 |
rows | Integer | 行数 |
readonly | Boolean | 若无true,字段不可编辑 |
wrap | String | 指定textarea中的内容是否应该被包装起来 |
checkbox标签
与此标签绑定的Java端的属性必须是Boolean类型的。对于一系列的多个相同名字的复选框可以使用checkboxlist标签。checkbox只关注true或者false选择。
重要的checkbox标签属性
属性 | 数据类型 | 描述 |
fieldValue | String | 被checkbox提交的真实值。可能是true或者false,默认为true |
value | String | 与fieldValue一起使用,决定checkbox是否被选中,若fieldValue=true,且value=true,则组件被选中 |
<s:checkbox name=”receiveMail”fieldValue=”true” label=”check to receive mail”/>
相应的HTML checkbox元素
<label for=”Register_receiveMail”>checkto receive mail</label>
<input type=”checkbox” name=”receiveMail”value=”true” id=”Register_receiveMail”/>
select标签——基于集合的组件
select(下拉列表)标签。用于构建下拉列表,允许用户从一系列选项中选择一个值。
简单事例:
<s:select name=”user.name”list=”{‘MIKE’,’Payal’,’Silas’}”/>
list 属性指向了为这个组件提供数据的数据集。通常情况会使用一个OGNL表达式指向ValueStack上的一系列的数据。
呈现的HTML标记
<select name=”user.name” id=”xx_user_name”><!—xx表示表单的action属性值-->
<optionvalue=”Mike”>Mike</option>
<optionvalue=”Payal”>Payal</option>
<optionvalue=”Silas”>Silas</option>
</select>
重要的select标签属性
属 性 | 数据类型 | 描 述 |
list | Collection、Map、Array或者Iterator | 用来生成下拉列表选项的数据集合 |
listKey | String | 当list中的元素是复杂类型时,用来指定用作被提交的值的list元素的属性,默认值是key |
listValue | String | 当列表中的元素是复杂类型时,用来指定用作选项内容的list元素属性,用户所看到的字符串,默认值是value |
headerKey | String | 与标题一起使用,若用户选择了标题,则指定提交的值 |
headerValue | String | 作为list的标题呈现给用户,例如”States”、“Countries” |
emptyOption | Boolean | 与标题一起使用,在标题和真实选项之间放置一个空的间隔选项 |
multiple | Boolean | 用户可以选择多个值 |
size | String | 一次显示的选项个数 |
示例如下:
让list属性指向java端的Collection对象
<s:form action=”xx”>
<s:selectname=”username” list=”users” listKey=”username”
listValue=”username”label=”Seclect an artist”/>
<s:submitvalue=”Browse”/>
</s:form>
让list属性指向java端的Map对象其中users的key为username,value为User对象
<s:form action=”xx”>
<!--listKey默认被设置为key,会指向username,在此可以省略
listValue属性默认被设置为value,指向User对象,在这里指向User对象的
username属性更合适,即value.username-->
<s:selectname=”username” list=”users” listKey=”key”
listValue=”value.username”label=”Seclect an artist”/>
<s:submitvalue=”Browse”/>
</s:form>
上述两种情况生成的html标记完全一样。
radio标签——基于集合的组件
单选组件。使用类似于select标签
重要的radio标签属性
属 性 | 数据类型 | 描 述 |
list | Collection、Map、Array或者Iterator | 用来生成单选项的数据集合 |
listKey | String | list中集合元素的属性,指定用作被提交的值,默认值是key |
listValue | String | list中集合元素的属性,指定用作选项内容,用户所看到的字符串,默认值是value |
checkboxlist标签——基于集合的组件
复选框列表组件,类似于select组件。
重要的checkboxlist标签属性
属 性 | 数据类型 | 描 述 |
list | Collection、Map、Array或者Iterator | 用来生成单选项的数据集合 |
listKey | String | list中集合元素的属性,指定用作被提交的值,默认值是key |
listValue | String | list中集合元素的属性,指定用作选项内容,用户所看到的字符串,默认值是value |
label标签
会生成一个只读的一个类似textfield的视图组件
<s:labelname=”username” label=”Username”/>
生成的结果:Username:Chad
hidden标签
隐藏请求的参数,而不展示给用户
<s:hidden name=”username”/>
doubleselect标签
关联下拉列表:将两个下拉列表链接在一起,当从第一个下拉列表中选择一个值时,第二个下拉列表中的选项会根据第一个下拉列表中的内容而变化。例如将省名和城市名称链接起来。
首先,需要告诉组件哪些数据用来生成第一个下拉列表,这些设置与select标签方式类似,使用相同的属性,相同方式执行。其次,需要指定另外一个属性用来提供第二个下拉列表需要使用的一系列数据。这个属性能够引用多个数据列表,每个列表对应着第一个下拉列表的选项。
示例如下:
<s:formaction=”xx”>
<s:doubleselect name=”username”list=”users” listKey=”username”
listValue=”username” doubleName=”portfolioName”doubleList=”portfolios”
doubleListKey=”value.name” doubleListValue=”value.name”/>
<s:submit value=”View”/>
</s:form>
其中portfolios是User对象的一个map集合属性。
<s:token/>标签防止重复提交
首先需要在表单中添加<s:token/>标签
<s:form>
<s:token/>
<s:textfieldkey=”coinToss”/>
<s:submit action=”flipCoin”/>
</s:form>
使用令牌拦截器声明动作映射
<actionname=”xx” method=”xxx” class=”xxxx”>
<interceptor-refname=”myTokenStack”/>
<resultname=”success”>samplePage</result>
<result name=”invalid.token”>duplicatePage</result>
</action>
当检测到一个重复请求时,这个拦截器会返回invalid.token.
拦截器栈:
<interceptor-stackname=”myTokenStack”>
<interceptor-ref name=”token”/>
<interceptor-refname=”defaultStack”/>
</interceptor-stack>
比token拦截器更智能的版本是tokenSession拦截器,提供了更复杂的检查逻辑。
类中的某些方法不想参与测试令牌:
<interceptor-refname=”tokenSession”>
<param name=”includeMethods”>sava,proceed</param>
或者<param name=”excludeMethods”>someMethods</param>
</interceptor-ref>
若包含和排除参数同时存在,则包含优于排除。
或者
<interceptor-refname=”myTokenStack”>
<paramname=”tokenSession.includeMethods”>sava,proceed</param>
或者<param name=”tokenSession.excludeMethods”>someMethods</param>
</interceptor-ref>