目录
Struts2的介绍与执行流程 介绍:执行流程:运行环境搭建基础示例Action类的编写 介绍:访问servlet API补充:配置文件 常见配置文件:常量的配置:struts.xml配置: Action的访问配置:结果视图result的配置:数据封装: 普通属性的封装复杂类型的数据封装数据封装中的报错:Input逻辑视图:补充:OGNL 介绍:OGNL的使用认识:使用:值栈获取值栈:操作值栈: 小演示:OGNL中的特殊符号EL的强化:补充:数据校验 手动定义校验规则:使用自带的数据校验器:校验器的参数如何配置常见自带数据校验器:补充:拦截器 拦截器的介绍:struts2自带的拦截器自定义拦截器:struts2的标签: 通用标签: 控制标签:数据标签:表单标签:用来取代html标签错误显示标签:补充:数据回显:写在最后首发日期:2018-08-15 修改日期:
2018-09-08:发现序号与我使用的markdown文本编辑器不同,所以把markdown序号改成了纯文字序号。2018-10-26:重新整理了一下文字。1.在官网下载struts2:https://struts.apache.org/
我使用的版本是2.3.34 2.解压下载到的压缩文件,可以看到几个比较重要的文件夹和其他的文件
重要文件夹: apps:存放着struts2的实例程序,都是一些war文件,war文件直接放入到tomcat中就可以运行。docs:存放着struts2的文档lib:存放着struts2的核心类库、第三方插件类库src:存放着struts2的全部源代码 3.struts2基本运行所需要的依赖包(13个):asm-3.3.jarasm-commons-3.3.jarasm-tree-3.3.jarcommons-fileupload-1.3.1.jar:Struts2文件上传组件依赖包commons-io-2.2.jar:包含处理IO的一些工具类commons-lang3-3.2.jarfreemarker-2.3.22.jar:Struts2标签模板使用的类库javassist-3.11.0.GA.jar:javassist的类库,javassist可以用于反射和代理对象。log4j-api-2.2.jar:包含了Struts2的日志管理组件依赖包的APIlog4j-core-2.2.jar:Struts2的日志管理组件依赖包ognl-3.0.6.jar:OGNL表达式语言的依赖包struts2-core-2.3.24.jar:struts2的核心包xwork-core-2.3.24.jar:WebWork核心库 ,struts2的运行依赖于webwork 上面的13个依赖包你可以慢慢地一个个找出来,你也可以利用struts2提供的基础示例工程来获取(一个基础的工程肯定已经搭建好了环境),在apps文件夹中,有一个struts2-blank.war,它是一个已经搭建好了环境的空白工程,我们可以右键用压缩文件打开,在struts2-blank.war\WEB-INF\lib下就有我们所需要的13个依赖包。 【如果你会maven,那么也可以使用maven来搭建struts2的环境】【这里提醒一下,log4j的两个包在struts-2.3.34\lib中是找不到的,你可以在struts2-blank.war找到它,或者自己下载】
通常来说,这13个依赖包就足够我们的struts2的基本使用了。如果功能不足够,我们可以后续再从lib中导入需要的依赖包。
下面的示例基于eclipse。
1.新建web工程 2.导入依赖包
3.创建一个Action类。(这是用来处理请求的,是MVC中的Model。这里遵循了一种规范,使得它能够处理struts转发过来的请求,这些规范将在后面讨论。)
package work.action1; public class HelloAction { public String execute() { System.out.println("我接收到你的请求了"); return "success";//这里返回的结果影响视图的显示 } }4.在src目录下创建struts.xml,在struts.xml中对Action进行配置,这里配置的是请求转发信息和请求结果视图。action标签配置了请求转发信息,这样struts2就知道哪个请求交给哪个程序处理了;result标签配置了请求结果视图,struts2就知道请求结束后跳转到哪个视图了。
<?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 name="demo1" extends="struts-default" namespace="/"> <!--下面指明了,当发起的请求为“hello.action”的时候,给HelloAction来处理。 --> <action name="hello" class="work.action1.HelloAction"> <!--下面指明了HelloAction中返回值为success时,跳转到哪个视图 --> <result name="success">/index.html</result> </action> </package> </struts>5.在web.xml中配置struts2的核心过滤器,struts2的运行依赖于过滤器,核心过滤器相当于它的前端控制器,有了核心过滤器struts2才能够将请求转发给Action。
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5"> <display-name>MystrutsDemo</display-name> <welcome-file-list> <welcome-file>index.html</welcome-file> <welcome-file>index.htm</welcome-file> <welcome-file>index.jsp</welcome-file> <welcome-file>default.html</welcome-file> <welcome-file>default.htm</welcome-file> <welcome-file>default.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>6.部署工程到服务器上 7.访问http://localhost:8080/MystrutsDemo/hello.action,结果是控制台打出了“我接收到你的请求了”,页面也跳转到index.html中了。【注意这里访问的是hello.action】
在struts.xml中还可以可以配置结果视图页面,action类中的方法返回的结果如果与struts.xml中action下的result匹配,那么处理完请求后会跳转到result指定的结果视图。
方式1:POJO类的方式:创建一个简单类(懵懂初学的你可以先认作是只有目标方法的类),创建自定义方法,方法的格式要求是public String 方法名(),这里方法名是什么不重要,请求过来调用什么方法是可以配置的(这个在struts.xml中配置,后面会讲)。如果你不想配置调用什么方法的话,那么要求方法名要求是execute。请求的处理默认就是调用这个方法处理的。
package work.action1; public class HelloAction { public String execute() { System.out.println("我接收到你的请求了"); return "success";//这里返回的结果影响视图的显示 //如果你不想进行页面跳转,你可以return null; } } 方式2:定义一个类,实现Action接口;实现Action接口需要实现execute方法【因为它是默认的请求处理方法所以需要进行实现,如果你不需要它,那么你可以空实现,然后定义其它你想要的方法,但格式要是public String 方法名(),然后在struts.xml中配置哪些请求交给哪个action处理】 【Action接口给我们提供了五个常量:SUCCESS成功("success"),ERROR错误("error"),LOGIN登录出错的跳转("login"),INPUT表单校验出错的跳转("input"),NONE不跳转("null")。我们可以“考虑”用这些常量作为我们的返回值,使用常量能帮助我们指明某个视图的意义,但也可以不使用。】 package work.action1; import com.opensymphony.xwork2.Action; public class ActionDemo2 implements Action { @Override public String execute() throws Exception { System.out.println("你的请求交给了ActionDemo2来处理了。"); return null; } } 方式3:继承ActionSupport,execute由于是默认的请求方法,所以你可能需要进行重写。但如果你每个请求都有指定的方法来处理,那么你只需要定义那些需要的方法即可。【继承ActionSupport是最常用的,ActionSupport实现了Action接口和继承了一些常用类,它能够提供数据校验、错误消息处理等功能】【ActionSupport实现了Action接口,所以它也有上面所说的五个变量】 package work.action1; import com.opensymphony.xwork2.ActionSupport; public class ActionDemo3 extends ActionSupport { @Override public String execute() { System.out.println("你的请求交给了ActionDemo2来处理了"); return null; } public String findAll() { System.out.println("给findAll.action指定调用findAll来处理"); return null; } //findAll需要这样配置<action name="findAll" class="work.action1.ActionDemo3" method="findAll"></action> }上面谈完action的编写之后,那么就应该谈到业务逻辑的处理了,业务逻辑通常有几个核心:接受数据、处理数据、返回数据。其中处理数据由于大部分是数据库内容或者可自定义内容,与struts没有强烈的关联,所以这里不讲。而接受数据与返回数据,在以前的servlet学习中都是使用数据域来存储和获取数据的。而struts2虽然高级于servlet,但也脱不开servlet,下面将讲述struts2中servlet API的使用。也正是由于struts2高级于servlet,所以数据接收和返回数据的手段也比servlet多,而这一部分将留到数据封装和数据回显中讲述。
直接访问servlet API:
利用ServletActionContext来直接获取request、session、context等对象 package work.action1; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.apache.struts2.ServletActionContext; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; public class ActionDemo5 extends ActionSupport { @Override public String execute() throws Exception { HttpServletRequest request = ServletActionContext.getRequest(); HttpServletResponse response = ServletActionContext.getResponse(); ServletContext servletContext = ServletActionContext.getServletContext(); //获取到对象之后,你就可以使用在servlet中学到的操作来操作了 return NONE;//不跳转 } } 实现对应接口(比如获取request需要实现ServletRequestAware,基本都是ServletXXXXXAware)。实现对应接口需要实现对应的setXXX方法,方法有我们需要的对象的形参,可以直接把形参赋值给成员变量。 package work.action1; import javax.servlet.http.HttpServletRequest; import org.apache.struts2.interceptor.ServletRequestAware; import com.opensymphony.xwork2.ActionSupport; public class ActionDemo6 extends ActionSupport implements ServletRequestAware { private HttpServletRequest request; @Override public void setServletRequest(HttpServletRequest request) { this.request=request;//把形参赋值给成员变量 } @Override public String execute() throws Exception { request.setAttribute("name", "neo"); return NONE; } }从基础示例中,我们可以看到两个配置文件的出现:web.xml和struts.xml,struts2的运行需要配置文件的帮助,下面我将介绍struts2中的配置文件。
web.xml:这个web.xml是我们之前的servlet中认识的web.xml,它在struts2中的主要作用是配置核心过滤器,因为struts2的运行依赖于核心过滤器。
<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:struts.xml是我们自行定义的配置文件(struts2会自动读取,struts.xml可以存放到src目录下,必须是classpath包含的路径),struts.xml主要用来配置action和result,用来使业务请求跟处理业务请求的业务逻辑处理建立上关系。
<struts> <package name="demo2" extends="struts-default" namespace="/"> <action name="actionDemo21" class="work.action2.ActionDemo21" > <!-- 局部视图结果视图 --> <result name="saveUI">/add.jsp</result> </action> </package> </struts>struts-default.xml:struts-default.xml是struts2自带的配置文件,它里面有很多预定义的常量、预定义的组件配置和拦截器等内容。(自带的意味不能修改)在struts2的使用中,struts-default.xml中有一个struts-default包,我们通常用它来作为struts.xml的包中的父包,使子包继承了父包中的常量、拦截器等配置,省去了很多的人工配置。
<struts> <package name="demo2" extends="struts-default" namespace="/"> <!-- 在struts.xml中,上面的extends中的struts-default实际上就是struts-default.xml中的struts-default包 --> </package> </struts>default.properties:default.properties是struts2自带的配置文件,里面有很多预定义的常量,这些常量定义了很多struts2的配置信息,比如“请求以什么字符编码(默认是utf-8)”、“默认请求扩展名(默认是.action或者空)”。
struts.properties:struts.properties是我们自行定义的配置文件(struts2会自动读取,struts.properties可以存放到src目录下,必须是classpath包含的路径),主要用来配置属性,由于default.properties中定义的常量不能修改,我们可以在struts.properties中对已定义的常量的值进行覆盖。struts.properties中常量生效的优先级要高于default.properties中的。
配置文件的加载顺序:
default.propertiesstruts-default.xmlstruts-plugin.xmlstruts.xmlstruts.propertiesweb.xml在上面的default.properties和struts.properties提到了常量的配置,这些常量影响struts2就好比线程池的配置文件影响线程池。struts2在default.properties中默认定义了很多常量,不然让程序员一个个变量配置过去就不知道猴年马月了。
default.properties是struts2是所有的常量配置信息的根源,它给所有struts2中定义的常量都赋予了值。所以我们需要配置常量的时候,都可以参考default.properties。
在default.properties中有默认的常量,如果我们想要修改常量,我们需要利用struts.xml 、web.xml和struts.properties
struts.xml中的写法:<constant name="配置名" value="配置值"></constant> <struts> <constant name="struts.enable.DynamicMethodInvocation" value="true"></constant> <constant name="struts.action.extension" value="action"></constant> <package name="demo1" extends="struts-default" namespace="/"> <!--内容省去,请关注上面的常量定义的配置方法--> </package> </struts>struts.properties中的写法
struts.enable.DynamicMethodInvocation=true struts.action.extension=actionweb.xml中的写法:使常量作为核心过滤器的初始化参数。
<filter> <filter-name>struts2</filter-name> <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class> <init-param> <param-name>struts.action.extension</param-name> <param-value>action</param-value> </init-param> </filter> <!-- 省去下面的内容 -->常见常量配置:
struts.action.extension :指定struts2处理的请求的后缀,即符合后缀才会交给struts2处理,否则404。默认是“action,,”即要求请求为xxx.action或xxxstruts.enable.DynamicMethodInvocation :指定是否开启动态方法访问配置生效优先级:后配置的常量会覆盖前面定义过的常量,下面的常量配置优先级下面的高于上面的。
default.propertiesstruts-default.xmlstruts-plugin.xmlstruts.xmlstruts.propertiesweb.xmlstruts.xml需要xml约束,这里给一下:
<!DOCTYPE struts PUBLIC "-//Apache Software Foundation//DTD Struts Configuration 2.3//EN" "http://struts.apache.org/dtds/struts-2.3.dtd"> package标签:主要用来方便管理多个action,没有特殊意义,但要求在一个工程中package标签的name属性不能重复 属性: name:package的名称,没有特殊意义,只是方便更好的管理,就好像通常我们会给实体类的包命名成domain。extends:指明这个package继承哪个包,通常值为struts-default,继承的意义是继承了别的包中定义好了的配置。struts-default(对应struts2-core-2.3.34.jar/struts-default.xml中的struts-default pageckage)为我们定义了很多配置,这方便了我们的使用。namespace:名称空间,用来跟action中的name共同决定请求路径(namespace + name-->最终请求路径)abstract :是否是抽象的包,是抽象的包的话就可以被别的包继承。action标签:用来配置请求跟action的对应信息,配置哪个请求交给哪个action来处理(这个步骤英文名为workflow path) 属性: name:与namespace共同决定访问路径 (比如namespace="/",而name="hello"时,那么请求路径是localhost:8080/工程名/hello.action,这里有个后缀.action是struts默认的常量,是可以更改的。)class:指定请求交给哪个action来处理,值为action类的全路径method:指定业务请求处理执行action中的哪个方法,默认为execute。【这里有其他的配置方法,留到下面的Action的访问配置中讲】converter:类型转换器。【这里不讲这个】include标签:一个struct.xml中可以包含其他的struct.xml(这里名字一样只是代表功能一样,名字可以更改,比如下面的例子中,但最终的使用include的要是struct.xml),在struts标签下使用include标签就可以引入其他的struct.xml include标签方便于模块化设计。(就好像java中一个包用来设计实体类,另一个包负责其他功能,它只需要导入实体类所在的包就能使用到实体类)<include file="work/action2/struts_demo2.xml"></include>给一个参考示例:
<?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> <constant name="struts.action.extension" value="action"/> <package name="demo1" extends="struts-default" namespace="/"> <action name="customer_*" class="customerAction" method="{1}"> <result name="saveUI">/jsp/customer/add.jsp</result> </action> </package> </struts>默认情况:直接配置name和class,默认请求交给class中的类的execute方法来处理
<action name="actionDemo2" class="work.action1.ActionDemo2"></action>通过method来直接指定调用哪个方法来处理,下面的findAll.action会交给ActionDemo3中的findAll方法来处理
<action name="findAll" class="work.action1.ActionDemo3" method="findAll"></action>利用通配符来指定调用哪个方法来处理,在name中使用通配符"*"来代表匹配任意字符串,在method中获取通配符匹配到的结果{1}代表获取第一个匹配的字符串,后面的获取也是{2}、{3}。下面中如果请求为customer_findAll.action,那么会调用ActionDemo7中的findAll方法。【通配符也可以在class中使用,所以你甚至可以根据请求来决定处理的类】
<action name="customer_*" class="work.action1.ActionDemo7" method="{1}"></action>动态方法访问:动态方法访问有点类似上面的通配符的方式,但它主要在
首先需要在struts.xml的struts标签下开启动态方法访问:
<constant name="struts.enable.DynamicMethodInvocation" value="true"></constant>然后在struts.xml配置,class中填用于处理的类的路径,name中用于填写”标识“,这个标识的作用可以说成用来标识某个Action,当请求为”标识“+"!"+"方法名".action时,就会调用Action的对应方法。(不理解的话看下面页面中的请求编写就理解了)
<action name="user" class="work.action1.ActionDemo8"></action>最后,请求的编写是重点,请求要:”标识“+"!"+"方法名".action,发起什么样的请求就执行什么样的方法。
比如http://localhost:8080/MystrutsDemo/user!list.action就会调用ActionDemo8的list方法局部结果视图配置:配置只针对result所在的action
<struts> <package name="demo2" extends="struts-default" namespace="/"> <action name="actionDemo21" class="work.action2.ActionDemo21" > <!-- 局部视图结果视图 --> <result name="saveUI">/add.jsp</result> </action> </package> </struts>全局结果视图配置:当想要某个结果视图在包下所有的action中都生效,那么可以使用全局结果视图,只要action返回值是这个,那么就会触发这个结果视图。【局部的结果视图优先级高于全局结果视图,如果在action中定义了同名的,那么局部的生效】
<struts> <package name="demo2" extends="struts-default" namespace="/"> <!-- 全局结果视图定义开始 --> <global-results> <result name="success">/success.jsp</result> <!-- 可以有多个result --> </global-results> <!-- 全局结果视图定义结束 --> <action name="actionDemo21" class="work.action2.ActionDemo21" > <result name="saveUI">/add.jsp</result> </action> </package> </struts>分隔线:上面讲了action,result和struts.xml,一个大致的请求处理流程就已经结束了,下面的内容会更深一点。
在上面的action的讲解中,我们讲了使用原始的servlet来获取数据,但struts2比servlet高级得多,它可以自动封装表单提交过来的数据。
属性驱动:Action中提供带有setter方法的属性,页面中提交与属性同名的表单项。当调用Action来处理的时候,同名的数据会对应封装到Action中的属性中。
Action的编写:
package work.action2; import com.opensymphony.xwork2.ActionSupport; public class ActionDemo22 extends ActionSupport { //有属性 private String username; private String password; //属性有setter public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } @Override public String execute() throws Exception { System.out.println(username+".."+password);//查看封装结果 return NONE; } }页面的表单项的写法:要求表单项的name与Action中的属性同名
<form action="${pageContext.request.contextPath }/actionDemo22.action" method="post"> <h1>属性驱动:属性有setter的方式</h1> <!-- 表单项的name与Action中的属性同名 --> <input type="text" name="username" /> <input type="password" name="password" /> <input type="submit" /> </form>属性驱动:Action中提供一个带有getter、setter方法的类对象,页面中提交以类对象变量名.类对象属性*为命名的表单项。当调用Action来处理的时候,提交的数据会封装到Action中的类对象中。
Action的写法:
package work.action2; import com.opensymphony.xwork2.ActionSupport; import domain.User; public class ActionDemo23 extends ActionSupport { //有类对象 private User user; //有setter,getter public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public String execute() throws Exception { System.out.println(user.getUsername()+".."+user.getPassword());//查看封装结果 return NONE; } }页面表单项的写法:要求表单项的name为Action中的“类对象.类的属性”
<form action="${pageContext.request.contextPath }/actionDemo23.action" method="post"> <h1>属性驱动:类对象有setter、getter的方式</h1> <!-- 表单项的name为Action中的类对象.类的属性 --> <input type="text" name="user.username" /> <input type="password" name="user.password" /> <input type="submit" /> </form>模型驱动:Action实现ModelDriven<T>接口,T为目标类,实现里面的getModel方法。首先,要求Action中手动初始化一个类对象,然后,将getModel方法中的返回值改成这个类对象。当页面中提供与类对象的属性同名的表单项时,提交的数据会通过ModelDriven自动封装到类对象中,并经过getModel返回,返回后类对象中就已经封装好了提交上来的数据。
action的写法
package work.action2; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.ModelDriven; import domain.User; public class ActionDemo24 extends ActionSupport implements ModelDriven<User> { //有类对象 private User user=new User(); //在getModel中将返回值改成类对象的变量名 @Override public User getModel() { return user;//此处将数据封装到user中 } @Override public String execute() throws Exception { System.out.println(user.getUsername()+".."+user.getPassword());//查看封装结果 return NONE; } }页面的写法:要求表单项的name为Action中的类对象的属性
<form action="${pageContext.request.contextPath }/actionDemo24.action" method="post"> <h1>模型驱动:实现ModelDriven的方式</h1> <!-- 表单项的name为Action中的类对象的属性 --> <input type="text" name="username" /> <input type="password" name="password" /> <input type="submit" /> </form>补充: 第二种跟第三种比较常用,第二种好处是可以封装多个对象,第三种就只能封装一个对象,但它相对方便快捷。List类型的数据封装:在Action中提供List集合对象(不需要实例化),并提供getter和setter。页面中提交的表单项要求格式为"List变量名[下标]"。提交的数据按照下标封装到List集合中,页面中有多少就封装多少。如果集合中的对象为类,那么还可以使用List变量名[下标].属性来给集合中的对象封装属性
action的写法:
package work.action2; import java.util.Iterator; import java.util.List; import com.opensymphony.xwork2.ActionSupport; import domain.User; //测试list类型数据封装 public class ActionDemo25 extends ActionSupport { //有list集合 private List<User> users; //提供getter,setter public List<User> getUsers() { return users; } public void setUsers(List<User> users) { this.users = users; } @Override public String execute() throws Exception { for (User user : users) { System.out.println(user); } return NONE; } }页面表单项的写法:要求表单项的name为Action中的list集合对象[需要].对象的属性
<form action="${pageContext.request.contextPath }/actionDemo25.action" method="post"> <h1>list类型的数据封装</h1> <!-- 表单项的name为Action中的list集合对象[需要].对象的属性 --> <input type="text" name="users[0].username" /> <input type="password" name="users[0].password" /> <input type="text" name="users[1].username" /> <input type="password" name="users[1].password" /> <input type="submit" /> </form>Map类型的数据封装:在Action中提供Map集合对象(不需要实例化),并提供getter和setter。页面中提交的表单项要求格式为"Map变量名[自定义的key]"。提及的数据会按照键值对存储到Map集合对象中。取出来的时候需要根据存储的时候用的key来获取。如果集合中的对象为类,那么还可以使用Map变量名[自定义的key].属性来给集合中的对象封装属性
action的编写方法:
package work.action2; import java.util.Iterator; import java.util.Map; import com.opensymphony.xwork2.ActionSupport; import domain.User; public class ActionDemo26 extends ActionSupport { //有map private Map<String,User> users; //提供getter,setter public Map<String, User> getUsers() { return users; } public void setUsers(Map<String, User> users) { this.users = users; } @Override public String execute() throws Exception { for (String key : users.keySet()) { User user=users.get(key); System.out.println("key:"+key+","+user); } return NONE; } }页面表单项的编写方法:
<form action="${pageContext.request.contextPath }/actionDemo26.action" method="post"> <h1>map类型的数据封装</h1> <!-- 表单项的name为Action中的map集合对象[key].对象的属性 --> <input type="text" name="users['A'].username" /> <input type="password" name="users['A'].password" /> <input type="text" name="users['B'].username" /> <input type="password" name="users['B'].password" /> <input type="submit" /> </form>由于ognl主要是用来获取数据的,在struts中,它一般使用在视图上.
获取属性:<s:property value="ognl表达式" />这样就可以把ognl的值取出来,并显示在视图上了.
调用对象的方法:(ognl表达式调用对象的方法格式是:对象.方法名())
<s:property value="ognl表达式" />例如:<s:property value="user.getName()" />【假设存储了一个user对象】调用对象的静态方法:(ognl表达式调用对象的静态方法格式是:@类@方法名(),其中要求类包含路径)
首先需要配置常量允许ognl调用对象的静态方法,常量可以在struts.xml中配置,也可以在struts.properties中配置:
<constant name="struts.ognl.allowStaticMethodAccess" value="true" />然后调用方法:
例如:<s:property value="@java.lang.Math@random()" />在struts2中,ognl表达式默认是从值栈中获取数据的,并且由于值栈是struts2的很多数据的中转站,所以要讲一下值栈,然后再讲述ognl的使用。
在Action中通过ActionContext获取:
ValueStack valueStack = ActionContext.getContext().getValueStack();在Action中通过request获取:
ValueStack valueStack = (ValueStack)ServletActionContext.getRequest().getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);向值栈中存储数据:【下面讲的都是存放数据到root区域中的方式,如果想存储到context区域,需要理由request\session这些数据域来存储值,request这些数据域就存储在context区域中。】
由于action也会被存储到值栈中,所以action中定义的属性也会存储到值栈中,但如果你想要获取这种方式存储的数据,那么Action的属性要提供getter。
调用push方法来存储,存储格式是对象存储,获取时根据对象中的属性名来获取,不是使用对象.属性来获取。
public class ActionDemo32 extends ActionSupport { @Override public String execute() throws Exception { ValueStack valueStack = ActionContext.getContext().getValueStack(); User user= new User(); valueStack.push(user); return NONE; } }调用set方法来存储,存储格式是键值对,获取的时候根据key来获取。
public class ActionDemo32 extends ActionSupport { @Override public String execute() throws Exception { ValueStack valueStack = ActionContext.getContext().getValueStack(); User user= new User(); valueStack.set("user", user); return NONE; } }获取值栈数据:
值栈的数据通常在视图中使用,通常我们使用ognl表达式来获取值栈中的数据,并且通常在视图中使用ognl表达式来获取数据,比如你可以使用<s:property value="ognl表达式" />来获取值栈中的数据当值栈有同名数据时,接近栈顶的生效,所以如果某个属性与对象中的属性同名时,要小心使用。比如: 获取user对象的属性<s:property value="user.username" />【假设值栈中存储了一个对象叫user】调用user对象的方法<s:property value="user.getUsername" />【假设值栈中存储了一个对象叫user】获取存储到request域中的属性:<s:property value="#request.username" />【假设request中存储了一个属性叫username】在action中使用push来存储一个数据,在action中向request中存储一个数据.
package work.action3; import org.apache.struts2.ServletActionContext; import com.opensymphony.xwork2.ActionContext; import com.opensymphony.xwork2.ActionSupport; import domain.User; public class ActionDemo33 extends ActionSupport { private String phone; public String getPhone() { return phone; } @Override public String execute() throws Exception { phone="10086";//测试action方式存储数据 ActionContext.getContext().getValueStack().set("user", "lilei");//set方式方式存储数据 User user= new User(); user.setUsername("炸弹人"); user.setPassword("123456"); ActionContext.getContext().getValueStack().push(user);//push方式方式存储数据 ServletActionContext.getRequest().setAttribute("address", "1314");//向request中存储 return "show"; } }在页面中使用OGNL获取出来。
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="/struts-tags" prefix="s" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> action中的phone:<s:property value="phone" /><br> set方式存储的user:<s:property value="user" /><br> push方式存储的对象的username:<s:property value="username" /><br> push方式存储的对象的password:<s:property value="password" /><br> request中的password:<s:property value="#request.password"/> </body> </html>#号
获取context数据,例如:<s:property value="#request.username" />在视图中也可以可以定义数据的,特别是使用struts标签时,#可以构建一个map集合【这个是标签的内容,这里不讲述,留到下面讲】%号:
强制解析OGNL,在struts2表单标签里面使用ognl表达式是不解析,只有%之后才会强制解析。
<!-- 下面是不解析的 --> <s:textfield name="name" value="#request.name" /> <!-- 强制解析 --> <s:textfield name="name" value="%{#request.name}" />强制不解析OGNL(少用,有兴趣自查)
$号:
在配置文件中使用OGNL,在配置文件中使用${ognl表达式}就能在配置文件中使用ognl。重写ActionSupport的validate方法,在到达目标业务处理方法之前,validate先执行,并且它是属于拦截器的内容,它可以向错误区域中存储错误信息,使得workflow拦截器拦截请求到方法。【获取错误信息可以使用actionerror和fielderror标签获取,什么类型的错误使用什么标签获取,fielderror一般都是表单错误,使用可以参考下面的页面编写】
Action的编写:
要校验数据,必须先获取数据,所以属性要提供setter;其次,检验中如果发生错误,必须向错误域中写入错误信息,这样才能拦截请求。package work.action3; import com.opensymphony.xwork2.ActionSupport; public class LoginAction extends ActionSupport { //要校验 ,先获取 private String username; private String password; public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } @Override public String execute() throws Exception { System.out.println("登录成功!"); return NONE; } @Override public void validate() { if(username==null ||username.trim().length()==0) { //向错误区域写错误 this.addFieldError("username", "用户名不能为空");//往错误区存入错误使workflow拦截器拦截运行 } if(password==null ||password.trim().length()==0) { //向错误区域写错误 this.addFieldError("passoword", "密码不能为空");//往错误区存入错误使workflow拦截器拦截运行 } } }页面的编写:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib uri="/struts-tags" prefix="s" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>Insert title here</title> </head> <body> <form action="${pageContext.request.contextPath }/login.action" method="post" /> <h3> <s:fielderror /><!-- 用来显示错误的 --> </h3> 用户名:<input type="text" name="username" /> 密码:<input type="password" name="password" /> <input type="submit"> </form> </body> </html>上面的重写validate会对所有的方法进行校验,如果只想校验某个方法,可以使用validateXXX,XXX为方法名(首字母大写)。由于跟上面的差不多,只给个小例子。
//只校验login方法的数据 public void validateLogin() { if(username==null ||username.trim().length()==0) { //向错误区域写错误 this.addFieldError("username", "用户名不能为空");//往错误区存入错误使workflow拦截器拦截运行 } if(password==null ||password.trim().length()==0) { //向错误区域写错误 this.addFieldError("passoword", "密码不能为空");//往错误区存入错误使workflow拦截器拦截运行 } }struts2提供了不少数据校验器给我们,我们要使用他提供的数据校验器,那么我们需要进行xml配置。
1.在Action的包下创建一个xml:xxx-validation.xml 【xxx是Action的名字】,其中dtd文件在xwork依赖包的根目录下,根据现在的版本,这里使用xwork-validator-1.0.3.dtd,在注释里面拷贝一下里面的dtd约束
-
2.在xxx-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是要检验的表单项名称 --> <field name="username"> <!-- field-validator是校验器,type的值是我们要选择的校验器 --> <field-validator type="requiredstring"> <!-- message是违反校验时的报错信息 --> <message>用户名不能为空!</message> </field-validator> </field> <field name="password"> <field-validator type="requiredstring"> <message>密码不能为空!</message> </field-validator> </field> </validators>3.在action中配置:要给属性提供getter和setter,setter是封装从表单提交过来的数据,getter是用于给自带的校验体提供数据。
package work.action3; import com.opensymphony.xwork2.ActionSupport; //测试数据校验 public class LoginAction extends ActionSupport { //要校验 ,先获取 private String username; private String password; public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public String getUsername() { return username; } public String getPassword() { return password; } @Override public String execute() throws Exception { System.out.println("登录成功!"); return NONE; } }上面的方法也是给action进行校验,如果想要仅仅想要给action中的某个方法使用校验器,那么xml的文件名应该为Action类名-方法对应的访问路径-validation.xml (这里的方法对应的访问路径是指struct.xml中action中的name,假如校验一个叫LoginAction中的execute方法,然而访问路径是login,那么文件名是LoginAction-login-validation.xml),然后xml里面的配置与上面的一样。
对于一些校验器要提供参数来帮助校验,这里讲一下怎么配置校验器的参数
stringlength校验器是用来校验输入的字符串的长度的,所以它需要提供范围参数。我们利用这个来查看 参数: dotrimminLengthmaxLength设置参数值的方法: -想要查看校验器有哪些参数,可以看一下xwork依赖包中的com/opensymphony/xwork2/validator/validators/下的类,这些类就是对应的校验器,它们里面有什么属性,就说明可以赋予哪些参数。这些参数大部分都是有默认值的。想知道有什么默认的数据校验器,可以参考xwork依赖包中的com/opensymphony/xwork2/validator/validators/default.xml
required:要求必须填写,但可以是空格。requiredstring:要求必须填写,但不可以是空格。int:要求必须是整数,且数值要在校验器参数的min和max之间。date:要求必须是日期,且日期要在校验器参数的min和max之间。email:必须是合法的emailurl:必须是合法的urlregex:根据校验器的参数来进行正则校验。stringlength:要求输入的字符串长度必须符合规则,要求长度要在校验器参数的minLength和maxLength之间。expression:校验与某个表单项的值是否相同(重复密码校验?)。(只介绍几个,只为帮助了解拦截器的作用。)
params:这个可以帮助我们把请求提交过来的数据封装到javabean中(上面提到的数据封装)。servlet-config:这个可以帮助我们把servlet API传给action。fileupload:这个可以帮助我们封装上传的文件数据,所以说,struts2是可以实现上传文件的功能的。validation:帮助我们进行数据校验,比如email表单项没有填正确的话,那么它会把错误信息存储起来。modelDriven:帮助封装数据,这个就是ModelDrivern注入法所依赖的拦截器.workflow:这个负责最终工作流向,在最终到action之前,workflow会检测前面的拦截器是否发生了错误(拦截器发生错误不会立即停止,会把错误信息存储起来,然后workflow检查有没有错误信息),如果发生了错误,跳转到INPUT视图,正常则将请求交给action。编写一个类实现Interceptor接口或者继承AbstractInterceptor类(Interceptor接口的空实现类)。
实现Interceptor接口需要实现三个方法,但主要实现intercept方法,其他两个可以空实现,Intercept方法的实现可以参考下面继承AbstractInterceptor类版本的。
package work.interceptor; import com.opensymphony.xwork2.ActionInvocation; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.interceptor.AbstractInterceptor; public class MyInterceptor2 extends AbstractInterceptor { @Override public String intercept(ActionInvocation invocation) throws Exception { boolean flag=true; if(flag) {//这里做演示,所以不给明显条件,假设flag为用户是否已登录。 //处理。。。 System.out.println("你经过了我的拦截器"); return invocation.invoke();//登录了,放行 }else { //处理。。。 //不放行的时候要考虑给视图进行返回所以这里要获取Action ActionSupport action = (ActionSupport) invocation.getAction(); return action.LOGIN;//不放行,返回登录页面 } } }创建了拦截器之后,要告诉struts2新增了拦截器,要在struts.xml中对拦截器进行配置,有两种配置方法【但记住struts默认当作是覆盖了原来的拦截器,如果你还需要原有的拦截器,需要加上,默认的拦截器栈为defaultStack;拦截器加载是有顺序的,依据配置的顺序,所以如果想要在默认拦截器栈之前使用自定义的拦截器,可以先引入自定义的,再引入默认的。】: 配置方法1:先在interceptors中用interceptor声明新建了一个拦截器,name是自定义的拦截器的名字,class是拦截器的类路径;再使用interceptor-ref把拦截器配置到action中,name是拦截器的名字。由于默认栈很重要,所以我们经常要加回。当需要给多个action配置拦截器的时候,上面那种方法会导致需要很多重复操作,所以可以把新定义的拦截器放到一个拦截器栈(拦截器栈在interceptor中使用interceptor-stack声明)中,然后再在action中配置拦截器栈(这样重复次数少很多)迭代标签:<s:iterator>
属性:
begin:如果指定,则迭代将在该索引上开始end:如果指定迭代将在该索引上结束(包括)status:迭代元素的索引对象,可以使用getIndex() 获取当前值step:迭代的步数。value:用来迭代的对象【可选的,如果没有给定迭代的对象,那么应该给begin和end,用来指定迭代一个列表】value属性的值无论来自对象栈还是map栈都可以不加#。var:迭代过程中,存放到值栈中的指向迭代元素的对象【可选的】【会存放到root区域】在<s:iterator>中可以使用<s:property />来输出变量的值
<s:iterator value="{'1','2','3','4','5','6'}" > <s:property /> </s:iterator> <!-- 结果: 1 2 3 4 5 6 --> <br> <s:iterator var="i" status="s" value="{'1','2','3','4','5','6'}" > <s:property value="i" />-- <s:property value="#s.getIndex()" /> </s:iterator> <!-- 结果:1-- 0 2-- 1 3-- 2 4-- 3 5-- 4 6-- 5 -->显示数据标:<s:property />
核心属性:
value:要获取的值,可以使用ognl表达式default :如果value属性为null,则显示的值<s:property value="username"/><!-- 假设值栈中存储着username --> <s:property value="user.name"/><!-- 假设值栈中存储着user对象 --> <s:property value="#request.flag"/><!-- 假设request中存储着flag属性 -->提一下的是:<s:property value="model.属性名"/>可以获取使用模型驱动封装的属性。
下面的代码是使用模型驱动封装User对象的属性,然后使用<s:property value="model.属性名"/>获取属性。
<s:property value="model.username"/> <s:property value="model.password"/> 设置数据的标签,把数据存储到值栈中:<s:set var=”i” value=”5” scope=”request” /> 属性: var :相当于key,用于标识存储到值栈中的值。value :要存储的值。scope :数据存储到哪里,可以是application, session, request, page或action(默认值,).在页面上显示格式化的日期:<s:date />
核心属性:
format :定义如何格式化日期name :用于被格式化的日期数据<!-- 假设存储了一个person对象,里面有birthday --> <s:date name="person.birthday" nice="true" /> <s:date name="person.birthday" /> <s:date name="person.birthday" format="dd/MM/yyyy" /><s:textfield /> :代替text输入框
通用属性:cssClass 【相当于class】,cssStyle 【相当于style】,id ,label 【给输入框加上label】,name 【相当于name】,value 【相当于value】,<s:password />:代替password输入框
通用属性:cssClass 【相当于class】,cssStyle 【相当于style】,id ,label 【给输入框加上label】,name 【相当于name】,value 【相当于value】,<s:radio /> :代替单选框
通用属性:cssClass 【相当于class】,cssStyle 【相当于style】,id ,label 【给输入框加上label】,name 【相当于name】,value 【相当于value】,
在radio标签中,它是“一组”单选框,所以我们要给它提供能够形成一组单选框的值。
<!-- 提供一个genders属性在值栈中,通过list获取,那么会把list的值作为多个单选框的值,key是name --> <s:radio label="性别" name="gender" list="genders"/> <!-- 下面的方式把gender作为name,把list中的元素作为单选框的值和提示文字 --> <s:radio label="性别" name="gender" list="{'男','女'}"/> <!-- 下面的方式把gender作为name,把list中的元素中的key作为单选框的值,value作为提示文字 --> <s:radio label="性别" name="gender" list="#{'1':'男','2':'女'}"/><s:file /> :代替文件上传框
通用属性:cssClass 【相当于class】,cssStyle 【相当于style】,id ,label 【给输入框加上label】,name 【相当于name】,value 【相当于value】,<s:form /> :代表form表单
核心属性: action:表单中的action,要求是访问路径,比如说访问hello.action,那么直接写hello.action即可。method:提交的方法通用属性:cssClass 【相当于class】,cssStyle 【相当于style】,id ,label 【给输入框加上label】,name 【相当于name】,value 【相当于value】,<s:hidden />
核心属性: name:隐藏域的名字value:隐藏域的值与属性驱动方式的数据封装对应的数据回显:在数据封装时,要求action中的属性提供setter,回显的时候要求提供getter
action中的编写【测试的流程是,页面提交数据到action中,action返回刚才的页面,查看页面时候回显了刚才提交的数据。】
package work.action3; import com.opensymphony.xwork2.ActionSupport; public class ActionDemo35 extends ActionSupport { //有属性 private String username; private String password; //属性有setter,getter public void setUsername(String username) { this.username = username; } public void setPassword(String password) { this.password = password; } public String getUsername() { return username; } public String getPassword() { return password; } @Override public String execute() throws Exception { System.out.println(username+".."+password);//查看封装结果 return "reShow"; } }页面的编写
<s:form action="actionDemo35.action" method="post" > <s:textfield label="用户名" name="username"></s:textfield> <s:password label="密码" name="password"></s:password> <s:submit></s:submit> </s:form>与属性驱动方式的数据封装对应的数据回显:在数据封装时,要求action中的对象提供setter,getter,回显的时候不再需要额外提供getter
action的编写:【测试的流程是,页面提交数据到action中,action返回刚才的页面,查看页面时候回显了刚才提交的数据。】
package work.action3; import com.opensymphony.xwork2.ActionSupport; import domain.User; public class ActionDemo35 extends ActionSupport { //有类对象 private User user; //有setter,getter public User getUser() { return user; } public void setUser(User user) { this.user = user; } @Override public String execute() throws Exception { System.out.println(user.getUsername()+".."+user.getPassword());//查看封装结果 return "reShow"; } }页面的编写:
<s:form action="actionDemo35.action" method="post" > <s:textfield label="用户名" name="user.username"></s:textfield> <s:password label="密码" name="user.password"></s:password> <s:submit></s:submit> </s:form>与模型驱动方式的数据封装对应的数据回显与第一个数据回显的方式页面编写方式相同,不同只是action,action不需要额外提供setter和getter.
action的编写:
package work.action3; import com.opensymphony.xwork2.ActionSupport; import com.opensymphony.xwork2.ModelDriven; import domain.User; public class ActionDemo35 extends ActionSupport implements ModelDriven<User> { private User user=new User(); @Override public User getModel() { return user; } @Override public String execute() throws Exception { System.out.println(user.getUsername()+".."+user.getPassword());//查看封装结果 return "reShow"; } }页面的编写:
<s:form action="actionDemo35.action" method="post" > <s:textfield label="用户名" name="username"></s:textfield> <s:password label="密码" name="password"></s:password> <s:submit></s:submit> </s:form> 这篇博文的出发点是启蒙和认识使用,所以这篇博文中并没有讲述struts2的所有内容,也没有对所有知识点讲清楚它的运行原理。上面的内容学习完毕后,你就可以大致的开发出一个项目了。如果你想要了解更多,你可以参考专业的书籍。但如果你仅仅想要学习如何使用,你可以根据我这篇博文的内容,通过百度或者学习论坛来进行扩展学习即可,因为专业书籍通常都会涉及到一些不常用的内容,但这所谓的不常用就仁者见仁智者见智了。
附上一些想写但没写上的内容:【出于篇幅以及系统化讲述(一个系列的内容只讲一个小东西很不习惯,所以我也需要去查一下)的考虑所以没写】
类型转换问题:当进行封装的时候,可能会有封装类型不符合的问题,所以有时候需要自定义类型转换。
jsp的取代品:freemarker、velocity
上传和下载
数据校验:自定义校验器
转载于:https://www.cnblogs.com/progor/p/9482710.html
相关资源:JAVA上百实例源码以及开源项目