jsp
JSP要经过两次的“编码”,第一阶段会用pageEncoding,第二阶段会用utf-8的.Java文件至utf-8的.class文件,第三阶段就是由Tomcat传回浏览器的网页, 用的是contentType。
可以简单认为是,pageEncoding是jsp文件本身的编码;contentType的charset是指服务器发送给客户端时的内容编码。例如:pageEncoding="GBK"。这句话的意思是,告诉JVM 这个jsp本身采用的"GBK"编码,在JSP编译成Servlet传给JVM的时候,就用“GBK”的编码方式将Jsp网页源文件翻译成统一的UTF-8形式的Java字节码。如果不加设定,则JVM默认的用ISO-8859-1这种编码方式。contentType里的charset=gbk,指的是此网页文件输出到浏览器的输出方式为gbk。在这个过程中,一个JSP的源文件需要经过三个阶段,两次编码,才能完成一次完整的输出。
第一阶段:将jsp编译成Servlet(.java)文件。用到的指令是pageEncoding,根据pageEncoding=“XXX”的指示,找到编码的规则为“XXX”,服务器在将JSP文件编译成.java文件时会根据pageEncoding的设定读取jsp,结果是由指定的编码方案翻译成统一的UTF-8编码的JAVA源码(即.java)。
第二阶段:从Servlet文件(.java)到Java字节码文件(.class),从UTF-8到UTF-8。在这一阶段中,不论JSP编写时候用的是什么编码方案,经过这个阶段的结果全部是UTF-8的encoding的java源码。JAVAC用UTF-8的encoding读取java源码,编译成UTF-8编码的二进制码(即.class),这是JVM对常数字串在二进制码(Java encoding)内表达的规范。这一过程是由JVM的内在规范决定的,不受外界控制。
第三阶段:从服务器到浏览器,这在一过程中用到的指令是contentType。服务器载入和执行由第二阶段生成出来JAVA二进制码,输出的结果,也就是在客户端可见到的结果,在这次输出过程中,由contentType属性中的charset来指定,将UTF8形式的二进制码以charset的编码形式来输出。如果没有人为设定,则默认的是ISO-8859-1的形式。
可见,pageEncoding和contentType都可以设置JSP源文件和响应正文中的字符集编码。但也有区别:
设置JSP源文件字符集时,优先级为pageEncoding>contentType。如果都没有设置,默认ISO-8859-1。
设置响应输出的字符集时,优先级为contentType>pageEncoding。如果都没有设置,默认ISO-8859-1。
struts2
一,自定义类型转化器,只有在表单提交到对应字段和对应字段在回显时才会起作用。
转换器中报异常的话并不会返回到input的result,而只有出现error时才会
二,验证器,只要Action使用到了对应的字段,就回去验证,包活在表单提交时。
1.modelDriver拦截器的拦截过程(若栈顶对象为null,那么栈顶对象依旧是Action类对象)
@Override public String intercept(ActionInvocation invocation) throws Exception { Object action = invocation.getAction(); if (action instanceof ModelDriven) { ModelDriven modelDriven = (ModelDriven) action; ValueStack stack = invocation.getStack(); Object model = modelDriven.getModel(); if (model != null) { stack.push(model); } if (refreshModelBeforeResult) { invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model)); } } return invocation.invoke(); }
2.ExceptionMappingInterceptor
声明式异常处理。
在struts.xml中<action>节点下<exception-mapping result="input" exception="java.lang.RuntimeException"></exception-mapping>
或者在<package>节点下声明整个包的异常映射
<global-exception-mappings>
<exception-mapping result="" exception=""></exception-mapping>
</global-exception-mappings>
那么若在exception拦截器之后的执行过程中出现<exception-mapping>中配置的异常,那么直接将结果转到对应配置的result。并将ExceptionHolder对象放到栈顶
拦截器的拦截方法为下
@Override public String intercept(ActionInvocation invocation) throws Exception { String result; try { result = invocation.invoke(); } catch (Exception e) { if (isLogEnabled()) { handleLogging(e); } List<ExceptionMappingConfig> exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings(); ExceptionMappingConfig mappingConfig = this.findMappingFromExceptions(exceptionMappings, e); if (mappingConfig != null && mappingConfig.getResult()!=null) { Map parameterMap = mappingConfig.getParams(); // create a mutable HashMap since some interceptors will remove parameters, and parameterMap is immutable invocation.getInvocationContext().setParameters(new HashMap<String, Object>(parameterMap)); result = mappingConfig.getResult(); publishException(invocation, new ExceptionHolder(e)); } else { throw e; } } return result; }
CRUD defaultStack(即默认拦截顺序)(自上而下的依次拦截) <interceptor-stack name="defaultStack"> <interceptor-ref name="exception"/> <interceptor-ref name="alias"/> <interceptor-ref name="servletConfig"/> <interceptor-ref name="i18n"/> <interceptor-ref name="prepare"/> <interceptor-ref name="chain"/> <interceptor-ref name="scopedModelDriven"/> <interceptor-ref name="modelDriven"/> <interceptor-ref name="fileUpload"/> <interceptor-ref name="checkbox"/> <interceptor-ref name="multiselect"/> <interceptor-ref name="staticParams"/> <interceptor-ref name="actionMappingParams"/> <interceptor-ref name="params"> <param name="excludeParams">dojo..*,^struts..*,^session..*,^request..*,^application..*,^servlet(Request|Response)..*,parameters...*</param> </interceptor-ref> <interceptor-ref name="conversionError"/> <interceptor-ref name="validation"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> <interceptor-ref name="workflow"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> <interceptor-ref name="debugging"/> </interceptor-stack> paramsPrepareParamsStack的拦截顺序 <interceptor-stack name="paramsPrepareParamsStack"> <interceptor-ref name="exception"/> <interceptor-ref name="alias"/> <interceptor-ref name="i18n"/> <interceptor-ref name="checkbox"/> <interceptor-ref name="multiselect"/> <interceptor-ref name="params"> <param name="excludeParams">dojo..*,^struts..*,^session..*,^request..*,^application..*,^servlet(Request|Response)..*,parameters...*</param> </interceptor-ref> <interceptor-ref name="servletConfig"/> <interceptor-ref name="prepare"/> <interceptor-ref name="chain"/> <interceptor-ref name="modelDriven"/> <interceptor-ref name="fileUpload"/> <interceptor-ref name="staticParams"/> <interceptor-ref name="actionMappingParams"/> <interceptor-ref name="params"> <param name="excludeParams">dojo..*,^struts..*,^session..*,^request..*,^application..*,^servlet(Request|Response)..*,parameters...*</param> </interceptor-ref> <interceptor-ref name="conversionError"/> <interceptor-ref name="validation"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> <interceptor-ref name="workflow"> <param name="excludeMethods">input,back,cancel,browse</param> </interceptor-ref> </interceptor-stack> 类型转换错误的显示及自定义 什么是类型转换? jsp页面中提交的字段的类型都是String或者String[]的,当Action类或者model类中接受对应字段的属性不为String时就要进行类型转换 如何显示错误消息? 1). Action类要实现ValidationAware接口(因为ActionSupport实现了接口,所以继承ActionSupport即可) 2). jsp页面中的表单的主体不为simple即可 问题1: 如何覆盖默认的错误消息? 1). 在对应的 Action 类所在的包中新建 ActionClassName.properties 文件, ActionClassName 即为包含着输入字段的 Action 类的类名 2). 在属性文件中添加如下键值对: invalid.fieldvalue.fieldName=xxx(表单项的名字) 问题2: 如果是 simple 主题, 还会自动显示错误消息吗? 如果不会显示, 怎么办 ? 1). 通过 debug 标签, 可知若转换出错, 则在值栈的 Action(实现了 ValidationAware 接口) 对象中有一个 fieldErrors 属性. 该属性的类型为 Map<String, List<String>> 键: 字段(属性名), 值: 错误消息组成的 List. 所以可以使用 LE 或 OGNL 的方式 来显示错误消息: ${fieldErrors.age[0]} 2). 还可以使用 s:fielderror 标签来显示. 可以通过 fieldName 属性显示指定字段的错误. 问题3. 若是 simple 主题, 且使用 <s:fielderror fieldName="age"></s:fielderror> 来显示错误消息, 则该消息在一个 ul, li, span 中. 如何去除 ul, li, span 呢 ? 在 template.simple 下面的 fielderror.ftl 定义了 simple 主题下, s:fielderror 标签显示错误消息的样式. 所以修改该 配置文件即可. 在 src 下新建 template.simple 包, 新建 fielderror.ftl 文件, 把原生的 fielderror.ftl 中的内容 复制到新建的 fielderror.ftl 中, 然后剔除 ul, li, span 部分即可.
这里定义的类型转换器,仅是在表单的提交和回显时才会进过转换器。
若想使页面上的Date类型按指定格式显示在页面上,则可以
问题4. 如何自定义类型转换器 ? 1). 为什么需要自定义的类型转换器 ? 因为 Struts 不能自动完成 字符串 到 引用类型 的 转换. 2). 如何定义类型转换器: I. 开发类型转换器的类: 扩展 StrutsTypeConverter 类. II. 配置类型转换器: 有两种方式 ①. 基于字段的配置: > 在字段所在的 Model(可能是 Action, 可能是一个 JavaBean) 的包下, 新建一个 ModelClassName-conversion.properties 文件 > 在该文件中输入键值对: fieldName=类型转换器的全类名. > //第一次使用该转换器时创建实例. > 类型转换器是单实例的! ②. 基于类型的配置: > 在 src 下新建 xwork-conversion.properties > 键入: 待转换的类型=类型转换器的全类名.(java.util.Date=conversion.DateConversion 类型须为全称) > //在当前 Struts2 应用被加载时创建实例. //(所以在它的构造函数中不能用ServletActionContext.getServletContext().getInitParameter("dateForm");) //切记:当字符串切割“.”时血的教训(正则,要写成“\.”) form 标签的 name 属性可以被映射到一个属性的属性.如Manger.birth Struts 还允许填充 Collection 里的对象, 这常见于需要快速录入批量数据的场合 例:表单中标签名字为mgrs[0,1,2,3...].birth Action中属性为private Collection<Manager> mgrs = null;即可 配置整个App的初始化参数? 在Web.xml中 <context-param> <param-name>dateForm</param-name> <param-value>yyyy年MM月dd日 hh:mm:ss</param-value> </context-param> 国际化的目标 1). 如何配置国际化资源文件 I. Action 范围资源文件: 在Action类文件所在的路径建立名为 ActionName_language_country.properties 的文件 II. 包范围资源文件: 在包的根路径下建立文件名为 package_language_country.properties 的属性文件, 一旦建立,处于该包下的所有 Action 都可以访问该资源文件。注意:包范围资源文件的 baseName 就是package,不是Action所在的包名。 III. 全局资源文件 > 命名方式: basename_language_country.properties > struts.xml <constant name="struts.custom.i18n.resources" value="baseName"/> IV. 国际化资源文件加载的顺序如何呢 ? 离当前 Action 较近的将被优先加载. 假设我们在某个 ChildAction 中调用了getText("username"): (1) 加载和 ChildAction 的类文件在同一个包下的系列资源文件 ChildAction.properties (2) 加载 ChildAction 实现的接口 IChild,且和 IChildn 在同一个包下 IChild.properties 系列资源文件。 (3) 加载 ChildAction 父类 Parent,且和 Parent 在同一个包下的 baseName 为 Parent.properties 系列资源文件。 (4) 若 ChildAction 实现 ModelDriven 接口,则对于getModel()方法返回的model 对象,重新执行第(1)步操作。 (5) 查找当前包下 package.properties 系列资源文件。 (6) 沿着当前包上溯,直到最顶层包来查找 package.properties 的系列资源文件。 (7) 查找 struts.custom.i18n.resources 常量指定 baseName 的系列资源文件。 (8) 直接输出该key的字符串值。 2). 如何在页面上 和 Action 类中访问国际化资源文件的 value 值 I. 在 Action 类中. 若 Action 实现了 TextProvider 接口, 则可以调用其 getText() 方法获取 value 值 > 通过继承 ActionSupport 的方式。 II. 页面上可以使用 s:text 标签; 对于表单标签可以使用表单标签的 key 属性值 > 若有占位符, 则可以使用 s:text 标签的 s:param 子标签来填充占位符 > 可以利用标签和 OGNL 表达式直接访问值栈中的属性值(对象栈 和 Map 栈) time=Time:{0} <s:text name="time"> <s:param value="date"></s:param> </s:text> ------------------------------------ time2=Time:${date} <s:text name="time2"></s:text> 3). 实现通过超链接切换语言. I. 关键之处在于知道 Struts2 框架是如何确定 Local 对象的 ! II. 可以通过阅读 I18N 拦截器知道. III. 具体确定 Locale 对象的过程: > Struts2 使用 i18n 拦截器 处理国际化,并且将其注册在默认的拦截器栈中 > 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 对象。 IV. 具体实现: 只需要在超连接的后面附着 request_locale 的请求参数, 值是 语言国家 代码. <a href="testI18n.action?request_locale=en_US">English</a> <a href="testI18n.action?request_locale=zh_CN">中文</a> > 注意: 超链接必须是一个 Struts2 的请求, 即使 i18n 拦截器工作! //注意<a href="index.jsp?request_locale=en_US">English</a>此请求是不经过struts拦截器的 Struts2 的验证 1). 验证分为两种: > 声明式验证* >> 对哪个 Action 或 Model 的那个字段进行验证 >> 使用什么验证规则 >> 如果验证失败, 转向哪一个页面, 显示是什么错误消息 > 编程式验证 2). 声明式验证的 helloworld I. 先明确对哪一个 Action 的哪一个字段进行验证: age II. 编写配置文件: > 把 struts-2.3.15.3appsstruts2-blankWEB-INFclassesexample 下的 Login-validation.xml 文件复制到 当前 Action 所在的包下. > 把该配置文件改为: 把 Login 改为当前 Action 的名字. > 编写验证规则: 参见 struts-2.3.15.3/docs/WW/docs/validation.html 文档即可. > 在配置文件中可以定义错误消息: <field name="age"> <field-validator type="int"> <param name="min">20</param> <param name="max">50</param> <message>^^Age needs to be between ${min} and ${max}</message> </field-validator> </field> > 该错误消息可以国际化吗. 可以 <message key="error.int"></message>. 再在 国际化资源文件 中加入一个键值对: error.int=^^^Age needs to be between ${min} and ${max} III. 若验证失败, 则转向 input 的那个 result. 所以需要配置 name=input 的 result <result name="input">/validation.jsp</result> IV. 如何显示错误消息呢 ? > 若使用的是非 simple, 则自动显示错误消息. > 若使用的是 simple 主题, 则需要 s:fielderror 标签或直接使用 EL 表达式(使用 OGNL) ${fieldErrors.age[0] } OR <s:fielderror fieldName="age"></s:fielderror>* 3). 注意: 若一个 Action 类可以应答多个 action 请求, 多个 action 请求使用不同的验证规则, 怎么办 ? > 为每一个不同的 action 请求定义其对应的验证文件: ActionClassName-AliasName-validation.xml > 不带别名的配置文件: ActionClassName-validation.xml 中的验证规则依然会发生作用. 可以把各个 action 公有的验证规则 配置在其中. 但需要注意的是, 只适用于某一个 action 的请求的验证规则就不要这里再配置了. 4). 声明式验证框架的原理: > Struts2 默认的拦截器栈中提供了一个 validation 拦截器 > 每个具体的验证规则都会对应具体的一个验证器. 有一个配置文件把验证规则名称和验证器关联起来了. 而实际上验证的是那个验证器. 该文件位于 com.opensymphony.xwork2.validator.validators 下的 default.xml <validator name="required" class="com.opensymphony.xwork2.validator.validators.RequiredFieldValidator"/> 5). 短路验证: 若对一个字段使用多个验证器, 默认情况下会执行所有的验证. 若希望前面的验证器验证没有通过, 后面的就不再验证, 可以使用短路验证 <!-- 设置短路验证: 若当前验证没有通过, 则不再进行下面的验证 --> <field-validator type="conversion" short-circuit="true"> <message>^Conversion Error Occurred</message> </field-validator> <field-validator type="int"> <param name="min">20</param> <param name="max">60</param> <message key="error.int"></message> </field-validator> 6). 若类型转换失败, 默认情况下还会执行后面的拦截器, 还会进行 验证. 可以通过修改 ConversionErrorInterceptor 源代码的方式使 当类型转换失败时, 不再执行后续的验证拦截器, 而直接返回 input 的 result Object action = invocation.getAction(); if (action instanceof ValidationAware) { ValidationAware va = (ValidationAware) action; if(va.hasFieldErrors() || va.hasActionErrors()){ return "input"; } } 7). 关于非字段验证: 不是针对于某一个字段的验证. <validator type="expression"> <param name="expression"><![CDATA[password==password2]]></param> <message>Password is not equals to password2</message> </validator> 显示非字段验证的错误消息, 使用 s:actionerror 标签: <s:actionerror/> 8). 不同的字段使用同样的验证规则, 而且使用同样的响应消息 ? error.int=${getText(fieldName)} needs to be between ${min} and ${max} age=u5E74u9F84 count=u6570u91CF 详细分析参见 PPT 159. 9). 自定义验证器: I. 定义一个验证器的类 > 自定义的验证器都需要实现 Validator. > 可以选择继承 ValidatorSupport 或 FieldValidatorSupport 类 > 若希望实现一个一般的验证器, 则可以继承 ValidatorSupport > 若希望实现一个字段验证器, 则可以继承 FieldValidatorSupport > 具体实现可以参考目前已经有的验证器. > 若验证程序需要接受一个输入参数, 需要为这个参数增加一个相应的属性 II. 在配置文件中配置验证器 > 默认情况下, Struts2 会在 类路径的根目录下(src中)加载 validators.xml 文件. 在该文件中加载验证器. 该文件的定义方式同默认的验证器的那个配置文件: 位于 com.opensymphony.xwork2.validator.validators 下的 default.xml > 若类路径下没有指定的验证器, 则从 com.opensymphony.xwork2.validator.validators 下的 default.xml 中的验证器加载 III. 使用: 和目前的验证器一样. IV. 示例代码: 自定义一个 18 位身份证验证器 1. 文件的上传: 1). 表单需要注意的 3 点 2). Struts2 的文件上传实际上使用的是 Commons FileUpload 组件, 所以需要导入 commons-fileupload-1.3.jar commons-io-2.0.1.jar 3). Struts2 进行文件上传需要使用 FileUpload 拦截器 4). 基本的文件的上传: 直接在 Action 中定义如下 3 个属性, 并提供对应的 getter 和 setter //文件对应的 File 对象 private File [fileFieldName]; //文件类型 private String [fileFieldName]ContentType; //文件名 private String [fileFieldName]FileName; 5). 使用 IO 流进行文件的上传即可. 6). 一次传多个文件怎么办 ? 若传递多个文件, 则上述的 3 个属性, 可以改为 List 类型! //多个文件域的 name 属性值需要一致. 7). 可以对上传的文件进行限制吗 ? 例如扩展名, 内容类型, 上传文件的大小 ? 若可以, 则若出错, 显示什么错误消息呢 ? 消息可以定制吗 ? 可以的! 可以通过配置 FileUploadInterceptor 拦截器的参数的方式来进行限制 maximumSize (optional) - 默认的最大值为 2M. 上传的单个文件的最大值 allowedTypes (optional) - 允许的上传文件的类型. 多个使用 , 分割 allowedExtensions (optional) - 允许的上传文件的扩展名. 多个使用 , 分割. 注意: 在 org.apache.struts2 下的 default.properties 中有对上传的文件总的大小的限制. 可以使用常量的方式来修改该限制 struts.multipart.maxSize=2097152 定制错误消息. 可以在国际化资源文件中定义如下的消息: 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, 可以提供更多的定制信息. 2. 文件的下载: 1). Struts2 中使用 type="stream" 的 result 进行下载即可 2). 具体使用细节参看 struts-2.3.15.3-all/struts-2.3.15.3/docs/WW/docs/stream-result.html 3). 可以为 stream 的 result 设定如下参数 contentType: 结果类型 contentLength: 下载的文件的长度 contentDisposition: 设定 Content-Dispositoin 响应头. 该响应头指定接应是一个文件下载类型, 一般取值为 attachment;filename="document.pdf". inputName: 指定文件输入流的 getter 定义的那个属性的名字. 默认为 inputStream bufferSize: 缓存的大小. 默认为 1024 allowCaching: 是否允许使用缓存 contentCharSet: 指定下载的字符集 4). 以上参数可以在 Action 中以 getter 方法的方式提供! 3. 表单的重复提交问题 1). 什么是表单的重复提交 > 在不刷新表单页面的前提下:? >> 多次点击提交按钮 >> 已经提交成功, 按 "回退" 之后, 再点击 "提交按钮". >> 在控制器响应页面的形式为转发情况下,若已经提交成功, 然后点击 "刷新(F5)" > 注意: >> 若刷新表单页面, 再提交表单不算重复提交 >> 若使用的是 redirect 的响应类型, 已经提交成功后, 再点击 "刷新", 不是表单的重复提交 2). 表单重复提交的危害: 3). Struts2 解决表单的重复提交问题: I. 在 s:form 中添加 s:token 子标签 > 生成一个隐藏域 > 在 session 添加一个属性值 > 隐藏域的值和 session 的属性值是一致的. II. 使用 Token 或 TokenSession 拦截器. > 这两个拦截器均不在默认的拦截器栈中, 所以需要手工配置一下 > 若使用 Token 拦截器, 则需要配置一个 invalid.token 的 result ; 且Action类要实现 TextProvider 接口,否则会报空指针异常,简单的方法是继承ActionSupport > 若使用 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. 4. 自定义拦截器 1). 具体步骤 I. 定义一个拦截器的类 > 可以实现 Interceptor 接口 > 继承 AbstractInterceptor 抽象类 II. 在 struts.xml 文件配置. <interceptors> <interceptor name="hello" class="com.atguigu.struts2.interceptors.MyInterceptor"></interceptor> </interceptors> <action name="testToken" class="com.atguigu.struts2.token.app.TokenAction"> <interceptor-ref name="hello"></interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref> <result>/success.jsp</result> <result name="invalid.token">/token-error.jsp</result> </action> III. 注意: 在自定义的拦截器中可以选择不调用 ActionInvocation 的 invoke() 方法. 那么后续的拦截器和 Action 方法将不会被调用. Struts 会渲染自定义拦截器 intercept 方法返回值对应的 result