1. OGNL表达式基础概念解析
在Struts2框架中,OGNL(Object-Graph Navigation Language)扮演着至关重要的角色。这种强大的表达式语言最初作为独立的项目开发,后被整合到Struts2中用于实现数据访问和操作。OGNL的核心功能是允许开发者通过简洁的语法导航对象图、访问属性值和执行方法调用。
OGNL表达式的基本结构包含三个关键元素:根对象(Root Object)、上下文环境(Context)和表达式本身。在Struts2的实现中,ValueStack作为OGNL的根对象,而ActionContext则提供了完整的上下文环境。这种设计使得OGNL能够无缝访问Action属性、静态方法、集合对象等各种数据。
2. $符号在Struts2中的核心作用
2.1 配置文件中的动态内容引用
在Struts2的XML配置文件中,$符号的主要作用是引用在struts.properties或struts.xml中定义的常量值。这种用法实际上是OGNL表达式的一种特殊形式,专门用于配置文件的动态值替换。
典型应用场景包括:
xml复制<constant name="struts.devMode" value="${struts.custom.devMode}" />
在这个例子中,${struts.custom.devMode}会从配置属性中查找struts.custom.devMode对应的值进行替换。
2.2 国际化资源文件中的变量插值
$符号在资源文件中也用于动态内容引用,特别是在国际化消息处理时:
properties复制welcome.message=欢迎,${username}!
当使用getText("welcome.message")时,Struts2会自动用实际值替换${username}占位符。
2.3 与%符号的配合使用
在Struts2标签中,$常与%符号配合使用,强制OGNL表达式的求值:
jsp复制<s:property value="%{#session.user.${currentRole}}"/>
这种组合用法可以实现更灵活的动态属性访问。
3. #符号的多场景应用剖析
3.1 访问非根对象上下文
#符号在OGNL中最主要的用途是访问不在ValueStack根对象中的上下文变量。Struts2维护了一个包含多个命名空间的上下文环境,包括:
- #parameters:HTTP请求参数
- #request:请求属性
- #session:会话属性
- #application:应用上下文属性
- #attr:按page→request→session→application顺序查找属性
典型用法示例:
jsp复制<s:property value="#session.user.name"/>
3.2 创建Map和List集合
#符号可用于快速创建集合对象:
jsp复制<!-- 创建List -->
<s:set var="colors" value="#{'red','green','blue'}" />
<!-- 创建Map -->
<s:set var="colorCodes" value="#{'red':'#FF0000','green':'#00FF00'}" />
3.3 方法调用和静态访问
通过#可以调用Action中的方法或访问静态成员:
jsp复制<s:property value="#myAction.calculateTotal()"/>
<s:property value="@java.lang.Math@PI"/>
4. $与#的深度对比与选择策略
4.1 作用域差异
| 符号 | 主要作用域 | 典型应用场景 |
|---|---|---|
| $ | 配置文件、资源文件 | 常量引用、消息格式化 |
| # | JSP页面、标签属性 | 上下文访问、集合创建 |
4.2 执行时机对比
- $符号的处理通常发生在配置加载阶段,属于早期绑定
- #符号的求值发生在页面渲染时,属于运行时绑定
4.3 性能考量
由于#符号涉及更复杂的上下文查找,在性能敏感场景下应谨慎使用。而$符号的解析开销较小,但灵活性有限。
5. 实际开发中的最佳实践
5.1 安全注意事项
使用OGNL表达式时需特别注意安全问题:
jsp复制<!-- 危险示例:直接使用请求参数 -->
<s:property value="#parameters.userInput"/>
<!-- 安全做法:先进行过滤处理 -->
<s:property value="@com.util.SecurityUtils@filter(#parameters.userInput)"/>
5.2 常见问题排查
- 表达式不生效:检查是否遗漏%符号强制求值
- NullPointerException:使用Elvis运算符提供默认值:
#session.user?.name ?: 'Guest' - 性能瓶颈:避免在循环中频繁使用复杂的#表达式
5.3 调试技巧
在开发过程中,可以使用<s:debug/>标签查看完整的ValueStack和ActionContext内容,帮助诊断OGNL表达式问题。
6. 高级应用场景
6.1 动态方法调用
结合#和$实现灵活的方法派发:
jsp复制<s:property value="#handler['do'+${actionType}]()"/>
6.2 类型转换与格式化
利用OGNL表达式进行复杂类型处理:
jsp复制<s:date name="#session.lastLogin" format="yyyy-MM-dd HH:mm"/>
6.3 集合投影与选择
OGNL强大的集合操作能力:
jsp复制<!-- 投影 -->
<s:property value="#users.{name}"/>
<!-- 选择 -->
<s:property value="#users.{? #this.age > 18}"/>
在实际项目中,理解$和#符号的细微差别可以显著提高开发效率。我个人的经验是,在JSP页面中优先使用#符号访问上下文数据,而在配置文件中则使用$符号进行值引用。当遇到复杂的数据处理需求时,考虑将部分逻辑移到Action中,而不是过度依赖OGNL表达式,这样可以获得更好的可维护性和性能表现。