2.Struts2和Servlet的对比
3.Struts2程序运行流程
4.Struts2的配置文件
包括自己内部的.properties、default.xml以及自定义的配置文件struts.xml和web.xml
先加载内部自己的配置文件,后加载用户自定义的配置文件,后加载的会覆盖先加载的文件。
在web.xml中会配置struts2的前端控制器(StrutsPrepareAndExecuteFilter)
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.5"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd">
<display-name></display-name>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>
在核心配置文件struts.xml中配置Action
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
"-//Apache Software Foundation//DTD Struts Configuration 2.3//EN"
"http://struts.apache.org/dtds/struts-2.3.dtd">
<struts>
<!--
package:包:以前学习java的时候,
包的作用:管理java类,不同的包中可以存放同名的java类
不同的包中存放的代码的功能是不一样
在struts2中,包的作用也是如此
name:包名
extends:继承,必须继承struts-defalut,在struts-defalut包中实现了非常多的功能
namespace:名称空间,与访问路径相关的
-->
<package name="default" extends="struts-default" namespace="/">
<!-- Hello的action
name:请求的名字
class:Action对应的完整的包路径
-->
<action name="hello" class="cn.itcast.a_hello.HelloAction">
<!-- 配置业务逻辑处理完成之后,跳转的action
name:与action中return的String类型的值对应
-->
<result name="success">/a_hello/result.jsp</result>
</action>
</package>
</struts>
Package包的名字必须是唯一的,一个包里面可以有多个action,一个struts.xml中可以有多个package包,每一个name是区别它们不同的唯一标识。主要用来区分action的。
Namespace是访问路径。
4.1.核心配置文件(struts.xml)
4.1.1.package
? package标签:
功能:用来定义不同的模块,管理和配置Action,并且实现包内配置复用 (通过包继承实现 )
? name属性:包的名称,在struts容器中具有唯一性(在开发中可以用模块名称作为包名)
? extends属性:继承父package中的功能,通常都继承struts-default。由于该默认包内定义了大量结果集类型和拦截器,所以struts强制你继承struts-default(不继承struts-defalut,就会报错)。
? namespace属性:名称空间用来标识一个路径,来区分不同action的访问路径。
?
Action的访问路径= namespace包名称空间 + action的name属性
默认为“”,在实际开发中,通常采用模块名字作为访问路径
【注意点一】
在不同的package中,可以存放相同name的action,在访问的时候通过namaspace进行区分
如果namespace="/",那么访问路径就是 /*.action
如果namespace="/xxx",那么访问路径就是/xxx/*.action
如果namespace="/xxx/ooo", 那么访问路径就是/xxx/ooo/*.action
在项目实际开发过程中,namespace可以写成模块名字
【注意点二】
访问action的时候,struts有个内部规律(了解)
如果你访问的路径中,没有定义action,会自动向上层路径寻找。
http://localhost:8080/struts_day01/aa/bb/cc/hello.action
先在aa/bb/cc/找hello.action
找到就ok,找不到,继续:
aa/bb/中找hello。Action
aa/
……..
一直找到
/找hello。action
其实最终会找到
“”下的hello.action
如果最终还是没有找到,就会报错
缺点:代码可读性不好。
【注意点三】为什么要继承struts-default?
解答:
参考 struts-core.jar 提供 struts-default.xml 配置文件 ,在这个xml文件中定义了大量的结果集类型和拦截器
【查看类型代码】
通过快捷键shift+ctrl+T打开搜索框,然后输入
【说明】:在struts.xml文件中,可以配置多个package标签
4.1.2.Action
? action标签:用来管理具体的Action,负责接收客户端的请求,进行处理,并且完成响应
1.name属性:action的名字,用于配置请求的URL路径。
2.class属性:action对应的完整包路径,该类编写了action具体业务逻辑代码。
3.找到action之后,系统会默认会执行Action类中的execute方法。
4.若没有class属性,系统会默认执行ActionSupport中execute方法,而ActionSupport的execute会默认返回 success逻辑视图,这种处理方式在struts-default.xml 文件已经进行了规定。
5.当访问路径中action的名字不存在的时候,系统会报错
若果访问路径下没有匹配的action,则执行默认的action。如下配置:
<package name="anotherPackage" extends="struts-default" namespace="/xxx/yyy">
<!-- 指定默认执行的action‘ -->
<default-action-ref name="errorPage"></default-action-ref>
<!-- 在同一个包中不能存在同名的action
action :
class:Action对应的完整的包路径 ,
可以省略,那么如果class属性省略了,那么系统会如何执行?
答: 如果没有class,会默认执行ActionSupport类的execute方法
-->
<action name="hello" >
<result name="success">/b_config/result.jsp</result>
</action>
<!-- 配置默认执行的action,当不配class 的时候,会默认执行ActionSupport类中的execute方法 -->
<action name="errorPage">
<result name="success">/b_config/errorPage.jsp</result>
</action>
</package>
4.1.3.Result
? result标签:结果集视图,标签内的值是响应的地址。
? name属性:结果集视图的名字,action会根据该名字跳转到对应的地址。result的name属性默认值为“success”。
5.Action的编写方式和访问方式
5.1.Action的三种编写方式
第一种:实现Action接口(可以使用结果集常量字符串)
第二种:继承ActionSupport类:(重点中的重点,推荐的方式)
* 对请求参数进行校验
* 设置错误信息
* 读取国际化信息
第三种:pojo类(非侵入性、简单、干净)没有extends 父类,也没有implements 接口
5.1.1.实现Action接口方式
package cn.itcast.c_action;
import com.opensymphony.xwork2.Action;
/**
* Action的第一种实现方式:自定义类实现Action接口
* 在Action接口中存在5个常量(SUCCESS/INPUT/ERROR/NONE/LOGIN)和一个公共抽象的方法
* 重点SUCCESS/INPUT/NONE
* @author yuanxinqi
*
*/
public class MyAction1 implements Action{
@Override
public String execute() throws Exception {
System.out.println("这是Action的第一种实现方式");
// return "success";
return SUCCESS;
}
}
Action 接口提供一组 常用的内置的逻辑视图名称:
? SUCCESS 成功视图,默认值
? NONE 没有结果视图,用户自己生成响应数据
? ERROR 错误视图
? INPUT 输入视图 (数据输入非法,要求用户重新输入)
? LOGIN 登陆视图 (如果用户未登陆,使用登陆视图)
5.1.2.继承ActionSupport类(重点)
继承ActionSupport相当于间接实现Action接口,该类提供了更多功能,如数据校验、 国际化等,用的最多,使用的时候需要手动覆盖execute()。
package cn.itcast.c_action;
import com.opensymphony.xwork2.ActionSupport;
/**
* Action的第二种实现方式,继承ActionSupport,这是推荐的方式!!!
* 然后重写execute方法
* 这种实现方式不光可以使用5个常量还可以使用ActionSupport类中实现的方法
* @author yuanxinqi
*
*/
public class MyAction2 extends ActionSupport{
@Override
public String execute() throws Exception {
System.out.println("Action的第二种实现方式");
//这个时候为什么可以使用常量
return SUCCESS;
}
}
主要使用这种方式。
5.2.Action方法的调用(通配符)
通过通配符的使用简化Action的配置,从而实现不需要配置多个Action,就可以访问多个方法
【问题】单纯使用method属性来配置action的方法,调用不同的方法,需要配置不同的action。配置较多。
【解决方案】可以通过通配符的方式来解决。
【一个通配符的情况】重点掌握
? 在配置<action>元素的时候,允许在指定name属性的时候,使用模式字符串:*代表一个或者多个任意字符
? 在class、method、result子元素中可以通过{N}形式代表前面的第N个*匹配子串
访问规则:
{1} 对应 name中第一个* 匹配的内容。 例如: 访问user_login ------ * 匹配 login -------- method就是login。
二个通配符的情况】(了解)
更复杂的情况:
【示例】
实现N个action的N个方法的访问。
<!-- 多个通配符的情况 -->
<action name="*_*" class="cn.itcast.d_method.{1}" method="{2}">
<result>/d_method/{2}.jsp</result>
</action>
6.Action使用Servlet的API
【需求分析】为了简化开发,struts2默认情况下将servlet api(比如:request对象、response对象、session对象、application对象)都隐藏起来了。但是在某些情况下,我们还需要调用servlet api,比如,登录的时候将用户信息保存到session域中。
Struts2中提供了3种方式来获取servlet的api
第一种方式:解耦方式获取:借助ActionContext获取(这个是struts2官方的推荐的方式,但是不利于理解,所以我们不推荐)
第二种方式:接口注入方式操作Servlet API(了解)
第三种方式:通过ServletActionContext 类的静态方法直接获取Servlet API(掌握)
6.1.解耦合(ActionContext)
package cn.itcast.e_servletapi;
import java.util.Map;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class IndirectServletAPIAction extends ActionSupport{
@Override
public String execute() throws Exception {
//获取工具类
ActionContext actionContext = ActionContext.getContext();
//获取页面的参数信息
Map<String, Object> parameters = actionContext.getParameters();
//接收的结果是一个数组?此处为什么要设计为数组接收页面参数?
//因为通过数组的方式,如果页面传递的是一个值,数组也可以接收,
//如果页面传递的是两个值,数组也可以接收
// Object object = parameters.get("name");
String[] values = (String[]) parameters.get("name");
System.out.println(values[0]);
//存值:这个操作等同于request.setAttribute("xx",xx);
// actionContext.put("name", values[0]);
ActionContext.getContext().put("name", values[0]);
return SUCCESS;
}
}
6.2.耦合的方式(ServletActionContext)
package cn.itcast.e_servletapi;
import javax.servlet.http.HttpServletRequest;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class DirectServletAPIAction extends ActionSupport{
@Override
public String execute() throws Exception {
//通过ServletActionContext的静态方法调用Servlet的API
HttpServletRequest request = ServletActionContext.getRequest();
String value = request.getParameter("name");
System.out.println(value);
//赋值
request.setAttribute("name", value);
return SUCCESS;
}
}
【总结】
1> 该方案虽然可以避免Action类实现xxxAware接口,但是Action与ServletAPI直接耦合,所以在开发中
Struts2官方建议采用ActionContext这种方式
2> 实际开发中,看你的编写习惯来选择(个人:ServletActionContext)
7.Result结果集
针对于结果集,我们主要学习以下三个知识点:
1. 局部结果集
2. 全局结果集
3. 结果集类型
7.1.局部结果集
在<action> 标签内部配置的<result>元素。
作用范围:局部结果集只能给当前的action使用,只对当前Action有效
当响应的结果集名字不存在的时候,会报错:
7.2.全局结果集
在包的标签中<global-results>中配置
作用范围:对package内所有Action生效
struts.xml中先配置Action的访问路径
<!-- 全局结果集 -->
<action name="global" class="cn.itcast.f_result.GlobalResultAction">
</action>
然后配置全局跳转路径
【注意】同名的局部结果集会覆盖全局结果集。先走局部,如果局部不存在,再去走全局,如果还不存在,则报错
7.3.结果集类型type
作用:控制响应的方式(转发、重定向)
配置<result> 元素时, name是逻辑视图名称, type是结果集类型。
Struts2提供的常用结果集类型都定义在struts-default.xml 中:
内置的结果集类型:
序号 结果集类型名 描述
1 dispatcher 默认结果类型,用来呈现JSP页面(请求跳转至另外一个jsp)
2 chain 将action和另外一个action链接起来(请求跳转至另外一个Action)
3 redirect 将用户重定向到一个已配置好的URL(jsp)
4 redirectAction 将用户重定向到一个已定义好的action
5 stream 将原始数据作为流传递回浏览器端,该结果类型对下载的内容和图片非常有用
6 freemarker 用于FreeMarker整合的结果类型
7 httpheader 返回一个已配置好的HTTP头信息响应
8 velocity 呈现Velocity模板
9 xslt 呈现XML到浏览器,该XML可以通过XSL模板进行转换
10 plainText 返回普通文本内容
dispatcher请求跳转到jsp页面:
<!-- type="dispatcher":这是默认值 ,请求跳转至另外一个jsp页面,无法跳转至另外一个action-->
<result type="dispatcher">/f_result/result.jsp</result>
chain:请求跳转到Action
<!-- type="chain": 跳转至另外一个action -->
<result type="chain">local</result>
redirect:重定向跳转到jsp页面
<!-- type="redirect":重定向跳转,action中的参数无法传递 -->
<result type="redirect">/f_result/result.jsp</result>
redirectAction:重定向到Action
<!-- redirectAction:重定向到另外一个Action -->
<result type="redirectAction">local</result>
? dispatcher(默认值):请求转发。(最常用)
作用:服务器内部是同一次请求,可采用request传递数据,URL不变。
? redirect(从定向到外部资源)
作用:重定向到某个jsp页面,服务器发起了一次新的请求,不能通过request传递参数,URL改变为新的地址。
应用场景举例:登录后重定向到网站的主页面。
? redirectAction
作用:重定向到另外一个Action
? chain(了解)
作用:请求转发到另外一个Action中
8.Struts2注解开发和约定
注解的好处:
1、结果集可以自定义
2、访问的action的名字可以自定义,但action的扫描还是约定(action还得符合包+类名约定。)
注解依赖于约定,如何理解?
你使用注解的时候,你的包名得包含action,actions,struts,struts2这个四个关键字之一
注解怎么用?
答:注解是在类代码中进行配置,不需要单独 XML文件,注解依赖约定扫描 (也就是说:Action 还是存在于 action、actions、struts、 struts2)
主要使用两个注解:@Action 和@Result,分别用来配置 Action访问路径 和 结果集页面位置。
8.1.约定开发
【原理分析】为什么Action能被struts2注册和访问到?
(1)注册扫描Action的约定:
action、actions、struts、struts2:这四个关键字只要含有一个就够了(含有多个,会出问题)
根据约定中的包名规则和类名后缀规则(即那四个包下+以Action后缀的类),就可以扫描到对应的Action了。我们这里的HelloAction符合要求。
(2)访问Action的约定:
(3)结果集页面 Result 约定:
? 默认情况下。Convention总会到Web应用的WEB-INF、content路径下定位结果资源
? <constant name=”struts.convention.result.path” value=”/WEB-INF/content/”>
例如:
? 访问cn.itcast.struts.user.UserManagerAction返回success
Convention优先使用WEB-INF/content/user/user-manager-success.jsp
? 如果user-manager-success.jsp不存在,会使用user-manager-success.html
如果user-manage-success.html不存在,会使用user.jsp
如果还没有,就报错
8.2.Action和Result的用法
【用法一】局部单结果集
【用法二】全局单结果集
【用法三】局部多结果集(重点掌握)
【用法四】全局多结果集
8.3. ParentPackage和Namespace的用法
Package属性设置 @Namespace 名称空间 、 @ParentPackage 父包
相当于 xml 配置的:
【扩展补充】:如果使用了@namespace则@Action中配置的路径名字可以不带/。
问题:实际开发中,约定和注解的选择?
约定不写注解,少写代码;注解更清晰,更容易理解。有的企业会混合使用。
后期推荐:
约定扫描(扫描action)+注解(action名字定义+结果集的定义)
9.请求参数
9.1.请求参数的接收机制
作为MVC框架,Struts2要负责解析HTTP请求参数,并将其自动封装到Model对象中。表现层一个核心职责 , 负责接收客户端提交请求数据 ,负责与客服端交互(request,reponse)
9.1.1.属性驱动
方式一:Action作为model,通过成员变量的属性和setter方法接受参数进行封装
Action本身作为model对象,里面放入属性,(属性名称和页面form表单里面的字段名相同),通过setter方法,将页面表单的值set进action里面的属性中。
缺点:需要将数据传递给service层时,就需要在将数据封装进一个新的model中。
方式二:创建独立的model对象,里面放入属性,然后将mdel对象单独放入action中(同时提供setter和getter方法,Model中也一样,页面通过OGNL表达式封装数据)
如下:
【第一步】:在login.jsp中编写页面代码
<h3>1.1.2.Action中创建独立的model对象,提供setter、getter方法,页面通过ognl表达式进行封装(方式二)</h3>
<form action="${pageContext.request.contextPath }/login2.action" method="post">
username:<input type="text" name="userInfo.username" /><br>
password:<input type="password" name="userInfo.pwd" /><br>
<input type="submit" value="登录" />
</form>
【第二步】:在cn.itcast.a_model包中,创建表单模型UserInfo类:
package cn.itcast.a_model;
/**
*专门用来封装页面表单数据
*首先提供与页面表单的name一致的属性,并且为这些属性提供getter和setter 方法
*
* @author yuanxinqi
*
*/
public class UserInfo {
private String username;
private String pwd;
public UserInfo()
{
System.out.println("UseInfo的构造方法");
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
System.out.println("setUsername....");
this.username = username;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
System.out.println("setPwd...");
this.pwd = pwd;
}
@Override
public String toString() {
return "UserInfo [username=" + username + ", pwd=" + pwd + "]";
}
}
【第三步】:在cn.itcast.a_model包中创建LoginAction2类,继承ActionSupport,具体代码人乤:
package cn.itcast.a_model;
import com.opensymphony.xwork2.ActionSupport;
/**
* 1.1.2.Action中创建独立的model对象,提供setter、getter方法,页面通过ognl表达式进行封装(方式二)
* 操作步骤:
* 1 创建独立的model对象,在这个对象中提供私有的属性(属性名字和表单的name一致),并且为属性提供setter方法
* 2 model对象作为属性放入Action中,并且提供getter和setter方法
* @author yuanxinqi
*
*/
public class LoginAction2 extends ActionSupport{
//独立的model
//这个模型对象什么时候被创建?
//答:第一个属性进行赋值的时候,系统会自动创建这个对象,并且封装到Action中
private UserInfo userInfo ;
@Override
public String execute() throws Exception {
System.out.println(userInfo.toString());
return NONE;
}
public UserInfo getUserInfo() {
System.out.println("getUserInfo...");
return userInfo;
}
public void setUserInfo(UserInfo userInfo) {
System.out.println("setUserInfo...");
this.userInfo = userInfo;
}
}
【第四步】:配置
<!-- 配置参数封装方式二的Action -->
<action name="login2" class="cn.itcast.a_model.LoginAction2" />
【1、分析】
【2、原理】 (必须同时提供model getter和setter 方法)
解析:
struts会自动先获取到参数名为“userInfo.username”的值,struts2会根据.来截取字符串,通过反射机制,操作:
先userInfo --->getUserInfo()---->获取userinfo对象,
struts2会判断,userinfo对象是否是null,如果是,则自动反射机制,帮你new 了一个UserInfo对象,
然后通过setter方法:setUserInfo(UserInfo userinfo)
调用userinfo.setUsername(“admin”)。
9.1.2.模型驱动(★)
使用ModelDriven接口(模型驱动),对请求的数据进行封装
9.1.3.区别(★)
若模型驱动和属性驱动都存在的情况下,模型驱动优先于属性驱动,在struts2中的拦截器中,模型拦截器在前,参数拦截器在后,被模型拦截器拦截后,会先判断是否继承了modelDriven接口,是再判断Model是否为空,不为空就继续执行。若没有继承modelDriven接口,就会执行下一步,被参数拦截器拦截,继续执行。(模型驱动中的属性名和属性驱动中的属性名相同)
当模型驱动中不存在摸个属性的时候,属性驱动能够正常使用。
9.2.请求参数类型转换机制
Struts2提供了功能强大的类型转换器,用于将请求数据封装到model对象。
9.2.1.内置参数类型转换
Struts2内置了常见数据类型多种转换器,如下:
? boolean 和 Boolean
? char和 Character
? int 和 Integer
? long 和 Long
? float 和 Float
? double 和 Double
? Date 可以接收 yyyy-MM-dd格式字符串
? 数组 可以将多个同名参数,转换到数组中
? 集合 支持将数据保存到 List 或者 Map 集合
当内置转换器不能满足转换功能时,可以自定义类型转换器。
9.2.2.自定义类型转换器
自定义转换器包含局部类型转换器与全局类型转换器。局部转换器只针对当前action有效,而全局转换器对整个项目有效。
用户需要对特殊数据进行转换,需自定义转换器,就必须实现ognl.TypeConverter接口,可以采用的编写方式:
? 编写类 实现 TypeConverter 接口
? 编写类 继承 DefaultTypeConverter 类
? 编写类 继承 StrutsTypeConverter 类
例如:
在cn.itcast.b_conversion中创建MyDateConverter,继承DefaultTypeConverter 类,具体代码如下:
package cn.itcast.b_conversion;
import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter;
public class MyDateConverter extends DefaultTypeConverter{
/**
* value:an object to be converted to the given type
* 要被转换的值
* toType:class type to be converted to
* 转换之后的类型
* 转换器对应着两个功能:
* 1 页面提交数据去后台,此时是从String-->Date
* 2 页面数据回显:服务器日期类型是Date-->String
*
*/
public Object convertValue(Object value, Class toType) {
DateFormat dateFormat = new SimpleDateFormat("yyyy/MM/dd");
// /页面请求:String-->Date
if(toType==Date.class){
//将value的值转成Date类型
//value其实是一个数组
String[] values = (String[]) value;
//将String类型的值转成date
try {
return dateFormat.parse(values[0]);
} catch (ParseException e) {
// TODO Auto-generated catch block
e.printStackTrace();
throw new RuntimeException("日期类型转换失败");
}
}
else{
// 页面回显功能
//value:Date类型,而toType是String类型
return dateFormat.format(value);
}
}
}
注意:value 在请求转换数据时,是一个String[]
局部转换器:
【第一步】:在Action类所在的包下放置ActionClassName-conversion.properties。
在properties文件中的内容是:
属性名称=类型转换器的全类名
【第二步】:对于本例而言,RegistAction-conversion.properties文件中的内容为:
birthday=cn.itcast.b_conversion.MyDateConverter
如图:
该文件放置的位置如下图:
提示:局部转换器是针对属性名进行转换的
注意:当你配置了局部转换器之后,再运行action,则该属性的字段会自动使用自定义转换器
缺点:局部转换器是跟表单属性绑定了,只能作用action中的指定属性
全局转换器:
xwork-conversion.properties
提示:全局转换器是针对类型进行转换的。
转换器的调用优先级
如果没有添加自定义类型转换器:则执行默认的类型转换器;
如果一个自定义类型转换器,定义了一种数据类型的转换规则(例如Date),默认该数据类型转换器则失效(例如Date)
自定义转换器的调用优先级:
如果既有局部也有全局,则局部覆盖全局
在实际开发中,全局和局部不会同时去对同一个类型进行转换
9.3.请求参数的合法性校验
客户端校验和服务器端校验。
客户端校验包括:手动校验,xml配置规则校验,注解方式校验。
手动校验代码耦合性太高。所以一般用xml校验。
执行xml配置校验的要求: Action 必须继承ActionSupport类 (为了实现 Validateable接口)。
这里根据校验规则生效的范围分为全局校验和局部校验两种。
全局校验:对Action中的所有的方法都生效
局部校验:对Action中的某个方法生效
9.3.1.全局校验(★)
编写xml的方法:在Action类所在包,创建 Action类名-validation.xml
全局性的校验文件:直接是Action的名字-validation.xml
每个校验器的作用:
校验器 作用
required 必填校验器,要求被校验的属性不能为null
requiredstring 必填字符串校验器,要求被校验的属性不能为null,长度必须大于0,默认情况下会对字符串首尾去空格
stringlength 字符串长度校验器,要求被校验的属性必须在指定的范围内,否则校验失败
minLength:指定最小长度
maxLength:指定最大长度
trim:指定校验属性被校验时,是否去除字符串前后空格
regex 正则表达式校验器,检查被校验的属性是否匹配某个正则表达式
expression:指定正则表达式
caseSensitive:指定进行正则表达式匹配时,是否区分大小写,默认值为true
Int 整数校验器,要求field的整数值必须在指定范围内
min:指定最小值
max:指定最大值
double 双精度浮点型校验器,要求field的值必须在指定范围内
min:指定最小值
max:指定最大值
fieldexpression 字段ognl表达式校验器,要求field满足一个ognl表达式,
expression:指定ognl表达式
该逻辑表达式基于ValueStack进行求值,返回true时,验证通过
email 邮件地址校验器,如果field的内容不为空,则必须是合法邮件地址
url 网址校验器,如果field的内容不为空,则必须是合法的url地址
date 日期校验器,field的内容必须在某个范围内
min:指定最小值
max:指定最大值
conversion 转换校验器,指定在类型转换失败时,提示的错误信息
visitor 用于校验action中复合类型的属性,他指定一个校验文件,校验复合类型中属性
expression expression:指定ognl表达式,该逻辑表达式给予ValueStack进行求值,返回true是校验通过
提示:这些都是struts2内置的校验器,每一种校验器都可以实现一种校验规则方式。只需要记住常用的几个就行。
注意:使用xml配置校验的字段属性必须都有getter(因为需要将值取出来校验)
内置校验器小分析:
RequiredStringValidator校验器的作用是必须存在,必须是字符串,且默认值不能是空格。
为什么requiredstring能表示被校验的字段不能是空值呢?:原因是
【示例】如果你将trim的值设置为false的时候,校验的时候,不去除空格
【注意】属性必须提供getter方法。否则校验器无法得到数据,进行校验。
9.3.2.局部校验(★)
编写xml的方法:在Action类所在包,创建 类名-<Action>URL访问路径-validation.xml
【第五步】MyRegistAction-myRegist-validation.xml文件的配置。
使用常用校验器进行校验:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC
"-//Apache Struts//XWork Validator 1.0.3//EN"
"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
<validators>
<!-- 验证用户名 :(非空,且长度为3-10位)-->
<field name="username">
<!-- 非空 -->
<field-validator type="requiredstring">
<message>用户名不能为空</message>
</field-validator>
<!-- 长度验证 -->
<field-validator type="stringlength">
<param name="maxLength">10</param>
<param name="minLength">3</param>
<message>用户名必须是3-10位</message>
</field-validator>
</field>
<!-- 验证密码:(必须,且长度为6-12) -->
<field name="pwd">
<field-validator type="requiredstring">
<message>密码不能为空</message>
</field-validator>
<field-validator type="stringlength">
<param name="maxLength">12</param>
<param name="minLength">6</param>
<message>密码必须是6-12位</message>
</field-validator>
</field>
<!-- 重复密码:(必须和密码一致)
fieldexpression:专门用来比较表单字段的
-->
<field name="repwd">
<field-validator type="fieldexpression">
<param name="expression"><![CDATA[pwd==repwd]]></param>
<message>两次密码输入不一致</message>
</field-validator>
</field>
<!-- 验证年龄:(年龄在18-90之间) -->
<field name="age">
<field-validator type="int">
<param name="min">18</param>
<param name="max">90</param>
<message>年龄必须在18-90之间</message>
</field-validator>
</field>
<!-- 验证手机号码:(手机号规则,11位数字) -->
<field name="phone">
<field-validator type="regex">
<param name="regex"><![CDATA[^\d{11}$]]></param>
<message>手机号码必须是11位</message>
</field-validator>
</field>
<!-- email:符合邮箱的格式 -->
<field name="email">
<field-validator type="email">
<message>邮箱不符合格式</message>
</field-validator>
</field>
</validators>
提示:在Action 执行xml 校验时, 必须要为 变量提供 getter方法!!!
在所有的校验规则中,最负责的,无非是正则表达式校验。
9.3.3.自定义校验
自定义校验规则的作用:解决了struts2内置的校验规则中没有的校验规则。
自定义校验器,实现Validator接口, 一般可以继承FieldValidatorSupport
定义PhoneValidator.java类
package cn.itcast2.c_validation;
import com.opensymphony.xwork2.validator.ValidationException;
import com.opensymphony.xwork2.validator.validators.FieldValidatorSupport;
public class PhoneValidator extends FieldValidatorSupport{
/**
* 参数:就是Action对象
要校验的对象,数据模型对象
*/
@Override
public void validate(Object object) throws ValidationException {
//获取需要验证的表单名字
String fieldName=super.getFieldName();
//根据表单名字获取表单内容
Object fieldValue=super.getFieldValue(fieldName, object);
//判断表单内容是否是String类型,如果是,则进行正则表达式验证
if(fieldValue.getClass()==String.class)
{
//将类型转换成String类型
String str=(String)fieldValue;
//进行正则表达式验证
boolean flag=str.matches("^\\d{11}$");
//如果验证不通过,则添加错误信息
if(!flag)
{
this.addFieldError(fieldName, object);
}
}
}
}
【第二步】: 注册校验器
在src新建 validators.xml
引入DTD xwork core 下面 xwork-validator-config-1.0.dtd
validators.xml的配置:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC
"-//Apache Struts//XWork Validator Config 1.0//EN"
"http://struts.apache.org/dtds/xwork-validator-config-1.0.dtd">
<validators>
<!-- name:给校验器起的名字
class:对应校验器所在的完整包路径
-->
<validator name="phoneValidator" class="cn.itcast.c_validation.PhoneValidator">
</validator>
</validators>
【第三步】: 使用自定义校验器,在MyRegistAction-myRegist-validation.xml中添加:
<!-- 验证 手机号码(手机号规则,11位数字)-->
<!-- <field name="phone">
<field-validator type="regex">
<param name="regex"><![CDATA[\d{11}]]></param>
<message>手机号码必须是11位</message>
</field-validator>
</field> -->
<field name="phone">
<field-validator type="phoneValidator">
<message>手机号码必须是11位</message>
</field-validator>
</field>
10.国际化信息机制
Struts2对国际化的api进行了封装,只需要配置即可使用。下面根据作用范围,分别进行讲解:
? 全局范围的国际化文件:对整个项目中的所有的Action/jsp页面 都起作用
? Action范围的国际化文件:只对某个Action起作用
? Package范围的国际化文件:只对Package包中的Action起作用
10.1.合法性校验信息处理
10.1.1.全局范围国际化文件
全局国际化文件,对所有Action 生效,任何程序都可以访问到(针对某个工程)
在classpath路径下(src路径下),编写messages.properties配置文件,编写国际化信息:
在struts.xml中,通知struts加载国际化配置文件,配置常量 struts.custom.i18n.resources指定信息文件
此时加载的是src下的messages.properties文件。
<!-- 配置国际化信息资源文件的名字 -->
<constant name="struts.custom.i18n.resources" value="messages"></constant>
对添加商品 添加校验(xml局部校验): 在ProductAction所在的包内,创建ProductAction-product_add-validation.xml文件,
添加校验信息:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE validators PUBLIC
"-//Apache Struts//XWork Validator 1.0.3//EN"
"http://struts.apache.org/dtds/xwork-validator-1.0.3.dtd">
<validators>
<field name="name">
<field-validator type="requiredstring">
<message key="product.name.required"></message>
</field-validator>
</field>
</validators>
测试:
我们当前所在的区域是中国大陆,我们创建中国大陆的资源文件messages_zh_CN.properties
测试:
经过测试,你会发现,他自动选择语言国家的资源文件信息进行调用
如果把这两个资源文件的名字改掉:
测试
系统将找不到任何可用的资源文件,所以直接用key作为提醒信息了
10.1.2.package范围的国际化文件
作用范围:对package 下所有Action 都生效 (包括子包 )
配置方法:在package下面 建立 package.properties (无需在struts.xml配置 )
测试:
10.1.3.Action范围的国家化文件
作用范围:只对当前Action 生效
配置方法:在Action类所在包中创建 Action类名.properties (无需在struts.xml 配置 )
测试:效果
【小结】:
1. 三种国际化文件的有效的优先级:Action优先于package,package优先于全局
2. 如果国际化的信息中的key写错了,或者key在properties中找不到对应的信息,显示信息的地方会直接打印key
3. 在struts.xml中配置的时候默认省略properties扩展名
10.2.程序中获取国际化信息
10.2.1.在jsp页面获取
国际化的页面上读取:
使用<s:text name=”国际化的key”/>,在页面上实现语言的国际化。
修改product.jsp页面:
国际化,自动读struts.xml中配置的,配置国际化的信息,找到messages.properties文件
messages.properties文件配置:
读取非默认的国际化文件<s:i18n>的用法:
在src下,定义messages123.properties文件
测试:
10.2.2.在Action代码中获取
super.getText()的用法
在message_zh.properties文件中添加ProductAction.addsuccess属性
可以看到在控制台中输出:
商品添加成功。
提示:
1. 通过 getText 可以读取国际化文件
2. getText读取的顺序:依次读取 Action范围、package范围、全局
11.拦截器(★)
11.1.拦截器概述
拦截器就是一个类,它能够拦截Action的请求,并进行预处理。
Struts2 拦截器在访问某个 Action 方法之前或之后实施拦截,(在action之前调用的称之为前置拦截器,之后也称之为后置拦截)
拦截器是可插拔的,是一种AOP 实现 (AOP Spring 面向切面编程 )
----- AOP 理解为 代理思想 ,使用代理模式
aop思想简单理解:在不改变原来代码的情况下,对原来的代码功能进行控制和增强(增加或减少)
Filter:字符集编码的过滤器
Struts2 将拦截器定义拦截器栈,作用于目标Action
默认执行拦截器栈 defaultStack
Struts core 包 struts-default.xml --链条...
11.2.struts2底层分析
【原理分析】
1.当web.xml被加载之后,会初始化StrutsPrepareAndExecuteFilter,它会调用init方法初始化,准备struts相关的环境,加载相应配置文件(6个--包括struts.xml)--会将所有的action的name都加载到环境中。
2.访问/*,----StrutsPrepareAndExecuteFilter—-默认会执行doFilter,
ActionMapping mapping = prepare.findActionMapping(request, response, true);
找你访问的这个action有没有配置(是不是存在)
如果不存在,chain.doFilter(request, response); 直接过滤拦截,忽略后面的所有的过滤器和action的执行,并告诉你说action不存在,那么就不往下走。
如果存在,execute.executeAction(request, response, mapping);准备执行action
3.准备执行action,
ActionProxy proxy = config.getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
namespace, name, method, extraContext, true, false);
生成action的代理对象----增强---使用过滤器增强,
proxy.execute();代理
invocation.invoke();
ActionInvocation增强器里面的invoke方法,判断 if (interceptors.hasNext())-配置的那些拦截器有没有执行完,如果没有执行完,就执行resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
具体的拦截器,执行之后 return invocation.invoke();,返回到原来的调用对象,原来的调用对象又会自动调用invoke方法。--(链式递归)递归调用
4.当拦截器都执行完成之后(增强完成之后),resultCode = invokeActionOnly();--》执行具体的action:invokeAction(getAction(), proxy.getConfig());返回了结果集。
11.3.内置拦截器
常用的拦截器:
? Exception : 异常处理机制拦截器
? i18n : 处理国际化问题
? modelDriven : 将请求参数,封装model对象 (Action 实现ModelDriven接口)
? fileUpload :文件上传
? params : 请求参数转换封装
? conversionError : 将类型转换异常进行处理
? validation : 请求参数校验
workflow : 判断fieldError 是否存在,如果存在,自动跳转input视图
11.4.自定义拦截器
程序中每个拦截器 都必须实现 Interceptor 接口,开发人员 也可以继承 AbstractInterceptor 只需要覆盖 intercept 方法 ;开发人员 也可以继承 MethodFilterInterceptor ,只需要覆盖 doIntercept 方法 可以设置哪些方法 不进行过滤拦截
自定义的拦截器,MyInterceptor.java类:
package cn.itcast.e_interceptor;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.interceptor.Interceptor;
import com.opensymphony.xwork2.interceptor.MethodFilterInterceptor;
/**
* 自定义拦截器,继承MethodFilterInterceptor类
* @author yuanxinqi
*
*/
public class MyInterceptor extends MethodFilterInterceptor{
@Override
protected String doIntercept(ActionInvocation invocation) throws Exception {
//从session中获取user对象,如果获取不到,直接返回
if(ServletActionContext.getRequest().getSession().getAttribute("user")==null)
{
return "no_login";
}
//如果user对象不为空,则继续执行下一步操作
return invocation.invoke();
}
}
配置struts.xml:
<package name="interceptor" extends="struts-default" namespace="/interceptor">
<!-- 注册拦截器 -->
<interceptors>
<!-- name:名字 class:拦截器对应的完整的包路径 -->
<interceptor name="myInterceptor" class="cn.itcast.e_interceptor.MyInterceptor">
<!-- 登录的方法放行呢?
excludeMethods:放行的方法:
includeMethods:只拦截的方法
-->
<param name="excludeMethods">login</param>
</interceptor>
<!-- 设置拦截器栈 -->
<interceptor-stack name="myStack">
<interceptor-ref name="myInterceptor"></interceptor-ref>
<interceptor-ref name="defaultStack"></interceptor-ref>
</interceptor-stack>
</interceptors>
<!-- 告诉系统,默认的拦截器栈 -->
<default-interceptor-ref name="myStack"></default-interceptor-ref>
<!-- 配置全局结果集 -->
<global-results>
<result name="no_login">/e_interceptor/login.jsp</result>
</global-results>
<!-- UserAction -->
<action name="user_*" class="cn.itcast.e_interceptor.UserAction" method="{1}">
<result name="login_success">/e_interceptor/addbook.jsp</result>
<result name="login_fail">/e_interceptor/login.jsp</result>
</action>
<!-- BookAction -->
<action name="book_*" class="cn.itcast.e_interceptor.BookAction" method="{1}">
<result>/e_interceptor/addbooksuccess.jsp</result>
</action>
</package>
排除方法:
在MethodFilterInterceptor中定义了两个属性,一个属性是拦截器需要拦截的方法,还是一个是拦截器放行的方法
excludeMethods:拦截器拦截所有的方法,在是exclude中的方法不拦截
IncludeMethods:拦截器只拦截这个里面定义的方法
拦截器和过滤器 区别 ?
过滤器 javaweb学习,拦截服务器端所有资源的访问 (静态、 动态)。在web.xml
拦截器 struts2 学习,在struts2框架内部,只对Action访问进行拦截 (默认拦截器 ,无法拦截静态web资源(jsp、html), 可以将静态web资源放入WEB-INF, 通过Action间接访问)
12.OGNL表达式
OGNL是Object Graphic Navigation Language(对象图导航语言)的缩写,是一个使用简单、功能强大的、开源的表达式语言,可以方便地操作任何的对象属性、方法等。
struts2框架使用OGNL作为默认的表达式语言,主要用于页面的取值。它类似于EL表达式语言,但比EL语法强大很多。
EL Expression Language 表达式语言, 主要用来获取 JSP页面四个域范围数据 (page、 request、 session、 application )
例如:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!-- 引入struts的标签库 -->
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My JSP ‘ognl.jsp‘ starting page</title>
</head>
<body>
<%--
使用ONGL表达式
如何使用呢?答:OGNL 表达式需要借助struts的标签进行使用,在struts的 标签中可以使用ognl表达式
其实Struts的很多标签都支持ognl表达式,此处我们先选择<s:property>这个标签进行使用
--%>
<!-- ognl表达式用法一:直接访问java对象
value:写ognl表达式
-->
<s:property value="‘itcast‘"/><br>
<!-- ognl表达式用法二:访问对象的方法 -->
<s:property value="‘itcast‘.toUpperCase()"/><br>
<!-- ognl表达式用法三:访问静态方法
借助@@进行访问
第一个@: 对应类的完整包路径
第二个@:方法名
在默认情况之下,struts禁用静态方法的,所以使用静态方法的时候需要开启常量
struts.ognl.allowStaticMethodAccess=true
-->
<s:property value="@[email protected](10,21)"/><br>
<!-- ognl表达式用法四:可以直接参与计算 -->
<s:property value="1+2"/> <br>
</body>
</html>
struts.xml文件中的配置如下:
<!-- 开启静态方法的 调用 -->
<constant name="struts.ognl.allowStaticMethodAccess" value="true"></constant>
格式:@[类全名(包括包路径)]@[方法名|值名],如:@[email protected](10,20)、@[email protected]_NAME
注意:必须通过配置struts2的常量来开启静态方法调用功能:
struts.ognl.allowStaticMethodAccess=true
测试:
【小结】
1、ognl 表达式,需要结合struts2的<s:property > 标签进行使用,它的value属性支持ognl表达式
2、OGNL表达式最强大的功能是可以操作值栈(ValueStack)。
13.值栈(★★)
13.1.值栈(ValueStack)概述
值栈(ValueStack),是Struts2的数据中转站,栈中自动保存了当前Action对象和其他相关对象(包括常用的Web对象的引用,如request、session、application等),也可以手动保存自己的数据对象,同时也可以随时随地将对象从值栈取出或操作(通过OGNL表达式)
值栈(ValueStack),实际是一个接口的对象的称呼,接口是ValueStack类,实现类是OgnlValueStack类,该对象是Struts2利用OGNL的基础,或者说Struts2中Ognl使用都是基于值栈完成的。
如何数据中转站?
答:可以看成是一个容器,是一个临时的小型数据池,在这个里面存储着系统运行过程中产生的数据,这些数据包括(request、response/session/application..都会进入值栈)
为什么说是临时的呢?因为它是存储在内存中的
Struts2框架将ValueStack对象保存在request域中,键为“struts.valueStack”,即值栈是request域中的一个对象,一个请求对应一个Action实例和一个值栈对象
13.2.值栈数据存储结构
在值栈的内部有两个逻辑部分:
? ObjectStack(对象栈):又称为root栈,保存了Action的相关对象和动作,数据存储结构是List。
? ContextMap(上下文栈):又称为map栈,保存了各种映射关系,如常用的web对象的引用,数据存储结构是Map。
【示例1】值栈的获取方式:
1) request.getAttribute(“struts.valueStack”):用的较少
2) ActionContext.getContext().getValueStack():用的非常多:底层使用的还是第一种方式获取
ActionContext(Action上下文,工具类)
创建cn.itcast.b_valuestack包,并在包中创建ValueStackAction类,类中代码如下:
package cn.itcast.b_valuestack;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.util.ValueStack;
public class ValueStackAction extends ActionSupport{
@Override
public String execute() throws Exception {
//获取值栈 方式一
ValueStack valueStack1 = (ValueStack) ServletActionContext.getRequest().getAttribute("struts.valueStack");
//获取值栈的方式二:第二种方式的底层使用的还是第一种方式
ValueStack valueStack2 = ActionContext.getContext().getValueStack();
//我们在Action中获取了两次值栈,那这两个值栈是同一个对象吗?
//通过对结果的观察,发现,两种获取值栈的方式获取的值栈是同一个对象
System.out.println(valueStack1==valueStack2);
System.out.println(valueStack1.hashCode());
System.out.println(valueStack2.hashCode());
//当我们发出了两次请求,两次请求的值栈的hashcode不一致,表明每次请求都会重新创建值栈
return NONE;
}
}
13.3.值栈的操作
包括:存值和取值
13.3.1.存值
栈是一种数据结构,它按照先进后出的原则存储数据,即先进入的数据被压入栈底,最后进入的数据在栈顶,需要读取数据的时候,从栈顶开始弹出数据(即最后一个数据被第一个读出来)。
栈也被称为先进后出表,可进行插入和删除操作,插入称之为进栈(压栈)(push),删除称之为退栈(pop),允许操作的一端称为栈顶(top),另外一端就称为栈底(bottom)。栈底固定,而栈顶浮动。
对于栈就只能每次访问它的栈顶元素。
示例:
package cn.itcast.b_valuestack;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.util.ValueStack;
public class ValueStackAction extends ActionSupport{
@Override
public String execute() throws Exception {
//获取值栈 方式一
ValueStack valueStack1 = (ValueStack) ServletActionContext.getRequest().getAttribute("struts.valueStack");
//获取值栈的方式二:第二种方式的底层使用的还是第一种方式
// ValueStack valueStack2 = ActionContext.getContext().getValueStack();
//我们在Action中获取了两次值栈,那这两个值栈是同一个对象吗?
//通过对结果的观察,发现,两种获取值栈的方式获取的值栈是同一个对象
// System.out.println(valueStack1==valueStack2);
// System.out.println(valueStack1.hashCode());
// System.out.println(valueStack2.hashCode());
//当我们发出了两次请求,两次请求的值栈的hashcode不一致,表明每次请求都会重新创建值栈
/**
* 由于值栈中有两块数据结构,所以我们在存值的时候,分别向两块存储结构中存值
* 最终取得的时候,也是分别取出
*/
//向root栈中存值方式一:通过匿名的方式存值
ActionContext.getContext().getValueStack().push("itcast1");
ActionContext.getContext().getValueStack().push("itcast2");
//向root栈中存值方式二:有名字压栈
ActionContext.getContext().getValueStack().set("name", "itcast_root");
//向map栈中存值:为什么此处是向map栈中存值?
//
ActionContext.getContext().put("name", "rose_map");
return SUCCESS;
}
}
13.3.2.取值
对OGNL表达式的操作都是基于OgnlContext(map栈)对象,访问的规则如下:
? 如果访问 root栈内容(CompoundRoot 对象栈内容), 不需要#,直接通过元素的名称来访问。
? 如果访问 Map栈内容 (如request、response、session、servletContext、attr、parameters), 需要#key来引用访问,例如 #request.name 相当于 request.getAttribute("name" )
示例:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My JSP ‘valuestack.jsp‘ starting page</title>
</head>
<body>
<!-- 访问值栈 -->
<!-- 访问root栈:如何访问这个匿名对象呢? -->
<s:property value="[0].top"/>|<s:property value="top"/><br>
<!-- 如果我还想获取itcast1,如何获取呢? -->
<s:property value="[1].top"/><br>
<!-- 获取map栈中的内容 -->
<s:property value="#name"/><br>
<!-- 如何获取root栈中的有名字对象 -->
<s:property value="name"/>
<hr>
<s:debug />
</body>
</html>
13.3.3.存取小结
存进root栈,就不通过#获取;
存进map栈,就通过#获取
1. 如何向值栈保存数据
1) ValueStack.push(obj) :保存数据到Root栈顶-压栈顶(对象本身)-匿名
2) ActionContext.getContext().put(key,value) :保存数据到Map栈中
3) ValueStack.set(key,value):将数据保存到Root栈顶(数据对象自动被封装为Map来保存,栈顶是个map,map里面有个属性是对象)--有名字
4) 提供Action成员变量,提供getter方法(Action就在root栈中,Action属性可以被搜索)
2.ognl表达式如何获取值栈的数据
? JSP页面获取
1) <s :property value= “name”/> 先搜索root栈对象属性(getter方法:getXxx-->xxx),再搜索map的key
2) <s:property value=”#name” /> 搜索map的key
3) 通过 [index].top 指定访问root栈某层对象 ,例如 [0].top 栈顶对象
? Action代码获取
ValueStack.findValue(ognl表达式) ; 获取值栈数据
//在代码中获取root值栈中的值
String str1=ActionContext.getContext().getValueStack().findString("username");
//在代码中获取map值栈中的值
String str2=ActionContext.getContext().getValueStack().findString("#username");
System.out.println("str1:"+str1);
System.out.println("str2:"+str2);
13.3.4.值栈搜索
底层api:valuestack.findvalue(ognl)
示例:
package cn.itcast.b_valuestack;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
import com.opensymphony.xwork2.util.ValueStack;
public class ValueStackAction extends ActionSupport{
private String name = "action中的name";
@Override
public String execute() throws Exception {
System.out.println("ValueStackAction执行了....");
/**
* 值栈的获取方式
* 第一种:通过request获取
* 第二种:通过ActionContext获取
*/
//第一种获取方式
ValueStack valueStack1 = (ValueStack) ServletActionContext.getRequest().getAttribute("struts.valueStack");
//第二种获取方式
// ValueStack valueStack2 = ActionContext.getContext().getValueStack();
// 两个对象是同一个对象吗?:通过运行多次,我们发现,每次都会产生新的valueStack对象
//但是在同一次请求,不管通过哪种方式获取值栈,都是同一个值栈
// System.out.println(valueStack1==valueStack2);
// System.out.println(valueStack1.hashCode());
// System.out.println(valueStack2.hashCode());
/**
* 由于值栈中有两块数据存储结构,所以我们要分别向两块存储结构中存值
* 然后通过断点观察值栈结构的变化
*/
//向root栈存值方式一:匿名的方式存值
// ActionContext.getContext().getValueStack().push("itcast1");
//此处我们可以push 多次,但是,最后push肯定在root栈顶
// ActionContext.getContext().getValueStack().push("itcast2");
//向root栈存值方式二:有名字压栈
// ActionContext.getContext().getValueStack().set("name", "root栈中的jack");
//向map栈中存值
ActionContext.getContext().put("name", "map栈中的rose");
return SUCCESS;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
编写valuestack.jsp页面
<!-- 值栈的默认搜索 -->
<s:property value="name"/><br/>
<!-- 通过默认搜索,假如root栈找不到,找map -->
<s:property value="itcast"/>
【结果】
Action本身作为数据存放在root栈中,所以action中的属性,我们也是可以访问的
当值栈中存在着一个name对象,而Action的属性中也存在一个name属性的时候,那么当搜索name的时候会直接最先(第一次)找到的对象。
先搜索root栈,再搜索map栈,一找到就停止搜索,直接返回值
root栈的操作效率非常高
13.3.5.模型驱动优于属性驱动
当属性驱动和模型驱动都存在时,而且属性名相同时,会优先加载模型驱动,因为在action在root栈中,root栈在栈顶,Root栈中包括模型驱动的属性名和属性驱动的属性名,而且模型驱动的属性名在栈顶。如下:
13.3.6.生命周期
值栈的生命周期,就是request生命周期,也就是Action的生命周期
13.4.OGNL对值栈的操作
Struts 2支持以下几种表达式语言:
? OGNL(Object-Graph Navigation Language),可以方便地操作对象属性的开源表达式语言;
? EL(Expression Language),可以方便的操作JSP页面四个域范围数据 (page、 request、 session、 application );
? JSTL(JSP Standard Tag Library),JSP 2.0集成的标准的表达式语言;
? Groovy,基于Java平台的动态语言,它具有时下比较流行的动态语言(如Python、Ruby和Smarttalk等)的一些起特性;
? Velocity,严格来说不是表达式语言,它是一种基于Java的模板匹配引擎,据说性能要比JSP好。
Struts 2默认的表达式语言是OGNL,原因是它相对其它表达式语言更简单、强大, 最重要的是可以直接操作值栈。
在Struts2中,OGNL表达式三种特殊符号的使用方式 :
? # 号用法
? % 号用法
? $ 号用法
13.4.1.#使用
【#用法一】:
访问 Map栈中常用对象,包括web对象(request/response/session/application..), 添加#进行访问
提示:#attr 按照 page --- request --- session --- application的顺序依次进行搜索
【示例】在action中添加代码,向request、session、application域中放入对象,然后在页面通过ognl表达式获取,具体代码如下:
创建cn.itcast.c_ognl包,在包中,创建OgnlAction类,在类中编写如下代码:
package cn.itcast.c_ognl;
import org.apache.struts2.ServletActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class OgnlAction extends ActionSupport{
@Override
public String execute() throws Exception {
//向Servlet的API中放入信息
//request
ServletActionContext.getRequest().setAttribute("name", "request域中的name");
//session
ServletActionContext.getRequest().getSession().setAttribute("name", "session域中的name");
//application
ServletActionContext.getServletContext().setAttribute("name", "application域中的name");
return SUCCESS;
}
}
在WebRoot下面,创建c_ognl文件夹,在文件夹中创建ognl.jsp页面,在页面中,编写如下代码:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My JSP ‘ognl.jsp‘ starting page</title>
</head>
<body>
<!-- 如何获取呢? -->
request:<s:property value="#request.name"/>|${requestScope.name }<br>
session:<s:property value="#session.name"/>|${sessionScope.name }<br>
application:<s:property value="#application.name"/>|${applicationScope.name }<br>
<%
//向page域中存值
pageContext.setAttribute("name", "page域中的name");
%>
<!-- #attr的用法:page-request-session-application -->
page:<s:property value="#attr.name"/>|${pageScope.name }<br>
<!--
parameters:可以接收页面传递过来的参数:比如下面的地址中的name,就可以传递过来
http://localhost:8080/struts2_day03/ognl.action?name=itcast -->
parameters:<s:property value="#parameters.name"/>
</body>
</html>
在struts.xml中配置OgnlAction类,代码如下:
<!-- 配置OGNLAction -->
<action name="ognl" class="cn.itcast.c_ognl.OgnlAction">
<result>/c_ognl/ognl.jsp</result>
</action>
访问:
【#用法二】:
如果不加 # 会直接调用ValueStack搜索功能(findValue),先搜索root栈对象的属性,后搜索Map栈,加#直接搜索map栈
Action 代码
JSP代码
【#用法三】:用来构造map集合,必须要配合标签进行使用,
list:{‘value1’,’value2’}----new arraylist()
map:#{‘key1’ :’value1’,’key2’ :’value2’} ---new hashmap() 相当于map对象
【实际应用场景】:注册的时候的性别、学历,就可以构造list集合,也可以通过 # 构造map集合
13.4.2.%的使用
主要作用:
1 强制解析
2 强制不解析
【示例】
【第一步】在cn.itcast.c_ognl包中,创建Ognl2Action类,在类中编写如下代码:
package cn.itcast.c_ognl;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class Ognl2Action extends ActionSupport{
@Override
public String execute() throws Exception {
ActionContext.getContext().getValueStack().set("username", "tom");
return SUCCESS;
}
}
【第二步】在c_ognl包中, 创建ognl2.jsp页面,页面中代码如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My JSP ‘ognl2.jsp‘ starting page</title>
</head>
<body>
<!-- 默认是解析的 -->
<s:property value="username"/><br>
<!--强制不解析 -->
<s:property value="%{‘username‘}"/>
<hr>
<!-- 默认不解析 -->
<s:textfield value="username" /><br>
<!-- 强制解析 -->
<s:textfield value="%{username}" /><br>
</body>
</html>
【第三步】在struts.xml文件中,配置Action,代码如下:
<!-- 配置OGNL2Action -->
<action name="ognl2" class="cn.itcast.c_ognl.Ognl2Action">
<result>/c_ognl/ognl2.jsp</result>
</action>
【第四步】访问:
13.4.3.$的使用
$主要作用:允许我们在配置文件中使用OGNL表达式,(换句话说:$可以在xml文件中获取值栈的值。)
配置文件主要指:struts.xml、国际化的文件(xxx.properties)、校验配置文件(xxx-validation.xml)。
【示例1】在资源文件中使用:在页面读取携带参数 --国际化文件中使用
【第一步】在src新建 messages.properties
点击ok之后,出现如下信息,点击保存
在上图中出现的${productName}-----获取值栈的值
【第二步】在struts.xml 配置国际化文件
【第三步】在cn.itcast.c_ognl包中创建Ognl3Action类,在类中编写如下代码:
package cn.itcast.c_ognl;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class Ognl3Action extends ActionSupport{
@Override
public String execute() throws Exception {
//向root栈中添加内容
ActionContext.getContext().getValueStack().set("productName", "iphone");
return SUCCESS;
}
}
【第四步】:在c_ognl文件夹中,创建ognl3.jsp文件,代码如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My JSP ‘ognl.jsp‘ starting page</title>
</head>
<body>
<!--通过键获取资源文件中的内容-->
<s:text name="productinfo"></s:text>
</body>
</html>
【第五步】:在struts.xml文件中进行如下配置:
<!-- 配置OGNL3Action -->
<action name="ognl3" class="cn.itcast.c_ognl.Ognl3Action">
<result>/c_ognl/ognl3.jsp</result>
</action>
【第六步】测试
【说明】通过上述证明,表明在资源文件中可以通过${productName}的方式获取值栈中的内容
【测试二】struts.xml中使用$的方式获取值栈中的值:URL请求重定向时,携带参数
【第一步】在cn.itcast.c_ognl包中创建Ognl4Action类,在类中编写如下代码:
package cn.itcast.c_ognl;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class Ognl4Action extends ActionSupport{
@Override
public String execute() throws Exception {
//向root栈中添加内容
ActionContext.getContext().getValueStack().set("productName", "iphone");
return SUCCESS;
}
}
【第二步】编写ognl4.jsp页面,代码如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My JSP ‘ognl4.jsp‘ starting page</title>
</head>
<body>
This is my JSP page. <br>
<!-- 当重定向只会,我们发现,值传不过来了,这个时候,这个页面还必须要使用那个productName,那怎么办呢? -->
<%--
<s:property value="productName"/>
--%>
<!-- 如何获取参数的? -->
<s:property value="#parameters.productName"/>
</body>
</html>
【第三步】当将结果集的跳转类型修改为redirect的时候,结果会如何呢?
<!-- 配置OGNL3Action -->
<action name="ognl3" class="cn.itcast.c_ognl.Ognl3Action">
<result type="redirect">/c_ognl/ognl3.jsp?productName=${productName}</result>
</action>
【第四步】测试运行
【注意】:
1 大括号{}不能是中文状态下的。
如果是重定向,则无法在下一个页面上获取到值栈的值,原因是:重定向是重新发起请求,值栈随着第一次的request请求的消亡而消亡。
13.4.4.servletActionContext和ActionContext
他们之间继承:
ServletActionContext拥有ActionContext的所有功能。
但是,我们习惯这么操作:
操作值栈用:ActionContext
操作Servlet相关的API用:ServletActionContext
13.5.EL表达式获取值栈中的数据
示例:
【第一步】在cn.itcast.c_ognl包中创建Ognl5Action类,在类中编写如下代码:
package cn.itcast.c_ognl;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class Ognl5Action extends ActionSupport{
@Override
public String execute() throws Exception {
//向root栈中添加内容
ActionContext.getContext().getValueStack().set("username", "tom");
return SUCCESS;
}
}
【第二步】:在c_ognl文件夹中,创建ognl5.jsp文件,代码如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My JSP ‘ognl.jsp‘ starting page</title>
</head>
<body>
<!--通过键获取资源文件中的内容-->
${username}
</body>
</html>
【第三步】:在struts.xml文件中进行如下配置:
<!-- 配置OGNL3Action -->
<action name="ognl5" class="cn.itcast.c_ognl.Ognl5Action">
<result>/c_ognl/ognl5.jsp</result>
</action>
【第四步】测试
【说明】通过上述证明,表明通过el表达式可以获取值栈中的内容
【原理分析】
EL 表达式原理, 在page、request、session、application 四个范围,调用getAttribute 获取数据。为什么也可以获取值栈的值呢?
【原理分析】:
阅读源码
Struts2 对request进行包装了,对request 的 getAttribute 方法增强
Struts2 框架 提供 StrutsRequestWrapper 包装类,
上述代码发现:优先使用 request.getAttribute取值,如果取不到,执行 valueStack的findValue方法
【因此】
request的 getAttribute方法被覆写,因此我这里称其为“神奇的request”。
当时用$获取值得时候,会先从request域中获取数据,如没有,就会从值栈中获取数据。
【问题思考】:
后台代码:
request.setAttribute(“name“, ”aaa“ ) ;
valueStack.set(“name“,”bbb“ )(root栈)
页面代码:
<s:property name=”name” /> ----->bbb
${name} ---->aaa
14.Struts2的标签(★)
14.1.通用标签(Generic)
通用标签主要指两类:数据类标签和控制类标签。
14.1.1.<s :property>数据类标签
作用:将OGNL表达式的内容输出到页面
属性:
? value属性,接收OGNL表达式,从值栈取值
? default 属性:显示默认值,如果当通过OGNL表达式没有获取到值,default设置显示默认值
? escapeHtml 属性, 是否对HTML标签转义输出 (默认是转义,可以关闭)
示例:
【第一步】创建cn.itcast.d_tag包,在包中创建TagAction类,
package cn.itcast.d_tag;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class TagAction extends ActionSupport{
public String execute() throws Exception {
//ActionContext.getContext().put("name", "lucy");
ActionContext.getContext().put("html", "<table border=‘1‘ width=‘100‘ height=‘100‘><tr><td>A</td></tr></table>");
return SUCCESS;
}
}
【第二步】在WebRoot下面创建d_tag文件夹,创建tag.jsp页面,编写如下代码:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!—此处需要引入struts2的核心标签库-->
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My JSP ‘tag.jsp‘ starting page</title>
</head>
<body>
<s:property value="#name" default="itcast"/><br>
<!-- escapeHtml:是否解析html文件
true:默认值,默认不解析html文本
false:解析html文本
-->
<s:property value="#html" escapeHtml="false"/><br>
</body>
</html>
【第三步】配置struts.xml文件
<!-- property的用法测试的Action -->
<action name="tag" class="cn.itcast.d_tag.TagAction">
<result>/d_tag/tag.jsp</result>
</action>
【第四步】访问
14.1.2.<s :iterator>标签
作用:遍历集合对象(可以是List、set和数组等),显示集合对象的数据。(跟jstl的<c:foreach>功能一样)
属性:
value:迭代的集合,支持OGNL表达式,如果没有设置该属性,则默认使用值栈栈顶的集合来迭代。
var:引用变量的名称,该变量是集合迭代时的子元素。
status:引用迭代时的状态对象IteraterStatus实例,其有如下几个方法:
int getCount():返回当前迭代了几个元素;
int getIndex():返回当前迭代元素的索引;
boolean odd:返回当前迭代元素的索引是否是奇数
boolean even:返回当前迭代元素的索引是否是偶数
boolean isFirst():返回当前迭代元素的索引是否是第一个
boolean isLast():返回当前迭代元素的索引是否是最后一个
【示例一】: 在jsp页面中使用循环的方式输出1- 10 (打印 1- 10)
在WebRoot下面创建d_tag文件夹,在文件夹中创建tag1.jsp页面,具体代码如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My JSP ‘tag1.jsp‘ starting page</title>
</head>
<body>
<!-- 在页面打印输出1--10
begin:起始值
end:结束值
step:步长
var:别名
status:状态
iterator的工作原理:
1 、将值以匿名的方式压入root栈顶
2、将该值以命名的方式放入map栈,名字就是var后面定义的名字
3、mystatus也是存放在map栈中
-->
<s:iterator begin="1" end="10" step="1" var="num" status="myStatus">
<s:property value="[0].top"/>|<s:property value="top"/>|
<s:property value="#num"/>|<s:property value="#myStatus.odd"/>|
<s:property value="#myStatus.index"/>|<s:property value="#myStatus.isLast()"/>|
<s:property value="#myStatus.isFirst()"/>
<br>
</s:iterator>
</body>
</html>
【示例二】: 遍历集合对象
【第一步】在cn.itcast.d_tag包中,创建User对象,具体代码如下:
package cn.itcast.d_tag;
public class User {
private String username;
private String pwd;
public User() {
super();
}
public User(String username, String pwd) {
super();
this.username = username;
this.pwd = pwd;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
}
【第二步】创建Tag2Action类,在类中创建一个List集合,放入数据,具体代码:
package cn.itcast.d_tag;
import java.util.ArrayList;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class Tag2Action extends ActionSupport{
@Override
public String execute() throws Exception {
ArrayList<User> list = new ArrayList<User>();
list.add(new User("lucy", "123"));
list.add(new User("tom","123"));
list.add(new User("rose", "123"));
ActionContext.getContext().getValueStack().set("list", list);
return SUCCESS;
}
}
【第三步】在d_tag文件夹中,创建tag2.jsp页面,在页面中通过<iterator>标签访问值栈中的集合,代码如下:
?
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My JSP ‘tag2.jsp‘ starting page</title>
</head>
<body>
<!-- value:从值栈获取值 -->
<s:iterator value="list" var="user">
<!-- 遍历的过程:
1 将值以匿名的方式压入root栈顶
2 以有名字的方式放入map栈中(var定义的键)
-->
<%--
此处获取方式较多,建议先掌握一种
<s:property value="username"/>:<s:property value="pwd"/>
--%>
<s:property value="[0].top.username"/>:<s:property value="[0].top.pwd"/>|
<s:property value="username"/>:<s:property value="pwd"/>|
<s:property value="#user.username"/>:<s:property value="#user.pwd"/>|
${user.username }:${user.pwd }<br>
${username }:${pwd }<br>
</s:iterator>
</body>
</html>
【第四步】在struts.xml文件中配置如下:
<!-- s:iterator的用法测试的Actioin -->
<action name="tag2" class="cn.itcast.d_tag.Tag2Action">
<result>/d_tag/tag2.jsp</result>
</action>
【第五步】访问测试:
遍历对象的属性
? <s:property value=”name” /> 从root栈顶取值
? <s:property value=”#product.name” /> 从map取值
? ${name} 搜索,先从request对象中取值,如果取不到,就去值栈进行默认搜索
14.1.3.<s :if><s :elseif><s :else>
作用:页面判断,其中的test属性可以接收OGNL表达式。
【示例】
根据后台传入的态值用户状(0,1,2),在页面判断并显示中文(管理员、普通用户、游客)
action:
页面:
14.1.4.<s :a>超链接标签
作用:生成a标签链接
注意:传值的两种方式
<s:a action="tag" namespace="/">
<!--第一种方式,value中填写ognl表达式-->
<s:param name="name" value="’tom’"></s:param>
<!--第二种方式:直接在标签中间定义-->
<s:param name="name">tom</s:param>
new链接
</s:a>
【示例】使用<s:a>标签生成一个链接
【第一步】创建tag3.jsp页面,在页面中编写如下代码:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My JSP ‘tag3.jsp‘ starting page</title>
</head>
<body>
<a href="${pageContext.request.contextPath }/tag3.action?name=lucy">tag3--old请求</a><br>
<!-- action:请求的URL
namespace:请求的是哪个namespace下面的action
超链接传值方式一
-->
<s:a action="tag3" namespace="/">
<s:param name="name">tom</s:param>
new请求1
</s:a><br>
<!-- 超链接传值方式二
错误的传值写法:<s:param name="name" value="itcast"></s:param>
-->
<s:a action="tag3" namespace="/">
<!-- value:ognl表达式,访问是对象 -->
<s:param name="name" value="‘itcast‘"></s:param>
new请求2
</s:a>
</body>
</html>
【第二步】创建Tag3Action,具体代码如下:
package cn.itcast.d_tag;
import com.opensymphony.xwork2.ActionSupport;
public class Tag3Action extends ActionSupport{
private String name;
@Override
public String execute() throws Exception {
System.out.println(name);
return NONE;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
【第三步】配置struts.xml
<!-- a的用法 -->
<action name="tag3" class="cn.itcast.d_tag.Tag3Action"> </action>
【第四步】访问测试
其他用过的标签:
<s:fielderror/>
<s:i18n>
<s:param>
<s:text>...
14.2.用户界面(UI)标签
包含:表单类标签和其他类标签
form表单的Struts2标签和传统标签的对比:(参考)
14.2.1.<s :form>标签
作用:生成form标签。
属性:
? action属性,对应 struts.xml <action>元素name属性;
? namespace属性,对象 struts.xml <package>元素 namespace属性
【示例】创建tag4.jsp页面:代码如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My JSP ‘tag4.jsp‘ starting page</title>
</head>
<body>
<!-- 传统的表单: -->
<form action="${pageContext.request.contextPath }/tag.action" method="post">
</form>
<!-- struts2的表单 -->
<!--
action:请求的URL
namespace:请求的名称空间
-->
<s:form action="tag" namespace="/">
</s:form>
</body>
</html>
14.2.2. <s:textfield>, <s:password>, <s:hidden>, <s:textarea>
作用:
? <s:textfield> 文本框 ,生成 <input type=”text” >
? <s:password> 密码域 ,生成<input type=”password” >
? <s:hidden> 隐藏域 , 生成 <input type=”hidden” >
? <s:textarea>文本域,生成<textarea></textarea>
属性:
【示例】
文本框、密码框、隐藏域、文本域编写
<s:form action="tag" namespace="/">
<!-- 文本域
name:文本框的名字
label:生成文本框前面的提示的
-->
<s:textfield name="username" label="username" /><br>
<!-- 密码框 -->
<s:password name="password" label="password" /><br>
<!-- 隐藏域 :页面是看不到的-->
<s:hidden name="age" />
<!-- 文本域 -->
<s:textarea />
</s:form>
14.2.3. <s:radio>、<s:checkboxlist>、<s:select>
作用:(#构造map集合)
? <s:radio> 接收list或者map 生成一组单选按钮
? <s:select> 接收list或者map ,生成一组下拉列表
? <s:checkboxlist> 接收list或者map ,生成一组复选框
【示例】
<!-- radio:单选按钮 -->
<!-- 构造是list集合 -->
<s:radio list="{‘男‘,‘女‘}" name="sex" />
<!-- 构造map集合 -->
<s:radio list="#{‘male‘:‘男‘,‘female‘:‘女‘ }" name="sexmap" />
<!-- checkboxlist:复选框 -->
<s:checkboxlist list="{‘唱歌‘,‘跳舞‘,‘溜冰‘}" name="favor1" /><br>
<!-- 构造map集合 -->
<s:checkboxlist list="#{‘sing‘:‘唱歌‘,‘dance‘:‘跳舞‘,‘skiing‘:‘溜冰‘ }" name="favor2" />
<br>
<!-- select:下拉列表框 -->
<s:select list="{‘上海‘,‘北京‘}" name="city1" /><br>
<s:select list="#{‘shanghai‘:‘上海‘,‘beijing‘:‘北京‘ }" name="city2" />
14.2.4. <s:submit>、<s:reset>
作用:
? <s:submit>、<s:reset>分别对应html中的提交和重置
【示例】
<s:submit value="提交"/>
<s:reset value="重置" />
【示例完整代码】
【第一步】在d_tag文件夹中创建tag4.jsp页面,代码如下:
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s" %>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My JSP ‘tag4.jsp‘ starting page</title>
</head>
<body>
<!-- 传统的表单: -->
<form action="${pageContext.request.contextPath }/tag.action" method="post">
</form>
<!-- struts2的表单 -->
<!--
action:请求的URL
namespace:请求的名称空间
-->
<s:form action="tag" namespace="/">
<!-- 文本域
name:文本框的名字
label:生成文本框前面的提示的
-->
<s:textfield name="username" label="username" /><br>
<!-- 密码框 -->
<s:password name="password" label="password" /><br>
<!-- 隐藏域 :页面是看不到的-->
<s:hidden name="age" /><br>
<!-- 文本域 -->
<s:textarea /><br>
<!-- radio:单选按钮 -->
<!-- 构造是list集合 -->
<s:radio list="{‘男‘,‘女‘}" name="sex" />
<!-- 构造map集合 -->
<s:radio list="#{‘male‘:‘男‘,‘female‘:‘女‘ }" name="sexmap" />
<!-- checkboxlist:复选框 -->
<s:checkboxlist list="{‘唱歌‘,‘跳舞‘,‘溜冰‘}" name="favor1" /><br>
<!-- 构造map集合 -->
<s:checkboxlist list="#{‘sing‘:‘唱歌‘,‘dance‘:‘跳舞‘,‘skiing‘:‘溜冰‘ }" name="favor2" />
<br>
<!-- select:下拉列表框 -->
<s:select list="{‘上海‘,‘北京‘}" name="city1" /><br>
<s:select list="#{‘shanghai‘:‘上海‘,‘beijing‘:‘北京‘ }" name="city2" /><br>
<s:submit value="提交"/>
<s:reset value="重置" />
</s:form>
</body>
</html>
【第二步】运行:
14.3.主题样式
主题的作用:
Struts2 模板文件,支持两种Freemarker生成 (*.ftl模板文件) , Velocity生成 (*.vm 模板文件)
提供四种主题 :
? Simple 最简单主题
? Xhtml 通过 布局表格 自动排版 (默认主题 )
? css_xhtml 通过CSS进行排版布局
? ajax 以Xhtml模板为基础,增加ajax功能
在struts核心包,提供模板主题
问题: 如何修改主题
开发中,在struts.xml 配置常量,修改默认主题样式,对所有form生效
<!-- 修改struts标签的默认模板 -->
<constant name="struts.ui.theme" value="simple"></constant>
15.Struts2的文件上传
15.1.普通的文件上传
? 客户端
<form> 设置enctype编码类型(MIME类型) multipart/form-data
<form> 设置 method 提交方式 post
<input type=”file”> 元素,必须提供name属性
? 服务器
apache commons-fileupload 组件
jsp-smartupload 组件
Servlet3.0 以后 API内置文件上传API
COS 文件上传组件
【实现代码】
package cn.itcast.web.servlet;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Date;
import java.util.List;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.fileupload.FileItem;
import org.apache.commons.fileupload.disk.DiskFileItemFactory;
import org.apache.commons.fileupload.servlet.ServletFileUpload;
import cn.itcast.service.MyFileItemService;
import cn.itcast.utils.UploadUtils;
import cn.itcast.vo.MyFileItem;
public class UploadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
// 1、获得数据 2、保存文件到硬盘 3、保存相关信息到数据库
// 定义javabean对象
MyFileItem myFileItem = new MyFileItem();
if (ServletFileUpload.isMultipartContent(request)) {
DiskFileItemFactory diskFileItemFactory = new DiskFileItemFactory();
ServletFileUpload servletFileUpload = new ServletFileUpload(
diskFileItemFactory);
// 设置上传文件名乱码处理
servletFileUpload.setHeaderEncoding("utf-8");
try {
List<FileItem> fileItems = servletFileUpload
.parseRequest(request);
for (FileItem fileItem : fileItems) {
if (fileItem.isFormField()) {
// 普通表单域
// 获得name和value
String name = fileItem.getFieldName();
String value = fileItem.getString("utf-8");
System.out.println(name + "===" + value);
if (name.equals("realname")) {
myFileItem.setRealname(value);
} else if (name.equals("description")) {
myFileItem.setDescription(value);
}
} else {
// 文件上传域
String name = fileItem.getName();
String contentType = fileItem.getContentType();
InputStream in = new BufferedInputStream(fileItem
.getInputStream());
// 生成唯一文件名
String uuidname = UploadUtils
.generateRandonFileName(name);
// 生成目录
String dir = UploadUtils.generateRandomDir(uuidname);
File dirFile = new File(getServletContext()
.getRealPath("/WEB-INF/upload")
+ dir);
// 此时目录不一定存在
dirFile.mkdirs();
File outputFile = new File(dirFile, uuidname);
OutputStream out = new BufferedOutputStream(
new FileOutputStream(outputFile));
int temp;
while ((temp = in.read()) != -1) {
out.write(temp);
}
in.close();
out.close();
// 删除临时文件
fileItem.delete();
myFileItem.setSavepath(dir); // /8/9
myFileItem.setUuidname(uuidname);
myFileItem.setUploadtime(new Date(System
.currentTimeMillis()));
// 操作数据库保存
MyFileItemService service = new MyFileItemService();
service.addFileItem(myFileItem);
response.getWriter().println("upload success!");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
15.2.struts2的文件的上传
Struts2 内部文件上传,默认采用 apache commons-fileupload
Struts2 默认导入文件上传jar包
defaultStack 默认拦截器栈,提供 fileUpload的拦截器,用于实现文件上传
【提示】上传的表单一定要添加 enctype="multipart/form-data"这个值
【第一步】编写页面:在webRoot下面创建e_upload文件夹,编写upload.jsp 上传文件表单
<%@ page language="java" import="java.util.*" pageEncoding="UTF-8"%>
<%@ taglib uri="/struts-tags" prefix="s"%>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>My JSP ‘upload.jsp‘ starting page</title>
</head>
<body>
<!-- 原始的上传表单 -->
<form action="${pageContext.request.contextPath }/upload.action" method="post" enctype="multipart/form-data">
<input type="file" name="upload"><br>
<input type="submit" value="提交">
</form>
------------------------------------------------------------------------------------------
<!-- 使用struts标签实现的表单 -->
<s:form action="upload" method="post" namespace="/" enctype="multipart/form-data">
<s:file name="upload" />
<s:submit value="提交" />
</s:form>
</body>
</html>
【第二步】编写UploadAction 接收上传后的文件
package cn.itcast.e_upload;
import java.io.File;
import org.apache.commons.io.FileUtils;
import com.opensymphony.xwork2.ActionSupport;
public class UploadAction extends ActionSupport{
/**
* 创建文件对象,用来接收文件
* 拦截器会自动访问这些属性
*/
//文件对象
private File upload;
//文件名,默认命名:文件名+FileName(区分大小写)
private String uploadFileName;
//文件类型,默认命名:文件名+ContentType(区分大小写)
private String uploadContentType;
@Override
public String execute() throws Exception {
System.out.println("UploadAction ....");
//将上传的文件放到磁盘上的指定位置
FileUtils.copyFile(upload, new File("f://"+uploadFileName));
return NONE;
}
public File getUpload() {
return upload;
}
public void setUpload(File upload) {
this.upload = upload;
}
public String getUploadContentType() {
return uploadContentType;
}
public void setUploadContentType(String uploadContentType) {
this.uploadContentType = uploadContentType;
}
public String getUploadFileName() {
return uploadFileName;
}
public void setUploadFileName(String uploadFileName) {
this.uploadFileName = uploadFileName;
}
}
【注意】成员变量的名字必须和页面对应:
【第三步】配置struts.xml
<!-- 配置Action -->
<action name="uploadAction" class="cn.itcast.e_upload.UploadAction">
<result name="input">/e_upload/upload.jsp</result>
</action>
【第四步】访问
【提示】文件会默认上传到:盘符:\apache-tomcat-7.0.69\work\Catalina\localhost\struts2_day03
因为这是个临时的存储位置,一旦系统运行结束,就自动删除
15.3.文件上传的参数设置
在struts2 文件上传,存在一些限制参数,当违背参数,跳转 input 视图
【知识点1】设定允许上传的文件后缀名:
【知识点2】在文件上传的过程中,会出现大小问题,这里我们进行可以大小设定
struts有两个地方来限制文件上传大小的!
1)核心配置文件中的常量(最常用最有效的方式)
2)在action中配置:(第二种方式的大小其实受第一种方式限制)
【问题】:这两种有什么区别:
全局常量配置的大小是struts同时能处理的文件的大小。(几个线程同时上传,同一时间总的大小不能超过这个数字,如果超过了,服务器就抛出没有响应的错误。)
action中配置的,是针对一次上传的文件大小。这个大小不能超过全局常量的配置,否则要么超过了报错,要么无效。
结论:全局一般设置大一些,局部的一般根据要求设置。
16.Struts2的文件的下载
16.1.原始文件的下载
要点:两头一流
? 使用response输出流,将文件信息打印到客户端
? 设置Content-Type头信息, 通过servletContext.getMimeType(文件名) 获取
? 设置Content-Disposition 头信息 , attachment;filename=文件名
【原始下载代码回顾】
package cn.itacst.download;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import sun.misc.BASE64Encoder;
public class DownloadServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
//获取请求的文件名
String parameter = request.getParameter("fileName");
//解决中文乱码
String fileName = new String(parameter.getBytes("iso-8859-1"),"utf-8");
//加载要被下载的文件数据
String realPath = getServletContext().getRealPath("/upload");
File file = new File(realPath,fileName);
//通知浏览器以下载的方式请求资源
//设置响应消息头
//info.txt 截取点之后的后缀名,
//设置文件媒体格式
response.setContentType(getServletContext().getMimeType(fileName));
//处理中文文件名的乱码问题
//根据不同的浏览器,不同处理
String header = request.getHeader("User-Agent");
if(header.contains("Firefox") ){
//当前是火狐浏览器
BASE64Encoder base64Encoder = new BASE64Encoder();
fileName = "=?utf-8?B?" + base64Encoder.encode(fileName.getBytes("utf-8")) + "?=";
}else{
fileName = URLEncoder.encode(fileName,"utf-8");
}
//通知浏览器要被下载的文件的文件名 text/html;charset=utf-8
response.setHeader("Content-Disposition", "attachment;filename=" + fileName);
//使用输入流读取数据
FileInputStream in = new FileInputStream(file);
ServletOutputStream out = response.getOutputStream();
//设置一个缓冲区
byte[] buf = new byte[8192];
while(in.read(buf)!=-1){
out.write(buf);
out.flush();
}
in.close();out.close();
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
16.2.struts2的文件的下载
【第一步】:创建cn.itcast.f_download包,在包中创建DownloadAction类:
package cn.itcast.f_download;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import org.apache.struts2.ServletActionContext;
import cn.itcast.utils.FileUtils;
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionSupport;
public class DownloadAction extends ActionSupport{
@Override
public String execute() throws Exception {
String path = "f://";
String filename="1你好.avi";
//下载需要两头一流
//contentType:文件类型
String contentType = ServletActionContext.getServletContext().getMimeType(filename);
//inputStream:流
InputStream inputStream = new FileInputStream(new File(path+filename));
//如果下载的文件名中含有中文,咋们就进行中文编码
String agent = ServletActionContext.getRequest().getHeader("user-agent");
filename = FileUtils.encodeDownloadFilename(filename, agent);
//contentDisposition:设置文件的打开方式,和打开的文件的名字
String contentDisposition = "attachment;filename="+filename;
//将两头一流放入值栈中
ActionContext.getContext().put("contentType", contentType);
ActionContext.getContext().put("contentDisposition", contentDisposition);
ActionContext.getContext().put("inputStream", inputStream);
return SUCCESS;
}
}
【第二步】编写FileUtils类,用来给文件的文件名进行编码,放置出现乱码
package cn.itcast.struts.e_download;
import java.io.IOException;
import java.net.URLEncoder;
import sun.misc.BASE64Encoder;
public class FileUtils {
/**
* 下载文件时,针对不同浏览器,进行附件名的编码
*
* @param filename
* 下载文件名
* @param agent
* 客户端浏览器
* @return 编码后的下载附件名
* @throws IOException
*/
public static String encodeDownloadFilename(String filename, String agent)
throws IOException {
if (agent.contains("Firefox")) { // 火狐浏览器
filename = "=?UTF-8?B?"
+ new BASE64Encoder().encode(filename.getBytes("utf-8"))
+ "?=";
filename = filename.replaceAll("\r\n", "");
} else { // IE及其他浏览器
filename = URLEncoder.encode(filename, "utf-8");
filename = filename.replace("+"," ");
}
return filename;
}
}
【第三步】配置struts.xml:
<!-- 配置文件下载的Action -->
<action name="download" class="cn.itcast.f_download.DownloadAction">
<!-- stream:专门用来下载用的结果集类型 -->
<result name="success" type="stream"></result>
</action>
注意结果集的配置。
【第四步】
16.3.struts2文件下载机制
我们为什么要在struts.xml中配置contentType和contentDisposition属性?
Struts2 实现文件下载,内置了 stream 类型结果集,打开struts-default.xml文件,如下:
找到StreamResult类
原理:
需要三个东西
? inputName :用来配置 文件读取输入流 方法的名称 (默认值 inputStream , 在Action提供 getInputStream 方法);
? contentType : 下载文件 Mime类型;
? contentDisposition : 以附件形式下载 (attachment;filename=文件名)。IE 采用 URL编码;火狐 采用 Base64编码;