软考过后,有些闲工夫,打算捡起之前所学的Struts2研究研究源码。
一、抛出问题
关于表单显示页面跟表单请求页面的地址
通常情况下来讲,表单的展示地址即 get 请求表单页地址跟表单 post 提交的地址是不同的。一个是表单的展示,一个是表单提交数据请求的处理,但是这里往往会遇到表单验证不通过回显的问题。在 Struts2 中可以对不同的 action method 渲染同一个 result,那么就会变成表单提交前,表单提交后回显的页面地址不同,但是页面内容大致一样(多出来一些验证不通过的 fieldErrors 内容)
如何达到请求与回显表单地址一致?
二、思考
- 首先 Struts2 验证器实现有两种方式,一种是声明式、一种是继承 Validateable 接口,复写 validate() 方法、还有自定义验证器(不讨论)
- 这两种方式中第二种无疑可以通过 ServletActionContext.getRequest().getMethod() 获取请求类型进行判断实现上面的问题
- 设法通过声明式的方式实现会更有价值(后面会说价值所在)
三、实现
如何实现?
步骤
在该类中找到 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