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的文件上传和下载

Struts2文件上传
文件上传必要前提:
请求必须是post
enctype属性必须是multipart/form-data
提供文件选择域
以下是jsp代码和action代码

<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)
第一种方式:如果想更改就会想到default.properties中的常量属性
struts.multipart.maxSize=2097152
所以在xml去覆盖常量值(加载顺序请看Struts2原理上)
<constant name="struts.multipart.maxSize" value="5242880"></constant>
第二种方式:通过FileUploadInterceptor拦截器静态注入参数,看源码属性有个叫maximumSize(行不通····,可能是上面问题把)
<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上传类型和扩展名
看FileUploadInterceptor源码(注意设置param的时候,通过set属性名称,获取属性名称开头转为小写)
  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改成数组就可以了

Struts2文件下载
通过结果类型stream来下载文件
<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(非常重要)

什么是OGNL
对象图导航语言,struts2框架使用的ognl作为默认的表达式语言。
OGNL的功能
在struts2中,想使用OGNL必须使用struts2的标签库
自定义jstl标签
1.编写一个普通的类,提供一个实现功能的静态方法
2.在WEB-INF目录中创建一个扩展名.tld文件,文件不能放在classes和lib目录中
<?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"%>
${my:caps}来调用转换大小写功能!!!
el表达式:只能调用静态方法,而OGNL表达式,它可以访问普通方法

表达式(el,jsp表达式,struts2表达式)

el表达式:${}
jsp表达式:<%=%>
OGNL表达式:<s:property value="OGNL-Expression"/>输出结果为0
value属性中内容不在是字符串,它是一个OGNL表达式,如果想变成字符串,在值上套单引号
<s:property value="'OGNL-Expression'"/>输出为OGNL-Expression  
<s:property value="'OGNL-Expression'.length()"/>打印长度(可以看出,能调用普通方法)
访问静态属性的方式:@全类名@静态属性名称
<s:property value="@java.lang.Integer@MAX_VALUE"/>
访问静态方法的方式:@全类名@静态方法名称

<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(非常重要)

OGNL的上下文就是contextMap(贯穿整个action的生命周期),除了value stack(list集合,特性是栈)外其他都是map类型(特性是不可重复)


明确:动作类是多列的,每次动作访问,动作类都会实例化,所以线程是安全的

问题:

每次请求时,都会产生请求数据,这些数据存放在那里?

明确:

在每次动作类执行前,核心控制器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表达式的改变(非常重要

问题:
为什么el表达式也能获取到action类中属性,el表达式查找循序明明是page->request->session->application
明确:
这是因为el表达式在获取request的时候获取的是一个包装的request对象,源码
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表达式变成
page->request->valueStack->contextMap->session->application
而OGNL表达式(当我们不写#号,他就会从值栈的栈顶找对应的属性,如果没有属性,还要去ActionContext中map的key找)

Struts2常用标签
<s:iterator value="" var="">//value属性:要遍历的集合,是OGNL表达式,获取stack直接名称,获取map加上#
遍历过程中集合被压入栈顶,所以通过${s.属性名}一样可以取出

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的标签<s:token>令牌的意思。
要想让标签生效,需要使用struts2的一个token拦截器
这时候重复提交没有效果,并且后退再次提交会报错错误提示是No result defined for action result  invalid.token
意思是没有定义一个结果视图invalid.token,所以我们需要在<result name="invalid.token">/错误页面.jsp</result >
问题:
虽然上面解决了用户后退提交问题,告诉你不该重复提交,但是用户并不知道重复提交带来的问题,有什么办法让提交一次后面再次提交没有作用。(默认只处理第一次提交数据,后面的就不会处理了)
明确:
使用struts2的标签<s:token>令牌的意思。
要想让标签生效,需要使用struts2的一个tokensession拦截器