Struts2 修改驗證器原始碼,能夠區分GET、POST請求
軟考過後,有些閒工夫,打算撿起之前所學的Struts2研究研究原始碼。
一、丟擲問題
關於表單顯示頁面跟表單請求頁面的地址
通常情況下來講,表單的展示地址即 get 請求表單頁地址跟表單 post 提交的地址是不同的。一個是表單的展示,一個是表單提交資料請求的處理,但是這裡往往會遇到表單驗證不通過回顯的問題。在 Struts2 中可以對不同的 action method 渲染同一個 result,那麼就會變成表單提交前,表單提交後回顯的頁面地址不同,但是頁面內容大致一樣(多出來一些驗證不通過的 fieldErrors 內容)
如何達到請求與回顯表單地址一致?
二、思考
- 首先 Struts2 驗證器實現有兩種方式,一種是宣告式、一種是繼承 Validateable 介面,複寫 validate() 方法、還有自定義驗證器(不討論)
- 這兩種方式中第二種無疑可以通過 ServletActionContext.getRequest().getMethod() 獲取請求型別進行判斷實現上面的問題
- 設法通過宣告式的方式實現會更有價值(後面會說價值所在)
三、實現
如何實現?
宣告中驗證檔案的名稱為 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