struct2源码解读之拦截器

   上篇博文介绍了拦截器的工作原理:通过invocation.invoke()方法迭代拦截器链。那么拦截器链里面的拦截器有什作用呢?下面为大家一一探讨。我们先来看下默认拦截器链有哪些拦截器。

   我们找到struct-default.xml文件

     <interceptor-stack name="defaultStack">
                <interceptor-ref name="exception"/>
                <interceptor-ref name="alias"/>
                <interceptor-ref name="servletConfig"/>
                <interceptor-ref name="i18n"/>
                <interceptor-ref name="prepare"/>
                <interceptor-ref name="chain"/>
                <interceptor-ref name="debugging"/>
                <interceptor-ref name="scopedModelDriven"/>
                <interceptor-ref name="modelDriven"/>
                <interceptor-ref name="fileUpload"/>
                <interceptor-ref name="checkbox"/>
                <interceptor-ref name="multiselect"/>
                <interceptor-ref name="staticParams"/>
                <interceptor-ref name="actionMappingParams"/>
                <interceptor-ref name="params">
                  <param name="excludeParams">dojo\..*,^struts\..*</param>
                </interceptor-ref>
                <interceptor-ref name="conversionError"/>
                <interceptor-ref name="validation">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
                <interceptor-ref name="workflow">
                    <param name="excludeMethods">input,back,cancel,browse</param>
                </interceptor-ref>
     </interceptor-stack>


 一、exception-声明式异常处理

   1.1.应用

第一个拦截器"exception"是用来声明式异常处理的。

如:我们希望在执行action方法时,如果发生异常,则跳到指定的页面,则我们可以在action中配置

<action name="user_*" class="userAction" method="{1}">
	<result name="toLoginUI">/WEB-INF/jsp/forward/index/Login.jsp</result>
	<exception-mapping result="error" exception=""></exception-mapping>
	<result name="error">/WEB-INF/jsp/forward/index/error.jsp</result>
</action>

  当执行userAction类并发生异常时(我们可以在exception属性设置是什么样的异常),就会调到result(result属性)指定的result(result标签)。如果全部action的同类异常都想跳转到同一个页面,则可设置全局异常处理

<global-exception-mappings>
	<exception-mapping result="" exception=""></exception-mapping>
</global-exception-mappings>

<global-results>
	<result name="error">/WEB-INF/jsp/forward/index/error.jsp</result>
</global-results>

   1.2.实现原理:

   我们来看看这个exception的拦截器

<interceptor name="exception" class="com.opensymphony.xwork2.interceptor.ExceptionMappingInterceptor"/>

   我们找到这个类的intercept方法,因为action请求是在invocation.invoke()方法迭代执行的,这里用到了一个try-catch,当try里面的代码执行发生异常,则找到exceptionMapping,然后找到exceptionMapping里面的result,如果这个result存在,就返回这个result值,并把exceptionHolder这个对象压入值栈,后面的 的就是执行result了。

  public String intercept(ActionInvocation invocation) throws Exception {
        String result;
        try {
             //执行action请求
            result = invocation.invoke();
        } catch (Exception e) {
            if (isLogEnabled()) {
                handleLogging(e);
            }
            //找到actionConfig里面的exceptionMapping
            List<ExceptionMappingConfig> exceptionMappings = invocation.getProxy().getConfig().getExceptionMappings();
             //找到exceptionMapping里面的result
            String mappedResult = this.findResultFromExceptions(exceptionMappings, e);
            if (mappedResult != null) {
                result = mappedResult;
                //如果找到了,就把exceptionHolder对象压入值栈
                publishException(invocation, new ExceptionHolder(e));
            } else {
                throw e;
            }
        }
        return result;
    }

    所以在xml中配置exceptionMapping就能达到声明式异常管理。


二、servletConfig-完成request注入

  2.1.应用

   servletConfig拦截器是用来获取request\session\application等对象的。在struct2中获取request、session、application对象,只需继承RequestAware,SessionAware接口即可

public abstract class BaseAction<T> extends ActionSupport implements ModelDriven<T>,RequestAware,SessionAware{
	
	protected Map<String, Object> request;
	protected Map<String, Object> session;
	//get()和set()方法略
}

  这样就能直接在子类中用Map<string,object> request去操作request等对象了

public void exit() throws Exception {
	//把user从session中移除
	session.remove("user");

}

2.2.实现原理

    为什么实现了RequestAware,SessionAware等接口就能获取相应的对象了呢?我们也来看看这个拦截器的intercept()方法

 <interceptor name="servletConfig" class="org.apache.struts2.interceptor.ServletConfigInterceptor"/>

    从这个拦截器中可以看出,struct2会先对这些action判断是否是这些接口的实例,如果是话就用set()方法,把context里面的request注入到map requset变量中。所以通过实现这些接口,就可以获得相应的对象。

    public String intercept(ActionInvocation invocation) throws Exception {
        final Object action = invocation.getAction();
        final ActionContext context = invocation.getInvocationContext();
         //ServletRequest
        if (action instanceof ServletRequestAware) {
            HttpServletRequest request = (HttpServletRequest) context.get(HTTP_REQUEST);
            ((ServletRequestAware) action).setServletRequest(request);
        }
        //Application
        if (action instanceof ApplicationAware) {
            ((ApplicationAware) action).setApplication(context.getApplication());
        }
        //Session
        if (action instanceof SessionAware) {
            ((SessionAware) action).setSession(context.getSession());
        }
        //Request
        if (action instanceof RequestAware) {
            ((RequestAware) action).setRequest((Map) context.get("request"));
        }
        return invocation.invoke();
    }

   从这个拦截器可以看到,我会基本可以获取到所有的http对象,比如说request,session,application,servletRequest,Parameter等等。


三、fileupload-文件上传

  3.1.应用

   fileupload这个拦截器是用来文件上传用的。当我们需要上传文件的时候,在action定义几个变量,就能获取上传文件的全部信息。

        private File upload;   //上传的文件
	private String uploadContentType; //上传文件类型
	protected String uploadFileName;// 上传文件名
	 //这个uoload是<input name="upload">相一致
	//get()和set()方法略。

  3.2.实现原理  

   为什么定义了这些变量就能获取文件了呢?我们来看看这个拦截器的实现

<interceptor name="fileUpload" class="org.apache.struts2.interceptor.FileUploadInterceptor"/>

    在这个拦截器的intercept()方法中,这个方法很多内容,我就截取几段关键代码

 public String intercept(ActionInvocation invocation) throws Exception {
  
        if (!(request instanceof MultiPartRequestWrapper)) {
            //如果不是文件类型,下一个拦截器
            return invocation.invoke();
        }

         //文件类型的request
        MultiPartRequestWrapper multiWrapper = (MultiPartRequestWrapper) request;
      
        Enumeration fileParameterNames = multiWrapper.getFileParameterNames();
        while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {
 

            if (isNonEmpty(contentType)) {
                // filename就是input的value值
                String[] fileName = multiWrapper.getFileNames(inputName);

                if (isNonEmpty(fileName)) {
                    // get a File object for the uploaded File
                    File[] files = multiWrapper.getFiles(inputName);
               if (files != null && files.length > 0) {
                     //这个inputName是input这个按钮里面的属性,如在<input value="upload">则fileName为uploadFileName,如果<input value="te">,则为teFileName
                        String contentTypeName = inputName + "ContentType";
                        String fileNameName = inputName + "FileName";

                   if (!acceptedFiles.isEmpty()) {
                       Map<String, Object> params = ac.getParameters();
           //把这三个属性放到params,然后就可以根据get()取到
            params.put(inputName, acceptedFiles.toArray(new File[acceptedFiles.size()]));
            params.put(contentTypeName, acceptedContentTypes.toArray(new String[acceptedContentTypes.size()]));
            params.put(fileNameName, acceptedFileNames.toArray(new String[acceptedFileNames.size()]));
                        }
                }
        }
        // 下一个拦截器
        String result = invocation.invoke();
        fileParameterNames = multiWrapper.getFileParameterNames();
        while (fileParameterNames != null && fileParameterNames.hasMoreElements()) {

        }
        return result;
    }