Struts
使用Filter作为控制器的MVC POJO普通的一个java类
模型:封装应用程序的数据和业务逻辑 POJO
视图:实现应用程序的信息显示功能 JSP
控制器:接受来自用户的输入,调用模型层,响应对应的视图组件 Servlet Filter
使用Filter 作为控制器可以方便的在应用程序里对所有资源的进行控制访问
Servlet VS Filter
servlet能做的Filter也能做
但是Filter可以拦截资源servlet缺是不行
eg:在一个form表单中action="input.action" action中的参数就是一个servletPath
在自定义的filter中继承 filter 在doFilter中 通过request对象获取servletPath
然后只要进行一个判断字符串是否相等 相等就执行响应的内容 并进行跳转之类的动作
跳转后要有一个 return; 因为在filter中 有个chain.doFilter(); 这个方法会进入到下一个过滤器中过滤 如果没有return 的话会抛异常
Struts2是一个用来开发MVC应用程序的框架,它提供了一些web应用程序开发过程中一些常见的解决方案
对来自用户输入的数据进行合法性验证
统一的布局
可扩展性
国际化和本地化
支持Ajax
表单的重复提交
支持的文件下载
搭建Struts2的环境
1.加jar包 struts\apps\struts2-blank\WEB-INF\lib下多有的jar包
2.在web.xml文件中配置struts2 复制 struts\apps\struts2-blank\WEB-INF\web.xml文件中的过滤器的配置到当前的web.xml 文件中
3.在web应用的classPath下添加 struts2的配置文件 struts.xml 复制struts\apps\struts2-blank\WEB-INF\classes下的struts.xml 到当前的web应用的src目录下
添加DTD提示 http://struts.apache.org/dtds/struts-2.3.dtd 选择的key type 是uri
在struts.xml下的配置
配置Struts可以受理的请求的扩展名
<constant name="struts.action.extension" value="action,do,"> 表示servletPath的扩展名可以使.action .do 或者没有扩展名
关于Struts2请求扩展名的问题
>org.apache.struts2包下的default.properties 中配置了Struts2应用的常量
>struts.action.extension 定义当前Struts2应用可以接受的请求的扩展名
>可以在Struts.xml中一常量配‘置的方式修改default.properties 所配置的常量
<constant name="struts.configuration.xml.reload" value="true"></constant> <!-- 这是配置struts修改了配置不用重启 -->
package: 包 struts 通过使用package来组织模块
name属性:必须 用于其它的包应用当前的包
class属性:默认值com.opensymphony.xwork2.ActionSupport
nnmethod:默认值 execute
extends: 当前包继承哪个包,可以继承其中的所有配置 通常情况下继承 struts-default
extends:struts-default struts-default 这个包 必须是要继承才能使用
namespace: 是可选的 默认值为"/"
若有一个非默认值 则要想要调用这个包中的Action,就必须把这个属性所定义的命名空间添加到有关的uri字符串中
http://localhost:8080/contextPath/namespace/actionName.action
<package name="hello" extends="struts-default">
<action name="prodect-input"> 这个name是Action的虚拟路径
一个action 可以有多个result子节点 用name区分 success是默认的值 name是Action类中方法的返回值
type属性是结果类型默认值为dispatcher(内部跳转) redirect(重定向)-不好用
<result name="success">/WEB-INF/pages/input.jsp</result>
</action>
</package>
struts2 vs 自实现
1.要搭建环境
2.不需要显示的定义Filter 而使用的是Struts2配置文件
3.以前写el表达式的页面变得简单了
Action
action VS Action
action :代表的是一个action struts2请求
Action 类代表能够处理Struts请求的类 。 eg:在实体类中写着一个处理struts2请求的方法
Action 类 属性的名字必须要遵守javaBeans 属性名相同的命名规范 ,属性的类型可以使任意类型,从字符串到非字符串之间的数据转换可以自动发生
必须要有一个无参的构造方法
至少有一个供struts在执行这个action时调用的方法
同一个Action类可以包含多个action方法
struts2会为每一个http请求创建一个新的Action实例
在Action中如何访问Web资源
1.什么是web资源
HttpServletrequest,HttpSession,ServletContext等原生的servlet API
2.为什么访问web资源
B\S的应用的Controller 必然要
3.如何访问
1.和servletAPI解耦的方式:只能访问到有限的servletAPI对象,且只能访问到有限的方法(读取请求参数,读取域对象的属性,使session失效...)
> 使用ActionContext
//先获取ActionContext对象 这个对象是Action的上下文对象 可以冲从中获取到当前Action需要的一切信息
ActionContext actionContext = ActionContext.getContext();
//1.获取application对应的Map ,可以通过map获取属性 也可以设置
Map<String, Object> applicationMap = actionContext.getApplication();
applicationMap.put("applicationKey", "applicationValue");
//2.session
Map<String, Object> sessionMap = actionContext.getSession();
sessionMap.put("sessionKey", "sessionValue");
//3.request
//actionContext中没有提供getRequest 方法来获取request对应的Map 需要手动的调用 get("request")方法 来获取
Map<String, Object> requestMap = (Map<String, Object>) actionContext.get("request");
requestMap.put("requestKey", "requestValue");
//4.获取请求参数对应的Map,并获取指定的参数值
//键:请求参数的名字 值:请求参数对应的字符串数组
//注意:1.getParameters 的返回值为 Map<String, Object> 而不是 Map<String,String[]>
// 2.parameter这个map只能读不能写入数据 如果写入不会报错,也不起作用 只有uri上转发来的才能获取到
Map<String, Object> parameters = actionContext.getParameters();
System.out.println(((String[])parameters.get("name"))[0]);
> 实现XxxAware接口 ApplicationAware,SessionAware,RequestAware,ParameterAware
实现ApplicationAware接口 重写set方法 自己写一个属性 这是一种依赖注入 setApplication方法是struts2调用的
public String execute(){
//向application中添加一个 属性 applicationKey2 applicationValue2
applicationMap.put("applicationKey2", "applicationValue2");
//从 application中获取一个属性
System.out.println(applicationMap.get("date"));
return "success";
}
private Map<String, Object> applicationMap;
public void setApplication(Map<String, Object> applicationMap) {
this.applicationMap = applicationMap;
}
选用的建议 :若一个Action类中有多个action方法,且多个方法都要使用域对象的map或parameters 则建议使用实现Aware接口的方式
注意session对应的Map 实际上是SessionMap类型的 强转后调用invalidate() 方法,可以使session失效
2.和servletAPI耦合的方式:可以访问更多的ServletAPI对象,且可以调用其原生的方法
> 使用ServletActionContext
HttpServletRequest request = ServletActionContext.getRequest();
HttpSession session = ServletActionContext.getRequest().getSession();
ServletContext servletContext = ServletActionContext.getServletContext();
> 实现ServletXxxAware接口
可以由struts2注入 需要的servlet需要的相关对象
ServletRequestAware, 注入ServletRequest对象
ServletContextAware, 注入ServletContext对象
SerlvetResponseAware 注入SerlvetResponse对象 很少使用
ActionSupport
com.opensymphony.xwork2.ActionSupport类 是默认的Action类
在写一个action节点的时候 不写 class默认就是它
在手工完成字段验证,显示错误信息,国际化等情况下推荐继承ActionSupport
Result
是action节点的子节点
代表action方法执行后,可能去的一个目的地
一个action节点可以配置多个result子节点
result的name属性的值对应着action方法可能有的一个返回值
result中的type属性 表示结果的响应类型
type属性的值在struts-default包的result-types节点的name属性中定义
常用的有
>dispatcher(默认的):转发 同servlet中的转发是一样的
>redirect: 重定向 可以重定向到页面 也可以是action
>redirectAction: 重定向到action
<result name="" type="redirectAction">
<param name="actionName">TestAction</param>
<param name="namespace"></param>
</result>
<result name="" type="redirect">TestAction.action</result>
用redirect也可以重定向到action
>chain: 转发到action
<result name="" type="chain">
<param name="actionName">TestAction</param>
<param name="namespace"></param>
</result>
<result name="" >TestAction.action</result>这个是不行的
不能通过一个dispatcher的方式转发到一个action
动态的配置 uri上有!标识
<a href="userAction!addUser">添加</a>
在struts配置文件中 不再需要指定的方法 !后的addUser已经指定了方法
会动态的调用到action类中的方法
通配符映射
<action name="*-*" class="com.zr.action.{1}Action" method="{2}">
<result name="{2}">/{2}.jsp</result>
</action>
<action name="UserAction-*" class="" method="{1}">
<result name="{1}-success">/{1}.jsp</result>
</action>
*表示一端字符串 {1} 表示是* 代表的这段字符串
通配符映射的规则
>若找到多个匹配,没有通配符的胜出 是要找精确的那个
>若指定的动作不存在,Struts将会尝试把这个URI与任何一个包含通配符* 的动作名进行匹配
>被通配符匹配到的URI字符串的子串可以用{1},{2}来引用
>{0} 匹配整个URI
>若Struts找到带有通配符的匹配不止一个, 则按先后顺序匹配
全局结果集 这个几点必须在action节点前 是有顺序的
<global-results>
<result></result>
</global-results>
关于值栈 是在Struts2的过滤器中创建出来的 每经过一次过滤器 都会创建一个新的valueStack
1.在第一个struts的项目中${prodectName} 读取prodectName值,实际上该属性并不存在request等域对象的中,而是从值栈中获取
2.ValueStack
>可以从ActionContext中获取值栈对象
>值栈分为两个逻辑部分
>Map栈:实际上是OglnContext类型,是一个Map,也是对ActionContext的一个引用,里边保存着各种Map
requestMap ,sessionMap,applicationMap,parameterMap,attr
>对象栈:实际上是CompoundRoot类型,是一个使用ArrayList定义的栈,里边保存各种和当前Action实例相关的对象,
是一个数据结构意义的栈。
OGNL
Ognl.getValue(String expression,Map context,Object root);
从ognl中取值的方法
三个参数的时候如果取的是map中的值 要表达式中要加# 取的是object中的时候不用加 直接属性名
如果是两个参数的时候
Ognl.getValue(String expression,?) ?这个位置上可以是一个map类型 也可以是一个object类型
在这个方法中取map中的值的时候表达式中不用加# 因为这时候map已经作为了 根对象
其实 跟对象就是一个键值对 (传三个参数的时候 感觉就和根对象放入了一个map里)
struts2利用s:property 标签和OGNL表达式来读取值栈中的属性值
值栈中的属性值:
> 对于对象栈:对象栈中某一个对象的属性值
> Map栈:request,session,application的一个属性值或一个请求参数的值
>读取对象栈中对象的属性:
若想访问Object Stack 里某个对象的属性,可以使用以下几种形式之一:
object.propertyName; object['propertyName'];object["propertyName"]
Object Stack里的对象可以通过一个从零开始的下标来引用。Object Stack里的栈顶对象可以用[0]来引用
它下面的那个对象可以用[1]引用
eg:[0].message;
[n]的含义是从第n个对象开始搜索,而不是只搜索第n个对象
若从栈顶对象开始搜索,则可以省略下标的部分:message
结合s:property 标签:<s:property value="[0].message"/> 等同于 <s:property value="message"/>
在jsp页面上<%@ taglib prefix="s" uri="/struts-tags" %>
>读取Map栈中的属性值 可以使用一下几种形式
#object.propertyName; #object['propertyName']; #object["propertyName"]
可以利用OGNL调用
任何一个java类的静态字段和方法
被压入到ValueStack栈对象上的公共字段和方法
在默认情况下Struts2不允许调用java类的静态方法 需要重新设置 struts.ohnl.allowStaticMethodAccess 标记变量为 true
<constant name="struts.ohnl.allowStaticMethodAccess" value="true"></constant>
调用静态字段或方法需要使用一下的语法
@fullQualifiedClassName@fieldNames
@fullQualifiedClassName@methodName(argumentList);
调用一个实例字段或方法的语法 , 其中object是Object Stack栈里的某个对象的引用
.object.fieldName
.object.methodName(argumentList)
<s:property value="setProductName('aaa')" />
<s:property value="productName"/>
访问数组类型的属性
可以使用下标访问数组中指定的元素:color[0]
可以通过调用其length字段查出给定数组中有多少个元素 color.length
访问List类型的属性
与数组的用法类似
可以使用ognl表达式创建一个List,创建一个List与声明一个java数组是相同的{"red","green","blue"}
访问Map类型的属性
希望获取map中的某个值时:map[key]
size或size() 可以获取map中的键值对个数
isEmpty ,isEmpty()
可以使用以下的方法创建一个map
#{key1:value1,key2:value2...}
声明式异常处理
exception-mapping元素 :配置当前action的声明式异常 配置在action节点中result之前
有两个属性 result:指定一个响应结果,该结果将在捕获到指定异常时执行,既可以来自当前的action声明,也可以来自global-results声明
exception:指定要捕获的异常
可以通过 global-exception-mappings元素为应用程序提供一个全局性的异常捕获映射,但在global-exception-mappings元素下声明的任何exception-mapping元素
只能引用在global-results元素下声明的某个result元素
eg:<global-results> global-results元素必须要在global-exception-mappings这个元素的之前
<result name="input"></result>
</global-results>
<global-exception-mappings>
<exception-mapping result="input" exception="异常的全类名"></exception-mapping>
</global-exception-mappings>
在异常发生的时候会在ValueStack中添加两个对象
exception
exceptionStack
可以在视图上通过<s:property>标签显示异常信息
Struts2自动的把Action对象放入到值栈中
放入的时间点为:struts2终将调用Action类的Action方法。但在调用该方法之前:
1.先创建了一个叫StrutsActionProxy 对象,
2.在创建StrutsActionProxy 之后,对其进行初始化时候,把Action对象放入了值栈中
通用标签
> s:property 打印值栈中的属性的值
对于对象栈,打印值栈中对象的属性值
对应Map栈,打印request,session,application 的某个属性值或某个请求参数的值
在许多情况下如果只是打印的话 用EL的话更简单点
> s:url标签 常用的属性 action method namespace value var
eg:<s:url value="/getProduct" var="url">
<s:param>元素下的value属性中的值 会自动ognl 解析 也就是在值栈中找到对应的值
<s:param name="productId" value="productId"></s:param>
</s:url>
${url } 这个EL表达式可以输出 对应的 contentPath/getProduct?productId=id (id为解析出来的值)
<s:url value="/getProduct" var="url">
<s:param>元素下的value属性中的值 如果不希望被ognl解析 加单引号
<s:param name="productId" value="'abc'"></s:param>
</s:url>
构建一个请求action的地址
<s:url action="testAction" namespace="/hello" method="save" var="url"></s:url>
获取到请求参数的值
<s:url value="/getProduct" var="url" includeParams="all"></s:url>
>s:set
set标签用来在以下Map对象里创建一个键值对
ValueStack值栈的ContextMap值栈
Map类型的session对象
Map类型的application对象
Map类型的request对象
Map类型的page对象
对value属性值自动的进行OGNL解析
<s:set name="productName" value="Intel" scope="request"></s:set>
>s:push 把一个对象在标签开始是压入值栈,标签结束的时候弹出值栈。
<s:push value="">
在这里写压入值栈的值
</s:push>
>s:if s:else和s:elseif 标签 test的值也是可以使值栈解析获取到price的值
<s:if test="price>1000">
....
</s:if>
<s:elseif test="price>800">
....
</s:elseif>
<s:else>
....
</s:else>
>s:iterator 用来遍历一个数组,collection,map 并把这个可遍历的对象里的每一个元素一次压入和弹出值栈
<s:iterator value="#request.persons"> 如果persons这个集合在对象栈中 value=persons 就行
${name } ${age} //这里直接通过EL获取person对象中的属性
</s:iterator>
加另一个属性 status="status" 可以在标签内 写入
${status.index } 和${status.count } 这两个是一个序列 index是从0开始 count是从1开始
>s:sort 进行排序的
<s:sort comparator="#request.comparator" source="" var="persons"></s:sort>
comparator的值时一个实现了comparator接口的类
>s:date
<s:date name="#session.date" format="yyyy-mm-dd" var="date2"/>
${date2}
>s:a %{} 表示强制进行ognl解析
<s:a href="psersonAction.action?name=%{name }">${name}</s:a>
表单标签
使用表单标签的优点
>表单会显
>对页面进行布局和排版
和html中的form差不多
s:form 会自动进行排版
<s:form action="" method="post(默认值)">
<s:hidden name="userId"></s:hidden>
<s:textfield name="userName" label="UserName"></s:textfield>
<s:password name="pasword" label="Password"></s:password>
<s:textarea name="desc" label="Desc"></s:textarea>
<s:submit></s:submit>
</s:form>
s:form是如何回显的
是把栈顶对象的属性直接显示出来的, 若栈顶没有对应的值 向下查找匹配
<s:checkbox> 用于提交一个布尔值 并不是 html表单标签中的多选框
<s:checkbox name="married" label="婚否"></s:checkbox> 相对于html中的checkbox隐藏了一个hidden标签
在html的checkbox中如果没有勾选是不会提交的 而 struts中的checkbox 由于有了这个隐藏的标签 没有提交的时候会提交一个false
对于struts下的radio select checkboxlist 的几个重要的属性 list listKey listValue
<s:radio name="sex" list="#{'1':'man','2':'woman'}" label="性别"></s:radio>
listKey 是city类中的cityId属性名 listValue 也是对应city类中的属性
<s:checkboxlist name="city" list="#request.citys" listKey="cityId" listValue="cityName" label="City"></s:checkboxlist>
在使用checkboxlist时 服务端需要使用list类型 以保证数据能够正常的回显
radio checkboxlist list属性的值 要以#开头
Struts2 主题
simple 把标签翻译成最简单的HTML对应的元素
xhtml 是默认的主题
css_xhtml 与默认的相似 这个可以进行css进行排版
ajax 增加了一些ajax的功能
通过theme="" 来修改主题 可以为整个表单 或者某一个元素
在page,request,session,application 中添加一个theme属性
修改Struts.properties文件中的struts.ui.theme 属性
Struts2 CRUD操作中的一些拦截器
Params拦截器
这个拦截器讲把表单中字段映射到ValueStack栈的栈顶对象的各个属性,如果某个字段在模型里边没有匹配的属性
再匹配值栈中的下一个对象
ModelDriven拦截器 把action和model隔离开 (在做save操作时候)
在action类中实现ModelDriven<object>接口,重写getModel方法 后 将把返回的对象放置于 值栈的栈顶
实现接口后 在action类中 加上一个object作为action中的一个属性
重写getModel方法
public Object getModel(){
obj = new Object();
return obj;
}
在struts-default.xml 文件中的默认的拦截器栈 ModelDriven拦截器在Params拦截器之前执行
所以在这么写后执行 在页面上 栈顶的队形已经变为object对象 而不是action对象
运行流程
先回执行ModelDrivenIntercptor中的intercept
首先先获取action对象,此时的action对象应该是已经实现了ModelDriven拦截器的
再判断action是不是ModelDriven的实例
是的话 强制转换为 ModelDriven类型
获取值栈
调用ModelDriven接口的getModel()
再把返回的对象置于栈顶
执行ParametersInterceptor 的 intercept 方法:把请求参数的值赋给栈顶对象对应的属性,若栈顶对象没有对应的属性,则查询
值栈中下一个对象对应的属性
注意 :
getModel 不能提供一下实现 这样写 当前action的成员变量 obj 却是null;
public object getModel(){
return new Object();
}
在做update的时候
从数据库中根据id获取到的对象赋给action类中的成员变量(原来是栈顶对象) 经过赋值后就不是栈顶对象了,必须要重新压栈
eg: employee = dao.get(employee.getEmployeeId()); 这样是不能实现回显的
可以这样 ActionContext.getContext().getValueStack().push(dao.get(employee.getEmployeeId()));
这样写的话浪费内存 因为这样压栈的话前两个对象都是 employee对象 第一个对象属性值都有 第二个只有id
合理的方法是在getModel()中进行一个判断 判断是save 还是update
若是save 则 object = new Object();
若是update 则 object = dao.get(object.objectId());
这样的话只能通过判断时候 有id 来判断是哪一个操作
所以就还需要给id 复制 那么 就需要经过 params拦截器
这时候有struts提供了一种paramsPrepareParamsStack 拦截器栈实现 那么就需要在struts.xml 中配置这个拦截器栈
在package节点下<default-interceptor-ref name="paramsPrepareParamsStack "></default-interceptor-ref>
在这个拦截器中 先进行 params拦截器 然后是 ModelDriven拦截器 最后是params 拦截器
public object getModel(){
if(objectId==null){
object = new Object();
}else{
object = dao.get(object.objectId());
}
return object;
}
使用paramsPrepareParamsStack拦截器栈后的流程 paramsPrepareParamsStack和defaultStack 都是拦截器栈 默认使用的是后者
paramsPrepareParamsStack 拦截器栈
用法在与 params-> modelDriven->params
所以可以先把请求参数赋给action类对应的属性,再根据赋给action的那个属性值决定压到值栈栈顶的对象,最后再为栈顶对象的属性赋值
问题:在执行delete操作的时候 id不为null getModel却加载了一个对象,不该加载
在执行list 方法的时候 由于object 为 null 所以 getModel也会加载 一个空的对象 浪费空间
解决方案 :使用 PrepareInterceptor和Prepareable 接口
给action类 实现 Prepareable 接口 重写prepare()方法
prepare方法主要是用来为getModel方法准备model的
关于 PrepareInterceptor
首先获取Action实例
判断是否实现了Prepareable接口
根据当前拦截器的firstCallPrepareDo(默认为false) 属性确定prefixes数组
若为false prefixes[prepare,prepareDo]
再调用前缀方法 PrefixMethodInvocationUtil.invokePrefixMethod(invocation,prefixes);
根据当前拦截器的alwaysInvokePrepare(默认为true) 决定是否调用Action类中的prepare方法
PrefixMethodInvocationUtil.invokePrefixMethod(invocation,prefixes); 这个方法
获取action实例
获取要调用action方法的名字
获取前缀方法 getPrefixedMethod(prefixes,methodName,action);
若方法不为空,则通过反射调用前缀方法
PrefixMethodInvocationUtil.getPrefixedMethod(prefixes,methodName,action); 这个方法
先把方法的首字母变为大写
遍历前缀数组 通过拼接的方式 得到前缀方法名
利用反射从action中获取从action中获取对应的方法 ,若有则直接返回 并结束循环
若 action实现了Prepareable接口, 则Struts将尝试执行prepare[ActionMethodName]方法,
如果prepare[ActionMethodName]方法不存在,则将尝试执行prepareDo[ActionMethodName]方法
若都不存在就不执行了
若PrepareInterceptor 的alwaysInvokePrepare属性为false,则struts2将不会调用实现了Prepareable接口的Action的prepare()方法
那么 问题的解决方法
可以为每一个ActionMethod准备prepare[ActionMethodName]方法,而抛弃原来的prepare()方法 就是在有些方法下 给出一个prepareSave()或prepareUpdate()等方法
将PrepareInterceptor的alwaysInvokePrepare属性置为false,避免调用prepare() 方法
如何在配置文件中为 PrepareInterceptor 拦截器栈的alwaysInvokePrepare属性置为false
<interceptors>
<interceptor-stack name="myStack">
<interceptor-ref name="paramsPrepareParamsStack">
<param name="prepare.alwaysInvokePrepare">false</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="myStack"/>
拦截器只能完成字符串和基本数据类型的转换
类型转换
类型转换出错的时候转到哪个页面
显示错误的信息
类型转换错误
若Action类没有实现ValidationAware接口:继续调用其方法,好像什么都没有发生
若Action实现了ValidationAware接口:不会在继续调用action的方法,检查相关action元素的声明是否包含着一个name=input的result
如果有把控制权交给result元素 ,如果没有就抛出一个异常
通常是继承ActionSupport的方式 间接的实现 ValidationAware接口
如何覆盖默认的错误消息?
1.在对应的Action类所在的包中新建 ActionClassName.preperties文件,ActionClassName即为包含着输入字段的Action类的全类名
2.在属性文件中添加键值对:invalid.fieldvalue.fieldName=xxx xxx表示自定义的错误消息
如果是simple主题,还会自动的显示错误消息吗?如果不会显示,该怎么办?
simple主题不能显示
怎么显示呢 ?
1.通过debug标签 可以知道在值栈的Action(实现了ValidationAware接口)对象中有一个fieldErrors属性,该属性的类型为
Map<String,List<String>> 键:字段名(属性名) 值:错误消息组成的List 所以可以使用EL或者OGNL的方式 显示错误
消息 eg:${fieldErrors.age[0] }
2.还可以使用s:fielderror 标签来显示。可以通过fieldName属性显示指定字段的错误
3.若是simple主题,并且使用了<s:fielderror >标签来显示错误信息,则该信息在一个 ul,li,span 中,如何除去ul,li,span呢
在template.simple 下面的fielderror.ftl 定义了simple主题下,s:fielderror 标签显示错误信息的样式。
所以修改该配置文件 , 在src下新建template.simple 包,新建fielderror.ftl 文件 把原生的复制进来 剔除ul,li,span
如何定义自己类型转换器?
为什么需要自定义呢?struts不能完成字符串到引用类型的转换
如何定义呢?
1.开发类型转换器的类 :继承StrutsTypeConverter 类
2.配置类型转换器
两种方式
>基于字段的配置 局部转换器
在字段所在的Model(可能是Action或JavaBean)的包下,新建一个ModelClassName-converter.preperties文件
在该文件中输入键值对:fieldName=类型转换器的全类名。
第一次使用该转换器创建一个实例
>基于类型的配置 全局的转换器
在src下新建xwork-conversion.preperties
在文件中输入待转换的类型=转换类型的全类名
在应用加载的时候创建了两个实例
扩展StrutsTypeConverter 类
自定义一个类 继承StrutsTypeConverter
public class MyConverter extends StrutsTypeConverter{
private DateFormat dateFormat;
public MyConverter(){
//dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
类型转换不要放在构造方法中 因为在基于类型的配置时候给转换器创建实例的时候是在struts应用加载之前
如果 时间的格式写在web.xml文件中 那么基于类型的配置的时间的格式是获取不到的 应用这个类实例化是在应用加载前
}
public DateFormat getDateFormat(){
if(dateFormat == null){
dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
}
return dateFormat;
}
public Object convertFromString(Map context,String[] values,class toclass){
if(toclass == Date.class){
if(values!=null&&values.length>0){
String value= values[0];
return getDateFormat().parseObject(value);
}
}
return values;
}
public String convertToString(Map context,Object o){
if(o instanceof Date){
Date date = (Date)o;
return getDateFormat().format(date);
}
return null;
}
}
类型转换 与 复杂属性的配合使用
例子 : 两个 Model Department(id,deptname,mgr) 和 Manager(name,date)
1.在页面deptname可以直接录入 那么 mgr的属性 该如何录入呢 ?
struts2的表单标签的name属性可以被赋为属性的属性name=mgr.name name=mgr.date
2.mgr中有一个Date类型的date ,Struts2可以进行自动转换吗 ?
全局的类型转换器 还是可以正常工作的
Struts2的国际化
1.如何配置国际化资源文件
>Action 范围资源文件:在Action类文件所在的路径简历名为
ActionName_language_country.properties的文件
>包范围资源文件:在包的根路径下建立文件名为
package_language_country.properties 的属性文件
一旦建立,处于该包下的所有Action都可以访问该资源文件。
注意:包范围资源文件的 baseName 及时package 不是Action所在的包名
>全局资源文件
命名方式:basename_language_country.properties
struts.xml <constant name="struts.custom.i18n.resources" value="baseName"/>
国际化资源加载的顺序 : 离action 较近的将优先加载
2.如何在页面上和Action类中访问国际化资源文件的value值
在Action类中,若action实现了TextProvider接口,则可以调用其getText()方法获取value值
通过继承ActionSupport的方式实现 TextProvider接口
在页面上可以使用s:text标签;对于表单标签可以使用表单标签的key属性值
key属性值直接在资源文件中找对应的值
利用占位符,则可以使用s:text标签的s:param子标签来填充占位符(time=Time:{0})0就是占位符
可以利用标签和ognl表达式直接访问值栈中的属性值(对象栈和Map栈)
页面上的label属性使用%{getText('username')} 的方式也可以从国际化资源中获取到值
是因为此时在对象栈中有DefaultTextProvider的一个实例,该对象中提供了访问国际化资源文件的getText()方法
同时还需要通知Struts2框架 label中放入的已经不在是一个指定的字符串,而是一个OGNL表达式 使用 ${}包起来进行强制解析
页面上如果是simple的主题 需要添加<s:text name="username"> 这样的标签 就能实现国际化
有关占位符访问的
资源文件中的内容 time=Time:{0}
页面的代码 <s:text name="time">
<s:param value="date"></s:param>
</s:text>
time=Time:${date}
<s:text name="time"/>
通过超链接实现国际化
关键在于struts2框架是如何确定Locale对象的 可以通过I18N拦截器知道
流程 :>i18n拦截器在执行action方法之前,自动查找请求中一个名为request_locale的参数。
如果参数存在拦截器就将其作为参数转换成Locale对象,并将其设为用户默认的Locale,
并把其设为session的WW_TRANS_I18N_LOCALE属性
>若request没有名为request_locale的参数,则i18n拦截器会从session中获取WW_TRANS_I18N_LOCALE的属性值
若该值不为空,则将该属性值设为浏览器默认的Locale
>若Session中的WW_TRANS_I18N_LOCALE属性值为空,则从ActionContext中获取Locale对象
具体实现只要在超链接的后面加上request_locale=zh_CN(en_US)
注意:超链接只能是Struts2的请求 i18n拦截器只能在struts下工作
Struts2运行流程分析
1.请求发送给StrutsPrepareAndExecuteFilter
2.StrutsPrepareAndExecuteFilter询问ActionMapper:该请求是否是一个Struts请求(即返回一个非空的ActionMapping对象)
3.若ActionMapper认为该请求是一个Struts2的请求,则StrutsPrepareAndExecuteFilter把请求处理交给ActionProxy
4.ActionProxy通过Configuration Manager 询问框架的配置文件,确定需要调用的Action类及action方法
5.ActionProxy创建一个ActionInvocation的实例,并进行初始化,
6.ActionInvocation实例在调用Action的过程前后涉及到相关拦截器的调用
7.Action执行完毕,ActionInvocation负责根据Struts.xml中找到对应的返回结果。调用结构的execute()方法,渲染结果
8.执行各个拦截器invocation.invoke()之后的代码
9.把结果发送到客户端
Struts2的输入验证 主要是服务端的验证
分为两种
1、编程式验证
Action类继承ActionSupport
重写validator方法进行这个类中的公共的校验
如果要对指定的方法校验的话 方法名 必须是validateLogin()
在这两个方法中 使用addFieldError(String key,String value) 来放错误消息
在页面中可以通过 <s:debug>标签来查看错误消息
注意:validator方法中一旦放入错误信息,程序就不会进入原申请的action方法中去 直接返回一个input的字符串(在struts的配置文件中配置一个全局的result name="input")
2、声明式验证
对哪个action或者model的哪个字段进行验证
使用声明验证规则
如果验证失败,转向哪个页面,显示什么消息
声明式验证的一般流程
1.先明确对哪个action的哪一个字段进行验证
2.编写配置文件
> 把struts-2.3.15.3\apps\struts2-blank\WEB-INF\classes\example下的Login-validation.xml文件复制到当前的Action所在的包下
> 把该配置文件名改为 :把Login改为当前Action的名字
> 编写验证规则:参见E:\SSH\Struts\struts-2.3.15.3-all\struts-2.3.15.3\docs\WW\docs\validation.html 文档
> 在配置文件中可以配置错误消息的
<field name="age"> 要验证的字段
<field-validator type="int"> 使用的验证规则 叫int
<param name="min">20</param>
<param name="max">50</param>
<message>Age needs to be Between ${min} and ${max} </message>
</field-validator>
</field>
3.若验证失败,则转向input 的那个result 所以需要配置name=input 的result
4.如何显示错误消息呢 ?
若使用的是非simple 主题 错误消息会自动显示
若使用的是simple主题则需要s:fielderror标签 或 直接使用ognl表达式
<s:fielderror fieldName="age"></s:fielderror>
注意 :若一个Action类可以应答多个action请求,多个action请求使用不同的验证规则 怎么办?
为每一个不同的action请求定义其对应的验证文件 : ActionClassName-AliasName-validation.xml
不带别名的配置文件ActionClassName-validation.xml 中的验证规则依然会发生作用。可以把各个action共有的验证规则配置其中,
但只适用某一个action的请求的验证规则就不要在这里配置了
Struts一共有15个内置的验证程序
设置短路验证:若当前验证没有通过,则不再进行下面的验证
<validator />元素和<field-validator /> 元素可以指定一个可选的short-circuit属性,该属性指定该验证器是否是短路验证器,默认为false
若类型转换失败,默认情况下还会执行后面的拦截器,还会进行验证,可以通过修改ConversionErrorInterceptor 源代码的方式使
当类型转换失败时,不再进行后续的拦截器验证,而直接返回input 的result
Object action = invocation.getaction();
if(action instanceof ValidationAware){
ValidationAware va = (ValidationAware)action;
if(va.hasFieldErrors() || va.hasActionErrors()){
return "input";
}
}
非字段验证 expression
<validators type="int">
<validator type="expression">
<param name="expression"><![CDATE[password==password2]]></param>
<message>password must be equal</message>
</validator>
</validators>
显示的时候用 <s:actionerror />
错误消息的重用性 多个字段使用同样的验证规则 能不能使用同一条验证消息
只需要<message>Age needs to be Between ${min} and ${max} </message>改为
<message>${getText(fieldName)} needs to be Between ${min} and ${max} </message>
在i18n文件中
<message>${getText(fieldName)} needs to be Between ${min} and ${max} </message>
age=年龄
count=数量
这样写 就可以显示中文的错误信息
自定义验证器
1.定义一个 验证器的类
>需要实现一个接口Validator
可以选择继承ValidatorSupport或FieldValidatorSupport
若希望实现一个一般的验证器继承ValidatorSupport 若希望实现一个字段验证器,则继承FiledValidatorSupport
2.在配置文件中配置
>默认情况下,Struts2会在类路径的根目录下加载validators.xml文件。在该文件中加载验证器
3.使用
Action传递json对象的时候
首先需要导入Struts2-json-plugin-2.3.8.jar
还有jackson=core-2.2.3.jar
struts.xml文件中的package需要继承json-default
action中对应的result不需要写返回值
result中的type属性需要设置为json
使用Filter作为控制器的MVC POJO普通的一个java类
模型:封装应用程序的数据和业务逻辑 POJO
视图:实现应用程序的信息显示功能 JSP
控制器:接受来自用户的输入,调用模型层,响应对应的视图组件 Servlet Filter
使用Filter 作为控制器可以方便的在应用程序里对所有资源的进行控制访问
Servlet VS Filter
servlet能做的Filter也能做
但是Filter可以拦截资源servlet缺是不行
eg:在一个form表单中action="input.action" action中的参数就是一个servletPath
在自定义的filter中继承 filter 在doFilter中 通过request对象获取servletPath
然后只要进行一个判断字符串是否相等 相等就执行响应的内容 并进行跳转之类的动作
跳转后要有一个 return; 因为在filter中 有个chain.doFilter(); 这个方法会进入到下一个过滤器中过滤 如果没有return 的话会抛异常
Struts2是一个用来开发MVC应用程序的框架,它提供了一些web应用程序开发过程中一些常见的解决方案
对来自用户输入的数据进行合法性验证
统一的布局
可扩展性
国际化和本地化
支持Ajax
表单的重复提交
支持的文件下载
搭建Struts2的环境
1.加jar包 struts\apps\struts2-blank\WEB-INF\lib下多有的jar包
2.在web.xml文件中配置struts2 复制 struts\apps\struts2-blank\WEB-INF\web.xml文件中的过滤器的配置到当前的web.xml 文件中
3.在web应用的classPath下添加 struts2的配置文件 struts.xml 复制struts\apps\struts2-blank\WEB-INF\classes下的struts.xml 到当前的web应用的src目录下
添加DTD提示 http://struts.apache.org/dtds/struts-2.3.dtd 选择的key type 是uri
在struts.xml下的配置
配置Struts可以受理的请求的扩展名
<constant name="struts.action.extension" value="action,do,"> 表示servletPath的扩展名可以使.action .do 或者没有扩展名
关于Struts2请求扩展名的问题
>org.apache.struts2包下的default.properties 中配置了Struts2应用的常量
>struts.action.extension 定义当前Struts2应用可以接受的请求的扩展名
>可以在Struts.xml中一常量配‘置的方式修改default.properties 所配置的常量
<constant name="struts.configuration.xml.reload" value="true"></constant> <!-- 这是配置struts修改了配置不用重启 -->
package: 包 struts 通过使用package来组织模块
name属性:必须 用于其它的包应用当前的包
class属性:默认值com.opensymphony.xwork2.ActionSupport
nnmethod:默认值 execute
extends: 当前包继承哪个包,可以继承其中的所有配置 通常情况下继承 struts-default
extends:struts-default struts-default 这个包 必须是要继承才能使用
namespace: 是可选的 默认值为"/"
若有一个非默认值 则要想要调用这个包中的Action,就必须把这个属性所定义的命名空间添加到有关的uri字符串中
http://localhost:8080/contextPath/namespace/actionName.action
<package name="hello" extends="struts-default">
<action name="prodect-input"> 这个name是Action的虚拟路径
一个action 可以有多个result子节点 用name区分 success是默认的值 name是Action类中方法的返回值
type属性是结果类型默认值为dispatcher(内部跳转) redirect(重定向)-不好用
<result name="success">/WEB-INF/pages/input.jsp</result>
</action>
</package>
struts2 vs 自实现
1.要搭建环境
2.不需要显示的定义Filter 而使用的是Struts2配置文件
3.以前写el表达式的页面变得简单了
Action
action VS Action
action :代表的是一个action struts2请求
Action 类代表能够处理Struts请求的类 。 eg:在实体类中写着一个处理struts2请求的方法
Action 类 属性的名字必须要遵守javaBeans 属性名相同的命名规范 ,属性的类型可以使任意类型,从字符串到非字符串之间的数据转换可以自动发生
必须要有一个无参的构造方法
至少有一个供struts在执行这个action时调用的方法
同一个Action类可以包含多个action方法
struts2会为每一个http请求创建一个新的Action实例
在Action中如何访问Web资源
1.什么是web资源
HttpServletrequest,HttpSession,ServletContext等原生的servlet API
2.为什么访问web资源
B\S的应用的Controller 必然要
3.如何访问
1.和servletAPI解耦的方式:只能访问到有限的servletAPI对象,且只能访问到有限的方法(读取请求参数,读取域对象的属性,使session失效...)
> 使用ActionContext
//先获取ActionContext对象 这个对象是Action的上下文对象 可以冲从中获取到当前Action需要的一切信息
ActionContext actionContext = ActionContext.getContext();
//1.获取application对应的Map ,可以通过map获取属性 也可以设置
Map<String, Object> applicationMap = actionContext.getApplication();
applicationMap.put("applicationKey", "applicationValue");
//2.session
Map<String, Object> sessionMap = actionContext.getSession();
sessionMap.put("sessionKey", "sessionValue");
//3.request
//actionContext中没有提供getRequest 方法来获取request对应的Map 需要手动的调用 get("request")方法 来获取
Map<String, Object> requestMap = (Map<String, Object>) actionContext.get("request");
requestMap.put("requestKey", "requestValue");
//4.获取请求参数对应的Map,并获取指定的参数值
//键:请求参数的名字 值:请求参数对应的字符串数组
//注意:1.getParameters 的返回值为 Map<String, Object> 而不是 Map<String,String[]>
// 2.parameter这个map只能读不能写入数据 如果写入不会报错,也不起作用 只有uri上转发来的才能获取到
Map<String, Object> parameters = actionContext.getParameters();
System.out.println(((String[])parameters.get("name"))[0]);
> 实现XxxAware接口 ApplicationAware,SessionAware,RequestAware,ParameterAware
实现ApplicationAware接口 重写set方法 自己写一个属性 这是一种依赖注入 setApplication方法是struts2调用的
public String execute(){
//向application中添加一个 属性 applicationKey2 applicationValue2
applicationMap.put("applicationKey2", "applicationValue2");
//从 application中获取一个属性
System.out.println(applicationMap.get("date"));
return "success";
}
private Map<String, Object> applicationMap;
public void setApplication(Map<String, Object> applicationMap) {
this.applicationMap = applicationMap;
}
选用的建议 :若一个Action类中有多个action方法,且多个方法都要使用域对象的map或parameters 则建议使用实现Aware接口的方式
注意session对应的Map 实际上是SessionMap类型的 强转后调用invalidate() 方法,可以使session失效
2.和servletAPI耦合的方式:可以访问更多的ServletAPI对象,且可以调用其原生的方法
> 使用ServletActionContext
HttpServletRequest request = ServletActionContext.getRequest();
HttpSession session = ServletActionContext.getRequest().getSession();
ServletContext servletContext = ServletActionContext.getServletContext();
> 实现ServletXxxAware接口
可以由struts2注入 需要的servlet需要的相关对象
ServletRequestAware, 注入ServletRequest对象
ServletContextAware, 注入ServletContext对象
SerlvetResponseAware 注入SerlvetResponse对象 很少使用
ActionSupport
com.opensymphony.xwork2.ActionSupport类 是默认的Action类
在写一个action节点的时候 不写 class默认就是它
在手工完成字段验证,显示错误信息,国际化等情况下推荐继承ActionSupport
Result
是action节点的子节点
代表action方法执行后,可能去的一个目的地
一个action节点可以配置多个result子节点
result的name属性的值对应着action方法可能有的一个返回值
result中的type属性 表示结果的响应类型
type属性的值在struts-default包的result-types节点的name属性中定义
常用的有
>dispatcher(默认的):转发 同servlet中的转发是一样的
>redirect: 重定向 可以重定向到页面 也可以是action
>redirectAction: 重定向到action
<result name="" type="redirectAction">
<param name="actionName">TestAction</param>
<param name="namespace"></param>
</result>
<result name="" type="redirect">TestAction.action</result>
用redirect也可以重定向到action
>chain: 转发到action
<result name="" type="chain">
<param name="actionName">TestAction</param>
<param name="namespace"></param>
</result>
<result name="" >TestAction.action</result>这个是不行的
不能通过一个dispatcher的方式转发到一个action
动态的配置 uri上有!标识
<a href="userAction!addUser">添加</a>
在struts配置文件中 不再需要指定的方法 !后的addUser已经指定了方法
会动态的调用到action类中的方法
通配符映射
<action name="*-*" class="com.zr.action.{1}Action" method="{2}">
<result name="{2}">/{2}.jsp</result>
</action>
<action name="UserAction-*" class="" method="{1}">
<result name="{1}-success">/{1}.jsp</result>
</action>
*表示一端字符串 {1} 表示是* 代表的这段字符串
通配符映射的规则
>若找到多个匹配,没有通配符的胜出 是要找精确的那个
>若指定的动作不存在,Struts将会尝试把这个URI与任何一个包含通配符* 的动作名进行匹配
>被通配符匹配到的URI字符串的子串可以用{1},{2}来引用
>{0} 匹配整个URI
>若Struts找到带有通配符的匹配不止一个, 则按先后顺序匹配
全局结果集 这个几点必须在action节点前 是有顺序的
<global-results>
<result></result>
</global-results>
关于值栈 是在Struts2的过滤器中创建出来的 每经过一次过滤器 都会创建一个新的valueStack
1.在第一个struts的项目中${prodectName} 读取prodectName值,实际上该属性并不存在request等域对象的中,而是从值栈中获取
2.ValueStack
>可以从ActionContext中获取值栈对象
>值栈分为两个逻辑部分
>Map栈:实际上是OglnContext类型,是一个Map,也是对ActionContext的一个引用,里边保存着各种Map
requestMap ,sessionMap,applicationMap,parameterMap,attr
>对象栈:实际上是CompoundRoot类型,是一个使用ArrayList定义的栈,里边保存各种和当前Action实例相关的对象,
是一个数据结构意义的栈。
OGNL
Ognl.getValue(String expression,Map context,Object root);
从ognl中取值的方法
三个参数的时候如果取的是map中的值 要表达式中要加# 取的是object中的时候不用加 直接属性名
如果是两个参数的时候
Ognl.getValue(String expression,?) ?这个位置上可以是一个map类型 也可以是一个object类型
在这个方法中取map中的值的时候表达式中不用加# 因为这时候map已经作为了 根对象
其实 跟对象就是一个键值对 (传三个参数的时候 感觉就和根对象放入了一个map里)
struts2利用s:property 标签和OGNL表达式来读取值栈中的属性值
值栈中的属性值:
> 对于对象栈:对象栈中某一个对象的属性值
> Map栈:request,session,application的一个属性值或一个请求参数的值
>读取对象栈中对象的属性:
若想访问Object Stack 里某个对象的属性,可以使用以下几种形式之一:
object.propertyName; object['propertyName'];object["propertyName"]
Object Stack里的对象可以通过一个从零开始的下标来引用。Object Stack里的栈顶对象可以用[0]来引用
它下面的那个对象可以用[1]引用
eg:[0].message;
[n]的含义是从第n个对象开始搜索,而不是只搜索第n个对象
若从栈顶对象开始搜索,则可以省略下标的部分:message
结合s:property 标签:<s:property value="[0].message"/> 等同于 <s:property value="message"/>
在jsp页面上<%@ taglib prefix="s" uri="/struts-tags" %>
>读取Map栈中的属性值 可以使用一下几种形式
#object.propertyName; #object['propertyName']; #object["propertyName"]
可以利用OGNL调用
任何一个java类的静态字段和方法
被压入到ValueStack栈对象上的公共字段和方法
在默认情况下Struts2不允许调用java类的静态方法 需要重新设置 struts.ohnl.allowStaticMethodAccess 标记变量为 true
<constant name="struts.ohnl.allowStaticMethodAccess" value="true"></constant>
调用静态字段或方法需要使用一下的语法
@fullQualifiedClassName@fieldNames
@fullQualifiedClassName@methodName(argumentList);
调用一个实例字段或方法的语法 , 其中object是Object Stack栈里的某个对象的引用
.object.fieldName
.object.methodName(argumentList)
<s:property value="setProductName('aaa')" />
<s:property value="productName"/>
访问数组类型的属性
可以使用下标访问数组中指定的元素:color[0]
可以通过调用其length字段查出给定数组中有多少个元素 color.length
访问List类型的属性
与数组的用法类似
可以使用ognl表达式创建一个List,创建一个List与声明一个java数组是相同的{"red","green","blue"}
访问Map类型的属性
希望获取map中的某个值时:map[key]
size或size() 可以获取map中的键值对个数
isEmpty ,isEmpty()
可以使用以下的方法创建一个map
#{key1:value1,key2:value2...}
声明式异常处理
exception-mapping元素 :配置当前action的声明式异常 配置在action节点中result之前
有两个属性 result:指定一个响应结果,该结果将在捕获到指定异常时执行,既可以来自当前的action声明,也可以来自global-results声明
exception:指定要捕获的异常
可以通过 global-exception-mappings元素为应用程序提供一个全局性的异常捕获映射,但在global-exception-mappings元素下声明的任何exception-mapping元素
只能引用在global-results元素下声明的某个result元素
eg:<global-results> global-results元素必须要在global-exception-mappings这个元素的之前
<result name="input"></result>
</global-results>
<global-exception-mappings>
<exception-mapping result="input" exception="异常的全类名"></exception-mapping>
</global-exception-mappings>
在异常发生的时候会在ValueStack中添加两个对象
exception
exceptionStack
可以在视图上通过<s:property>标签显示异常信息
Struts2自动的把Action对象放入到值栈中
放入的时间点为:struts2终将调用Action类的Action方法。但在调用该方法之前:
1.先创建了一个叫StrutsActionProxy 对象,
2.在创建StrutsActionProxy 之后,对其进行初始化时候,把Action对象放入了值栈中
通用标签
> s:property 打印值栈中的属性的值
对于对象栈,打印值栈中对象的属性值
对应Map栈,打印request,session,application 的某个属性值或某个请求参数的值
在许多情况下如果只是打印的话 用EL的话更简单点
> s:url标签 常用的属性 action method namespace value var
eg:<s:url value="/getProduct" var="url">
<s:param>元素下的value属性中的值 会自动ognl 解析 也就是在值栈中找到对应的值
<s:param name="productId" value="productId"></s:param>
</s:url>
${url } 这个EL表达式可以输出 对应的 contentPath/getProduct?productId=id (id为解析出来的值)
<s:url value="/getProduct" var="url">
<s:param>元素下的value属性中的值 如果不希望被ognl解析 加单引号
<s:param name="productId" value="'abc'"></s:param>
</s:url>
构建一个请求action的地址
<s:url action="testAction" namespace="/hello" method="save" var="url"></s:url>
获取到请求参数的值
<s:url value="/getProduct" var="url" includeParams="all"></s:url>
>s:set
set标签用来在以下Map对象里创建一个键值对
ValueStack值栈的ContextMap值栈
Map类型的session对象
Map类型的application对象
Map类型的request对象
Map类型的page对象
对value属性值自动的进行OGNL解析
<s:set name="productName" value="Intel" scope="request"></s:set>
>s:push 把一个对象在标签开始是压入值栈,标签结束的时候弹出值栈。
<s:push value="">
在这里写压入值栈的值
</s:push>
>s:if s:else和s:elseif 标签 test的值也是可以使值栈解析获取到price的值
<s:if test="price>1000">
....
</s:if>
<s:elseif test="price>800">
....
</s:elseif>
<s:else>
....
</s:else>
>s:iterator 用来遍历一个数组,collection,map 并把这个可遍历的对象里的每一个元素一次压入和弹出值栈
<s:iterator value="#request.persons"> 如果persons这个集合在对象栈中 value=persons 就行
${name } ${age} //这里直接通过EL获取person对象中的属性
</s:iterator>
加另一个属性 status="status" 可以在标签内 写入
${status.index } 和${status.count } 这两个是一个序列 index是从0开始 count是从1开始
>s:sort 进行排序的
<s:sort comparator="#request.comparator" source="" var="persons"></s:sort>
comparator的值时一个实现了comparator接口的类
>s:date
<s:date name="#session.date" format="yyyy-mm-dd" var="date2"/>
${date2}
>s:a %{} 表示强制进行ognl解析
<s:a href="psersonAction.action?name=%{name }">${name}</s:a>
表单标签
使用表单标签的优点
>表单会显
>对页面进行布局和排版
和html中的form差不多
s:form 会自动进行排版
<s:form action="" method="post(默认值)">
<s:hidden name="userId"></s:hidden>
<s:textfield name="userName" label="UserName"></s:textfield>
<s:password name="pasword" label="Password"></s:password>
<s:textarea name="desc" label="Desc"></s:textarea>
<s:submit></s:submit>
</s:form>
s:form是如何回显的
是把栈顶对象的属性直接显示出来的, 若栈顶没有对应的值 向下查找匹配
<s:checkbox> 用于提交一个布尔值 并不是 html表单标签中的多选框
<s:checkbox name="married" label="婚否"></s:checkbox> 相对于html中的checkbox隐藏了一个hidden标签
在html的checkbox中如果没有勾选是不会提交的 而 struts中的checkbox 由于有了这个隐藏的标签 没有提交的时候会提交一个false
对于struts下的radio select checkboxlist 的几个重要的属性 list listKey listValue
<s:radio name="sex" list="#{'1':'man','2':'woman'}" label="性别"></s:radio>
listKey 是city类中的cityId属性名 listValue 也是对应city类中的属性
<s:checkboxlist name="city" list="#request.citys" listKey="cityId" listValue="cityName" label="City"></s:checkboxlist>
在使用checkboxlist时 服务端需要使用list类型 以保证数据能够正常的回显
radio checkboxlist list属性的值 要以#开头
Struts2 主题
simple 把标签翻译成最简单的HTML对应的元素
xhtml 是默认的主题
css_xhtml 与默认的相似 这个可以进行css进行排版
ajax 增加了一些ajax的功能
通过theme="" 来修改主题 可以为整个表单 或者某一个元素
在page,request,session,application 中添加一个theme属性
修改Struts.properties文件中的struts.ui.theme 属性
Struts2 CRUD操作中的一些拦截器
Params拦截器
这个拦截器讲把表单中字段映射到ValueStack栈的栈顶对象的各个属性,如果某个字段在模型里边没有匹配的属性
再匹配值栈中的下一个对象
ModelDriven拦截器 把action和model隔离开 (在做save操作时候)
在action类中实现ModelDriven<object>接口,重写getModel方法 后 将把返回的对象放置于 值栈的栈顶
实现接口后 在action类中 加上一个object作为action中的一个属性
重写getModel方法
public Object getModel(){
obj = new Object();
return obj;
}
在struts-default.xml 文件中的默认的拦截器栈 ModelDriven拦截器在Params拦截器之前执行
所以在这么写后执行 在页面上 栈顶的队形已经变为object对象 而不是action对象
运行流程
先回执行ModelDrivenIntercptor中的intercept
首先先获取action对象,此时的action对象应该是已经实现了ModelDriven拦截器的
再判断action是不是ModelDriven的实例
是的话 强制转换为 ModelDriven类型
获取值栈
调用ModelDriven接口的getModel()
再把返回的对象置于栈顶
执行ParametersInterceptor 的 intercept 方法:把请求参数的值赋给栈顶对象对应的属性,若栈顶对象没有对应的属性,则查询
值栈中下一个对象对应的属性
注意 :
getModel 不能提供一下实现 这样写 当前action的成员变量 obj 却是null;
public object getModel(){
return new Object();
}
在做update的时候
从数据库中根据id获取到的对象赋给action类中的成员变量(原来是栈顶对象) 经过赋值后就不是栈顶对象了,必须要重新压栈
eg: employee = dao.get(employee.getEmployeeId()); 这样是不能实现回显的
可以这样 ActionContext.getContext().getValueStack().push(dao.get(employee.getEmployeeId()));
这样写的话浪费内存 因为这样压栈的话前两个对象都是 employee对象 第一个对象属性值都有 第二个只有id
合理的方法是在getModel()中进行一个判断 判断是save 还是update
若是save 则 object = new Object();
若是update 则 object = dao.get(object.objectId());
这样的话只能通过判断时候 有id 来判断是哪一个操作
所以就还需要给id 复制 那么 就需要经过 params拦截器
这时候有struts提供了一种paramsPrepareParamsStack 拦截器栈实现 那么就需要在struts.xml 中配置这个拦截器栈
在package节点下<default-interceptor-ref name="paramsPrepareParamsStack "></default-interceptor-ref>
在这个拦截器中 先进行 params拦截器 然后是 ModelDriven拦截器 最后是params 拦截器
public object getModel(){
if(objectId==null){
object = new Object();
}else{
object = dao.get(object.objectId());
}
return object;
}
使用paramsPrepareParamsStack拦截器栈后的流程 paramsPrepareParamsStack和defaultStack 都是拦截器栈 默认使用的是后者
paramsPrepareParamsStack 拦截器栈
用法在与 params-> modelDriven->params
所以可以先把请求参数赋给action类对应的属性,再根据赋给action的那个属性值决定压到值栈栈顶的对象,最后再为栈顶对象的属性赋值
问题:在执行delete操作的时候 id不为null getModel却加载了一个对象,不该加载
在执行list 方法的时候 由于object 为 null 所以 getModel也会加载 一个空的对象 浪费空间
解决方案 :使用 PrepareInterceptor和Prepareable 接口
给action类 实现 Prepareable 接口 重写prepare()方法
prepare方法主要是用来为getModel方法准备model的
关于 PrepareInterceptor
首先获取Action实例
判断是否实现了Prepareable接口
根据当前拦截器的firstCallPrepareDo(默认为false) 属性确定prefixes数组
若为false prefixes[prepare,prepareDo]
再调用前缀方法 PrefixMethodInvocationUtil.invokePrefixMethod(invocation,prefixes);
根据当前拦截器的alwaysInvokePrepare(默认为true) 决定是否调用Action类中的prepare方法
PrefixMethodInvocationUtil.invokePrefixMethod(invocation,prefixes); 这个方法
获取action实例
获取要调用action方法的名字
获取前缀方法 getPrefixedMethod(prefixes,methodName,action);
若方法不为空,则通过反射调用前缀方法
PrefixMethodInvocationUtil.getPrefixedMethod(prefixes,methodName,action); 这个方法
先把方法的首字母变为大写
遍历前缀数组 通过拼接的方式 得到前缀方法名
利用反射从action中获取从action中获取对应的方法 ,若有则直接返回 并结束循环
若 action实现了Prepareable接口, 则Struts将尝试执行prepare[ActionMethodName]方法,
如果prepare[ActionMethodName]方法不存在,则将尝试执行prepareDo[ActionMethodName]方法
若都不存在就不执行了
若PrepareInterceptor 的alwaysInvokePrepare属性为false,则struts2将不会调用实现了Prepareable接口的Action的prepare()方法
那么 问题的解决方法
可以为每一个ActionMethod准备prepare[ActionMethodName]方法,而抛弃原来的prepare()方法 就是在有些方法下 给出一个prepareSave()或prepareUpdate()等方法
将PrepareInterceptor的alwaysInvokePrepare属性置为false,避免调用prepare() 方法
如何在配置文件中为 PrepareInterceptor 拦截器栈的alwaysInvokePrepare属性置为false
<interceptors>
<interceptor-stack name="myStack">
<interceptor-ref name="paramsPrepareParamsStack">
<param name="prepare.alwaysInvokePrepare">false</param>
</interceptor-ref>
</interceptor-stack>
</interceptors>
<default-interceptor-ref name="myStack"/>
拦截器只能完成字符串和基本数据类型的转换
类型转换
类型转换出错的时候转到哪个页面
显示错误的信息
类型转换错误
若Action类没有实现ValidationAware接口:继续调用其方法,好像什么都没有发生
若Action实现了ValidationAware接口:不会在继续调用action的方法,检查相关action元素的声明是否包含着一个name=input的result
如果有把控制权交给result元素 ,如果没有就抛出一个异常
通常是继承ActionSupport的方式 间接的实现 ValidationAware接口
如何覆盖默认的错误消息?
1.在对应的Action类所在的包中新建 ActionClassName.preperties文件,ActionClassName即为包含着输入字段的Action类的全类名
2.在属性文件中添加键值对:invalid.fieldvalue.fieldName=xxx xxx表示自定义的错误消息
如果是simple主题,还会自动的显示错误消息吗?如果不会显示,该怎么办?
simple主题不能显示
怎么显示呢 ?
1.通过debug标签 可以知道在值栈的Action(实现了ValidationAware接口)对象中有一个fieldErrors属性,该属性的类型为
Map<String,List<String>> 键:字段名(属性名) 值:错误消息组成的List 所以可以使用EL或者OGNL的方式 显示错误
消息 eg:${fieldErrors.age[0] }
2.还可以使用s:fielderror 标签来显示。可以通过fieldName属性显示指定字段的错误
3.若是simple主题,并且使用了<s:fielderror >标签来显示错误信息,则该信息在一个 ul,li,span 中,如何除去ul,li,span呢
在template.simple 下面的fielderror.ftl 定义了simple主题下,s:fielderror 标签显示错误信息的样式。
所以修改该配置文件 , 在src下新建template.simple 包,新建fielderror.ftl 文件 把原生的复制进来 剔除ul,li,span
如何定义自己类型转换器?
为什么需要自定义呢?struts不能完成字符串到引用类型的转换
如何定义呢?
1.开发类型转换器的类 :继承StrutsTypeConverter 类
2.配置类型转换器
两种方式
>基于字段的配置 局部转换器
在字段所在的Model(可能是Action或JavaBean)的包下,新建一个ModelClassName-converter.preperties文件
在该文件中输入键值对:fieldName=类型转换器的全类名。
第一次使用该转换器创建一个实例
>基于类型的配置 全局的转换器
在src下新建xwork-conversion.preperties
在文件中输入待转换的类型=转换类型的全类名
在应用加载的时候创建了两个实例
扩展StrutsTypeConverter 类
自定义一个类 继承StrutsTypeConverter
public class MyConverter extends StrutsTypeConverter{
private DateFormat dateFormat;
public MyConverter(){
//dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
类型转换不要放在构造方法中 因为在基于类型的配置时候给转换器创建实例的时候是在struts应用加载之前
如果 时间的格式写在web.xml文件中 那么基于类型的配置的时间的格式是获取不到的 应用这个类实例化是在应用加载前
}
public DateFormat getDateFormat(){
if(dateFormat == null){
dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
}
return dateFormat;
}
public Object convertFromString(Map context,String[] values,class toclass){
if(toclass == Date.class){
if(values!=null&&values.length>0){
String value= values[0];
return getDateFormat().parseObject(value);
}
}
return values;
}
public String convertToString(Map context,Object o){
if(o instanceof Date){
Date date = (Date)o;
return getDateFormat().format(date);
}
return null;
}
}
类型转换 与 复杂属性的配合使用
例子 : 两个 Model Department(id,deptname,mgr) 和 Manager(name,date)
1.在页面deptname可以直接录入 那么 mgr的属性 该如何录入呢 ?
struts2的表单标签的name属性可以被赋为属性的属性name=mgr.name name=mgr.date
2.mgr中有一个Date类型的date ,Struts2可以进行自动转换吗 ?
全局的类型转换器 还是可以正常工作的
Struts2的国际化
1.如何配置国际化资源文件
>Action 范围资源文件:在Action类文件所在的路径简历名为
ActionName_language_country.properties的文件
>包范围资源文件:在包的根路径下建立文件名为
package_language_country.properties 的属性文件
一旦建立,处于该包下的所有Action都可以访问该资源文件。
注意:包范围资源文件的 baseName 及时package 不是Action所在的包名
>全局资源文件
命名方式:basename_language_country.properties
struts.xml <constant name="struts.custom.i18n.resources" value="baseName"/>
国际化资源加载的顺序 : 离action 较近的将优先加载
2.如何在页面上和Action类中访问国际化资源文件的value值
在Action类中,若action实现了TextProvider接口,则可以调用其getText()方法获取value值
通过继承ActionSupport的方式实现 TextProvider接口
在页面上可以使用s:text标签;对于表单标签可以使用表单标签的key属性值
key属性值直接在资源文件中找对应的值
利用占位符,则可以使用s:text标签的s:param子标签来填充占位符(time=Time:{0})0就是占位符
可以利用标签和ognl表达式直接访问值栈中的属性值(对象栈和Map栈)
页面上的label属性使用%{getText('username')} 的方式也可以从国际化资源中获取到值
是因为此时在对象栈中有DefaultTextProvider的一个实例,该对象中提供了访问国际化资源文件的getText()方法
同时还需要通知Struts2框架 label中放入的已经不在是一个指定的字符串,而是一个OGNL表达式 使用 ${}包起来进行强制解析
页面上如果是simple的主题 需要添加<s:text name="username"> 这样的标签 就能实现国际化
有关占位符访问的
资源文件中的内容 time=Time:{0}
页面的代码 <s:text name="time">
<s:param value="date"></s:param>
</s:text>
time=Time:${date}
<s:text name="time"/>
通过超链接实现国际化
关键在于struts2框架是如何确定Locale对象的 可以通过I18N拦截器知道
流程 :>i18n拦截器在执行action方法之前,自动查找请求中一个名为request_locale的参数。
如果参数存在拦截器就将其作为参数转换成Locale对象,并将其设为用户默认的Locale,
并把其设为session的WW_TRANS_I18N_LOCALE属性
>若request没有名为request_locale的参数,则i18n拦截器会从session中获取WW_TRANS_I18N_LOCALE的属性值
若该值不为空,则将该属性值设为浏览器默认的Locale
>若Session中的WW_TRANS_I18N_LOCALE属性值为空,则从ActionContext中获取Locale对象
具体实现只要在超链接的后面加上request_locale=zh_CN(en_US)
注意:超链接只能是Struts2的请求 i18n拦截器只能在struts下工作
Struts2运行流程分析
1.请求发送给StrutsPrepareAndExecuteFilter
2.StrutsPrepareAndExecuteFilter询问ActionMapper:该请求是否是一个Struts请求(即返回一个非空的ActionMapping对象)
3.若ActionMapper认为该请求是一个Struts2的请求,则StrutsPrepareAndExecuteFilter把请求处理交给ActionProxy
4.ActionProxy通过Configuration Manager 询问框架的配置文件,确定需要调用的Action类及action方法
5.ActionProxy创建一个ActionInvocation的实例,并进行初始化,
6.ActionInvocation实例在调用Action的过程前后涉及到相关拦截器的调用
7.Action执行完毕,ActionInvocation负责根据Struts.xml中找到对应的返回结果。调用结构的execute()方法,渲染结果
8.执行各个拦截器invocation.invoke()之后的代码
9.把结果发送到客户端
Struts2的输入验证 主要是服务端的验证
分为两种
1、编程式验证
Action类继承ActionSupport
重写validator方法进行这个类中的公共的校验
如果要对指定的方法校验的话 方法名 必须是validateLogin()
在这两个方法中 使用addFieldError(String key,String value) 来放错误消息
在页面中可以通过 <s:debug>标签来查看错误消息
注意:validator方法中一旦放入错误信息,程序就不会进入原申请的action方法中去 直接返回一个input的字符串(在struts的配置文件中配置一个全局的result name="input")
2、声明式验证
对哪个action或者model的哪个字段进行验证
使用声明验证规则
如果验证失败,转向哪个页面,显示什么消息
声明式验证的一般流程
1.先明确对哪个action的哪一个字段进行验证
2.编写配置文件
> 把struts-2.3.15.3\apps\struts2-blank\WEB-INF\classes\example下的Login-validation.xml文件复制到当前的Action所在的包下
> 把该配置文件名改为 :把Login改为当前Action的名字
> 编写验证规则:参见E:\SSH\Struts\struts-2.3.15.3-all\struts-2.3.15.3\docs\WW\docs\validation.html 文档
> 在配置文件中可以配置错误消息的
<field name="age"> 要验证的字段
<field-validator type="int"> 使用的验证规则 叫int
<param name="min">20</param>
<param name="max">50</param>
<message>Age needs to be Between ${min} and ${max} </message>
</field-validator>
</field>
3.若验证失败,则转向input 的那个result 所以需要配置name=input 的result
4.如何显示错误消息呢 ?
若使用的是非simple 主题 错误消息会自动显示
若使用的是simple主题则需要s:fielderror标签 或 直接使用ognl表达式
<s:fielderror fieldName="age"></s:fielderror>
注意 :若一个Action类可以应答多个action请求,多个action请求使用不同的验证规则 怎么办?
为每一个不同的action请求定义其对应的验证文件 : ActionClassName-AliasName-validation.xml
不带别名的配置文件ActionClassName-validation.xml 中的验证规则依然会发生作用。可以把各个action共有的验证规则配置其中,
但只适用某一个action的请求的验证规则就不要在这里配置了
Struts一共有15个内置的验证程序
设置短路验证:若当前验证没有通过,则不再进行下面的验证
<validator />元素和<field-validator /> 元素可以指定一个可选的short-circuit属性,该属性指定该验证器是否是短路验证器,默认为false
若类型转换失败,默认情况下还会执行后面的拦截器,还会进行验证,可以通过修改ConversionErrorInterceptor 源代码的方式使
当类型转换失败时,不再进行后续的拦截器验证,而直接返回input 的result
Object action = invocation.getaction();
if(action instanceof ValidationAware){
ValidationAware va = (ValidationAware)action;
if(va.hasFieldErrors() || va.hasActionErrors()){
return "input";
}
}
非字段验证 expression
<validators type="int">
<validator type="expression">
<param name="expression"><![CDATE[password==password2]]></param>
<message>password must be equal</message>
</validator>
</validators>
显示的时候用 <s:actionerror />
错误消息的重用性 多个字段使用同样的验证规则 能不能使用同一条验证消息
只需要<message>Age needs to be Between ${min} and ${max} </message>改为
<message>${getText(fieldName)} needs to be Between ${min} and ${max} </message>
在i18n文件中
<message>${getText(fieldName)} needs to be Between ${min} and ${max} </message>
age=年龄
count=数量
这样写 就可以显示中文的错误信息
自定义验证器
1.定义一个 验证器的类
>需要实现一个接口Validator
可以选择继承ValidatorSupport或FieldValidatorSupport
若希望实现一个一般的验证器继承ValidatorSupport 若希望实现一个字段验证器,则继承FiledValidatorSupport
2.在配置文件中配置
>默认情况下,Struts2会在类路径的根目录下加载validators.xml文件。在该文件中加载验证器
3.使用
Action传递json对象的时候
首先需要导入Struts2-json-plugin-2.3.8.jar
还有jackson=core-2.2.3.jar
struts.xml文件中的package需要继承json-default
action中对应的result不需要写返回值
result中的type属性需要设置为json