Struts 2 的 ValueStack 貫穿 Action 整個生命周期,如同傳送帶將屬性值在攔截器鏈中傳遞、變換,最終賦給 Action 實例。本文深度剖析此機制原理,並通過自定義 PropertyInterceptor 實現從屬性文件自動注入屬性值,展示攔截器順序對結果的決定性影響。
默認棧 defaultStack 中 staticParams 在 params 之前,故請求參數會覆蓋配置參數同名屬性。
功能:讀取屬性文件(key=value),自動映射到 Action 屬性,支持:
path)、編碼(encoding,默認 UTF-8). 處理:去除(person.name→personname)或替換分隔符(separator="_" → person_name)<param name="shortName">long.key.name</param>public class PropertyInterceptor extends AbstractInterceptor {
// ... 參數設置省略 ...
@Override
public String intercept(ActionInvocation invocation) throws Exception {
ActionConfig config = invocation.getProxy().getConfig();
Map<String, String> params = config.getParams();
if (params.containsKey("path")) {
String path = params.get("path");
String encoding = params.get("encoding") != null ? params.get("encoding") : "UTF-8";
String separator = params.get("separator") != null ? params.get("separator") : "";
// 加載屬性文件
Properties props = new Properties();
InputStream is = new FileInputStream(invocation.getAction().getClass().getResource(path).getPath());
props.load(new InputStreamReader(is, encoding));
ValueStack stack = invocation.getInvocationContext().getValueStack();
// 遍歷屬性映射到 ValueStack
for (Object keyObj : props.keySet()) {
String key = keyObj.toString();
Object value = props.get(key);
// 1. 直接匹配
if (!key.contains(".")) stack.setValue(key, value);
// 2. 去除點號匹配
String noDot = key.replaceAll("\\.", "");
if (params.containsKey(noDot)) stack.setValue(params.get(noDot), value);
else if (!key.contains(".")) stack.setValue(noDot, value); // fallback
// 3. 分隔符替換匹配
if (!separator.isEmpty()) {
String withSep = key.replaceAll("\\.", separator);
stack.setValue(withSep, value);
}
// 4. 顯式參數映射
String mapped = params.get(key);
if (mapped != null) stack.setValue(mapped, value);
}
}
return invocation.invoke();
}
}
public class MyAction {
private String name;
private Integer price;
private String log4jappenderstdout;
private String log4j_rootLogger;
private String conversionPattern;
// getter/setter 省略
public String execute() {
System.out.println("name: " + name);
System.out.println("price: " + price);
// ...
return null;
}
}
<package name="struts" extends="struts-default">
<interceptors>
<interceptor name="property" class="interceptors.PropertyInterceptor"/>
<interceptor-stack name="myStack">
<interceptor-ref name="defaultStack"/>
<interceptor-ref name="property"/> <!-- 放在最後,生效優先級最高 -->
</interceptor-stack>
</interceptors>
<action name="test" class="actions.MyAction">
<interceptor-ref name="myStack"/>
<param name="path">/log4j.properties</param>
<param name="encoding">UTF-8</param>
<param name="separator">_</param>
<param name="log4jappenderstdoutlayoutConversionPattern">conversionPattern</param>
</action>
</package>
name: 超人
price: 10000
log4jappenderstdout: org.apache.log4j.ConsoleAppender
log4j_rootLogger: error,stdout
conversionPattern: %d{ABSOLUTE}%5p%c{1}:%L-%m%n
配置中 property 放在 defaultStack 之後,其設置的值為最終結果。若交換順序(property 在前),則請求參數或配置參數可覆蓋屬性文件值。這證明:ValueStack 傳送帶上,最後經過的攔截器決定最終值。
stack.setValue() 修改 ValueStack,實現靈活數據注入。