Struts2国际化处理
浏览器根据当前的语言环境自动查找对应的语言环境资源包, 使jsp显示合适的语言数据环境
Struts2实现国际化, 动作类必须继承ActionSupport
- 创建资源包
资源包由多个properties文件组成, properties文件的文件名必须满足命名规范: 文件名_语言代码_国家代码.properties
语言代码/国家代码由iso规定
例: ms_zh_CN.properties
注:当properties文件命名为: 文件名.properties表名它为默认的资源包, 当浏览器没有找到对应的语言资源包的时候, 就会使用默认的资源包
- 配置资源包
1. 配置全局资源包覆盖default.properties文件中的常量struts.custom.i18n.resources例:<constant name="struts.custom.i18n.resources" value="com.test.message"></constant><package name="p1" extends="struts-default"><action name="action1" class="com.action.DemoAction"></action></package>com.test.message代表资源包所在的位置
2. 配置包范围的资源包命名规范: package_语言代码_国家代码.properties(固定命名格式)使用这种命名规范, 资源包只能被该包或子包的动作类访问
3. 局部资源包命名规范: 动作名称_语言代码_国家代码.properties只为动作类服务
注:资源包的搜索顺序, 满足就近原则: 局部 > 包 > 全局
- 在jsp中读取资源包中内容
借助struts2的标签进行读取
例:<%@ taglib uri="/struts-tags" prefix="s"%><s:text name="key"></s:text>
显示的数据对应的是properties文件中名为key的值, 此时输出的内容是全局资源包中的内容
原因: 没有经过动作类, 以及包, 直接搜索的是全局的资源包
当经过动作类的时候, 就是按照搜素顺序进行搜索资源包
- 获取指定的资源包
使用<s:i18n name="com.test.message">
直接获取message下面的properties文件
Struts2中的拦截器
Struts2中很多功能都是拦截器完成的, 并且都是AOP思想的一种体现
自定义拦截器
继承AbstractInterceptor类或实现Interceptor接口, 重写intercept方法
配置拦截器, 拦截器只有在配置的条件下才能使用
例如:
public class Demo1Interceptor extends AbstractInterceptor{public String intercept(ActionInvocation invocation) throws Exception{return null; }
}
//继承AbstractInterceptor重写intercept方法
声明拦截器:
struts.xml:
<interceptors><interceptor name="demo1Interceptor" class="com.interceptors.Demo1Interceptor" />
</interceptors>
使用拦截器:
<action name="action1" class="com.interceptors.Demo1Interceptor" method="save"><interceptor-ref name="demo1Interceptor"></interceptor-ref><result>/success.jsp</result>
</action>
在interceptors标签中可以配置多个拦截器声明
注:
使用拦截器必须先声明然后再使用, 并且一旦配置了任何一个拦截器, 默认的拦截器就都失效了
拦截器执行顺序
下面通过例子说明:
Struts.xml:
<struts><constant name="struts.devMode" value="true"></constant><package name="p1" extends="struts-default"><!--声明拦截器列表--><interceptors><interceptor name="demo1Interceptor" class="com.interceptors.Demo1Interceptor" /></interceptors><action name="action1" class="com.interceptors.Demo1Interceptor" method="save"><!--使用拦截器--><interceptor-ref name="demo1Interceptor"></interceptor-ref><result>/success.jsp</result></action></package>
</struts>
在拦截器类中的ntercept方法有如下操作:
public String intercept(ActionInvocation invocation) throws Exception{System.out.println("拦截器执行, 访问动作方法之前");String result = invocation.invoke();System.out.println("拦截器执行, 访问动作方法后");return result;
}Action类:
public class Demo1Action extends ActionSupport{public String save(){System.out.println("动作方法save执行");return SUCCESS;}
}success.jsp:
<body><%System.out.println("success.jsp执行了")%>
</body>
当访问save动作的时候有如下的运行顺序:拦截器执行了, 分文动作之前动作方法save执行success.jsp执行了拦截器执行了, 访问动作方法之后
从上面的运行顺序可以得出一下结论:
1.访问动作方法之前一定会先执行所有的拦截操作
2.拦截操作执行完,必须使用invocation的invoke方法做放行操作, 此处的操作类似于Filter中的doFilter操作
3.当动作类, 以及结果视图中的内容执行完之后又重新反向将拦截器都执行一遍
此处请看一下的Struts2的效果执行图:
其实通过Struts2的执行图就可以看出, 多个拦截器的运行过程, 在使用多个拦截器的时候, 它与单个拦截器的写法一样
intercept方法的返回值
正如上面例子看到的一样, intercept方法具有一个String类型的返回值
String result = invocation.invoke();
此时的result就是代表结果视图, 代表动作方法的返回值
解决自定义拦截器条件下, 默认拦截器出现无效的情况
正如前面提到的, 当我们使用自定义的拦截器的时候出现的问题就是, 默认系统提供的拦截器都会无效, 导致demo各种报错
所以现在需要做的就是将Struts2提供的默认的拦截器加载到我们的配置文件中
有如下的解决方案:
- 把默认的拦截器加入到配置文件中
将默认的拦截器加入到配置文件, 需要做的就是在使用自定义拦截器之前, 在使用自定义的拦截器之前多加一个系统默认的interceptor-ref使用
这里要注意的是, 系统默认的拦截器不需要做声明操作, 只需要在action标签中做修改
例:<action name="action1" class="com.interceptors.Demo1Interceptor" method="save"><!--使用系统默认的拦截器--><interceptor-ref name="defaultStack"></interceptor-ref><!--使用自定义的拦截器--><interceptor-ref name="demo1Interceptor"></interceptor-ref><result>/success.jsp</result></action>
但是上面的操作会暴露出一个问题: 就是每当我需要添加一个自定义的拦截器的时候都需要使用系统默认的拦截器, 重复代码过多, 所以下面采取的措施就是, 将每个action中的拦截器抽取出来, 统一使用一个新的package管理, 然后在触发action的时候使用拦截器
例如:
新建一个package
<package name="mydefault" extends="struts-default"'><interceptors><!--声明自定义的拦截器--><interceptor name="demo1Interceptor" class="com.interceptors.Demo1Interceptor"> </interceptor><!--声明拦截器栈--><interceptor-stack name="myDefaultStack"><!--定义使用拦截器--><interceptor-ref name="defaultStack"></interceptor-ref><interceptor-ref name="demo1Interceptor"></interceptor-ref></interceptor-stack></interceptors><!--覆盖默认拦截器定义使用参数, 使用我们自定义的拦截器栈--><default-interceptor-ref name="myDefaultStack"></default-interceptor-ref>
</package>
注: default-Interceptor-ref是struts-default中的标签, 它包含了struts2默认的所有拦截器定义使用(ref)
<package name="p2" extends="mydefault"><action name="A" class="com.test.action.Demo1Action" method="A"><result>/1.jsp</result> </action><action name="B" class="com.test.action.Demo1Action" method="B"><result>/2.jsp</result> </action>
</package>
经过上面的修改之后, 在访问动作A, B就不在需要进行拦截器的定义声明操作, 但是由此也暴露出一个问题, 就是当访问package p2下面的所有动作, 都将会触发拦截器, 如果有的动作我们不想触发拦截器, 那么就出现问题, 所以针对上面的问题, 有下面的解决方案
2. 继承AbstractInterceptor的抽象子类MethodFilterInterceptor
MethodFilterInterceptor将AbstractInterceptor的抽象方法intercept重写, 但是具有一个新的抽象方法doIntercept(ActionInvocation invocation);
MethodFilterInterceptor中具有两个属性:
Set< String> excludeMethods //哪些动作不需要拦截
Set< String> includeMethods //哪些动作需要拦截
上面的两个属性可以做注入操作, 通过配置struts.xml的方式做到配置拦截操作
所以在自定义拦截器的时候, extends MethodFilterInterceptor类, 实现doIntercept(ActionInvocation invocation);
//Interceptor中写法
public class CheckLogin2Interceptor extends MethodFilterInterceptor{protected String doIntercept(ActionInvocation invocation) throws Exception{HttpSession session = ServletActionContext.getRequest().getSession();Objective obj = session.getAttribute("user");if (obj != null){String result = invocation.invoke();return result; }else{return "error"; }}
}
//struts.xml写法
<package name="mydefault" extends="struts-default"'><interceptors><interceptor name="checkLogin2Interceptor" class="com.interceptors.CheckLogin2Interceptor"></interceptor><interceptor-stack name="myDefaultStack"><interceptor-ref name="defaultStack"></interceptor-ref><interceptor-ref name="checkLogin2Interceptor"><!--对A方法不拦截--><param name="excludeMethods">A</param></interceptor-ref></interceptor-stack></interceptors><default-interceptor-ref name="myDefaultStack"></default-interceptor-ref>
</package>
<package name="p2" extends="mydefault"><action name="A" class="com.test.action.Demo1Action" method="A"><result>/1.jsp</result> </action><action name="B" class="com.test.action.Demo1Action" method="B"><result>/2.jsp</result> </action>
</package>
注: 当我们需要进行方法拦截的时候, 就可以通过注入的方式, 进行指定拦截, 但是也暴露了一个问题, 如果只有在确定动作类, 动作方法之后才能确定param中的参数
所以针对这个问题, 有下面的解决方案:
我们不在拦截器定义使用的时候, 而是在触发action的时候对特定的方法使用特定的拦截器
上面struts.xml有如下的修改:
<package name="mydefault" extends="struts-default"'><interceptors><interceptor name="checkLogin2Interceptor" class="com.interceptors.CheckLogin2Interceptor"></interceptor><interceptor-stack name="myDefaultStack"><interceptor-ref name="defaultStack"></interceptor-ref><!--此处不配置param参数--><interceptor-ref name="checkLogin2Interceptor"></interceptor-ref></interceptor-stack></interceptors><!--自定义默认拦截器栈--><default-interceptor-ref name="myDefaultStack"></default-interceptor-ref>
</package>
<package name="p2" extends="mydefault"><action name="A" class="com.test.action.Demo1Action" method="A"><!--在action中配置interceptor--><interceptor-ref name="myDefaultStack"><!--对自定义的checkLogin2Interceptor拦截器中的excludeMethods, 进行参数注入操作, 在触发A动作的时候, 不拦截A操作--><param name="checkLogin2Interceptor.excludeMethods">A</param></interceptor-ref><result>/1.jsp</result></action><action name="B" class="com.test.action.Demo1Action" method="B"><result>/2.jsp</result> </action>
</package>
拦截器类图
// 从网上找的
Struts2的文件上传与下载
文件上传
与Servlet中的文件上传一样, Struts2中的文件上传必须明确:
1.表单method为post
2.enctype取值必须是multipart/form-data
3.提供文件选择域
下面使用Struts2提供的标签进行演示:
<s:form action="upload" enctype="multipart/form-data"><s:textfield name="username" lable="用户名"></s:textfield><s:file name="photo" lable="照片"></s:file><s:submit value="提交"></s:submit>
<s:form>
注: Struts2中使用file标签进行文件上传, Struts2文件上传的底层实现还是commons-fileupload和commons-io
动作类:
public class UploadAction extends ActionSupport{private String username;private File photo; //上传字段private String photoFileName; //上传文件名, 固定写法, 上传字段名称+FileName(区分大小写)private String photoContentType; //上传文件的MIME类型, 固定写法, 上传文件名+ContentType(区分大小写)//在上传文件后, struts会自动对这两个属性做注入处理public String upload(){String dir = ServletActionContext.getServletContext().getRealPath("/WEB-INF/files");//获取files路径File file = new File(dir);if (!file.exists()){file.mkdirs(); }photo.renameTo(new File(File, photoFileName));//将上传的photo存入file中return null;}//下面一堆get, set方法, 我就不写了
struts.xml:
<!--覆盖默认的maxSize大小, 默认大小为2M-->
<constant name="struts.multipart.maxSize" value="5242880"></constant>
<package name="upload" extends="struts-default"><action name="upload" class="com.action.UploadAction" method="upload"><interceptor-ref name="defaultStack"><!--使用默认的fileUpload拦截器, 对extension做注入处理, 限制上传文件扩展名--><param name="fileUpload.allowedExtension">jpg.png</param><!--限制上传文件类型--><param name="fileUpload.allowedTypes">image/jpg, image/pjpeg,image/png</param></interceptor-ref><result name="input">/upload.jsp</result></action>
</package>
修改上传文件出现的错误信息提示
当上传文件的时候会出现, 文件类型/大小/后缀名…错误, 需要通过国际化的方式修改错误提示信息
有如下操作:
1.创建资源文件, 例如: fileuploadmessage.properties 放在src下
2.添加错误信息, 例如: struts.messages.error.uploading=上传错误:{0}, 其余key查阅struts2-core.jar\org.apache.struts2\struts-message.properties, 只需要将value中英文值修改即可
3.在struts.xml中使用< constant name=“struts.custom.i18n.resource” value=“com…fileuploadmessage”/>, value代表资源文件位置
多文件上传
多文件上传, 在jsp中, 只需多添加file标签, 在UploadAction中修改上传字段, 上传文件名, 上传文件类型为数组/list类型即可, struts会自动将上传的file分配到数组/list中
文件下载
文件下载是一种结果类型(Stream)
Action类:
public class DownloadAction extends ActionSupport{private InputStream fileInputStream; //此处字段名不能写in, 会报错public String download() throws Exception{String filePath = ServletActionContext.getServletContext().getRealPath("/WEB-INF/files/1.jpg");fileInStream = new FileInputStream(filePath);return SUCCESS;}//下面一堆get, set操作, 进行注入
}
struts.xml配置:
<action name="download" class="com.action.DownloadAction" method="download"><result name="success" type="stream"><!--配置inputName属性, 将action类中的输入流--><param name="inputName">fileInputStream</param><!--设置响应消息头, 告知浏览器以下载的方式打开--><param name="contentDisposition">attachment;filename=image.jpg</param><!--设置响应消息头, 告知浏览器, 响应正文的MIME类型--><param name="contentType">application/octet-stream</param></result>
</action>
注: 在写filename会出现文件名写死的情况, 所以采用OGNL表达式解决, 写成: filename=${filename}, 在action类中获取filename属性, 注入处理
OGNL
OGNL类似于EL表达式, Struts2表达式语言, 在Struts2中使用OGNL必须使用Struts2标签库
例:
<s:property value="'OGNL_Expression'">
s:property类似于EL中<%=%>操作, value代表输出的内容, 双引号中的内容就是一个OGNL表达式
在OGNL中可以调用普通方法, 静态方法, 使用静态变量, 但是在EL表达式中只能使用静态方法
调用方法的时候, 需要写出当前方法的完整路径, 并且在路径以及方法名前加"@"符号
当调用静态方法的时候, 需要开启OGNL静态访问: < constant name=“struts.ognl.allowStaticMethodAccess” value=“true”></ constant>
此时才能在OGNL中调用静态方法
例:
<s:property value="@java.lang.Math@random">
在form表单中, radio以及check中就有OGNL的应用
例:
<s:radio name="gender" list="{'男', '女'}"></s:radio>
<s:radio name="gender" list="#{'1':'男', '0':"女"}">
创建List集合使用"{}", List的value直接就是男
创建Map对象使用"#{}", 上面的'1'代表男的value, key作为标签的value
contextMap
contextMap主要用于存放每次动作访问的数据
每次执行动作之前, 核心控制器StrutsPrepareAndExecuteFilter都会创建一个ActionContext和ValueStack对象。且每次动作访问都会创建, 这两个对象存储了整个动作访问期间用到的数据。并且把数据绑定到了线程局部变量(ThreadLocal)上
动作类是多例的, 每次动作访问都会实例化, 确保线程安全
contextMap中存放的内容
1.valuestack(root) : java.util.List; list类型, 非map
2.application: java.util.Map<String, Object>; 存放ServletContext中所有属性
3.session: java.util.Map<String, Object>; 存放HttpSession中所有属性
4.request: java.util.Map<String, Object>; 存放ServletRequest中所有属性
5.parameters: java.util.Map<String, Object>; 存放各种参数
6.attr: java.util.Map<String, Object>; 存放页面, 请求, 会话, 应用范围内的所有属性
注: 除valuestack是list, 其余都是map, contextMap也是map