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="@java.lang.Math@max(10,21)"/><br>
<!-- ognl表达式用法四:可以直接参与计算 --> <s:property value="1+2"/> <br>
</body> </html> struts.xml文件中的配置如下: <!-- 开启静态方法的 调用 --> <constant name="struts.ognl.allowStaticMethodAccess" value="true"></constant> 格式:@[类全名(包括包路径)]@[方法名|值名],如:@java.lang.Math@max(10,20)、@cn.itcast.MyConstant@APP_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编码; 标签 |