struts2 運行過程及集成原理

這個過程是從客戶端發起訪問,到服務端響應,並返回結果的整個過程。
以struts-2.5.5作爲研究對象

第一部分 從StrutsPrepareAndExecuteFilter開始

 StrutsPrepareAndExecuteFilter作爲struts2的一個核心過濾器(Filter),接受所有客戶端的請求,經過一系列的處理纔到我們的Action。對於Filter,他首先執行init方法初始化,因此我們先看看StrutsPrepareAndExecuteFilter的初始化做了什麼。

InitOperations
Dispatcher
Container
ActionMapping
ActionProxy
Interceptor

StrutsPrepareAndExecuteFilter.init 

public void init(FilterConfig filterConfig) throws ServletException {
        //初始化InitOperations 
        InitOperations  init = new InitOperations();
        Dispatcher dispatcher = null;
        try {
             //獲取web.xml的Filter內的init-param參數配置
            FilterHostConfig config = new FilterHostConfig(filterConfig);
            init.initLogging(config);
            dispatcher = init.initDispatcher(config);
            init.initStaticContentLoader(config, dispatcher);
            //當前請求運行之前的包含初始化的操作--僅產生PrepareOperations對象
            prepare = new PrepareOperations(dispatcher);
            //所有過濾器所包含運行操作--僅產生ExecuteOperations對象
            execute = new ExecuteOperations(dispatcher);
            this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);

            postInit(dispatcher, filterConfig);
        } finally {
            if (dispatcher != null) {
                dispatcher.cleanUpAfterInit();
            }
            init.cleanup();
        }
    }
 接着我們看具體dispatcher 的創建過程,createDispatcher方法。 
 InitOperations.initDispatcher 
  public Dispatcher initDispatcher( HostConfig filterConfig ) {
    **Dispatcher** dispatcher = createDispatcher(filterConfig);
    dispatcher.init();
    return dispatcher;
  }
  InitOperations.createDispatcher
  private Dispatcher createDispatcher( HostConfig filterConfig ) {
    Map<String, String> params = new HashMap<>();
     //遍歷filterConfig把init-param全部拿出來,就是那些在web.xml配置在Filter標籤裏面的初始化參數
    for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) {
        String name = (String) e.next();
        String value = filterConfig.getInitParameter(name);
        params.put(name, value);
    }
     //構造dispatcher
    return new Dispatcher(filterConfig.getServletContext(), params);
}
Dispatcher.init

  //Load configurations, including both XML and zero-configuration strategies,
// and update optional settings, including whether to reload configurations and resource files.

public void init() {

    if (configurationManager == null) {
        configurationManager =      createConfigurationManager(DefaultBeanSelectionProvider.DEFAULT_BEAN_NAME);
    }

    try {
        init_FileManager();
        init_DefaultProperties(); //加入默認配置供給器,解析org/apache/struts2/default.properties
        init_TraditionalXmlConfigurations(); // [2] struts-default.xml 
        init_LegacyStrutsProperties(); // [3] struts-plugin.xml
        init_CustomConfigurationProviders(); // [5] struts.properties
        init_FilterInitParameters() ; // [6] default.properties
        init_AliasStandardObjects() ; // [7] struts.custom.properties
           //  預加載配置,構建容器
           //  構造出容器,因此容器是擁有這些常量和配置裏面指定的bean的實例化的能力。說到容器的實例化的能力,其實實例化的過程有一部分是依賴於容器的依賴注入功能的,容器實例化類的時候是通過構造函數注入(ConstructorInjector)方式,通過反射獲取指定類的構造函數,如果有無參數的構造函數就直接使用無參數構造函數完成初始化,如果只有有參數的構造函數,就分析參數上面有沒有注入的標誌,如果有就通過容器的參數注入(ParameterInjector)方式注入依賴值給這個參數,然後通過這些有值的參數調用這個構造函數,達到實例化的目地,實例化之後還需要對其成員進行注入依賴,容器對成員注入是使用成員注入(MemberInjector,分別有MethodInjector和FieldInjector,針對方法還是屬性判斷使用哪一種方式)的方式,對靜態成員還有靜態注入(StaticInjector,其實質是通過MemberInjector實現的,只不過不需要穿入實例對象)方式;對於容器提供的依賴注入能力,其前提條件就是使用注入的類要配置在bean裏面,也就是說類要交給容器管理,並且在使用注入依賴的屬性、參數、方法要使用@Inject註解去指明,否則沒有辦法注入,或者注入了null,或者出現異常。
         然後通過container.getInstance(ObjectFactory.class) , 創建出我們的ObjectFactory,使用默認工廠對象創建這個ObjectFactory的實例,根據之前的介紹,可以看到struts2的struts.objectFactory被註釋了,並沒有指定,意味着是使用struts作爲name的ObjectFactory的bean,就是使用org.apache.struts2.impl.StrutsObjectFactory作爲真正的ObjectFactory實現。 
        我們之後的Action、Result、Interceptor等等一系列與我們密切相關的對象都是通過ObjectFactory創建的,它可以說是struts的心臟。**ObjectFactory實例化對象的默認方式不是使用容器的,而是通過一般的Class.newIntance()方法**,而注入纔是藉助容器的inject(Container.inject)方法給實例化後的對象注入依賴值,對於這樣的操作,一個解析就是避免影響核心類的性能,如果把所有的Action都交給容器來管理,會造成容器過於龐大,因爲有時候Action可能會是成千上萬的,這樣會拖慢容器的執行;另外一個原因,因爲Action、Result、Interceptor等都是需要多例的,而非單例,方便起見就直接用ObjectFactory,通過Class.newIntance()創建一個實例好了;估計還有一個方面想就是爲了擴展,因爲如果都交給容器,其他集成框架無法獲取到struts2的容器的,無法操作他,所以爲了擴展就使用了Class.newIntance()方法實例化。 

  **Container** container = init_PreloadConfiguration();
        container.inject(this);
        init_CheckWebLogicWorkaround(container);
          //觸發監聽器初始化事件
        if (!dispatcherListeners.isEmpty()) {
            for (DispatcherListener l : dispatcherListeners) {
                l.dispatcherInitialized(this);
            }
        }
        errorHandler.init(servletContext);

    } catch (Exception ex) {
        LOG.error("Dispatcher initialization failed", ex);
        throw new StrutsException(ex);
    }
}

Dispatcher.init_CustomConfigurationProviders
//加入自定義的供給器
private void init_CustomConfigurationProviders() {
    String configProvs = initParams.get("configProviders");
    if (configProvs != null) {
        String[] classes = configProvs.split("\\s*[,]\\s*");
        for (String cname : classes) {
            try {
                Class cls = ClassLoaderUtil.loadClass(cname, this.getClass());
                ConfigurationProvider prov = (ConfigurationProvider)cls.newInstance();
                if (prov instanceof ServletContextAwareConfigurationProvider) {
                      ((ServletContextAwareConfigurationProvider)prov).initWithContext(servletContext);
                }
                configurationManager.addContainerProvider(prov);
            } catch (InstantiationException e) {
                throw new ConfigurationException("Unable to instantiate provider: "+cname, e);
            } catch (IllegalAccessException e) {
                throw new ConfigurationException("Unable to access provider: "+cname, e);
            } catch (ClassNotFoundException e) {
                throw new ConfigurationException("Unable to locate provider class: "+cname, e);
            }
        }
    }
}
    從上面代碼可以看出struts2對配置文件的加載順序: 
           1、org/apache/struts2/default.properties 
           2、struts-default.xml 
           3、struts-plugin.xml(如果有) 
           4、struts.xml 
           5、struts.properties(如果有) 
           6、default.properties(如果有) 
           7、struts.custom.properties(如果有) 
        org/apache/struts2/default.properties是配置了struts2的常量的默認值,比如struts.i18n.encoding 、struts.action.extension、struts.i18n.reload等常量。 
        struts-default.xml裏面配置了struts2的核心bean,以及用constant配置的常量,比如ObjectFactory、ActionProxyFactory、ActionMapper等bean。 
        struts-plugin.xml是struts2的插件的配置文件,比如struts2-spring-plugin。 
        struts.xml是使用struts2時候基本配置,比如action、result、interceptor、constant等。 
        struts.properties、default.properties、struts.custom.properties是原來舊版本的一些配置。 
        注意:後面加載的配置文件可以覆蓋前面的配置文件的配置 
         從上面代碼可以知道,init_XXXX的方法是爲配置管理器收集配置供給器的信息,而最後調用init_PreloadConfiguration方法,構建出配置對象和容器,他們是struts2中非常重要的兩個對象,配置對象裏面包含了struts2的所有的配置信息,容器裏面包含了struts2中所有的對象的工廠對象。以後使用到的Action、攔截器(Interceptor)、Result等對象都是通過他們兩個協同創建出來的。 

         全部的配置都在這裏解析完成,並能夠組織起來,構成運行時的結構了
#第二部分:doFilter的工作過程           


  *StrutsPrepareAndExecuteFilter.doFilter*

  public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException,  ServletException {

    HttpServletRequest request = (HttpServletRequest) req;
    HttpServletResponse response = (HttpServletResponse) res;
try {
        String uri = RequestUtils.getUri(request);
        if (excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
            LOG.trace("Request {} is excluded from handling by Struts, passing request to other filters", uri);
            chain.doFilter(request, response);
        } else {
            LOG.trace("Checking if {} is a static resource", uri);
               *protected ExecuteOperations execute;*
              boolean handled = execute.executeStaticResourceRequest(request, response);
            if (!handled) {
                LOG.trace("Assuming uri {} as a normal action", uri);
                prepare.setEncodingAndLocale(request, response);
                prepare.createActionContext(request, response);
                prepare.assignDispatcherToThread();
                request = prepare.wrapRequest(request);
                `**ActionMapping**` mapping = prepare.findActionMapping(request, response, true);
                if (mapping == null) {
                    LOG.trace("Cannot find mapping for {}, passing to other filters", uri);
                    chain.doFilter(request, response);
                } else {
                    LOG.trace("Found mapping {} for {}", mapping, uri);
                    execute.executeAction(request, response, mapping);
                }
            }
        }
    } finally {
        prepare.cleanupRequest(request);
    }
}
*ActionMapping*
public ActionMapping(String name, String namespace, String method, Map<String, Object> params) {
    this.name = name;
    this.namespace = namespace;
    this.method = method;
    this.params = params;
}


   *// Tries to execute a request for a static resource*
     public boolean executeStaticResourceRequest(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
    // there is no action in this request, should we look for a static resource?
    String resourcePath = RequestUtils.getServletPath(request);

    if ("".equals(resourcePath) && null != request.getPathInfo()) {
        resourcePath = request.getPathInfo();
    }

    StaticContentLoader staticResourceLoader = dispatcher.getContainer().getInstance(StaticContentLoader.class);
    if (staticResourceLoader.canHandle(resourcePath)) {
        staticResourceLoader.findStaticResource(resourcePath, request, response);
        // The framework did its job here
        return true;

    } else {
        // this is a normal request, let it pass through
        return false;
    }
}
 *ExecuteOperations類*
public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
    dispatcher.serviceAction(request, response, mapping);
}
        *Dispatcher類*
        //Dispatcher.serviceAction是我們所有action的統一處理入口,進入這個方法,就是開始了action的處理了。Dispatcher.serviceAction主要是完成以下幾個工作, 
           1、創建Action對象,以代理對象的形式 
           2、執行mapping.execute或者開始調用攔截器(就是proxy.execute);如果執行了mapping.execute(執行Result返回客戶端),就停止下面操作了 
           3、通過反射調用Action的指定方法,並返回 
           4、執行Result返回客戶端,可以是頁面(視圖)跳轉或者其他操作 
        這些都是重點啊!我們先總體看看Dispatcher.serviceAction方法 

 public void serviceAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping)
        throws ServletException {

    Map<String, Object> extraContext = createContextMap(request, response, mapping);

    *// If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
     //重新創建ValueStack,這裏的ValueStack是我們後面的action用到的那個ValueStack*
    ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
    boolean nullStack = stack == null;
    if (nullStack) {
          //用指定的ValueStack來創建一個新的ActionContext,並設置爲本線程的ActionContext,
  //注意這裏已經不是之前的引導容器創建的ActionContext了,而是我們action使用的那個ActionContext,
  //引導容器的ActionContext已經在DefaultConfiguration.reloadContainer方法退出時候被清空了,
  //現在的ValueStack和ActionContext都是全新的。
        ActionContext ctx = ActionContext.getContext();
        if (ctx != null) {
            stack = ctx.getValueStack();
        }
    }
    if (stack != null) {
        extraContext.put(ActionContext.VALUE_STACK, valueStackFactory.createValueStack(stack));
    }

    String timerKey = "Handling request from Dispatcher";
    try {
        UtilTimerStack.push(timerKey);
        String namespace = mapping.getNamespace();
        String name = mapping.getName();
        String method = mapping.getMethod();
        //創建ActionProxy動態代理
        **ActionProxy** proxy = getContainer().getInstance(ActionProxyFactory.class).createActionProxy(
                namespace, name, method, extraContext, true, false);

        request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, proxy.getInvocation().getStack());

        // if the ActionMapping says to go straight to a result, do it!
        **if (mapping.getResult() != null) {
           //匹配到的action方法比如 login 
             如果是result.execute直接執行的情況,到這裏doFilter對一個請求的處理過程就執行完了
            Result result = mapping.getResult();
             result.execute(proxy.getInvocation());
        } else {
            //action中默認執行的方法
            //proxy.execute,我們action執行的過程,當然在action真正執行之前,當然還有攔截器的執行,然後action執行完後返回一個resultCode,再根據resultCode找到對應的Result,執行頁面(視圖)跳轉,返回客戶端之前當然還會生成視圖
            proxy.execute();
        }**

        // If there was a previous value stack then set it back onto the request
        if (!nullStack) {
             //設置值棧
             //再把修改過的ValueStack設置到request中,在Result之後返回視圖時候可以用得着這些參數,
             //因爲Action執行方法時候可能會改變了ValueStack的裏面的值
            request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
        }
    } catch (ConfigurationException e) {
        logConfigurationException(request, e);
        sendError(request, response, HttpServletResponse.SC_NOT_FOUND, e);
    } catch (Exception e) {
        e.printStackTrace();
        if (handleException || devMode) {
            sendError(request, response, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
        } else {
            throw new ServletException(e);
        }
    } finally {
        UtilTimerStack.pop(timerKey);
    }
}


       *DefaultActionInvocatio默認action代理類*
        public String invoke() throws Exception {
       String profileKey = "invoke: ";
       try {
          UtilTimerStack.push(profileKey);
          //防止多次執行action,保證action只執行一次
          if (executed) {
             throw new IllegalStateException("Action has already executed");
          }
          //通過之前的攔截器映射迭代器迭代取出攔截器映射對象
          if (interceptors.hasNext()) {
             //獲取攔截器映射對象
             final InterceptorMapping interceptor = (InterceptorMapping) interceptors.next();
             String interceptorMsg = "interceptor: " + interceptor.getName();
             UtilTimerStack.push(interceptorMsg);
             try {
                //通過映射對象獲取攔截器,執行**Interceptor攔截器**,注意,intercept參數是當前的action調用對象,
                //其實這裏面也調用DefaultActionInvocation.invoke,他們這樣是遞歸調用,通過這樣的方法遍歷完        interceptors
                resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
             } finally {
                UtilTimerStack.pop(interceptorMsg);
             }
          } else {
             //當遍歷完interceptors時候,執行正真的action操作,這裏是一個重點
             //返回的resultCode會用來匹配對應的Result
            resultCode = invokeActionOnly();
          }

          //action是否已經被調用,用來判斷是否第一次執行Result操作,這樣寫是因爲action執行完後,
          //攔截器會一個一個執行下面的操作,然後返回上一個調用的攔截器,就是按調用攔截器時候反過來的順序執行,
          //這是**遞歸的原理**,所以如果每個攔截器都執行Result那麼程序就會出錯,
          //所以當action執行完Result之後,就通過這種方式不允許攔截器執行Result
          if (!executed) { //第一次執行Result有效
             //執行相關的監聽器,Result執行前的監聽器
             if (preResultListeners != null) {
                for (Object preResultListener : preResultListeners) {
                   PreResultListener listener = (PreResultListener) preResultListener;
                   String _profileKey = "preResultListener: ";
                   try {
                      UtilTimerStack.push(_profileKey);
                      listener.beforeResult(this, resultCode);
                   } finally {
                      UtilTimerStack.pop(_profileKey);
                   }
                }
             }

             //如果支持執行Result,就執行,這個是構建action代理對象時候傳入去的參數,是true
             if (proxy.getExecuteResult()) {
                //執行我們的Result,這裏是另一個重點
                executeResult();
             }
             //標識已經被執行過
             executed = true;
          }
          //返回執行代碼
          return resultCode;
           } finally {
              UtilTimerStack.pop(profileKey);
           }
    }

  當攔截器執行完時候,執行action的地方,在上面我們看到了 resultCode = invokeActionOnly()

    *DefaultActionInvocation默認action代理類*
   public String invokeActionOnly() throws Exception {
    return invokeAction(getAction(), proxy.getConfig());
   }
    *DefaultActionInvocation默認action代理類*
    protected String invokeAction(Object action, ActionConfig actionConfig) throws Exception {
    String methodName = proxy.getMethod();

    LOG.debug("Executing action method = {}", methodName);

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

        Object methodResult;
        try {
            //獲取action,然後根據methodName的名稱,通過反射獲取這個方法對象,這個是沒有參數的方法
            methodResult = ognlUtil.callMethod(methodName + "()", getStack().getContext(), action);
        } catch (MethodFailedException e) {
            // if reason is missing method,  try checking UnknownHandlers
            if (e.getReason() instanceof NoSuchMethodException) {
                if (unknownHandlerManager.hasUnknownHandlers()) {
                    try {
                      //執行這個方法,設置action爲執行實例,大多數情況都是執行這個方法,返回值大多數情況也是String類型
                        methodResult = unknownHandlerManager.handleUnknownMethod(action, methodName);
                    } catch (NoSuchMethodException ignore) {
                        // throw the original one
                        throw e;
                    }
                } else {
                    // throw the original one
                    throw e;
                }
                // throw the original exception as UnknownHandlers weren't able to handle invocation as well
                if (methodResult == null) {
                    throw e;
                }
            } else {
                // exception isn't related to missing action method, throw it
                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);
    }
}
    從上面可以看出,這個就是我們的Action的方法執行的地方,然後根據返回值,如果這個值是Result類型,就直接用這個Result來執行會面的操作;如果是String類型,就是resultCode,就用這個resultCode來獲取Result,就是匹配result標籤裏面的name,這些配置早就儲存在result配置對象了,然後用獲取到的Result執行下面的操作,我們回到DefaultActionInvocation.invoke,看executeResult裏面的處理過程
       *DefaultActionInvocation默認action代理類*
    public String invoke() throws Exception {
    String profileKey = "invoke: ";
    try {
        UtilTimerStack.push(profileKey);

        if (executed) {
            throw new IllegalStateException("Action has already executed");
        }

        if (interceptors.hasNext()) {
            final InterceptorMapping interceptor = interceptors.next();
            String interceptorMsg = "interceptor: " + interceptor.getName();
            UtilTimerStack.push(interceptorMsg);
            try {
                resultCode = interceptor.getInterceptor().intercept(DefaultActionInvocation.this);
            } finally {
                UtilTimerStack.pop(interceptorMsg);
            }
        } else {
            resultCode = **invokeActionOnly**();
        }

        // this is needed because the result will be executed, then control will return to the Interceptor, which will
        // return above and flow through again
        if (!executed) {
            if (preResultListeners != null) {
                LOG.trace("Executing PreResultListeners for result [{}]", result);

                for (Object preResultListener : preResultListeners) {
                    PreResultListener listener = (PreResultListener) preResultListener;

                    String _profileKey = "preResultListener: ";
                    try {
                        UtilTimerStack.push(_profileKey);
                        listener.beforeResult(this, resultCode);
                    }
                    finally {
                        UtilTimerStack.pop(_profileKey);
                    }
                }
            }

            // now execute the result, if we're supposed to
            if (proxy.getExecuteResult()) {
                **executeResult();**
            }

            executed = true;
        }

        return resultCode;
    }
    finally {
        UtilTimerStack.pop(profileKey);
    }
}
    *DefaultActionInvocation默認action代理類*
   private void executeResult() throws Exception {
   //先創建一個Result
   result = createResult();

    String timerKey = "executeResult: " + getResultCode();
    try {
        UtilTimerStack.push(timerKey);
        if (result != null) {
            //如果創建成功
            result.execute(this);
        } else if (resultCode != null && !Action.NONE.equals(resultCode)) { //如果有返回值,並且Action不是沒有視圖的就拋出異常
            throw new ConfigurationException("No result defined for action " + getAction().getClass().getName() + " and result " + getResultCode(), proxy.getConfig());
        } else {  //沒有視圖,就是通過response直接寫回響應給客戶端哪些情況,比如json
            if (LOG.isDebugEnabled()) {
                LOG.debug("No result returned for action {} at {}", getAction().getClass().getName(), proxy.getConfig().getLocation());
            }
        }
    } finally {
        UtilTimerStack.pop(timerKey);
    }
}

“`

上面情況我們先看看createResult方法,我們假設返回的Result是一個org.apache.struts2.dispatcher.ServletDispatcherResult類型,就是默認類型,Action的默認頁面(視圖)跳轉方式,需要返回值,有視圖

當創建Result成功就可以執行了,前面說過假設是返回默認的Result ———— org.apache.struts2.dispatcher.ServletDispatcherResult,我們針對ServletDispatcherResult.execute來展開

result執行完了,跳轉到指定的頁面(視圖),然後正真生成好頁面,再返回給客戶端

好累呀

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章