探究Struts2運行機制:StrutsPrepareAndExecuteFilter 源碼剖析

            作者:niumd

            blog:http://ari.iteye.com

           一、概述

               Struts2的核心是一個Filter,Action可以脫離web容器,那么是什么讓http請求和action關聯在一起的,下面我們深入源碼來分析下Struts2是如何工作的。

          FilterDispatcher API 寫道
          Deprecated. Since Struts 2.1.3, use StrutsPrepareAndExecuteFilter instead or StrutsPrepareFilter and StrutsExecuteFilter if needing using the ActionContextCleanUp filter in addition to this one


               鑒于常規情況官方推薦使用StrutsPrepareAndExecuteFilter替代FilterDispatcher,我們此文將剖析StrutsPrepareAndExecuteFilter,其在工程中作為一個Filter配置在web.xml中,配置如下:

          <filter>
          <filter-name>struts2</filter-name>
          <filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
          </filter>
          <filter-mapping>
          <filter-name>struts2</filter-name>
          <url-pattern>/*</url-pattern>
          </filter-mapping>


          二、源碼屬性方法簡介

              下面我們研究下StrutsPrepareAndExecuteFilter源碼,類的主要信息如下:


          屬性摘要
          protected  List<Pattern> excludedPatterns 
                     
          protected  ExecuteOperations execute 
                     
          protected  PrepareOperations prepare 
                     


              StrutsPrepareAndExecuteFilter與普通的Filter并無區別,方法除繼承自Filter外,僅有一個回調方法,第三部分我們將按照Filter方法調用順序,由init—>doFilter—>destroy順序地分析源碼。

          方法摘要
           void destroy() 
                     繼承自Filter,用于資源釋放
           void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 
                     繼承自Filter,執行方法
           void init(FilterConfig filterConfig) 
                     繼承自Filter,初始化參數
          protected  void postInit(Dispatcher dispatcher, FilterConfig filterConfig) 
                    Callback for post initialization(一個空的方法,用于方法回調初始化)


          三、源碼剖析    


              1、init方法

                   init是Filter第一個運行的方法,我們看下struts2的核心Filter在調用init方法初始化時做哪些工作:

           public void init(FilterConfig filterConfig) throws ServletException {
          InitOperations init = new InitOperations();
          try {
          //封裝filterConfig,其中有個主要方法getInitParameterNames將參數名字以String格式存儲在List中
          FilterHostConfig config = new FilterHostConfig(filterConfig);
          // 初始化struts內部日志
          init.initLogging(config);
          //創建dispatcher ,并初始化,這部分下面我們重點分析,初始化時加載那些資源
          Dispatcher dispatcher = init.initDispatcher(config);
          init.initStaticContentLoader(config, dispatcher);
          //初始化類屬性:prepare 、execute
          prepare = new PrepareOperations(filterConfig.getServletContext(), dispatcher);
          execute = new ExecuteOperations(filterConfig.getServletContext(), dispatcher);
          this.excludedPatterns = init.buildExcludedPatternsList(dispatcher);
          //回調空的postInit方法
          postInit(dispatcher, filterConfig);
          } finally {
          init.cleanup();
          }
          }


             首先看下FilterHostConfig ,源碼如下:


          public class FilterHostConfig implements HostConfig {
          private FilterConfig config;
          /**
          *構造函數
          */
          public FilterHostConfig(FilterConfig config) {
          this.config = config;
          }
          /**
          *  根據init-param配置的param-name獲取param-value的值
          */
          public String getInitParameter(String key) {
          return config.getInitParameter(key);
          }
          /**
          *  返回初始化參數名的List
          */
          public Iterator<String> getInitParameterNames() {
          return MakeIterator.convert(config.getInitParameterNames());
          }
          public ServletContext getServletContext() {
          return config.getServletContext();
          }
          }

             只有短短的幾行代碼,getInitParameterNames是這個類的核心,將Filter初始化參數名稱有枚舉類型轉為Iterator。此類的主要作為是對filterConfig 封裝。



              重點來了,創建并初始化Dispatcher     

           public Dispatcher initDispatcher( HostConfig filterConfig ) {
          Dispatcher dispatcher = createDispatcher(filterConfig);
          dispatcher.init();
          return dispatcher;
          }

               創建Dispatcher,會讀取 filterConfig 中的配置信息,將配置信息解析出來,封裝成為一個Map,然后根絕servlet上下文和參數Map構造Dispatcher :

          private Dispatcher createDispatcher( HostConfig filterConfig ) {
          Map<String, String> params = new HashMap<String, String>();
          for ( Iterator e = filterConfig.getInitParameterNames(); e.hasNext(); ) {
          String name = (String) e.next();
          String value = filterConfig.getInitParameter(name);
          params.put(name, value);
          }
          return new Dispatcher(filterConfig.getServletContext(), params);
          }
          

            Dispatcher初始化,加載struts2的相關配置文件,將按照順序逐一加載:default.properties,struts-default.xml,struts-plugin.xml,struts.xml,……


          /**
          *初始化過程中依次加載如下配置文件
          */
          public void init() {
          if (configurationManager == null) {
          configurationManager = new ConfigurationManager(BeanSelectionProvider.DEFAULT_BEAN_NAME);
          }
          try {
          //加載org/apache/struts2/default.properties
          init_DefaultProperties(); // [1]
          //加載struts-default.xml,struts-plugin.xml,struts.xml
          init_TraditionalXmlConfigurations(); // [2]
          init_LegacyStrutsProperties(); // [3]
          //用戶自己實現的ConfigurationProviders類
          init_CustomConfigurationProviders(); // [5]
          //Filter的初始化參數
          init_FilterInitParameters() ; // [6]
          init_AliasStandardObjects() ; // [7]
          Container container = init_PreloadConfiguration();
          container.inject(this);
          init_CheckConfigurationReloading(container);
          init_CheckWebLogicWorkaround(container);
          if (!dispatcherListeners.isEmpty()) {
          for (DispatcherListener l : dispatcherListeners) {
          l.dispatcherInitialized(this);
          }
          }
          } catch (Exception ex) {
          if (LOG.isErrorEnabled())
          LOG.error("Dispatcher initialization failed", ex);
          throw new StrutsException(ex);
          }
          }


             初始化default.properties,具體的初始化操作在DefaultPropertiesProvider類中

            

           private void init_DefaultProperties() {
          configurationManager.addConfigurationProvider(new DefaultPropertiesProvider());
          }

              

             下面我們看下DefaultPropertiesProvider類源碼:


          public void register(ContainerBuilder builder, LocatableProperties props)
          throws ConfigurationException {
          Settings defaultSettings = null;
          try {
          defaultSettings = new PropertiesSettings("org/apache/struts2/default");
          } catch (Exception e) {
          throw new ConfigurationException("Could not find or error in org/apache/struts2/default.properties", e);
          }
          loadSettings(props, defaultSettings);
          }


             其他的我們再次省略,大家可以瀏覽下各個初始化操作都加載了那些文件


          3、doFilter方法

               doFilter是過濾器的執行方法,它攔截提交的HttpServletRequest請求,HttpServletResponse響應,作為strtus2的核心攔截器,在doFilter里面到底做了哪些工作,我們將逐行解讀其源碼,源碼如下:


              public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
          //父類向子類轉:強轉為http請求、響應
          HttpServletRequest request = (HttpServletRequest) req;
          HttpServletResponse response = (HttpServletResponse) res;
          try {
          //設置編碼和國際化
          prepare.setEncodingAndLocale(request, response);
          //創建Action上下文(重點)
          prepare.createActionContext(request, response);
          prepare.assignDispatcherToThread();
          if ( excludedPatterns != null && prepare.isUrlExcluded(request, excludedPatterns)) {
          chain.doFilter(request, response);
          } else {
          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);
          }
          }


              setEncodingAndLocale調用了dispatcher方法的prepare方法:


          /**
          * Sets the request encoding and locale on the response
          */
          public void setEncodingAndLocale(HttpServletRequest request, HttpServletResponse response) {
          dispatcher.prepare(request, response);
          }


             下面我們看下prepare方法,這個方法很簡單只是設置了encoding 、locale ,做的只是一些輔助的工作:

          public void prepare(HttpServletRequest request, HttpServletResponse response) {
          String encoding = null;
          if (defaultEncoding != null) {
          encoding = defaultEncoding;
          }
          Locale locale = null;
          if (defaultLocale != null) {
          locale = LocalizedTextUtil.localeFromString(defaultLocale, request.getLocale());
          }
          if (encoding != null) {
          try {
          request.setCharacterEncoding(encoding);
          } catch (Exception e) {
          LOG.error("Error setting character encoding to '" + encoding + "' - ignoring.", e);
          }
          }
          if (locale != null) {
          response.setLocale(locale);
          }
          if (paramsWorkaroundEnabled) {
          request.getParameter("foo"); // simply read any parameter (existing or not) to "prime" the request
          }
          }


             Action上下文創建(重點)

                 ActionContext是一個容器,這個容易主要存儲request、session、application、parameters等相關信息.ActionContext是一個線程的本地變量,這意味著不同的action之間不會共享ActionContext,所以也不用考慮線程安全問題。其實質是一個Map,key是標示request、session、……的字符串,值是其對應的對象:

          static ThreadLocal actionContext = new ThreadLocal();
          Map<String, Object> context;
          

           
             下面我們看下如何創建action上下文的,代碼如下:


          /**
          *創建Action上下文,初始化thread local
          */
          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;
          }
          //注意此處是從ThreadLocal中獲取此ActionContext變量
          ActionContext oldContext = ActionContext.getContext();
          if (oldContext != null) {
          // detected existing context, so we are probably in a forward
          ctx = new ActionContext(new HashMap<String, Object>(oldContext.getContextMap()));
          } else {
          ValueStack stack = dispatcher.getContainer().getInstance(ValueStackFactory.class).createValueStack();
          stack.getContext().putAll(dispatcher.createContextMap(request, response, null, servletContext));
          //stack.getContext()返回的是一個Map<String,Object>,根據此Map構造一個ActionContext
          ctx = new ActionContext(stack.getContext());
          }
          request.setAttribute(CLEANUP_RECURSION_COUNTER, counter);
          //將ActionContext存如ThreadLocal
          ActionContext.setContext(ctx);
          return ctx;
          }


              上面代碼中dispatcher.createContextMap,如何封裝相關參數:


          public Map<String,Object> createContextMap(HttpServletRequest request, HttpServletResponse response,
          ActionMapping mapping, ServletContext context) {
          // request map wrapping the http request objects
          Map requestMap = new RequestMap(request);
          // parameters map wrapping the http parameters.  ActionMapping parameters are now handled and applied separately
          Map params = new HashMap(request.getParameterMap());
          // session map wrapping the http session
          Map session = new SessionMap(request);
          // application map wrapping the ServletContext
          Map application = new ApplicationMap(context);
          //requestMap、params、session等Map封裝成為一個上下文Map,逐個調用了map.put(Map p).
          Map<String,Object> extraContext = createContextMap(requestMap, params, session, application, request, response, context);
          if (mapping != null) {
          extraContext.put(ServletActionContext.ACTION_MAPPING, mapping);
          }
          return extraContext;
          }


           我們簡單看下RequestMap,其他的省略。RequestMap類實現了抽象Map,故其本身是一個Map,主要方法實現:

          //map的get實現
          public Object get(Object key) {
          return request.getAttribute(key.toString());
          }
          //map的put實現
          public Object put(Object key, Object value) {
          Object oldValue = get(key);
          entries = null;
          request.setAttribute(key.toString(), value);
          return oldValue;
          }


             下面是源碼展示了如何執行Action控制器:


          public void executeAction(HttpServletRequest request, HttpServletResponse response, ActionMapping mapping) throws ServletException {
          dispatcher.serviceAction(request, response, servletContext, mapping);
          }
          public void serviceAction(HttpServletRequest request, HttpServletResponse response, ServletContext context,
          ActionMapping mapping) throws ServletException {
          //封裝執行的上下文環境,主要講相關信息存儲入map
          Map<String, Object> extraContext = createContextMap(request, response, mapping, context);
          // If there was a previous value stack, then create a new copy and pass it in to be used by the new Action
          ValueStack stack = (ValueStack) request.getAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY);
          boolean nullStack = stack == null;
          if (nullStack) {
          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();
          //獲取action配置的name屬性
          String name = mapping.getName();
          //獲取action配置的method屬性
          String method = mapping.getMethod();
          Configuration config = configurationManager.getConfiguration();
          //根據執行上下文參數,命名空間,名稱等創建用戶自定義Action的代理對象
          ActionProxy proxy = config.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!
          //執行execute方法,并轉向結果
          if (mapping.getResult() != null) {
          Result result = mapping.getResult();
          result.execute(proxy.getInvocation());
          } else {
          proxy.execute();
          }
          // If there was a previous value stack then set it back onto the request
          if (!nullStack) {
          request.setAttribute(ServletActionContext.STRUTS_VALUESTACK_KEY, stack);
          }
          } catch (ConfigurationException e) {
          // WW-2874 Only log error if in devMode
          if(devMode) {
          String reqStr = request.getRequestURI();
          if (request.getQueryString() != null) {
          reqStr = reqStr + "?" + request.getQueryString();
          }
          LOG.error("Could not find action or result\n" + reqStr, e);
          }
          else {
          LOG.warn("Could not find action or result", e);
          }
          sendError(request, response, context, HttpServletResponse.SC_NOT_FOUND, e);
          } catch (Exception e) {
          sendError(request, response, context, HttpServletResponse.SC_INTERNAL_SERVER_ERROR, e);
          } finally {
          UtilTimerStack.pop(timerKey);
          }
          }


             文中對如何解析Struts.xml,如何將URL與action映射匹配為分析,有需要的我后續補全,因為StrutsXmlConfigurationProvider繼承XmlConfigurationProvider,并在register方法回調父類的register,有興趣的可以深入閱讀下下XmlConfigurationProvider源碼:


           public void register(ContainerBuilder containerBuilder, LocatableProperties props) throws ConfigurationException {
          if (servletContext != null && !containerBuilder.contains(ServletContext.class)) {
          containerBuilder.factory(ServletContext.class, new Factory<ServletContext>() {
          public ServletContext create(Context context) throws Exception {
          return servletContext;
          }
          });
          }
          //調用父類的register,關鍵點所在
          super.register(containerBuilder, props);
          }



               struts2-core-2.2.1.jar包中struts-2.1.7.dtd對于Action的定義如下:

          <!ELEMENT action (param|result|interceptor-ref|exception-mapping)*>
          <!ATTLIST action
          name CDATA #REQUIRED
          class CDATA #IMPLIED
          method CDATA #IMPLIED
          converter CDATA #IMPLIED
          >

              從上述DTD中可見Action元素可以含有name 、class 、method 、converter 屬性。


             XmlConfigurationProvider解析struts.xml配置的Action元素:

             protected void addAction(Element actionElement, PackageConfig.Builder packageContext) throws ConfigurationException {
          String name = actionElement.getAttribute("name");
          String className = actionElement.getAttribute("class");
          String methodName = actionElement.getAttribute("method");
          Location location = DomHelper.getLocationObject(actionElement);
          if (location == null) {
          LOG.warn("location null for " + className);
          }
          //methodName should be null if it's not set
          methodName = (methodName.trim().length() > 0) ? methodName.trim() : null;
          // if there isnt a class name specified for an <action/> then try to
          // use the default-class-ref from the <package/>
          if (StringUtils.isEmpty(className)) {
          // if there is a package default-class-ref use that, otherwise use action support
          /* if (StringUtils.isNotEmpty(packageContext.getDefaultClassRef())) {
          className = packageContext.getDefaultClassRef();
          } else {
          className = ActionSupport.class.getName();
          }*/
          } else {
          if (!verifyAction(className, name, location)) {
          if (LOG.isErrorEnabled())
          LOG.error("Unable to verify action [#0] with class [#1], from [#2]", name, className, location.toString());
          return;
          }
          }
          Map<String, ResultConfig> results;
          try {
          results = buildResults(actionElement, packageContext);
          } catch (ConfigurationException e) {
          throw new ConfigurationException("Error building results for action " + name + " in namespace " + packageContext.getNamespace(), e, actionElement);
          }
          List<InterceptorMapping> interceptorList = buildInterceptorList(actionElement, packageContext);
          List<ExceptionMappingConfig> exceptionMappings = buildExceptionMappings(actionElement, packageContext);
          ActionConfig actionConfig = new ActionConfig.Builder(packageContext.getName(), name, className)
          .methodName(methodName)
          .addResultConfigs(results)
          .addInterceptors(interceptorList)
          .addExceptionMappings(exceptionMappings)
          .addParams(XmlHelper.getParams(actionElement))
          .location(location)
          .build();
          packageContext.addActionConfig(name, actionConfig);
          if (LOG.isDebugEnabled()) {
          LOG.debug("Loaded " + (StringUtils.isNotEmpty(packageContext.getNamespace()) ? (packageContext.getNamespace() + "/") : "") + name + " in '" + packageContext.getName() + "' package:" + actionConfig);
          }
          }



               工作中不涉及Struts2,本周工作有個2天的空檔期,稍微看了下struts2的文檔,寫了個demo,從源碼的角度研究了下運行原理,如有分析不當請指出,我后續逐步完善更正,大家共同提高。


          posted on 2011-05-10 20:58 空白 閱讀(7773) 評論(4)  編輯  收藏 所屬分類: Java

          評論

          # re: 探究Struts2運行機制:StrutsPrepareAndExecuteFilter 源碼剖析 2012-06-20 15:45 ligucoai2005

          我想知道你是如何分析源碼的?怎么搭建一個查看源碼的環境?
          關鍵還是在于知道代碼的流程,但是沒有一個好的環境的話,基本不可能去分析源碼  回復  更多評論   

          # re: 探究Struts2運行機制:StrutsPrepareAndExecuteFilter 源碼剖析[未登錄] 2012-07-26 13:23 匿名

          你所謂的探究基本就是給源碼加上一些注釋  回復  更多評論   

          # re: 探究Struts2運行機制:StrutsPrepareAndExecuteFilter 源碼剖析[未登錄] 2013-02-20 16:40 天天笑

          分析的很透徹!  回復  更多評論   

          # re: 探究Struts2運行機制:StrutsPrepareAndExecuteFilter 源碼剖析[未登錄] 2014-05-11 22:52 Jerry

          很贊  回復  更多評論   

          <2014年5月>
          27282930123
          45678910
          11121314151617
          18192021222324
          25262728293031
          1234567

          導航

          統計

          常用鏈接

          留言簿(1)

          隨筆分類(15)

          積分與排名

          最新評論

          閱讀排行榜

          評論排行榜

          主站蜘蛛池模板: 铁力市| 武鸣县| 名山县| 皋兰县| 无棣县| 漾濞| 阳城县| 邢台市| 长治市| 洪洞县| 五峰| 蓝山县| 延寿县| 阿拉尔市| 丁青县| 依安县| 申扎县| 双辽市| 静乐县| 会同县| 若羌县| 贵港市| 南汇区| 若尔盖县| 永吉县| 东阿县| 永平县| 茶陵县| 琼海市| 丹江口市| 娱乐| 疏勒县| 晋州市| 大石桥市| 娄底市| 濮阳市| 木兰县| 法库县| 新营市| 古交市| 卓尼县|