Struts2(原理笔记下)
来源:互联网 发布:json.dump 格式化输出 编辑:程序博客网 时间:2024/06/16 15:27
国际化和获得资源包内容
创建一个消息资源包
一个资源包由多个文件组成,这些文件名都有命名规范:主要文件名_语言代码.properties。语言代码:由iso规定。
当文件只有主要文件名.properties时,表明它是默认资源包。浏览器会根据不同语言环境找对应语言环境的资源包,当没有时,找默认的。
message_zh_CN.properties message_zh_HK.properties message_en_US.properties
读取资源包中内容
第一种方式:ResourceBundle.getBundle(String baseName):获取类加载器(src)的默认语言环境的资源包
第二种方式:jstl的fmt标签
第三种:Struts2的国际化
Struts2的国际化(了解)
1.前提,动作类必须继承ActionSupport类
2.回顾Struts2上部分,我们知道有default.properties加载配置文件有设置参数,其中就有国际化参数设置
struts.custom.i18n.resources=testmessages,testmessages2
在struts.xml中配置属性<constant name="" value="包名+文件名(不带后缀名properties)">3.在Action中使用getText("key");获取message属性(getText是ActionSupport类的一个国际化的方法)
action中资源包查找循序是就近原则(动作类资源(动作类名称)>包(package)>全局)
在jsp使用国际化,导入Struts2标签,使用<s:text name="key">,当找不到key值,直接把name属性的值输出页面
jsp中资源包是全局资源(struts.xml配置的地方),因为jsp不是action(没有动作类范围和包范围一说)
在jsp也可以使用<s:i18n name="资源包名称"><s:text name="key"/></s:i18n>指定配置文件地址
Struts2中的拦截器(特别重要)
拦截器的重要性:Struts2的很多功能都是由拦截器完成,比如
servletConfig拦截器:给action实现 ServletRequestAware,ServletResponseAware等注入对象。
staticParame拦截器:静态参数拦截器,主要是struts.xml文件中定义的param标签的参数进行动态注入。
params拦截器:动态参数拦截器,主要是把请求参数封装给action的属性中(拦截器从上往下执行,所以动态覆盖静态拦截器)
modelDriven拦截器:模型驱动,主要是给bean封装参数用的,参数不需要写对象名,因为压栈后,只有属性Key和value。
自定义拦截器:
1.实现Interceptor接口的三个方法
destroy();//销毁,只执行一次
init();//初始化方法,只执行一次
intercept(ActionInvocation invocation);//拦截方法,访问action之前执行,参数可以获取action类
或者继承AbstractInterceptor抽象类(推荐)
好处:只需要实现抽象方法intercept,初始化和销毁方法可以无视(类似适配器模式)
2.在struts2中配置拦截器声明
<interceptors name="demo1" class=""></interceptors>
3.使用自定义拦截器(当配置了任何一个拦截器后,默认的拦截器栈就不会工作了)
<interceptors-ref name="demo1">
4.这时候就拦截了,但是需要放行
invocation.invoke();//此方法有String类型的返回值,返回的就是结果视图的名称!!!
所以拦截器正着走一遍,执行完最后一个拦截器时候,返回结果视图,然后反着走一边!!!
既然是结果视图名称,所以可以动态的改变返回视图的名称,比如你明明返回success,但是因为
报错,直接返回到错误页面,就是因为拦截器动态的改变了结果视图名称。
拦截器的执行顺序
与声明无关,主要看<interceptor-ref>引用的顺序
使用拦截器后,默认拦截器失效(重点)
<interceptors>
<interceptor name="xxx"></interceptor>
<interceptor-stack name="myDefaultStack"><!--配置拦截栈-->
<interceptor-ref name="defaultStack"></interceptor><!--默认拦截栈-->
<interceptor-ref name="xxx"></interceptor><!--自定义拦截器-->
</interceptor-stack>
</interceptors>
以上设置后引用就可以使用,但还是需要在每个要用到地方使用拦截器应用
解决办法:覆盖struts-default.xml配置文件中默认的拦截器栈,让我们定义的称为默认的
<default-interceptor-ref name="myDefaultStack"></default-interceptor-ref>
但是又出现问题,登录动作类也被拦截了,这时候需要继承MethodFilterInterceptor(interceptor的实现类其中一种)
MethodFilterInterceptor中有两个参数
Set<String> excludeMethods:排除方法
Set<String>includeMethods:添加方法
我们都知道,可以在xml中静态注入参数(因为有staticParame拦截器)
<interceptors>
<interceptor name="xxx"></interceptor>
<interceptor-stack name="myDefaultStack"><!--配置拦截栈-->
<interceptor-ref name="defaultStack"></interceptor><!--默认拦截栈-->
<interceptor-ref name="xxx">
<param name="excludeMethods">login</param><!--动态注入不拦截的动作类-->
</interceptor><!--自定义并且继承MethodFilterInterceptor拦截器-->
</interceptor-stack>
</interceptors>
又有个问题是,我们一开始不知道拦截那个方法,所以直接在不需要被拦截的action中配置(开发中推荐)
<interceptor-ref name="myDefaultStack">
<param name="xxx.excludeMethods"><!--有很多拦截器,所以通过拦截器名字.方法注入不需要拦截方法-->
</interceptor>
Struts2的文件上传和下载
<s:form action="user" method="post" enctype="multipart/form-data"><s:textfield name="name" label="姓名"></s:textfield><s:file name="photo" label="上传文件"></s:file><s:submit value="提交"></s:submit></s:form>
private String name;private File photo;//上传文件private String photoFileName;//上传文件名称。上传字段名称+FileName 注意大小写private String photoContentType;//上传文件的MIME类型public String upload(){//拿到servletContextServletContext context = ServletActionContext.getServletContext();//此方法返回的是服务器真实地址(就是服务器发布后的地址,如果查看文件,只能在部署服务器的项目中去查看)String path = context.getRealPath("WEB-INF/files");//不存在创建File file=new File(path);if(!file.exists()){file.mkdirs();}//剪切:把临时文件剪切到指定位置并且给他重命名(因为文件过大会产生临时文件,剪切临时文件没有了)photo.renameTo(new File(file,photoFileName));return null;}
Struts2上传大小限制(默认是2MB)struts.multipart.maxSize=2097152
所以在xml去覆盖常量值(加载顺序请看Struts2原理上)<package name="user" extends="struts-default"> <action name="user" class="com.mck.resource.UserAction" method="upload"> <interceptor-ref name="defaultStack"> <!--拦截器定义名称.属性名 --> <param name="fileUpload.maxumumSize">31457280</param> </interceptor-ref> </action> </package>
Struts2上传类型和扩展名 protected Long maximumSize; protected Set<String> allowedTypesSet;//上传类型看set方法的属性名 protected Set<String> allowedExtensionsSet;//扩展名 看set方法的属性名 private PatternMatcher matcher; private Container container;
<action name="user" class="com.mck.resource.UserAction" method="upload"> <result name="input">/NewFile.jsp</result> <interceptor-ref name="defaultStack"> <!--拦截器定义名称.去掉set方法的小写开头属性名 --> <param name="fileUpload.allowedExtensions">png,jpg</param><!----> </interceptor-ref> </action>
Struts2多个文件上传,把file属性改成数组,fileFileName改成数组就可以了<result-type name="stream" class="org.apache.struts2.dispatcher.StreamResult"/>
<result name="success" type="stream"><!-- 给stream的结果类型注入参数:Content-Type --><param name="contentType">application/octet-stream</param><!-- 告知客户浏览器以下载的方式打开<param name="contentDisposition">attachment;filename=photo.jpg</param> --><!-- 使用OGNL表达式,来动态获取文件名 ${@java.net.URLEncoder@encode(filename,"UTF-8")} 把${}中间的内容当成一个OGNL表达式来看待--><param name="contentDisposition">attachment;filename=${@java.net.URLEncoder@encode(filename,"UTF-8")}</param><!-- 注入字节输入流 :取值要写动作类中set方法的名称,首字母改成小写--><param name="inputName">inputStream</param></result>
OGNL(非常重要)
<?xml version="1.0" encoding="UTF-8" ?><taglib xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-jsptaglibrary_2_0.xsd" version="2.0"> <tlib-version>1.0</tlib-version><!-- 指定标签库或方法库版本号 --> <short-name>mf</short-name><!-- 使用短名称,对应taglib指令中的prefix --> <uri>http://www.cc.com/function/myfunciton</uri><!-- 使用短名称,对应taglib指令中的url --> <!-- <tag>自定义标签 <name>helloWorld</name> <tag-class>jsp2.examples.simpletag.HelloWorldSimpleTag</tag-class> <body-content>empty</body-content> </tag> --> <function><!-- 自定义方法 --> <name>caps</name><!-- 方法名称,是jsp页面上使用的名称 --> <function-class>com.cc.MyFunction</function-class><!-- 指定执行的类 --> <!-- 指定执行的方法,方法名称必需和类中的方法名称保持一致 --> <function-signature>java.lang.String toUpperCase( java.lang.String )</function-signature> </function></taglib>
编写好的tld文件,在jsp调用<%taglib uri="" prefix="mf"%>
表达式(el,jsp表达式,struts2表达式)
<s:property value="@java.lang.Math@random()"/>
值得注意文件下载地方出现的${}也是OGNL表达式用法,
<param name="contentDisposition">attachment;filename=${@java.net.URLEncoder@encode(filename,"UTF-8")}</param>
注意:struts2默认是禁止访问静态方法的,需要覆盖常量struts.ognl.allowStaticMethodAccess=false改为true
Struts2中标签的OGNL使用
{}就相当于创建一个list集合,list属性中取值是一个OGNL表达式
<s:radio name="gender"list="{‘男’,'女'}"></s:radio>
#{}就相当于创建一个map集合,map属性key中取值是一个OGNL表达式
<s:radio name="gender"list="#{‘1’:'男','2':'女'}"></s:radio>
这里面牵扯到stack和contextmap问题,记住一句话,contextmap中的获取用#,stack的直接获取
contextMap(非常重要)
问题:
每次请求时,都会产生请求数据,这些数据存放在那里?
明确:
在每次动作类执行前,核心控制器StrutsPrepareAndExecuteFilter都会创建一个ActionContext(维护一个名字叫context的map)和ValueStack对象,且每次动作访问都会创建,并且ActionContext绑定在ThreadLocad中,保证的线程数据的唯一性和安全性
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; try { if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) { chain.doFilter(request, response); } else { prepare.setEncodingAndLocale(request, response); prepare.createActionContext(request, response);//给ActionContext设置值 prepare.assignDispatcherToThread(); request = prepare.wrapRequest(request); ActionMapping mapping = prepare.findActionMapping(request, response, true); if (mapping == null) { boolean handled = execute.executeStaticResourceRequest(request, response); if (!handled) { chain.doFilter(request, response); } } else { execute.executeAction(request, response, mapping); } } } finally { prepare.cleanupRequest(request); } }
public ActionContext createActionContext(HttpServletRequest request, HttpServletResponse response) { ActionContext ctx; Integer counter = 1; Integer oldCounter = (Integer) request.getAttribute(CLEANUP_RECURSION_COUNTER); if (oldCounter != null) { counter = oldCounter + 1; } ActionContext oldContext = ActionContext.getContext();//这是老的ActionContext if (oldContext != null) { // detected existing context, so we are probably in a forward ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));//原先的数据设置进去,因为有application } else { ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack(); stack.getContext().putAll(dispatcher.createContextMap(request, response, null));//把requset和response设置给stack栈中 ctx = new ActionContext(stack.getContext());//创建ActionContext并且把stack(list)存入ActionContext中 } request.setAttribute(CLEANUP_RECURSION_COUNTER, counter); ActionContext.setContext(ctx); return ctx; }
我们知道拦截器在拦截后,把请求和响应设置给ValueStack,从源码可知,ValueStack中维护一个context(map)集合。
这时候又把map设置给了ActionContext的map中,这不是多此一举?不,在上面已经说了ActionContext中维护一个ThreadLoad集合的ActionContext,也就是获取map仅当前线程的map,保证了数据的安全性!!!,并且次类的方法大部分是静态,方便调用方法,所以把ActionContext类也可以看做工具类
问题:
既然知道context(map)还是在ValueStack封装进去,那么,ValueStack我们还说是一个list集合呢?
明确:
ValueStack源码可知道,虽然维护了一个map,但是ValueStack为抽象类,而抽象类中的抽象方法被OgnlValueStack实现了,实现的就是栈存储结构方式,而OgnlValueStack有个属性叫做CompoundRoot,而CompoundRoot里面维护一个list
public class CompoundRoot extends CopyOnWriteArrayList<Object> { private static final long serialVersionUID = 8563229069192473995L; public CompoundRoot() { } public CompoundRoot(List<?> list) { super(list); } public CompoundRoot cutStack(int index) { return new CompoundRoot(subList(index, size())); } public Object peek() { return get(0); } public Object pop() { return remove(0); } public void push(Object o) { add(0, o); }}所以,我们操控的ValueStack的方法时候实际就是操控一个list集合,并且是压栈的方式存储数据,而context(map)中也添加了ValueStack的一份引用,所以ActionContext的map就是ValueStack中的map传递进来的,所以ValueStack叫值栈(栈结构),其他就是contextmap。哦了
问题:action是多列的,那么actionContext如何知道那个action对应那个actionContext,不会出现线程安全问题?
明确:actionContext内部使用ThreadLocal,所以返回都是当前线程的actionContext,保证了安全性(actionContext是map结构)
查看ContextMap中的数据,使用<s:debug/>标签查看(不列举图片),可以看出,上部分是value stack,下部分是contextMap
获取ActionContext
在动作类中使用ActionContext.getContext();//静态方法
ActionContext context=ActionContext.getContext();
context.put("abc","hello context");//把数据直接存入actionContext维护context中(大map中)
//如果想存入context(map)的小map中(session,request,application)
//第一种方式:获取session
context.getSession();//获取context中key为session的value(看源码可知)
//第二种方式:获取session
ServletActionContext.getRequset.getSession();
ActionContext和ServletActionContext都可以获得域对象,从代码片段可以看出,ServletActionContext是继承ActionContext,对域对象进行封装,也就是说,底层一样是获取map中的key值
public class ServletActionContext extends ActionContext implements StrutsStatics {
public static HttpServletRequest getRequest() { return (HttpServletRequest) ActionContext.getContext().get(HTTP_REQUEST);//还是操控map }
以上两种方式,能用ActionContext对象满足就尽量使用ActionContext,另外,不要在Action还没实例化的时候去通过ActionContext调用方法,因为Action实例在ActionContext实例之前创建,ActionContext中一些值还没有设置,会返回null。
总结:我们知道了ActionContext维护了一个线程安全的context(map),并且可以获取map中的key(session,request,application等获取这些value,还是map),并且推荐使用actionContext。
jsp页面获取ContextMap中的数据
使用<s:property>来获取ActionContext中的数据
<s:property value="#abc">//获取map中的key,自己添加的key
<s:property value="#session.abc>//获取大map中session(也是个map)在key,key.key
ValueStack操作
获取ValueStack方式
1.通过ActionContext.getRequest.getAttribute("struts.valueStack");
2.ActionContext.getContext.getValueStack();//推荐,上面已经说过map中维护着一份ValueStack的集合
给valueStack压入值
vs.push(new Student());
vs.setValue("name","张山")//把ValueStack中第一个name属性值换成张山,如果没有name属性,就报错!!!
vs.setValue("#name","张山")//把数据放在contextMap中key为name,value为张山
vs.set(key,object)//把数据存入到map中压入栈顶,获取直接key.属性(object.属性)
在jsp获取valueStack值
<s:property value="name">//取valueStack中的对象属性时,不使用#,它是从栈顶逐个对象查找指定属性,找到了就不找了。
<s:property value="[1].name">获取第二个name
默认Action是栈顶,上面操作,Action就不是栈顶
ValueStack的findValue和findString方法
vs.findValue("name");一样可以拿到valueStack的栈结构的属性<s:property>标签底层就是findValue
而 vs.findValue("[0].name");//是排除valueStack第一个索引往后的name
而 vs.findValue("[3].name");//是排除valueStack第三个索引往后的name开始找
底层用到的一个(cutStack(index))切片方法,[index],就从索引位置开始切片
subList(index,size()),可以看出给1的时候,list集合第1个位置没有了,也就是索引0没有了
Struts2对EL表达式的改变(非常重要)
public class StrutsRequestWrapper extends HttpServletRequestWrapper { public Object getAttribute(String key) { if (key == null) { throw new NullPointerException("You must specify a key value"); } if (disableRequestAttributeValueStackLookup || key.startsWith("javax.servlet")) { // don't bother with the standard javax.servlet attributes, we can short-circuit this // see WW-953 and the forums post linked in that issue for more info return super.getAttribute(key); } ActionContext ctx = ActionContext.getContext(); Object attribute = super.getAttribute(key);//调用父类的request看有没有key,有就直接返回,没有就执行下面方法 if (ctx != null && attribute == null) { boolean alreadyIn = isTrue((Boolean) ctx.get(REQUEST_WRAPPER_GET_ATTRIBUTE)); // note: we don't let # come through or else a request for // #attr.foo or #request.foo could cause an endless loop if (!alreadyIn && !key.contains("#")) { try { // If not found, then try the ValueStack ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.TRUE); ValueStack stack = ctx.getValueStack();//获取值栈 if (stack != null) { attribute = stack.findValue(key);//值栈中查找key,我们知道值栈默认压入action,而action中的属性名变成key } } finally { ctx.put(REQUEST_WRAPPER_GET_ATTRIBUTE, Boolean.FALSE); } } } return attribute; }源码一步了然,所以el表达式变成
而OGNL表达式(当我们不写#号,他就会从值栈的栈顶找对应的属性,如果没有属性,还要去ActionContext中map的key找)
Struts2常用标签
Struts2中的#,$,%符号的使用(重要)
#
a、取contextMap中key时使用,例如<s:property value="#name"/>
b、OGNL中创建Map对象时使用,例如:<s:radio list="#{'male':'男','female':'女'}" />
$
a、在JSP中使用EL表达式时使用,例如${name}
b、在xml配置文件中,编写OGNL表达式时使用,例如文件下载时,文件名编码。
struts.xml——>${@java.net.URLEncoder.encode(filename)}
%
在struts2中,有些标签的value属性取值就是一个OGNL表达式,例如<s:property value="OGNL Expression" />
还有一部分标签,value属性的取值就是普通字 符串,例如<s:textfield value="username"/>,如果想把一个普通的字符串强制看成时OGNL,就需要使用%{}把字符串套起来。
例如<s:textfield value="%{username}"/>。当然在<s:property value="%{OGNL Expression}"/>也可以使用,但不会这么用。
ModelDriven的工作原理
public String intercept(ActionInvocation invocation) throws Exception { Object action = invocation.getAction();//获取action if (action instanceof ModelDriven) {//如果实现ModelDriven接口 ModelDriven modelDriven = (ModelDriven) action;//强转 ValueStack stack = invocation.getStack();//获得valueStack Object model = modelDriven.getModel();//获得Model对象 if (model != null) { stack.push(model);//把model对象压入栈顶,所以在表单中不用输入对象.name就是因为值栈中有这个对象对应的属性名称 } if (refreshModelBeforeResult) { invocation.addPreResultListener(new RefreshModelBeforeResult(modelDriven, model)); } } return invocation.invoke(); }
Struts2中表单重复提交问题
- Struts2(原理笔记下)
- Struts2(原理笔记上)
- Struts2使用原理笔记(Spring整合)
- struts2 原理介绍 笔记
- Struts2 原理笔记
- Struts2学习笔记——Struts2原理
- Struts2学习笔记(2)-----Struts2注释(下)
- Struts2.1笔记(二):Struts2下的HelloWorld
- struts2学习笔记(8)——拦截器原理
- struts2学习笔记(1)——关于struts2的工作原理,以及基本组成
- 【Struts2学习笔记(5)】Struts2的处理流程及工作原理
- struts2学习笔记拦截器实现原理
- Struts2笔记11 拦截器底层原理
- struts2学习笔记(一)---工作原理
- struts2第十八讲学习笔记,struts2工作原理精华摘要
- Struts2学习笔记(五) Action(下)
- Struts2学习笔记(七) 结果(Result)(下)
- Struts2学习笔记(九) 拦截器(Interceptor)(下)
- java使用poi,导出数据致word模板,以提供下载。
- 工厂模式-1
- springmvc拦截器静态资源的访问 前台css js样式加载的问题
- python笔记1.__init__.py &future模块
- mongodb 复制collection时需要注意
- Struts2(原理笔记下)
- View.js
- 一些好的网站记录(PART 6)
- 冒泡排序
- 蚊子看了想咬人,人类看了都说好
- Mac 下WebStorm 常用快捷键
- 公有云的云主机实例在停机状态下是否收费?
- 【openpyxl】openpyxl对Excel表格的创建与写操作例程
- 大数据知识体系_探索数据_数据汇总_可视化_多维数据分析