struts2 跳转至404 页面的解决方案

对于使用了struts2的工程,以下几种情况,我觉得需要跳转至 404 页面告诉用户:

① 在地址栏里,直接输入一个不存在的jsp页面

     比如, http://xxx:port/webapp/test.jsp, 其中test.jsp根本就不存在

PS:下面两种情况是以使用“convention plugin”为前提的

② 在地址栏里,直接输入一个不存在的action

     比如, http://xxx:port/webapp/test!method1,其中,TestAction根本就不存在

③ 在地址栏里,输入了的action虽然存在,但是指定的方法,在action中却不存在

     比如,http://xxx:port/webapp/test!method2,其中TestAction存在,但是method2却不存在于TestAction中

 

下面对以上各种情况进行说明解决方案。

对于第①种,可以直接在web.xml中进行如下配置: 

<error-page>
    <error-code>404</error-code>
    <location>/jsp/error/error_forward.jsp?code=400</location>
</error-page>

上面的location对应的页面有如下限制: 

(1) 必须以“/”开头,这个是API中明确说了的 

The location element contains the location of the resource in the web application relative to the root of the web application. The value of the location must have a leading `/'.

 (2) jsp页面中不能使用struts2的tag,否则会报错: 

The Struts dispatcher cannot be found.  This is usually caused by using Struts tags without the associated filter. Struts tags are only usable when the request has passed through its servlet filter, which initializes the Struts dispatcher needed for this tag.

(3)无法指定一个action作为location的值。如果你指定了一个action,那么当出现404出错时,画面将会是一片空白。

 

有许有人会问,我上面那个地址为什么后面要跟 “?code=400”。

那么先来看一下“error_forward.jsp”的实现吧:

 

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
    
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<script type="text/javascript">
    window.onload = function() {
        var code = '<%=request.getParameter("code")%>';
        switch(code) {
            case '404' : 
                window.top.location.replace("<%=request.getContextPath()%>/forward!notFound");
                break;
            // other case can be listed here
            default:
                window.top.location.replace("<%=request.getContextPath()%>/forward!notFound");
                break;
        }
    };
</script>
</head>

<body>
</body>
</html>

从上面的代码来看,我们可以通过code来决定跳转至哪种错误页面,404就跳转到404的错误页面,500就跳转至500的错误页面,等等等等。

 

可能还会有人会说,那为什么不直接在 web.xml 中直接配置要跳转的页面,而是还要再中间这么一个跳转过程呢。

哎,说来苦逼啊。因为错误页面要支持国际化,即英文和中文情况下,错误页面里的内容是不一样的。如果直接在 web.xml 中配置,就无法使用struts2的tag来实现国际化(原因上面说了,请参照第(2)点)。

好了,到此,第①种情况解决完了了。

 

下面说第②种

其实,使用第①种的解决方案就可以解决这第②种的问题的,但是控制台会打出异常信息: 

http-bio-7070-exec-9 2015-07-16 10:46:12,212 WARN [org.apache.struts2.dispatcher.Dispatcher : 68] - <Could not find action or result: /test-fs/forward11!notFound>
There is no Action mapped for namespace / and action name forward11. - [unknown location]
	at com.opensymphony.xwork2.DefaultActionProxy.prepare(DefaultActionProxy.java:185)
	at com.opensymphony.xwork2.DefaultActionProxyFactory.createActionProxy(DefaultActionProxyFactory.java:70)
	at org.apache.struts2.rest.RestActionProxyFactory.createActionProxy(RestActionProxyFactory.java:53)
	at org.apache.struts2.dispatcher.Dispatcher.serviceAction(Dispatcher.java:554)
	at org.apache.struts2.dispatcher.ng.ExecuteOperations.executeAction(ExecuteOperations.java:81)
	at org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter.doFilter(StrutsPrepareAndExecuteFilter.java:99)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.jasig.cas.client.util.AssertionThreadLocalFilter.doFilter(AssertionThreadLocalFilter.java:50)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
	at org.jasig.cas.client.util.HttpServletRequestWrapperFilter.doFilter(HttpServletRequestWrapperFilter.java:70)
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:243)
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:210)
。。。。。。

所以,如果只想让页面跳转至404页面,而控制台不要打印上面这种异常信息的话,可以在 struts.xml 中进行如下配置: 

<package name="your-package" extends="struts-default">
    <default-action-ref name="notFound" />
    <action name="notFound" class="com.test.test.action.ForwardAction" method="notFound">
        <result name="notFound">/jsp/error/404.jsp</result>
    </action>
</package>

需要提醒的是,不要给上面的package指定namespace。另外,action的 result 一定要声明。除非在global-results中刚好有一个result 与你的“notFound()”这个方法所要跳转的result的名字 是一样的。 

上面的配置的意思就是,在package下配置一个 default action,这个当输入的action找不到的时候,就会使用此action来进行处理。

以我上面的配置来说,当一个action找不到时候,就会调用 com.test.test.action.ForwardAction 中的 notFound() 方法来对应。这个方法的实现如下: 

package com.test.test.action;

import org.apache.log4j.Logger;
import org.apache.struts2.convention.annotation.Result;
import org.apache.struts2.convention.annotation.Results;

import com.test.test.base.BaseAction;

@Results({ @Result(name = "notFound", location = "/jsp/error/404.jsp"))
public class ForwardAction extends BaseAction {
	/**
	 * forward to the 404 page
	 * 
	 * @return the logic view
	 */
	public String notFound() {
		addActionMessage(getText("page.404.tip"));
		return "notFound";
	}
}

 需要注意的是,上面的Action中,虽然声明了一个  @Result(name = "notFound", location = "/jsp/error/404.jsp")。但是这个Result仅仅服务于直接调用 ForwardAction中的 notFound()方法的情况。如果你是因为输入了一个不存在的action,通过default action来调用了ForwarAction中的这noFound()方法时,这个result的配置是不会起作用的。这也是我上面说的“另外,action的 result 一定要声明”的原因。

好了,到此,第②种情况解决完了了。

 

下面说第③种

这种情况是调查时间最长的。

首先,struts2中,执行action的类是: com.opensymphony.xwork2.DefaultActionInvocation中的invokeAction()方法。我们来看下: 

protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
    String methodName = proxy.getMethod();

    if (LOG.isDebugEnabled()) {
        LOG.debug("Executing action method = #0", methodName);
    }

    String timerKey = "invokeAction: " + proxy.getActionName();
    try {
        UtilTimerStack.push(timerKey);

        Object methodResult;
        try {
            methodResult = ognlUtil.getValue(methodName + "()", getStack().getContext(), action);
        } catch (OgnlException e) {
            // hmm -- OK, try doXxx instead
            try {
                String altMethodName = "do" + methodName.substring(0, 1).toUpperCase() + methodName.substring(1)  + "()";
                methodResult = ognlUtil.getValue(altMethodName, ActionContext.getContext().getContextMap(), action);
            } catch (OgnlException e1) {
                // well, give the unknown handler a shot
                if (unknownHandlerManager.hasUnknownHandlers()) {
                    try {
                        methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
                    } catch (NoSuchMethodException e2) {
                        // throw the original one
                        throw e;
                    }
                } else {
                    throw e;
                }
            }
        }
        return saveResult(actionConfig, methodResult);
    } catch (NoSuchPropertyException e) {
        throw new IllegalArgumentException("The " + methodName + "() is not defined in action " + getAction().getClass() + "");
    } catch (MethodFailedException e) {
        // We try to return the source exception.
        Throwable t = e.getCause();

        if (actionEventListener != null) {
            String result = actionEventListener.handleException(t, getStack());
            if (result != null) {
                return result;
            }
        }
        if (t instanceof Exception) {
            throw (Exception) t;
        } else {
            throw e;
        }
    } finally {
        UtilTimerStack.pop(timerKey);
    }
}

从代码来看,它是通过“ognlUtil.getValue()”去执行指定action中的指定方法的,若指定的方法不存在时,它会尝试着在在方法名前加上  “do” 构成一个新的方法名,然后再去执行这个新的方法,如果这个新的方法名还是不存在,那么,它就会调用所有配置好的 UnknownHandler 去处理这种情况。 

关键就是这一点。 UnknownHandler 怎么配置的?

在struts2-convention-plugin.jar中有一份struts-plugin.xml文件,里面有一个配置: 

<bean type="com.opensymphony.xwork2.UnknownHandler" name="convention" class="org.apache.struts2.convention.ConventionUnknownHandler"/>

 这个就是 struts2 convention plugin 默认使用的 UnknownHandler,它共实现了两个方法:handleUnknownAction、handleUnknownResult 

但是却没有实现  handleUnknownActionMethod, 而这个方法却是我们第②情况所需要的。

哎,即然convention plugin没实现它,那我们就自己来实现呗。

想法很简单,但是我花了2个多小时在网上查来查去,就是没查到个sample出来。哎,没办法,我就只能简单的让这个方法抛出一个 NoSuchMethodException 异常了。(如果有哪位兄弟知道如何实装这个方法的,请指导一下)。具体实现如下: 

public class CustomUnknownHandler implements UnknownHandler {

	/**
	 * Not used
	 * 
	 * @see org.apache.struts2.convention.ConventionUnknownHandler#handleUnknownAction
	 */
	@Override
	public ActionConfig handleUnknownAction(String namespace, String actionName) throws XWorkException {
		return null;
	}

	/**
	 * Not used
	 * 
	 * @see org.apache.struts2.convention.ConventionUnknownHandler#handleUnknownResult
	 */
	@Override
	public Result handleUnknownResult(ActionContext actionContext, String actionName, ActionConfig actionConfig,
			String resultCode) throws XWorkException {
		return null;
	}

	/**
	 * jump to the 404 page
	 */
	@Override
	public Object handleUnknownActionMethod(Object action, String methodName) throws NoSuchMethodException {
		throw new NoSuchMethodException(action.getClass().getName() + " does not have the mehod named '" + methodName
				+ "'.");
	}
}

实现了这个方法还只是第一步。接下来还要通过配置来使用它,打开你的 struts.xml,添加如下配置: 

    <package name="test-package" extends="rest-default">        
        <global-results>
            <result name="notFoundRes" type="chain">
                <param name="actionName">forward</param>
                <param name="method">notFound</param>
            </result>
        </global-results>
        
        <global-exception-mappings>
            <exception-mapping result="notFoundRes" exception="java.lang.NoSuchMethodException"></exception-mapping>
        </global-exception-mappings>

    </package>
    
    <!-- this is the handler of convention plugin. This is required by the convention plugin. -->
    <bean name="conventionUnknownHandler" class="org.apache.struts2.convention.ConventionUnknownHandler" type="com.opensymphony.xwork2.UnknownHandler" />
    <!-- this is the custom handler -->
    <bean name="myUnknownHandler" class="com.test.test.handler.CustomUnknownHandler" type="com.opensymphony.xwork2.UnknownHandler" />
    <unknown-handler-stack>
        <unknown-handler-ref name="conventionUnknownHandler"></unknown-handler-ref>
        <unknown-handler-ref name="myUnknownHandler"></unknown-handler-ref>
    </unknown-handler-stack>

首先,声明了两个bean,一个是convetion自己实现的handler,一个是我自己实现的handler,然后在,在unknown-handler-stack标签中引用它们,这样一来,在上面的“methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);”处,就会使用这里所引用的handler去处理未知情况。当然, handleUnknownAction、handleUnknownResult还是使用的是convention plugin中的实现,handleUnknownActionMethod使用的是我的实现。

 

最后,因为我的实现里,只是简单的返回 一个 NoSuchMethodException,所以,还需要配置一个global-exception-mappings 来捕获它。在上面的配置里也写了出来了。

另外,因为使用了 “unknown-handler-stack”,所以 struts.xml的必须使用 struts-2.1.dtd 或者 更高版本。

好了,至此,第③种情况解决完了。

 

最后,我给出我的struts.xml。见附件。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值