Struts2是一个用来来发MVC应用的框架,它提供了Web应用程序开发过程中一些常见问题的解决方案:
- 对来自用户的输入数据进行合法的验证
- 统一的布局
- 可扩展性、
- 国际化和本地化
- 支持AJAX
- 表单的重复提交
- 文件的上传下载
.........
搭建Struts2的环境:
- 加入jar包:复制struts\apps\struts2-blank\WEB-INF\lib下的所有jar包到当前应用的lib目录下
- 在web.xml文件中配置struts2:复制struts\apps\struts2-blank\WEB-INF\web.xml文件中的过滤器的配置到当前web应用的web.xml文件中
- 在当前web应用的classpath下添加struts2的配置文件struts.xml:struts\apps\struts2-blank\WEB-INF\classes下的struts.xml文件到当前web应用的src目录下
-----添加DTD约束:
配置action元素:
* 每个action元素都必须有一个name属性,该属性和用户请求servletPath之间存在着一一对应的关系
* action元素的class属性是可选的,如果没有配置class属性,struts将把com.opensymphony.xwork2.ActionSupport(在手工完成字段验证,显示错误消息,国际化等情况下,推荐继承ActionSuppor)作为其默认值,
如果配置了class属性,还可以使用method属性配置该类的一个动作方法,method属性的默认值为execute
* action元素嵌套在package元素内部,它表示一个struts请求
配置package元素:
* Struts2把各种action分门别类的组织成不同的包,可以把包想像成为一个模块,一个典型的struts.xml文件可以有一个或多个包
* 每个package元素都必须有一个name属性,该name属性可以被其他package引用
* namespace属性是可选的,如果它没有给出,则以"/"为默认值,若namespace有一个非默认值,则想要调用这个包里的Action,就必须把这个属性所定义的命名空间添加到有关类的URI字符串里
* package元素通常要对struts-default.xml文件里定义的struts-default包进行扩展,这样包里的所有动作就可以使用在struts-default.xml文件里的结果类型和拦截器了
编辑struts.xml文件:struts.xml文件是对struts应用程序里的Action进行配置的地方
* action VS Action类
- action:代表一个Struts2的请求
- Action类:能够处理Struts2的请求的类
> 属性的名字必须遵守与JavaBean属性名相同的命名规则,属性的类型可以是任意类型,从字符串到非字符串(基本数据库类型)之间的数据转化可以自动发生
> 必须有一个无参构造器:通过反射创建实例
> 至少有一个供struts在执行这个action时调用的方法
> 同一个Action类可以包含多个action方法
> Struts2会为每一个HTTP请求创建一个新的Action实例,即Action不是单例,线程是安全的
关于Struts2请求的扩展名问题:
1)org.apache.struts2包下的default.properties中配置了Struts2应用的一些常量
2)struts.action.extension定义了当前Struts2应用可以接受的请求扩展名
3)可以在Struts.xml文件中以常量配置的方式修改default.properties所配置的常量
关于Struts2应答的扩展名问题:
1)已知Struts2可以应答.action请求和不带任何扩展名的请求
2)打开org.apache.struts2下的default.properties:该文件中放置的是Struts2的一些常量
3)可以在struts2的配置文件中通过contant节点进行修改
在Action中访问WEB资源:
1)什么是WEB资源?
HttpServletRequets、HttpSession、ServletContext等原生Servlet API
2)为什么访问WEB资源?
B\S的应用的Controller中必然需要访问WEB资源:向域对象中读写属性,读写Cookie,获取realPath...
3)如何访问?
> 和Servlet API 解耦的方式:只能访问有限的Servlet API 对象,且只能访问其有限的方法(读取请求参数、读写域对象的属性)
* 为了避免与Servlet API 耦合在一起,方便Action做单元测试,struts2对HttpServletRequest,HttpSession和ServletContext进行了封装,构造了3个Map对象来替代这3个对象,
在Action中可以直接使用HttpServletRequest,HttpSession和ServletContext对应的Map对象来保存和读取数据
* 使用ActionContext:
- ActionContext是Action执行的上下文对象,在ActionContext中保存了Action执行所需的所有对象,包括parameters,requets,session,application等
- 获取HttpSession对应的Map对象:public Map getSession()
注意:session对应的Map实际上是SessionMap类型的!强转后若调用其invalidate()方法,可以使session失效!
- 获取ServletContext对应的Map对象:public Map getApplication()
- 获取请求参数对应的Map对象:public Map getParameters():键-->请求参数的名字,值-->请求参数的值对应额字符串数组,
注意:1.getParameters的返回值是Map
2.parameter这个Map只能读,不能写入数据,如果写入,不会出错,但也不起作用
- 获取HttpServletRequets对应的Map对象:public Object get(Object key):ActionContext类中没有提供类似getRequets()这样的方法来获取HttpServletRequest对应的Map对象,
要得到HttpServletRequets对应的Map对象,可以通过get()方法传递“request”实现
* 实现xxxAware接口:
建议--->若一个Action类中有多个action方法,且多个方法都需要使用域对象的Map或parameter,则建议使用Aware接口的方式
> 和Servlet API 耦合的方式:可以访问更多的Servlet API对象,且可以调用其原生的方法
* 直接访问Servlet API 将使Action与Servlet环境耦合在一起,测试时需要有Servlet容器,不便于对Action的单元测试
* 使用ServletActionContext:可以从中获取到当前Action对象需要的一切Servlet API 相关的对象,常用的对象如下
- 直接获取HttpServletRequest对象:ServletActionContext.getRequest()
- 直接获取HttpSession对象:ServletActionContext.getRequest().getSession()
- 直接获取ServletContext对象:ServletActionContext.getServletContext()
* 实现ServletxxxAware接口:可以由struts2注入需要的Servlet相关的对象,常用的对象如下
- ServletRequestAware:注入HttpServletRequest对象
- ServletContextAware:注入ServletContext对象
- ServletResponseAware:注入HttpServletResponse对象
result:
1)result是action节点的子节点
2)result代表action方法执行后,可能去的一个目的地
3)一个action节点可以配置多个result子节点
4)result的name属性值对应着action方法可能有一个返回值
5)result一共有两个属性,还有一个是type:表示结果的响应类型
6)result的type属性值在struts-default包的result-type节点的name属性中定义,常用的有如下:
> dispatcher(默认):转发
> redirect:重定向(也可以便捷的实现redirectAction功能)
> redirectAction:重定向到一个Action
> chain:转发到一个action(注意:不能通过type=dispatcher的方式转发到一个Action)
通配符映射:
* 一个WEB应用可能有成百上千个action声明,可以利用struts2提供的通配符映射机制把多个彼此相似的映射关系简化为一个映射关系
* 通配符映射规则:
- 若找到多个匹配,没有通配符的那个将胜出(精确匹配)
- 若指定的动作不存在,Struts将会尝试把这个URI与任何一个包含着通配符*的动作名进行匹配
- 被通配符匹配到的URI字符串的子串可以用{1},{2}来引用,{1}匹配第一个子串,{2}匹配第二个子串
- {0}匹配整个URI
- 若Struts找到的带有通配符额匹配不止一个,则按先后顺序进行匹配
- *可以匹配0个或多个字符,但不包括/字符,如果想把/字符包括在内,需要使用**,如果需要对某个字符进行转义,需要使用\
* 示例:
动态方法调用:
* 动态方法调用:通过url动态调用Action中的方法
* 默认情况下,Struts的动态方法调用处于禁用状态
值栈:
* ValueStack(值栈):贯穿整个Action的生命周期(每个Action类的对象实例都拥有一个ValueStack对象),相当于一个数据的中转站,在其中保存当前Action对象和其他相关对象
* Struts框架把ValueStack对象保存在名为“struts.valueStack”的请求属性中
* 在ValueStack对象的内部有两个逻辑部分
- 1.ObjectStack:Struts把Action和相关对象压入ObjectStack中
- 2.ContextMap:Struts把各种各样的映射关系(一些Map类型的对象)压入ContextMap中,实际上就是对ActionContext的一个引用,Struts会把下面这些映射压入ContextMap中
> parameters:该Map中包含当前请求的请求参数
> requets:该Map中包含当前requets对象中的所有属性
> session:该Map中包含当前session对象中的所有属性
> application:该Map中包含当前application对象中的所有属性
> attr:该Map按如下顺序来检索某个属性:requets、sessino、application
OGNL:
* 在JSP页面上可以利用OGNL(Object-Graph Navigation Language:对象-图导航语言)访问到值栈(ValueStack)里的对象属性
* 若希望访问值栈中ContextMap中数据,需要给出OGNL表达式加上一个前缀字符#,如果没有前缀字符#,搜索将在ObjectStack里进行
* property标签:用来输出值栈中的一个属性值
> 对于对象栈:对象栈中某一个对象的属性值
> Map栈:request,session,application的一个属性值或一个请求参数的值
* 读取ObjectStack里个对象的属性:
> 若想访问ObjectStack里的某个对象的属性,可以使用以下几种形式之一:
object.propertyName
object['propertyName']
object["propertyName"]
> ObjectStack里的对象可以通过一个从零开始的下标来引用:
ObjectStack里的栈顶对象可以用[0]来引用,它下面的那个对象可以用[1]引用,若希望返回栈顶对象的message属性值:[0].mesage或[0]["message"]或[0]['messge']
> 若在指定的对象里没有找到指定的属性,则到指定对象的下一个对象里继续搜索,即[n]的含义是从第n个开始搜索,而不是只搜索第n个对象
> 若从栈顶对象开始搜索,则可以省略下标部分
* 默认情况下,Action对象会被Struts2自动放到值栈的栈顶
* 读取Context Map里的对象的属性
> 若想访问ContextMap里的某个对象的属性,可以使用以下几种形式:
#object.propertyName
#object['propertyName']
#object["propertyName"]
* 调用字段和方法
> 可以利用OGNL调用:1.任何一个Java类里的静态字段或方法,2.被压入到ValueStack栈的对象上的公共字段和方法
> 默认情况下,Struts2不允许调用任意的Java类静态方法,需要重新设置struts.ognl.allowStaticMethodAccess标记变量的值为true
> 调用静态字段或方法需要使用如下所示的语法:
@fullyQualifiedClassName@fieldName
@java.util.Calendar@DECEMBER
@fullyQualifiedClassName@methodName(argumentList)
@app4.Util@now()
> 调用一个实例字段或方法的语法,其中object是Object Stack栈里的某个对象的引用:
.object.fieldName:[0].datePattern
.object.methodName(argumentList):[0].repeat(3,"Hello");
* 访问数组类型的属性
> 有些属性将返回一个对象数组而不是单个对象,可以像读取任何其他对象那样读取它们,这种数组型属性的各个元素以逗号分隔,并且不带方括号
> 可以使用下标访问数组中指定的元素:colors[0]
> 可以通过调用其length字段查出给定数组中有多少个元素:colors.length
* 访问List类型的属性
> 有些属性将返回的类型是java.util.List,可以像读取任何其他属性那样读取它们,这种List的各个元素是字符串,以逗号分隔,并且带方括号
> 可以使用下标访问List中指定元素:colors[0]
> 可以通过调用其size方法或专用关键字size的方法查出给定List的长度:colors.size或colors.size()
> 可以通过使用isEmpty()方法或专用关键字isEmpty来知道给定的List是不是空,colors.isEmpty或colors.isEmpty(0
> 还可以使用OGNL表达式来创建List,创建一个List与声明一个java数组是相同的:{"Red","Black","Green"}
* 访问Map类型额属性
> 读取一个Map类型的属性将以如下的格式返回它所有的键值对:
> 若希望检索出某个Map的值,需要使用如下格式:map[Key]
> 可以使用size或size()得出某个给定的Map的键值对的个数
> 可以使用isEmpty或isEmpty()检查某个给定Map是不是为空
> 可以使用如下语法来创建一个Map:
* 使用EL访问值栈中对象的属性
>
> 原理:Struts2将包装HttpServletRequest对象后的org.apache.struts2.dispatcher.StrutsRequetsWrapper对象传到页面上,而这个类重写了getAttribute()方法
异常处理:exception-mapping元素
* exception-mapping元素:配置当前action的声明式异常处理
* exception-mapping元素中有两个属性:1.exception:指定需要捕获的异常类型,2.result:指定一个响应结果,该结果将在捕获到指定异常时被执行,既可以来自当前action的声明,也可以来自global-result声明
* 可以通过global-exception-mappings元素为应用程序提供一个全局性的异常捕获映射,但在global-exception-mapping元素下声明的任何exception-mapping元素只能引用在global-results元素下声明的某个result元素
* 声明式异常处理机制有ExceptionMappingInterceptor拦截器负责处理,当某个exception-mapping元素声明的异常被捕获时,ExceptionMappingIntercetor拦截器就会向ValueStack添加两个对象:
- exception:表示被捕获异常的Exception对象
- exceptionStack:包含着被捕获异常的栈
* 可以在视图上通过
通用标签
*property标签
- property标签用来输出一个值栈属性的值,如果value属性没有给出,ValueStack值栈栈顶对象的值被输出,其实在更多情况下,JSP El 可以提供更简洁的语法
- Struts2自动把Action对象放入到值栈中:放入得时间点为--->Struts2终将调用Action类的Action方法,但在调用该方法之前:1)先创建一个StrutsActionProxy对象,2)在创建StrutsActionProxy之后,对其进行初始化,把Action对象放入了值栈中
*url标签
- url标签用来动态地创建一个URL
*param表签
- param标签用来把一个参数传递给包含着它的那个标签
- 无论在给出value值时有没有使用%{},Struts都会对它进行ognl求值
- 如果想传递一个String类型的字符串作为参数值,必须把它用单引号括起来
- 可以把value属性的值写在开始标签和结束标签之间,利用这种方式来传递一个EL表达式的值
*set标签
- set标签用来在以下Map对象里创建一个键值对:
> ValueStack值栈的ContextMap值栈
> Map类型的session对象
> Map类型的application对象
> Map类型的request对象
> Map类型的page对象
*push标签
- push标签的功能和set标签类似
- push标签将把一个对象压入ValueStack而不是压入ContextMap
- push标签在标签起始时把一个对象压入栈,标签结束时将对象弹出栈
*if,else,elseif标签
- 这三个标签用来进行条件测试,它们的用途和用法类似于if,else和elseif关键字。其中if和elseif必须有test属性
*iterator标签
- iterator标签用来遍历一个数组,Collection或一个Map,并把这个可遍历对象里的每一个元素依次压入和弹出ValueStack栈
- 在开始执行时,iterator标签会先把IteratorStack类的一个实例压入ContextMap,并在每次遍历循环时更新它,可以将一个指向IteratorStack对象的变量赋值status属性
*sort标签
- sort标签用来对一个可遍历对象里的元素进行排序
*date标签
- date标签用来对Date对象进行排版
- format属性的值必须是java.text.SimpleDateFormat类里定义的日期/格式之一
*a标签
- a标签将呈现为一个HTML连接,这个标签可以接受HTML语言中的a元素所能接受的所有属性
*action标签
- action标签用在页面上来执行一个action
- action标签还会把当前Action对象压入ValueStack值栈的ContextMap子栈
*bean标签
- bean标签将创建一个JavaBean,并把它压入ValueStack值栈的ContextMap子栈,这个标签的功能与JSP中的useBean动作元素很相似
*include标签
- include标签用来把一个Servlet或JSP页面的输出包含到当前页面来
*append,merge标签
- append标签用来合并可遍历对象
- merge标签用来交替合并可遍历对象
*表单标签
- 表单标签将在HTNL文档里被呈现为一个表单元素
- 使用表单标签的优点:1)表单回显、2)对页面进行布局和排版
- 标签的属性可以被赋值为一个静态的值或一个OGNL表达式,如果在赋值是使用了一个OGNL表达式并把它用%{}括起来,这个表达式将会被求值
form标签
- form标签用来呈现HTML语言中的表单元素
- 默认情况下,form标签将被呈现为一个表格形式的HTML表单,嵌套在form标签里的输入字段将被呈现为一个表格行,每个表格行由两个字段组成,一个对应着行标,一个对应着输入元素,提交按钮将被呈现为一个横跨两列单元格的行
*checkbox标签
- checkbox标签将呈现为一个HTML复选框元素,该复选框元素通常用于提交一个布尔值
- 当包含着一个复选框的表单被提交时,如果某个复选框被选中了,它的值将为true,这个复选框在HTTP请求里增加一个请求参数,但如果该复选框未被选中,在请求中就不会增加一个请求参数
- checkbox标签解决了这个局限性,它采取的办法是为单个复选框元素创建一个配对的不可见字段
- checkbox标签有一个fieldValue属性,该属性指定的值将在用户提交表单时作为被选中的单选框的实际值发送到服务器,如果没有使用fieldValue属性,单选框的值将为true或false
- list,listKey和listValue属性
> list,listKey,listValue这3个属性对radio,select,checkboxlist等标签非常重要
> 可以把一个String,一个数组,一个Enumeration,Iterator,Map或Collection赋给list属性
*radio标签
- radio标签将呈现为一组单选按钮,单选按钮的个数与程序员通过该标签的list属性提供的选项的个数相同
- 一般的,使用radio标签实现“多选一”,对于“真/假”则使用CheckBox表签
*select表签
- select表签将呈现一个select元素
*optiongroup标签
- optiongroup标签对select元素所提供的选项进行分组,每个选项都有它自己的来源
*checkboxlist标签
- checkboxlist标签将呈现一组多选框
- checkbox标签被映射到一个字符串数组或是一个基本类型的数组。若它提供的多选框一个也没有被选中,相应的属性将被赋值为一个空数组而不是空值
主题
* 默认情况下,form标签将呈现为一个HTML form元素和table元素
* 每一种输入标签都将呈现为一个带标签的输入元素,而这个输入元素将被包含在一个tr元素和td元素的内部
* 什么是主题?
为了让所有ui标签能够产生同样的视觉效果而归集到一起的一组模板,即风格相近的模板被打包为一个主题
- simple:把ui标签翻译成最简单的HTML对应元素,而且会忽略行标属性
- xhtml:xhtml是默认的主题,这个主题的模板通过使用一个布局表格提供了一种自动化的排版机制
- css xhtml:这个主题里的模板与xhtml主题里的模板很相似,但它们将使用css来进行布局和排版
- ajax:这个主题里的模板以xhtml主题里的模板为基础,但增加了一些Ajax功能
* 修改主题:
- 通过UI标签的theme属性
- 在一个表单里,若没有给出某个UI标签的theme属性,它将使用这个表单的主题
- 在page,request,session或application中添加一个theme属性
- 修改struts.properties文件中的struts.ui.theme属性
*Params拦截器
- Parameters拦截器将把表单字段映射到ValueStack栈的栈顶对象的各个属性中,如果某个字段在模型里没有匹配的属性,Param拦截器将尝试ValueStack栈中的下一个对象
* ModelDriven拦截器
- 当用户触发add请求时,ModelDriven拦截器将调用EmployeeAction对象的getModel()方法,并把返回的模型(Employee实例)压入到ValueStack栈
- 接下来Parameters拦截器将把表单字段映射到ValueStack栈的栈顶对象的各个属性中,因为此时ValueStack栈的栈顶元素是刚被压入的模型(Employee)对象,所以该模型将被填充,
如果某个字段在模型里没有匹配的属性,param拦截器将尝试ValueStack栈中的下一个对象
* paramsPrepareParamsStack拦截器栈
- paramsPrepareParamsStack从字面上理解来说,这个stack的拦截器调用顺序为:params--->prepare--->modelDriven--->params
- Struts2的设计上要求modelDriven在params之前调用,而业务中prepare要负责准备model,准备model又需要参数,这就需要在prepare之前运行params拦截器设置相关参数,这就是创建paramsPrepareParamsStack的原因
- 流程如下:
> 1.params拦截器首先给action中的相关参数赋值,如ID
> 2.prepare拦截器执行prepare方法,prepare方法会根据参数,如ID,去调用业务逻辑,设置model对象
> 3.modelDriven拦截器将model对象压入value stack,这里的model对象就是在prepare中创建的
> 4.params拦截器再将参数赋值给model对象
> 5.action的业务逻辑执行
*Preparable拦截器
- Struts2中的,modelDriven拦截器负责把Action类以外的一个对象压入到值栈栈顶
- 而prepare拦截器负责准备为getModel()方法准备model
*PrepareInterceptor拦截器使用方法
- 若Action实现Preparable接口,则Action方法需要实现prepare()方法
- PrepareInterceptor拦截器将调用prepare()方法,prepareActionMethodName()方法或prepareDoActionMethodName()方法
- PrepareInterceptor拦截器根据firstCallPrepareDo属性决定获取prepareActionMethodname、prepareDoActionMethodName的顺序,
默认情况下先获取prepareActionMethodName(),如果没有该方法,就寻找prepareDoActionMethodName(),如果找到对应的方法就调用该方法
- PrepareInterceptor拦截器会根据alwaysInvokePrepare属性决定是否执行prepare()方法
类型转换
*类型转换概述
> 从一个HTML表单到一个Action对象,类型转换是从字符串到非字符串(HTML没有“类型”的概念,每一项表单输入只可能是一个字符串或一个字符串数组,在服务器端,必须把String转换为特定的数据类型)
> 在struts2中,把请求参数映射到action属性的工作由Parameters拦截器负责,它是默认的defaultStack拦截器中的一员,Parameters拦截器可以自动完成字符串和基本数据类型之间的转换
*类型转换错误
> 如果类型转换失败:
- 若Action类没有实现ValidationAware接口:Struts在遇到类型转换错误时仍会继续调用其Action方法,就好像什么都没有发生一样
- 若Action类实现ValidationAware接口:Struts在遇到类型转换错误时将不会继续执行其Action方法,Struts将检查相关的action元素的声明是否包含着一个name=input的result,如果有,Struts将把控制权转交给那个result元素,若没有input结果,Struts抛异常
*类型转换错误消息的定制
> 作为默认的default拦截器的一员,ConversionError拦截器负责添加与类型转换有关的出错消息(前提:Action类实现了ValidationAware接口)和保存各请求参数的原始值
> 若字段标签使用的不是simple主题,则非法输入字段将导致一条有着一下格式的出错消息:
Invalid field value for field fieldName
> 覆盖默认的出错消息
- 在对应的Action类所在的包中新建ActionClassName.properties文件,ClassName即为包含着输入字段的Action类的类名
- 在属性文件中添加如下键值对:invalid.fieldvalue.fieldName=Custom error messge
> 定制出错消息的样式
- 每一条出错消息都被打包在一个HTML span元素里,可以通过覆盖其行标为errorMessge的那个css样式来改变出错消息的格式
> 显示错误消息
- 如果是simple主题,可以通过
*定制类型转换器
> 自定义类型转换器必须实现ongl.TypeConverter接口或对这个接口的某种具体实现做扩展
> 为什么需要自定义类型转换器?因为Struts不能自动完成字符串到引用类型的转换
>如何定义类型转换器:
1. 开发类型转换器的类:扩展StrutsTypeConverter类
2. 配置类型转换器:
- 有两种方式
①:基于字段的配置
> 在字段所在的Model(可能是Action,可能是一个JavaBean)的包下,新建一个ModelClassName-conversion.properties
> 在该文件中输入键值对:fieldName=类型转换器的全类名
> 第一次使用该转换器时创建实例
> 类型转换器是单例的!
②:基于类型的配置
> 在src下新建xwork-conversion.properties
> 输入:待转换的类型=类型转换器的全类名
> 在当前Struts2应用被加载时创建实例
*类型转换与复杂属性配合使用
- form标签的name属性可以被映射到一个属性的属性
- Struts还允许填充Collection里的对象,这常见于需要快速录入批量数据的场合
*消息处理与国际化
- 在程序设计领域,把在无需改写源代码即可让开发出来的应用程序能够支持多种语言和数据格式的技术称为国际化
- 与国际化对应的是本地化,指让一个具备国际化支持的应用程序支持某个特定的地区
- Struts2国际化是建立在Java国际化基础上的:
> 为不同国家/语言提供对应的消息资源文件
> Struts2框架会根据请求中包含的Locale加载对应的资源文件
> 通过程序代码取得该资源文件中指定的key对应的消息
- 配置国际化资源文件
> Action范围资源文件:在Action类文件所在路径建立名为ActionName_language_country.properties的文件
> 包范围资源文件:在包的根路径下建立文件名为package_language_country.properties的属性文件,一旦建立,处于该包下的所有Action都可以访问该资源文件,(注意:包范围文件的baseName就是package,不是Action所在的包名)
> 全局资源文件:
* 命名方式:basename_language_country.properties
- struts.xml:
- struts.properties:struts.custom.i18n.resources=baseName
> 临时指定资源文件:
- 如何在页面上和Action类中访问国际化资源文件的value值
> 在Action类中,若Action实现了TextProvider接口,则可以调用其getText()方法获取value值
- 通过继续ActionSupport的方式
> 页面上可以使用s:text标签,对于表单标签可以使用表单标签的key属性值
- 若有占位符,则可以使用s:text标签的s:param子标签来填充占位符
- 可以利用标签和OGNL表达式直接访问值栈中的属性值(对象栈和Map栈)
*利用超链接实现动态加载国际化资源文件
- Struts2使用i18拦截器处理国际化,并且将其注册在默认的拦截器中
- i18n拦截器在执行Action方法之前,自动查找请求中一个名为request_locale的参数,如果该参数存在,拦截器就将其作为参数,转换成Locale对象,并将其设为用户默认的Locale,并把其设置为session的WW_TRANS_I18N_LOCALE属性
- 若request没有名为request_locale的参数,则i18n拦截器会从Session中获取WW_TRANS_I18N_LOCALE的属性值,若该值不为空,则将该属性值设置为浏览器默认的Locale
- 若session中的WW_TRANS_I18N_LOCALE的属性值为空,则从ActionContext中获取Locale对象
- 注:超链接必须是一个Struts2的请求,即i18n拦截器必须工作
*Struts2的工作流程:
- 1.请求发送给StrutsPrepareAndExecuteFilter
- 2.StrutsPrepareAndExecuteFilter询问ActionMapping:该请求是否是一个Struts2请求(即是否返回一个非空的ActionMapping对象)
- 3.若ActionMapping认为该请求是一个Struts2请求,则StrutsPrepareAndExecuteFilter把请求的处理交给ActionProxy
- 4.ActionProxy通过Configuration Manager询问框架的配置文件,确定需要调用的Action类及Action方法
- 5.ActionProxy创建一个ActionInvocation的实例,并进行初始化
- 6.ActionInvocation实例在调用Action的过程前后,涉及到相关拦截器(Intercepter)的调用
- 7.Action执行完毕后,ActionInvocation负责根据struts.xml中的配置找到对应的返回结果,调用结果的execute方法,渲染结果,在渲染的过程中可以使用Struts2框架中的标签
- 8.执行各个拦截器invocation.invoke()之后的代码
- 9.把结果发送给客户端
*输入验证
*概述
- 一个健壮的web应用程序必须确保用户输入是合法的、有效的
- Struts2的输入验证:
> 基于XWork Validation Framework的声明式验证:Struts2提供了一个基于XWork Validation Framework的内建验证程序,使用这些验证程序不需要编程,只要在一个XML文件里对验证程序应该如何工作作出声明就可以了,需要声明的内容包括:
* 哪个字段需要进行验证
* 使用什么验证规则
Struts2内建的验证规则:
- conversion validator:转换验证器
- date validator:日期验证器
- double validator:浮点验证器
- email validator:email验证器
- expression validator:表达式验证器
- fieldexpression validator:字段表达式验证器
- int validator:整型验证器
- regex validator:正则表达式验证器
- required validator:非空验证器
- requiredstring validator:非空字符串验证器
- stringlength validator:字符串长度验证器
- url validator:url格式验证器
- visitor validator:复合属性验证器
* 在验证失败时应该把什么样的出错消息发送到浏览器端
> 声明式验证程序可以分为两类:
* 字段验证:判断某个字段属性的输入是否有效
* 非字段验证:不只针对某个字段,而是针对多个字段的输入值之间的逻辑关系进行效验,例如:对再次输入密码的判断
> 使用一个声明式验证程序效验3个步骤:
1.确定哪些Action字段需要验证
2.编写一个验证程序配置文件.它的文件名必须是以下两种格式之一:
- 若一个Action类的多个action使用同样的验证规则:ActionClass-validation.xml
- 若一个Action类的多个action使用不同的验证规则:ActionClass-alis-validation.xml
3.确定验证失败时的响应页面:在struts.xml文件中定义一个
> 声明式验证框架的原理:
- Struts2默认的拦截器栈中提供了一个validation拦截器
- 每个具体的验证规则都会对应具体的一个验证器,有一个配置文件把验证规则名称和验证器关联起来,而实际上验证的是那个验证器,该文件位于com.opensymphony.xwork2.validator.validators下的default.xml
> Struts2内建的验证程序
- required:确保某给定字段的值不是空值null
- requiredstring:确保某给定字段的值既不是空值null,也不是空白(trim参数.默认为true,表示struts在验证字段值之前先剔除前后空格)
- stringlength:验证一个非空的字段值是不是有足够的长度(minLength:相关字段的最小长度.若没有给出这个参数,该字段将没有最小长度限制;maxLength:相关字段的最大长度,.若没有给出这个参数,该字段将没有最大长度限制)
- date:确保某给定日期字段的值落在一个给定的范围内(max:相关字段的最大值,若没有给出这个参数,该字段将没有最大长度限制;min:相关字段的最小值,若没有给出这个参数,该字段将没有最小长度限制)
- email:检查给定String值是否是一个合法的email
- url:检查给定String值是否是一个合法的url
- regex:检查某给定字段的值是否与一个给定的正则表达式模式相匹配(expresssion*:用来匹配的正则表达式;caseSensitive:是否区分字母的大小写,默认为true)
- int:检查给定整数字段值是否在某一个范围内(max:相关字段的最大值,若没有给出这个参数,该字段将没有最大长度限制;min:相关字段的最小值,若没有给出这个参数,该字段将没有最小长度限制)
- conversion:检查对给定Action属性进行的类型转换是否会导致一个转换错误,该验证程序还可以在默认的类型转换消息的基础上添加一条自定义的消息
- expression和fieldexpression:用来验证给定字段是否满足一个OGNL表达式
----- 前者是一个非字段验证程序,后者是一个字段验证程序
----- 前者在验证失败时会生成一个action错误,而后者在验证失败时会生成一个字段错误
----- expression*:用来进行验证的OGNL表达式
- 短路验证器:
- 若类型转换失败,默认情况下还会执行后面额拦截器,还会进行验证,可以通过修改ConversionErrorInterceptor源代码的方式使得当类型转换失败时,不再执行后续的验证拦截器,而直接返回input的result
> 声明式验证的helloworld
1).先明确对哪一个Action的哪个字段进行验证:age
2).编写配置文件:
> 把 struts-2.3.15.3\apps\struts2-blank\WEB-INF\classes\example 下的 Login-validation.xml 文件复制到当前 Action 所在的包下.
> 把该配置文件改为: 把 Login 改为当前 Action 的名字.
> 编写验证规则: 参见 struts-2.3.15.3/docs/WW/docs/validation.html 文档即可.
> 在配置文件中可以定义错误消息:
3).若验证失败,则转向input的那个result.所以需要配置name=input的result
4).如何显示错误信息?
> 若使用的是非simple主题,则自动显示错误消息
> 若使用的是simple主题,则需要s:fielderror标签或直接使用EL表达式(OGNL)
5).该错误信息可以进行国际化
- 编程验证:通过编写代码来验证用户输入
*自定义验证器
- 自定义验证器必须实现Validator接口
- ValifatorSupport和FieldValidatorSupport实现了Validator接口
> 若需要普通的验证程序,可以继承ValidatorSupport类
> 若需要字段验证程序,可以继承FieldValidatorSupport类
> 若验证程序需要接受一个输入参数,需要为这个参数增加一个相应的属性
- 注册验证程序:自定义验证器需要在类路径里的某个validators.xml文件里注册,验证框架首先在根目录下找validators.xml文件,没找到validators.xml文件,验证框架将调用默认的验证设置,即default.xml里面的配置信息
*文件的上传下载
- 表单的准备:
> 要想使用HTML表单上传一个或多个文件,须把HTML表单的enctype属性设置为multipart/form-data,须把HTML表单的method属性设置为post,需添加字段
- Struts对文件上传的支持:
> 在Struts应用程序里,FileUpload拦截器和Jakarta Commons FileUpload 组件可以完成文件的上传
> Struts2的文件上传实际上使用的是Commons FileUpload组件,所以需要导入:commons-fileupload-1.3.jar和commons-io-2.0.1.jar
> 步骤:
1.在JSP页面的文件上传表单里使用file标签.如果需要一次上传多个文件,就必须使用多个file标签,但它们的名字必须是相同的
2.在Action中新添加3个和文件上传相关的属性,这3个属性的名字必须是一下格式:
* [File Name]:File - 被上传的文件,例如:data
* [File Name]ContentType:String - 上传文件的文件类型,例如:dataContentType
* [File Name]FileName:String - 上传文件的文件名,例如:dataFileName
* 基本的文件上传:直接在Action中定义如下3个属性,并提供对应的get和set方法,然后使用IO流进行上传即可
注:如果上传多个文件,则上述的3个属性,可以改为List类型,多个文件域的name属性需要一致
> 可以通过配置FileUploadInterceptor拦截器的参数的方式进行限制:
maximumSize(optional)- 默认的最大值为2M,上传的单个文件的最大值
allowedTypes(optional)- 允许的上传文件的类型,多个使用 , 分割
allowedExtensions(optional)- 允许的上传文件的扩展名,多个使用 , 分割
> 定制错误消息.可以在国际化资源文件中定义如下的消息:
struts.messages.error.uploading - 文件上传出错的消息
struts.messages.error.file.too.large - 文件超过最大值的消息
struts.messages.error.content.type.not.allowed - 文件内容类型不合法的消息
struts.messages.error.file.extension.not.allowed - 文件扩展名不合法的消息
注:此种方式定制的消息并不完善,可以参考org.apache.struts2下的struts-messages.properties,可以提供更多的定制信息
*文件下载概述
- 在某些应用程序里,可能需要动态地把一个文件发送到用户的浏览器中,而这个文件的名字和存放位置在编程时是无法预知的
- Struts结果类型
> Struts专门为文件下载提供了一种Stream结果类型.在使用一个Stream结果时,不必准备一个JSP页面
> Struts结果类型可以设置如下参数:
- contentType:被下载的文件的MIME类型,默认值为text/plain
- contentLength:被下载的文件的大小,以字节为单位
- contentDisposition:可以设置下载文件名的ContentDisposition响应头,默认值为inline,通常设置为如下格式:attachment:filename="document.pdf"
- inputName:Action中提供的文件的输入流,默认值为inputStream
- bufferSize:文件下载时缓冲区的大小,默认值为1024
- allowCaching:文件下载时是否允许使用缓存,默认值为true
- contentCharSet:文件下载时的字符编码
> Stream结果类型的参数可以在Action以属性的方式覆盖
*防止表单的重复提交
- 表单的重复提交:
> 若刷新表单页面,再提交表单不算重复提交
> 在不刷新表单页面的前提下:
多次点击提交按钮
已经提交成功,按“回退”之后,再点击“提交按钮”
在控制器响应页面的形式为转发情况下,若已经提交成功,然后点击刷新(F5)
- 重复提交的缺点:
> 加重了服务器的负担
> 可能导致错误操作
- Struts2解决表单重复提交
I. 在 s:form 中添加 s:token 子标签
> 生成一个隐藏域
> 在 session 添加一个属性值
> 隐藏域的值和 session 的属性值是一致的.
II. 使用 Token 或 TokenSession 拦截器.
> 这两个拦截器均不在默认的拦截器栈中, 所以需要手工配置一下
> 若使用 Token 拦截器, 则需要配置一个 token.valid 的 result
> 若使用 TokenSession 拦截器, 则不需要配置任何其它的 result
III. Token VS TokenSession
> 都是解决表单重复提交问题的
> 使用 token 拦截器会转到 token.valid 这个 result
> 使用 tokenSession 拦截器则还会响应那个目标页面, 但不会执行 tokenSession 的后续拦截器. 就像什么都没发生过一样!
IV. 可以使用 s:actionerror 标签来显示重复提交的错误消息.
该错误消息可以在国际化资源文件中覆盖. 该消息可以在 struts-messages.properties 文件中找到
struts.messages.invalid.token=^^The form has already been processed or no token was supplied, please try again.
*自定义拦截器
- 拦截器(Interceptor)是Struts2的核心组成部分
- Struts2很多功能都是构建在拦截器基础之上的,例如文件的上传和下载、国际化、数据类型转换和数据效验等等
- Struts2拦截器在访问某个Action方法之前或之后实施拦截
- Struts2拦截器是可拔插的,拦截器是AOP(面向切面编程)的一种实现
- 拦截器栈(Interceptor Stack):将拦截器按一定的顺序联结成一条链.在访问被拦截的方法时,Struts2拦截器链中的拦截器就会按其之前定义的顺序被依次调用
- Interceptor接口
> 每个拦截器都是实现了com.opensymphony.xwork2.interceptor.Interceptor接口的Java类
init:该方法将在拦截器被创建后立即调用,它在拦截器的生命周期内只被调用一次,可以在该方法中对相关资源进行必要的初始化
interecept:每拦截一个请求,该方法就会被调用一次
destroy:该方法将在拦截器被销毁之前被调用,它在拦截器的生命周期内也只被调用一次
> Struts会依次调用为某个Action而注册的每一个拦截器的interecept方法
> 每一次调用interecept方法时,Struts会传递一个ActionInvocation接口的实例
> ActionInvocation:代表一个给定Action的执行状态,拦截器可以从该类对象里获得与该Action相关联的Action对象和Result对象,在完成拦截器自己的任务之后,拦截器将调用ActionInvocation对象额invoke方法前进到Action处理流程的下一环节
> AbstractInterceptor类实现了Interceptor接口,并为init,destroy提供了一个空白的实现
-具体步骤
I. 定义一个拦截器的类
> 可以实现 Interceptor 接口
> 继承 AbstractInterceptor 抽象类
II. 在 struts.xml 文件配置.
III. 注意: 在自定义的拦截器中可以选择不调用 ActionInvocation 的 invoke() 方法. 那么后续的拦截器和 Action 方法将不会被调用.
Struts 会渲染自定义拦截器 intercept 方法返回值对应的 result