- 使用ognl表达式在页面debug,
- 使用的jar包不仅要放在library里,如果运行时需要,就要加载lib目录下
...
- action没有result,执行的方法没有返回值(void),方法里写上out.write(JSON); 调用action方就能收到JSON字符串
- result标签类型默认是转发,要显示说明是重定向
【struts运行过程】
1.浏览器的各种事件,发起一个URL的请求,
2.被项目的默认过滤器监听到了,调用对应的action,(需要配置好xml文件的package和action标签)
3.若action有绑定拦截器,就先执行拦截器里的方法
4.由action里运行方法(这里是真正的代码处理的地方)的return值来确定等会跳转的结果页面(配置xml文件的result标签)
【搭建Struts2开发环境】
(或者直接使用MyEclipse的快速搭建,struts2.1+Hibernate3.3.2+JSTL1.2.2(本机jdk7.45+tomcat7.08))
* 1、找到Struts2应用所需要使用到的JAR包(特别注意不能和Hibernate的JAR包有重复的,不然就报错)
Struts2.3.3版本的开发必须JAR包:路径在 f:\Tool\AllJar\SSH\struts23 sm-3.3.jar sm-commons-3.3.jar sm-tree-3.3.jar ommons-fileupload-1.2.2.jar ommons-io-2.0.1.jar ommons-lang3-3.1.jar ommons-logging-1.1.1.jar reemarker-2.3.19.jar avassist-3.11.0.GA.jar gnl-3.0.5.jar truts2-core-2.3.3.jar work-core-2.3.3.jar
- 2、 创建JSP文件
- 3、 创建Action文件(实现了Struts的Action接口的普通类,或者继承ActionSupport类)
- 4、 编写Struts2的配置文件Struts.xml
- 5、 在web.xml中加入Struts2 框架启动的过滤器配置(核心过滤器)
访问Action的URL:
package的namespace加上Action的名字加上后缀
URL 默认搜索特性 :
/a/b/c/d/df.action
/a/df.action
*这两个是等价的,上面长的那个同样分解为两部分,前半部分的路径是从d到a
依次向上搜索的,直到找到,否则才会报错,所以说,只要根是对的,中间可以乱写,但是会影响性能
如果没有给Action指定class:
struts.xml继承的struts-default.xml 中配置了一个默认的class,所以说不会报错
<default-class-ref name=""/>
如果找不到Action:
就需要配置默认的Action名称,没有的话,就会报错。有就执行那个了
<default-action-ref name=""/>
URL的后缀名可以自定义(会覆盖原本的配置)
在struts.xml 中配置:
(使用多个的话,用逗号分隔)默认是action和空
name : 框架自带配置文件 default.properties中常量名
value :常量值
< constant name=”struts.action.extension” value=”myth”>< /constant>
或者:
src下新建一个default.properties 里面只写需要修改的常量
struts.action.extension=myth
若两者都修改了按以下顺序:
1 struts-default.xml
2 struts-plugin.xml
3 struts.xml
4 struts.properties
5 web.xml
若有相同的常量配置好,后者覆盖前者
建议在struts.xml中配置
<-- 配置URL后缀 默认是action或空-->
<constant name="struts.action.extension" value="myth"></constant>
<-- 配置国际化资源文件被修改时,是否重新加载 默认是false -->
<constant name="struts.i18n.reload" value="true"></constant>
<-- 配置struts2框架的配置文件修改时,是否重新加载 默认是false-->
<constant name="struts.configuration.xml.reload" value="true"></constant>
<--
配置struts2的模式
false 生产模式 默认是false
true 开发模式 需要更多的调试信息 会自动将上面两个常量设置为true
-->
<constant name="struts.devMode" value="true"></constant>
================================================================
自定义使用的 struts.xml 不仅路径,还有名字,方便多人开发
规范一般是一个action对应于一个xml文件,所以在struts.xml中引入xml文件要:
<include file=""></include>
file的路径都是以src为起点,注意把点换成 /
【action标签的配置】
【关于result的配置】
<struts>
<package name="resulttype" namespace="/resulttype" extends="struts-default">
<!-- 这是action的执行入口,里面定义返回类型,或者转发重定向啥的 -->
<action name="resulttypeAction" class="com.myth.resulttype.resulttypeAction">
<!-- 默认是转发 type属性:是指定type类型-->
<!-- <result name="success" type="dispatcher">/resulttype/success.jsp</result> -->
<!--
result标签的标准写法 验证了一个特性,可以在重定向后再重定向,这在原本的JSP中是不允许的
转发:dispatcher
-->
<result name="success" type="dispatcher">
<param name="location">/resulttype/success.jsp</param>
</result>
<result name="jqgrid" type="dispatcher">
<param name="location">/resulttype/Jqgrid.jsp</param>
</result>
<!-- 重定向到jsp 和后面的重定向action底层代码是一样的-->
<!-- <result name="success" type="redirect">
<param name="location">/resulttype/success.jsp</param>
</result> -->
<!-- 重定向到Action (可以是别的配置文件里的,只要引入到了主配置文件struts.xml就可以)-->
<!-- <result name="success" type="redirectAction">
actionName:指定的是struts.xml文件 中action标签中name属性的值
namespace:指定的是struts.xml文件action对应的package的namespace值
<param name="actionName">helloWorldAction</param>
<param name="namespace">/primer</param>
</result> -->
</action>
<!-- 不写result就是默认返回文本 -->
<action name="JSONAction" class="com.myth.resulttype.resulttypeAction" method="Json">
<!-- <result type="json"/> -->
</action>
</package>
</struts>
【模式匹配】
<struts>
<package name="pattern" namespace="/pattern" extends="struts-default">
<!-- 全局result 之后的可以不用配置了相当于全局变量,如果之后的action配置了,那就是局部变量覆盖原理-->
<!-- <global-result>
<result name="success">/pattern/success.jsp</result>
</global-result> -->
<!-- 框架中默认是运行的execute,如果自定义就更改那个method属性 -->
<action name="BookAction" class="com.myth.pattern.BookAction">
<result name="success">/pattern/success.jsp</result>
<result name="add">/pattern/BookAction.jsp</result>
</action>
<!--
通配符的使用,可以匹配任意长字符
-->
<!-- <action name="*_add" class="com.myth.pattern.BookAction" method="add">
<result name="success">/pattern/success.jsp</result>
<result name="add">/pattern/BookAction.jsp</result>
</action> -->
<!-- 问题出现了,如果没有下面两个action,访问的都会是bookaction没错
可是有了上面的通配符的action,下面还能正常运行,这是因为覆盖? -->
<!-- <action name="BookAction_add" class="com.myth.pattern.BookAction" method="add">
<result name="success">/pattern/success.jsp</result>
<result name="add">/pattern/BookAction.jsp</result>
</action>
<action name="UserAction_add" class="com.myth.pattern.UserAction" method="add">
<result name="success">/pattern/success.jsp</result>
<result name="add">/pattern/UserAction.jsp</result>
</action> -->
<!--
上面两个可以改写 (1)匹配的是通配符的第一个子串
-->
<!-- <action name="*_add" class="com.myth.pattern.{1}" method="add">
<result name="success">/pattern/success.jsp</result>
<result name="add">/pattern/{1}.jsp</result>
</action> -->
<!-- <action name="BookAction_add" class="com.myth.pattern.BookAction" method="add">
<result name="add">/pattern/BookAction.jsp</result>
</action>
<action name="BookAction_delete" class="com.myth.pattern.BookAction" method="delete">
<result name="success">/pattern/success.jsp</result>
</action>
<action name="UserAction_add" class="com.myth.pattern.UserAction" method="add">
<result name="add">/pattern/UserAction.jsp</result>
</action>
<action name="UserAction_delete" class="com.myth.pattern.UserAction" method="delete">
<result name="success">/pattern/success.jsp</result>
</action> -->
<!--
改写:
{1}:通配符 * 的第一个子串
{2}:通配符 * 的第二个子串
{0}:通配符 * 的整个串
-->
<action name="*_*" class="com.myth.pattern.{1}" method="{2}">
<result name="success">/pattern/success.jsp</result>
<result name="add">/pattern/{1}.jsp</result>
</action>
<!-- 动态方法调用:(默认开启,已经配置关闭了)链接写法:namespace+actionname+!+方法名
那么在配置中不用配置method方法,而是由页面的指定的方法名来调用相应的方法
<a href="${pageContext.request.contextPath }/pattern/BookAction!add.action">添加图书</a>
<a href="${pageContext.request.contextPath }/pattern/BookAction!delete.action">删除图书</a>
-->
<!-- <action name="BookAction" class="com.myth.pattern.BookAction">
<result name="success">/pattern/success.jsp</result>
<result name="add">/pattern/BookAction.jsp</result>
</action> -->
</package>
</struts>
2.3以上版本使用通配符的特殊
<action name="user_*" class="userAction" method="{1}">
<result name="success">/WEB-INF/jsp/login.jsp</result>
<allowed-methods>login</allowed-methods>
</action>
总结:在struts2.3之前的版本,正常的配置就可以了,但在struts2.3版本之后,使用通配符调用方法时,内部会验证是否允许访问该方法,所以要加上<allowed-methods>方法名1,方法名2…</allowed-methods>
代码。
【action类型转换】
1、在JSP页面上的输入框提交给action时,只要在action中声明同名变量,定义setget方法
那之后直接使用get方法就能获取到值,封装成对象再setget也是一样可以的但是在JSP上的input的name上要加对象名做前缀
还可以批量添加数据,就使用Collection集合 Collection ts;同样的加setget方法,但是在JSP上的多个input的name就要写成这种格式
ts[0].name ts[1].name……2、当struts有些类型无法转换时,就需要自定义转换器
【基于字段】(局部)
* 在当前action包下新建 convert.properties 文件名是自定义的
* 在该文件中 xwork.default.fieldvalue=无效的字段值 “{0}”.
* 在struts.xml文件加载该资源文件
【与Servlet解耦】
struts2 对 HttpServletRequest HttpSession ServletContext进行了封装成了Map对象
* 【方法一】: 通过ServletActionContext类直接获取
这个类是action执行的上下文对象,包括了parameter request session application等。
//分别三个属性的设置request session application
HttpServletRequest request = ServletActionContext.getRequest();
request.setAttribute("username", "username_request");
Map sessionMap = ServletActionContext.getContext().getSession();
sessionMap.put("username", "username_session");
ServletContext sc = ServletActionContext.getServletContext();
sc.setAttribute("username", "username_application");
//JSP页面的获取:
${requestScope.username}<br>
${sessionScope.username}<br>
${applicationScope.username}<br>
【通过实现接口struts2自动注入】
实现这四个接口:ServletRequestAware,ServletResponseAware,ServletContextAware,SessionAware
实例化对象分别是HttpServletRequest,HttpServletResponse,Map,ServletContext
重写四个set方法,方法体写上this.** = **;
【文件上传】
- 【1】套路一致,但是在配置时,action里一定有input的result才可以
- 【2】<– 配置文件上传的总大小 –>
< constant name=”struts.multipart.maxSize” value=”2097152000”> - 【3】错误提示配置
新建一个properties文件,名字自定义
struts.messages.error.uploading=Error uploading: {0}
struts.messages.error.file.too.large=File too large: {0} "{1}" "{2}" {3}
struts.messages.error.content.type.not.allowed=Content-Type not allowed: {0} "{1}" "{2}" {3}
struts.messages.error.file.extension.not.allowed=File extension not allowed: {0} "{1}" "{2}" {3}
{0}:< input>标签的name 属性值
{1}:上传文件的真实名称
{2}:上传文件保存到临时目录的名称
{3}:上传文件的类型(对于too.large来说是上传文件的大小)
加入到struts配置文件中去
struts2框架的文件上传:
- 单文件上传:
- 在动作类action中声明相关属性:
* 在动作类action中,要声明与页面中表单name属性同名的属性,同名的属性的类型时File类型;
* 在动作类action中,要声明[同名的属性]ContentType,类型时String类型;
* 在动作类action中,要声明[同名的属性]FileName,类型时String类型
* 给所有属性提供get和set方法 - 在业务方法中,处理文件上传:
* 获取要上传文件的路径,保存的位置
* 在目标文件夹内,创建一个与上传文件同名的文件
* 通过FileUtils工具类提供copyFile()方法,将临时文件内容拷贝到目标文件夹下的那个同名的文件
- 在动作类action中声明相关属性:
* 设置上传文件的总大小
* 在struts.xml文件中,<constant name="struts.multipart.maxSize" value="2097152000"></constant>
* 设置上传文件的大小、类型和扩展名:
* 在自定义的配置文件中,在action标签下:
<!-- 配置拦截器的参数,这里是文件上传拦截器 -->
<interceptor-ref name="defaultStack">
<!--
配置文件上传拦截器的参数
* 与定义参数的顺序无关
* 允许的类型(allowedTypes)和允许的扩展名(allowedExtensions)必须保持一致
-->
<!--
* 配置上传文件的大小
* struts.xml文件中配置的是上传文件的总大小
* 这里配置的是上传文件的单个大小
-->
<param name="fileUpload.maximumSize">20971520</param>
<!-- 配置上传文件允许的类型,如果配置多个值的话,用","隔开 -->
<param name="fileUpload.allowedTypes">text/plain,application/msword</param>
<!-- 配置上传文件的扩展名,如果配置多个值的话,用","隔开 -->
<param name="fileUpload.allowedExtensions">.txt</param>
</interceptor-ref>
* 自定义上传文件的错误提示信息:
* 在动作类action同目录下,创建一个名为fileuploadmessage.properties资源文件(名为自定义)
* 改资源文件配置如下:
struts.messages.error.uploading=Error uploading: {0}
struts.messages.error.file.too.large=File too large: {0} "{1}" "{2}" {3}
struts.messages.error.content.type.not.allowed=Content-Type not allowed: {0} "{1}" "{2}" {3}
struts.messages.error.file.extension.not.allowed=File extension not allowed: {0} "{1}" "{2}" {3}
- 多文件上传:
* 所有流程于配置都与单文件上传一致。
* 需要注意的是:
* 在页面中,虽然是多文件上传,但是页面中表单的name属性的值必须保持一致;
* 在动作类action中声明的相关属性,类型改成数组;
* 在业务方法中,相关处理流程改成单文件上传的循环。
【文件下载】
1、下载文件时 压入值栈的名字如果含中文需要转码:fileName = new String(filename.getBytes(),”ISO-8859-1”);
配置文件 filename=${filename}.xls
【校验】
【struts2手动验证】:
也就是说手动的是直接在action里,重写个validate方法就是了
方法里只要按需求写this.addFieldError( key, value);语句就行了,后续的由框架来处理
* 首先要从页面中获取对应的标签name属性的值,在动作类action中声明 同名的属性,提供get和set方法
* 要继承ActionSupport类或者实现Validateable接口
* 重写Validateable接口的validate()方法
* 前提是:要保证setUsername()、validate()、login()方法要按照这个先后顺序执行
* 如果登录失败,如何处理:
* this.addFieldError( key, value);
* key:错误提示字段
* value:错误提示信息
* 什么时候才是验证通过?
* 验证通过:1、map集合不存在;2、map集合存在并为空
* 验证不通过:map集合存在并且不为空
* 分析需求:
* 用户名不能为null ,""
* 密码不能为null, "" ,并且密码的长度6-12之间
* 针对所有业务方法进行验证还是针对某个指定业务方法进行验证?
* 重写的validate()方法,针对所有业务方法进行验证
* 重写的validate()方法加上要验证的指定的业务方法名(业务方法名的首字母大写),实现针对某个指定的业务方法进行验证
* 为什么要这样进行拼接?因为struts2框架底层拼接,如果不这样写,底层就找不到对应方法名
【struts2框架验证(xml方式)】:
* 首先要从页面中获取对应的标签name属性的值,在动作类action中声明同名的属性,提供get和set方法
* 创建一个xml格式验证文件:
* 命名方式:ActionClassName-validation.xml,ActionClassName指的是动作类action的名称
* <validators>标签:根元素
* field:指定action中要校验的属性,实际上就是页面中表单的name属性的值
* name:指定页面中表单的name属性的值
* field-validator:指定验证规则
* type:指定验证规则名称,
struts2框架提供的验证规则放在xwork-core-xxx.jar
下的com\opensymphony\xwork2\validator\validators
的default.xml配置文件。
* param:向底层的验证规则传递的参数
* message:验证失败时,提供的错误提示信息
* 如果要对指定方法进行验证的话:
* xml验证文件的命名方式:ActionClassName-ActionName-validation.xml,
ActionName对应的是struts.xml文件对应的action标签的name属性的值
【自定义拦截器】
【拦截器 特性】:
拦截器一般是和对应的action绑定的,而原生的filter是对URL模式进行拦截的
- 执行顺序:执行完struts中配置的拦截器栈中所有intercept方法后再执行action的execute方法
- 当拦截器的 intercept方法 返回null就会继续执行action
如果在前面就调出来了action实例,并且执行了方法那么之后的action就不会再重复执行 - 检查是否还有拦截器待执行,有就去执行,没有就会得到null,同样的继续执行action
String result = invocation.invoke();
return result;
【如何自定义拦截器】
1、 所有的拦截器都需要实现Interceptor接口或者继承Interceptor接口的扩展实现类
2、要重写init()、intercept()、destroy()方法
* init()是在struts2框架运行时执行,在拦截器的生命周期中只执行一次,可以做必要的内容的初始化工作
* intercept(),是每一次请求就执行一次,做相关处理工作。
* intercept()方法接收一个ActionInvocation接口的实例
* 通过这个接口的实例,可以获取以下内容
:
//cn.itcast.aop.UserAction @15b5783, 动作类的对象
System.out.println("invocation.getAction() : "+invocation.getAction());
//cn.itcast.aop.UserAction @15b5783, 与invocation.getAction()方法获取的是同一的对象
System.out.println("invocation.getProxy().getAction() : "+invocation.getProxy().getAction());
//userAction_save,自定义配置文件中的action标签的name属性的值
System.out.println("invocation.getProxy().getActionName() : "+invocation.getProxy().getActionName());
//save,对应动作类指定要执行的方法名
System.out.println("invocation.getProxy().getMethod() : "+invocation.getProxy().getMethod());
// /aop,自定义配置文件中的package标签的namespace属性的值
System.out.println("invocation.getProxy().getNamespace() : "+invocation.getProxy().getNamespace());
* destroy()是在拦截器销毁前执行,在拦截器的声明周期中只执行一次。
3、 在struts.xml配置文件中,进行注册
* 在配置文件中的package标签下,进行相关配置:
<interceptors>
<!-- 声明自定义的拦截器 -->
<interceptor name="expessionInterceptor" class="cn.itcast.aop.ExpessionInterceptor" />
<!-- 声明自定义拦截器栈 -->
<interceptor-stack name="expessionStack">
<interceptor-ref name="defaultStack"/>
<!-- 配置使用自定义拦截器 -->
<interceptor-ref name="expessionInterceptor"/>
</interceptor-stack>
</interceptors>
<!-- 配置修改struts2框架运行时,默认执行的是自定义拦截器栈 -->
<default-interceptor-ref name="expessionStack" />
后面跟着的就是action的配置了
【ognl学习】
【valueStack】:
ValueStack实际上是一个接口,在struts2中利用OGNL时,实际上是哦那个的是实现了该接口的OgnlValueStack类,这个类是利用OGNL的基础
贯穿整个action生命周期,每个action类的对象都有一个valueStack对象,相当于一个数据的中转站,在其中保存了当前action对象和其他相关对象
struts框架把valueStack对象保存在名为 “struts.valueStack”的请求属性中(request中)
ValueStack vs = (ValueStack)request.getAttribute("struts.valueStack");
vs.set("key","value");//实际上是放在了Map集合里再放在栈里的
vs.getRoot().add(0,new Person());//把person对象压入List集合的0位置(栈顶)
【理解OGNL Context】 上下文
- OgnlValueStack类包含两个重要的属性:root 和 context
- 其中toot本质上是一个List集合
- Context是一个Map(确切的说是一个OgnlContext对象)
- 在这个OgnlContext对象中,有一个默认的顶层对象 root OGNL访问context中这个默认顶层对象中的元素时不需要#号,直接通过名称来引用
而访问其他对象时, request,session,attr 等则需要#号引用。 - 总结:ognl Context包含ObjectStack属性和ContextMap属性
- 在底层类中的部分代码:
public class OgnlValueStack implements ValueStack {
CompoundRoot root; --- list集合
transient Map
【OGNL表达式】【示例JSP】
实际操作的不是值栈,而是值栈的属性:Context的上下文(就是一个Map集合)
* 使用EL表达式取值:
requestScope.username
{sessionScope.username}
${applicationScope.username}
* 使用Ognl表达式取值: 【访问Map集合加#】
1.如果访问其他Context中的对象,由于他们不是根对象,所以在访问时,需要添加#前缀。
<s:property value="#request.username"/><br>
<s:property value="#session.username"/><br>
<s:property value="#application.username"/><br><br>
<s:property value="#request['username']"/><br>
<s:property value="#parameters.cid[0]"/><br>
<s:property value="#attr.username"/><br><br>
【访问对象栈中对象可不加#】
2.如果要访问根对象(即ValueStack)中对象的属性,则可以省略#命名对象,直接访问该对象的属性即可。
<s:property value="msg"/><br><br>
深入理解值栈中的 ObjectStack<br>
【后台代码:】 vs.getRoot().add(0,new Person());
<s:property value="name"/><br> 【若有多个name属性名】只取出栈中第一个
<s:property value="sex"/><br>
<s:property value="age"/><br>
<s:property value="salary"/><br><br>
用法3:构造Map
<s:radio list="#{'01':'男','02':'女'}"></s:radio><br><br><br><br>
%的用法:“%”符号的用途是在标签的属性值被理解为字符串类型时,告诉执行环境%{}里的是OGNL表达式。 <br>
<s:property value="#request.username"/><br>
<s:property value="%{#request.username}"/>
%{}是万能用法,无论里面的表达式是不是ognl表达式,都会强制理解为ognl表达式<br><br>
【“$”有两个主要的用途】
1 * 用于在国际化资源文件中,引用OGNL表达式<br>
<s:text name="ognl" /><br><br>
在properties文件中配置:ognl=${error} ognl
取的是值栈中的error属性 代码:valueStack1.set("error", "error_valueStack");
2 * 在Struts 2配置文件中,引用OGNL表达式<br>
<s:property value="#parameters.msg[0]"/><br><br>
<result name="s" >ognl/ognl.jsp?msg=${msg}</result>
这里的msg是request的param 使用 ${} 访问的都是值栈里的
<s:debug></s:debug> 能查看值栈状态