Struts2使用execAndWait在Action中调用getText报告java.lang.NullPointerException

使用 Struts2 编写页面,遇到一个要长时间运行的接口,因此增加了一个execAndWait ,结果在 Action 中调用 getText的时候报告异常

java.lang.NullPointerException
at com.opensymphony.xwork2.util.LocalizedTextUtil.findText(LocalizedTextUtil.java:361)
at com.opensymphony.xwork2.TextProviderSupport.getText(TextProviderSupport.java:208)
at com.opensymphony.xwork2.TextProviderSupport.getText(TextProviderSupport.java:123)
at com.opensymphony.xwork2.ActionSupport.getText(ActionSupport.java:103)
at com.infy.action.LoginAction.execute(LoginAction.java:19)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
at java.lang.reflect.Method.invoke(Method.java:597)
at com.opensymphony.xwork2.DefaultActionInvocation.invokeAction(DefaultActionInvocation.java:450)
at com.opensymphony.xwork2.DefaultActionInvocation.invokeActionOnly(DefaultActionInvocation.java:289)
at org.apache.struts2.interceptor.BackgroundProcess$1.run(BackgroundProcess.java:57)
at java.lang.Thread.run(Thread.java:662)

查询了很多评论,最终找到原因跟解决方案,具体解释在 http://stackoverflow.com/questions/16692658/execandwait-interceptor-not-redirecting-to-success-page-after-waiting

  1. execAndWait causes the action to be executed in a new thread.
  2. Since ActionContext is ThreadLocal, the new thread will not get the values stored in the parent thread version of ActionContext. Every thread has a unique version of ActionContext
  3. getText() will throw a NPE when it tries to execute in the new thread because it depends onActionContext

简单解释一下,就是说

execAndWait 会导致执行的Action 在另外一个线程中被执行,而getText 依赖 ActionContext ,他从 ActionContext 中获得当前的Locale 从而根据语言的不同加载不同的文字,可是,由于ActionContext 是ThreadLocal 的,而execAndWait 新开线程的时候并没有把父线程的ActionContext 传递给子线程 结果导致在新开的子线程中的ActionContext中的数据都是null ,因此出现异常信息就不足为怪了。

解决方法为

To fix this, you need to copy the parent threads ActionContext into the execAndWait thread. You can do this by extending the BackgroundProcess class, implementing the beforeInvocation()and afterInvocation() methods, and extending ExecuteAndWaitInterceptor, implementing the getNewBackgroundProcess() method.

代码例子如下,注意,原文中作者的代码存在多线程同步问题,具体体现在

beforeInvocation

被调用的时候,得到的 context 为null ,导致注入失败。

因此需要重载两个类,来解决这个问题

import org.apache.struts2.interceptor.BackgroundProcess;
import org.apache.struts2.interceptor.ExecuteAndWaitInterceptor;

import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionInvocation;

public class ExecAndWaitInterceptorEx extends ExecuteAndWaitInterceptor {

	private static final long serialVersionUID = -4456744368791451159L;
	  /**
     * {@inheritDoc}
     */
    @Override
    protected BackgroundProcess getNewBackgroundProcess(String arg0, ActionInvocation arg1, int arg2) {
    	ActionInvocationEx aActionInvocationEx = new ActionInvocationEx(arg1,ActionContext.getContext());
        return new BackgroundProcessEx(arg0, aActionInvocationEx, arg2);
    }

    private class BackgroundProcessEx extends BackgroundProcess {
		public BackgroundProcessEx(String threadName,
				ActionInvocation invocation, int threadPriority) {
			super(threadName, invocation, threadPriority);
		}

		private static final long serialVersionUID = -9069896828432838638L;
        /**
         * {@inheritDoc}
         * @throws InterruptedException 
         */
        @Override
        protected void beforeInvocation() throws InterruptedException {
        	ActionInvocationEx aActionInvocationEx = (ActionInvocationEx)this.invocation;
        	ActionContext context = aActionInvocationEx.getContext();
            ActionContext.setContext(context);
        }

        /**
         * {@inheritDoc}
         */
       @Override
        protected void afterInvocation() {
            ActionContext.setContext(null);
        }

    }
}
import com.opensymphony.xwork2.ActionContext;
import com.opensymphony.xwork2.ActionEventListener;
import com.opensymphony.xwork2.ActionInvocation;
import com.opensymphony.xwork2.ActionProxy;
import com.opensymphony.xwork2.Result;
import com.opensymphony.xwork2.interceptor.PreResultListener;
import com.opensymphony.xwork2.util.ValueStack;

public class ActionInvocationEx implements ActionInvocation {

	/**
	 * 
	 */
	private static final long serialVersionUID = 1L;

	private final ActionInvocation mActionInvocation;

	private final ActionContext context;

	public ActionInvocationEx(ActionInvocation aActionInvocation,ActionContext aContext)
	{
		mActionInvocation = aActionInvocation;
		context = aContext;
	}

	public Object getAction() {
		return mActionInvocation.getAction();
	}

	public boolean isExecuted() {
		return mActionInvocation.isExecuted();
	}

	public ActionContext getInvocationContext() {
		return mActionInvocation.getInvocationContext();
	}

	public ActionProxy getProxy() {
		return mActionInvocation.getProxy();
	}

	public Result getResult() throws Exception {
		return mActionInvocation.getResult();
	}

	public String getResultCode() {
		return mActionInvocation.getResultCode();
	}

	public void setResultCode(String resultCode) {
		mActionInvocation.setResultCode(resultCode);
	}

	public ValueStack getStack() {
		return mActionInvocation.getStack();
	}

	public void addPreResultListener(PreResultListener listener) {
		mActionInvocation.addPreResultListener(listener);
	}

	public String invoke() throws Exception {
		return mActionInvocation.invoke();
	}

	public String invokeActionOnly() throws Exception {
		return mActionInvocation.invokeActionOnly();
	}

	public void setActionEventListener(ActionEventListener listener) {
		mActionInvocation.setActionEventListener(listener);
	}

	public void init(ActionProxy proxy) {
		mActionInvocation.init(proxy);
	}

	public ActionInvocation serialize() {
		return mActionInvocation.serialize();
	}

	public ActionInvocation deserialize(ActionContext actionContext) {
		return mActionInvocation.deserialize(actionContext);
	}

	/**
	 * @return the context
	 */
	public ActionContext getContext() {
		return context;
	}
}

写完之后,在struts.xml 中配置一下拦截器,覆盖掉默认的拦截器,下面是我的配置例子

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE struts PUBLIC
    "-//Apache Software Foundation//DTD Struts Configuration 2.0//EN"
    "http://struts.apache.org/dtds/struts-2.0.dtd">

<struts>
	<constant name="struts.i18n.encoding" value="utf-8"></constant>
	<constant name="struts.multipart.maxSize" value="20971520"/>
    <constant name="struts.devMode" value="true" />

	<package name="FaeSupport" namespace="/" extends="struts-default">
	    <!-- 安装自定义的 execAndWait 拦截器,覆盖掉系统默认的,目的是解决在 execAndWait 出现的时候,Action调用 getText 报java.lang.NullPointerException-->
	    <interceptors > 
            <interceptor name="execAndWait" class="com.FaeSupport.Interceptor.ExecAndWaitInterceptorEx"/> 
        </interceptors > 
		<action name="faesupport" class="com.FaeSupport.Action.FaeSelAction" method="SelParametersCheck">
        <interceptor-ref name="defaultStack"/>
        <interceptor-ref name="execAndWait">
            <param name="delay">1500</param>
        </interceptor-ref>
        <result name="wait">/build_wait.jsp</result>			
		<result name="success">/build_wait.jsp</result>
		</action>
	</package>

</struts>

发布者