struts2 處理請求流程分析(結合源碼)2

2、過濾器中的doFilter(ServletRequest req, ServletResponse res, FilterChain chain) 方法

 

 

 

2.1、request = prepareDispatcherAndWrapRequest(request, response);分析

     我們知道JSTL默認是從page,request,session,application這四個Scope逐次查找相應的EL表達式所對應的對象的值。那麼如果要使用JSTL來讀取Action中的變量,就需要把Action中的變量,放到request域中才行Struts2,都使用另外一種整合方式:對HttpServletRequest進行裝飾(StrutsRequestWrapper )這個類會在Struts2初始化的時候,替換HttpServletRequest,運行於整個Struts2的運行過程中,當我們試圖調用request.getAttribute()的時候,就會執行上面的這個方法。(這是一個典型的裝飾器模式)在執行上面的方法時,會首先調用HttpServletRequest中原本的request.getAttribute(),如果沒有找到,它會繼續到ValueStack中去查找,而action在ValueStack中,所以action中的變量通過OGNL表達式,就能找到對應的值了。

 

	protected HttpServletRequest prepareDispatcherAndWrapRequest(
			HttpServletRequest request, HttpServletResponse response)
			throws ServletException {
		//獲取dispatch 的單例類,是由LocalThread 保存的,保證線程的安全
		Dispatcher du = Dispatcher.getInstance();
		// Prepare and wrap the request if the cleanup filter hasn't already,
		// cleanup filter should be
		// configured first before struts2 dispatcher filter, hence when its
		// cleanup filter's turn,
		// static instance of Dispatcher should be null.
		if (du == null) {
			//如果爲空的話,值保存進LocalThread 中
			Dispatcher.setInstance(dispatcher);
			// prepare the request no matter what - this ensures that the proper
			// character encoding
			// is used before invoking the mapper (see WW-9127)
			// request 編碼設置和response 本地化設置
			dispatcher.prepare(request, response);
		} else {
			dispatcher = du;
		}

		try {
			// Wrap request first, just in case it is multipart/form-data
			// parameters might not be accessible through before encoding
			// (ww-1278)
			//在這裏就開始包裝
			request = dispatcher.wrapRequest(request, getServletContext());
		} catch (IOException e) {
			String message = "Could not wrap servlet request with MultipartRequestWrapper!";
			LOG.error(message, e);
			throw new ServletException(message, e);
		}
		return request;
	}

 

 簡單看下這個方法Dispatcher.getInstance();

private static ThreadLocal<Dispatcher> instance = new ThreadLocal<Dispatcher>();
//other code
public static Dispatcher getInstance() {
        return instance.get();
}

 

主要的包裝在此方法進行request = dispatcher.wrapRequest(request, getServletContext());

    public HttpServletRequest wrapRequest(HttpServletRequest request, ServletContext servletContext) throws IOException {
        // don't wrap more than once 如果已經包裝了,就不用再包裝了
        if (request instanceof StrutsRequestWrapper) {
            return request;
        }
        String content_type = request.getContentType();
        //非表單提交的request 封裝,主要是圖片上傳等 
        if (content_type != null && content_type.indexOf("multipart/form-data") != -1) {
            MultiPartRequest multi = getContainer().getInstance(MultiPartRequest.class);
            //如果是非表單提交則包裝成MultiPartRequestWrapper
            request = new MultiPartRequestWrapper(multi, request, getSaveDir(servletContext));
        } else {
        	//如果是普通表單提交,在此包裝
            request = new StrutsRequestWrapper(request);
        }
        return request;
    }

 

2.2、mapping = actionMapper.getMapping(request, dispatcher.getConfigurationManager());方法的分析

   這裏的分析參照了:http://zddava.iteye.com/blog/215504

下面來看一下默認使用的ActionMapper實現DefaultActionMapper的#getMapping():

    public ActionMapping getMapping(HttpServletRequest request,
            ConfigurationManager configManager) {
        ActionMapping mapping = new ActionMapping();// (1)
        String uri = getUri(request);// (2)

        uri = dropExtension(uri);// (3)
        if (uri == null) {
            return null;
        }

        parseNameAndNamespace(uri, mapping, configManager);// (4)

        handleSpecialParameters(request, mapping);// (5)

        if (mapping.getName() == null) {
            return null;
        }

        if (allowDynamicMethodCalls) {// (6)
            String name = mapping.getName();
            int exclamation = name.lastIndexOf("!");
            if (exclamation != -1) {
                mapping.setName(name.substring(0, exclamation));
                mapping.setMethod(name.substring(exclamation + 1));
            }
        }

        return mapping;
    }

 

主要有6處需要重點說明:

(1) 關於ActionMapping類,它內部封裝瞭如下5個字段:

    private String name;// Action名
    private String namespace;// Action名稱空間
    private String method;// 執行方法
    private Map params;// 可以通過set方法設置的參數
    private Result result;// 返回的結果

 

這些在配置文件中都是可設置的,確定了ActionMapping類的各個字段的值,就可以對請求的Action進行調用了。

(2) String uri = getUri(request);

這個步驟用於獲取客戶端發送的請求的URI,源代碼如下:

    String getUri(HttpServletRequest request) {
        // handle http dispatcher includes.
        String uri = (String) request.getAttribute("javax.servlet.include.servlet_path");
        if (uri != null) {
            return uri;
        }

        uri = RequestUtils.getServletPath(request);
        if (uri != null && !"".equals(uri)) {
            return uri;
        }

        uri = request.getRequestURI();
        return uri.substring(request.getContextPath().length());
    }

 

     這個方法首先判斷請求是否來自於一個jsp的include,如果是,那麼請求的"javax.servlet.include.servlet_path"屬性可以獲得include的頁面uri,否則通過一般的方法獲得請求的uri,最後返回去掉ContextPath的請求路徑,比如http://127.0.0.1:8087/test/jsp/index.jsp?param=1,返回的爲/jsp/index.jsp。去掉了ContextPath和查詢字符串等。

(3) uri = dropExtension(uri); 負責去掉Action的"擴展名"(默認爲"action"),源代碼如下:

 

 String dropExtension(String name) {
    	//extensions 爲struts2 的後綴名,可有多個,默認爲action
    	// List extensions = new ArrayList() {{ add("action");}};
        if (extensions == null) {
            return name;
        }
        Iterator it = extensions.iterator();
        //分別遍歷後去掉後綴名
        while (it.hasNext()) {
            String extension = "." + (String) it.next();
            if (name.endsWith(extension)) {
                name = name.substring(0, name.length() - extension.length());
                return name;
            }
        }
        return null;
    }

 

注意,這個步驟對於不是以特地擴展名結尾的請求會返回一個null的uri,進而#getMapping()也會返回null,FilterDispatcher的#doFilter()就會把這次請求當作一個普通請求對待了。

(4) parseNameAndNamespace(uri, mapping, configManager);

此方法用於解析Action的名稱和命名空間,並賦給ActionMapping對象。源代碼如下:

    void parseNameAndNamespace(String uri, ActionMapping mapping, ConfigurationManager configManager) {
        String namespace, name;
	// 例如 http://127.0.0.1:8087/teststruts/namespace/name.action?param=1 
	// dropExtension()後,獲得uri爲/namespace/name 
        int lastSlash = uri.lastIndexOf("/");
        if (lastSlash == -1) {
            namespace = "";
            name = uri;
        } else if (lastSlash == 0) {
            namespace = "/";
            name = uri.substring(lastSlash + 1);
        } else if (alwaysSelectFullNamespace) {// alwaysSelectFullNamespace默認爲false,代表是否將最後一個"/"前的字符全作爲名稱空間。
            namespace = uri.substring(0, lastSlash);// 獲得字符串 namespace
            name = uri.substring(lastSlash + 1);// 獲得字符串 name
        } else {
	    // 例如 http://127.0.0.1:8087/teststruts/namespace1/namespace2/actionname.action?param=1
                  // dropExtension()後,獲得uri爲/namespace1/namespace2/actionname 
            Configuration config = configManager.getConfiguration();
            String prefix = uri.substring(0, lastSlash);// 獲得 /namespace1/namespace2
            namespace = "";
            // 如果配置文件中有一個包的namespace是 /namespace1/namespace2,那麼namespace爲/namespace1/namespace2,name爲actionname
            // 如果配置文件中有一個包的namespace是 /namespace1,那麼namespace爲/namespace1,name爲/namespace2/actionname
            for (Iterator i = config.getPackageConfigs().values().iterator(); i
                    .hasNext();) {
                String ns = ((PackageConfig) i.next()).getNamespace();
                if (ns != null && prefix.startsWith(ns) && (prefix.length() == ns.length() || prefix.charAt(ns.length()) == '/')) {
                    if (ns.length() > namespace.length()) {
                        namespace = ns;
                    }
                }
            }

            name = uri.substring(namespace.length() + 1);
        }

        if (!allowSlashesInActionNames && name != null) {// allowSlashesInActionNames代表是否允許"/"出現在Action的名稱中,默認爲false
            int pos = name.lastIndexOf('/');
            if (pos > -1 && pos < name.length() - 1) {
                name = name.substring(pos + 1);
            }
        }// 以 name = /namespace2/actionname 爲例,經過這個if塊後,name = actionname

        mapping.setNamespace(namespace);
        mapping.setName(name);
    }

 

(5) handleSpecialParameters(request, mapping); 此方法用於處理Struts框架定義的四種特殊的prefix:

下邊是struts2的javadoc裏提供的例子:

Method prefix:調用baz的另外一個方法"anotherMethod"而不是"execute"

  <a:form action="baz">
      <a:textfield label="Enter your name" name="person.name"/>
      <a:submit value="Create person"/>
      <a:submit name="method:anotherMethod" value="Cancel"/>
  </a:form>

 

Action prefix:調用anotherAction的"execute"

  <a:form action="baz">
      <a:textfield label="Enter your name" name="person.name"/>
      <a:submit value="Create person"/>
      <a:submit name="action:anotherAction" value="Cancel"/>
  </a:form>

 

Redirect prefix:將請求重定向,下例中爲定向到google

  <a:form action="baz">
      <a:textfield label="Enter your name" name="person.name"/>
      <a:submit value="Create person"/>
      <a:submit name="redirect:www.google.com" value="Cancel"/>
  </a:form>

 

Redirect-action prefix:重定向action,下例中爲定向到dashboard.action

  <a:form action="baz">
      <a:textfield label="Enter your name" name="person.name"/>
      <a:submit value="Create person"/>
      <a:submit name="redirect-action:dashboard" value="Cancel"/>
  </a:form>

 

handleSpecialParameters的源代碼如下:

    public void handleSpecialParameters(HttpServletRequest request, ActionMapping mapping) {
        Set<String> uniqueParameters = new HashSet<String>();
        Map parameterMap = request.getParameterMap();
        for (Iterator iterator = parameterMap.keySet().iterator(); iterator.hasNext();) {
            String key = (String) iterator.next();
            
            if (key.endsWith(".x") || key.endsWith(".y")) {// 去掉圖片按鈕的位置信息,具體情況我也不是很瞭解
                key = key.substring(0, key.length() - 2);
            }

            // 處理四種特殊的prefix:Method prefix,Action prefix,Redirect prefix,Redirect-action prefix
            if (!uniqueParameters.contains(key)) {
                ParameterAction parameterAction = (ParameterAction) prefixTrie.get(key);
                if (parameterAction != null) {// 當發現某種特殊的predix時
                    parameterAction.execute(key, mapping);// 調用它的execute方法,在DefaultActionMapper的構造函數中定義
                    uniqueParameters.add(key);// 下邊已經break了爲什麼還要把key加入到排重的Set裏呢??
                    break;
                }
            }
        }
    }

 (6) 處理調用的不是execute方法的情況:
Struts框架也可以處理"name!method"形式的action調用,碰到這種情況,在此處將name和method分別解析出來然後賦給ActionMapping對象。 

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