1. 程式人生 > >Struts2 修改驗證器原始碼,能夠區分GET、POST請求

Struts2 修改驗證器原始碼,能夠區分GET、POST請求

軟考過後,有些閒工夫,打算撿起之前所學的Struts2研究研究原始碼。

一、丟擲問題

關於表單顯示頁面跟表單請求頁面的地址

通常情況下來講,表單的展示地址即 get 請求表單頁地址跟表單 post 提交的地址是不同的。一個是表單的展示,一個是表單提交資料請求的處理,但是這裡往往會遇到表單驗證不通過回顯的問題。在 Struts2 中可以對不同的 action method 渲染同一個 result,那麼就會變成表單提交前,表單提交後回顯的頁面地址不同,但是頁面內容大致一樣(多出來一些驗證不通過的 fieldErrors 內容)

如何達到請求與回顯表單地址一致?

二、思考

  1. 首先 Struts2 驗證器實現有兩種方式,一種是宣告式、一種是繼承 Validateable 介面,複寫 validate() 方法、還有自定義驗證器(不討論)
  2. 這兩種方式中第二種無疑可以通過 ServletActionContext.getRequest().getMethod() 獲取請求型別進行判斷實現上面的問題
  3. 設法通過宣告式的方式實現會更有價值(後面會說價值所在)

三、實現

如何實現?

宣告中驗證檔案的名稱為 ActionClassName-[ActionName]-validation.xml 檔名中加入帶有 get、post 的固定欄位就能夠區分請求的型別驗證檔案

步驟

要知道一些基本的 Struts2 的執行原理。通過 struts-default.xml 的配置中找到攔截器棧,從中找到驗證攔截器


在該類中找到 doIntercept 方法,並找到跟驗證相關的方法 super.doIntercept();


進入該方法中找到相關的驗證程式碼段,如下兩種圖片所示, actionValiditorManager 為驗證操作的物件



private ActionValidatorManager actionValidatorManager;  是介面,通過 debug 斷點或者 @Inject 注入註解排查該介面的實現類是 AnnotationActionValidatorManager 

如下圖所示:



進入 AnnotationActionValidatorManager ,具體該類如何工作試用 debug 除錯,並且其中的程式碼並不是很難,這裡略過...

修改原始碼

要修改的地方有兩個部分,

一個是在驗證器在組裝檔名時加入我們想要的檔名

另一個是在驗證器獲取、存入快取中的 Key 需要修改

首先針對 buildValidatorKey 該方法修,只是在原來儲存的名字上增加了請求方式,因為驗證的xml檔案只會獲取一次,不管是獲取到了get或者是獲取到了post,在儲存的快取中有一個 Key 想對應,但是原有的 Key 並沒有區分請求的方式,這樣無法通過請求方式獲取到相應的驗證檔案中的規則,改如下:

	protected String buildValidatorKey(Class clazz, String context) {
		ActionInvocation invocation = ActionContext.getContext().getActionInvocation();
		ActionProxy proxy = invocation.getProxy();
		ActionConfig config = proxy.getConfig();

		StringBuilder sb = new StringBuilder(clazz.getName());
		// 新增加程式碼,拼接請求方式,並且加入到快取中
		String method = ServletActionContext.getRequest().getMethod();
		if (method != null && method.length() > 0) {
			sb.append("."+method.toLowerCase());
		}
		sb.append("/");
		if (StringUtils.isNotBlank(config.getPackageName())) {
			sb.append(config.getPackageName());
			sb.append("/");
		}

		// the key needs to use the name of the action from the config file,
		// instead of the url, so wild card actions will have the same validator
		// see WW-2996

		// UPDATE:
		// WW-3753 Using the config name instead of the context only for
		// wild card actions to keep the flexibility provided
		// by the original design (such as mapping different contexts
		// to the same action and method if desired)

		// UPDATE:
		// WW-4536 Using NameVariablePatternMatcher allows defines actions
		// with patterns enclosed with '{}', it's similar case to WW-3753
		String configName = config.getName();
		if (configName.contains(ActionConfig.WILDCARD) || (configName.contains("{") && configName.contains("}"))) {
			sb.append(configName);
			sb.append("|");
			sb.append(proxy.getMethod());
		} else {
			sb.append(context);
		}

		return sb.toString();
	}


驗證器在獲取驗證檔案內容時需要通過請求的方式區別,通過修改如下程式碼實現區分:

	private List<ValidatorConfig> buildValidatorConfigs(Class clazz, String context, boolean checkFile,
			Set<String> checked) {
		List<ValidatorConfig> validatorConfigs = new ArrayList<ValidatorConfig>();

		if (checked == null) {
			checked = new TreeSet<String>();
		} else if (checked.contains(clazz.getName())) {
			return validatorConfigs;
		}

		if (clazz.isInterface()) {
			Class[] interfaces = clazz.getInterfaces();

			for (Class anInterface : interfaces) {
				validatorConfigs.addAll(buildValidatorConfigs(anInterface, context, checkFile, checked));
			}
		} else {
			if (!clazz.equals(Object.class)) {
				validatorConfigs.addAll(buildValidatorConfigs(clazz.getSuperclass(), context, checkFile, checked));
			}
		}

		// look for validators for implemented interfaces
		Class[] interfaces = clazz.getInterfaces();

		for (Class anInterface1 : interfaces) {
			if (checked.contains(anInterface1.getName())) {
				continue;
			}

			validatorConfigs.addAll(buildClassValidatorConfigs(anInterface1, checkFile));

			if (context != null) {
				validatorConfigs.addAll(buildAliasValidatorConfigs(anInterface1, context, checkFile));
			}

			checked.add(anInterface1.getName());
		}

		validatorConfigs.addAll(buildClassValidatorConfigs(clazz, checkFile));
		
		// 新增加程式碼
		String fileName = null;
		String method = ServletActionContext.getRequest().getMethod().toLowerCase();
		try {
			if (Class.forName("com.opensymphony.xwork2.ActionSupport") != clazz
					&& Class.forName("com.opensymphony.xwork2.ActionSupport").isAssignableFrom(clazz)) {
				// 到達我們所建立並繼承 ActionSupport 的 Action 類
				// 拼裝完整檔名並且載入檔案,將載入的內容加入到驗證配置中
				fileName = clazz.getName().replace('.', '/') + "-" + method + VALIDATION_CONFIG_SUFFIX;
				validatorConfigs.addAll(loadFile(fileName, clazz, checkFile));
				fileName = clazz.getName().replace('.', '/') + "-" + context.replace('/', '-') + "-" + method
						+ VALIDATION_CONFIG_SUFFIX;
				validatorConfigs.addAll(loadFile(fileName, clazz, checkFile));
			}
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}

		if (context != null) {
			validatorConfigs.addAll(buildAliasValidatorConfigs(clazz, context, checkFile));
		}

		checked.add(clazz.getName());

		return validatorConfigs;
	}


執行結果如下:

點選提交按鈕後

實現了請求地址相同,但是能夠通過不同的請求方式實現載入不同的驗證 xml 檔案

本文主要的目的在於弄清楚 Struts2 的原始碼,實現功能次要。

附上 Archive File 的下載地址。

連結:https://pan.baidu.com/s/1qXPivSC 密碼:3bdr