1. OGNL表达式基础概念解析
在Struts2框架中,OGNL(Object-Graph Navigation Language)扮演着至关重要的角色。这种强大的表达式语言最初是为Java开发者设计的,用于在对象图中导航和操作数据。它允许开发者通过简洁的语法访问和操作对象属性、调用方法以及执行各种计算。
OGNL的核心功能包括:
- 对象属性访问(包括嵌套属性)
- 方法调用
- 集合操作(List、Map、数组等)
- 类型转换
- 表达式求值
在Struts2中,OGNL主要应用于以下几个场景:
- 视图层(JSP等)与Action之间的数据绑定
- 表单字段与后台对象的映射
- 标签库的属性赋值
- 验证规则的配置
- 拦截器的参数传递
2. $符号在Struts2中的核心作用
2.1 $符号的基本语法与功能
在Struts2中,$符号主要用于字符串的强制OGNL求值。当我们需要在字符串中嵌入动态内容时,$符号能够指示框架对该部分内容进行OGNL表达式解析。
基本语法格式为:
jsp复制${ognlExpression}
典型应用场景包括:
- 国际化消息中的动态参数
- 配置文件中的动态值
- SQL语句中的参数替换
- 日志消息中的变量输出
2.2 $符号的运行时解析机制
$符号的解析发生在运行时,具体过程如下:
- 框架检测到字符串中包含${}语法
- 提取大括号内的表达式内容
- 使用当前ValueStack上下文进行OGNL求值
- 将求值结果替换回原字符串位置
- 生成最终的字符串输出
这种延迟求值的特性使得$符号非常灵活,但也带来了一定的性能开销,因为每次使用都需要进行表达式解析。
2.3 $符号的实际应用示例
jsp复制<!-- 国际化消息中的动态参数 -->
<s:text name="welcome.message">
<s:param>${user.name}</s:param>
<s:param>${#session.loginTime}</s:param>
</s:text>
<!-- 配置文件中的动态值 -->
<constant name="struts.devMode" value="${systemProperties['development.mode']}"/>
<!-- SQL语句中的参数替换 -->
<query>
SELECT * FROM users WHERE username = '${#parameters.username}'
</query>
重要提示:在使用$符号进行SQL拼接时需特别注意SQL注入风险,应当优先使用参数化查询而非字符串拼接。
3. #符号在Struts2中的核心作用
3.1 #符号的基本语法与功能
#符号在OGNL中主要有两个用途:
- 访问非ValueStack中的对象(如request、session、application等作用域中的对象)
- 创建Map或List字面量
基本语法格式为:
jsp复制#ognlExpression
3.2 #符号的上下文访问功能
#符号最常见的用法是访问各种上下文对象:
jsp复制<!-- 访问request作用域 -->
#request['key']
#request.getAttribute('key')
<!-- 访问session作用域 -->
#session['user']
#session.getAttribute('user')
<!-- 访问application作用域 -->
#application['config']
#application.getAttribute('config')
<!-- 访问parameters -->
#parameters.id[0]
3.3 #符号的集合创建功能
#符号还可以用于创建集合字面量:
jsp复制<!-- 创建List -->
#{'apple', 'banana', 'orange'}
<!-- 创建Map -->
#{'key1':'value1', 'key2':'value2'}
这种语法在Struts2标签的属性赋值中非常有用,特别是当需要传递集合参数时。
4. $与#符号的深度对比分析
4.1 解析时机差异
| 特性 | $符号 | #符号 |
|---|---|---|
| 解析时机 | 运行时动态解析 | 编译时静态解析 |
| 性能影响 | 较高(每次都需要解析) | 较低(一次解析) |
| 灵活性 | 高(支持复杂动态内容) | 相对固定 |
4.2 作用域访问差异
jsp复制<!-- $符号访问ValueStack中的属性 -->
${user.name}
<!-- #符号访问特定作用域中的属性 -->
#session.user.name
$符号默认从ValueStack开始查找,而#符号需要明确指定查找的作用域。这种差异在实际开发中非常重要,特别是在处理同名属性时。
4.3 典型使用场景对比
-
$符号适用场景:
- 需要将OGNL表达式嵌入字符串的情况
- 国际化消息中的动态参数
- 需要延迟求值的场景
-
#符号适用场景:
- 明确访问特定作用域中的对象
- 创建集合字面量
- 在标签属性中直接使用OGNL表达式
5. 高级用法与实战技巧
5.1 复合使用$和#符号
在实际开发中,我们经常需要组合使用这两个符号:
jsp复制<!-- 在国际化消息中访问session属性 -->
<s:text name="welcome.back">
<s:param>${#session.user.name}</s:param>
</s:text>
<!-- 在配置文件中使用系统属性 -->
<constant name="upload.dir" value="${#systemProperties['user.home']}/uploads"/>
5.2 性能优化建议
- 避免在循环中过度使用$符号
- 对于静态内容,优先使用#符号
- 缓存频繁使用的OGNL表达式结果
- 在JSP中使用<s:set>标签缓存常用值
5.3 安全注意事项
- 永远不要直接将用户输入作为OGNL表达式执行
- 对动态生成的OGNL表达式进行严格验证
- 在Struts2配置中启用安全模式
- 及时更新框架版本以修复已知的安全漏洞
6. 常见问题排查与解决方案
6.1 表达式不生效问题
症状:OGNL表达式没有按预期执行,输出原样字符串。
可能原因:
- 表达式语法错误(如缺少$或#符号)
- 访问的属性不存在
- 作用域指定错误
解决方案:
- 检查表达式语法是否正确
- 添加默认值处理:$
- 使用调试工具检查ValueStack内容
6.2 性能瓶颈问题
症状:页面响应缓慢,特别是包含大量OGNL表达式的页面。
优化方案:
- 使用<s:set>标签缓存频繁访问的值
- 将复杂计算移到Action中完成
- 考虑使用静态内容替代动态表达式
6.3 安全漏洞问题
症状:系统出现未授权的数据访问或代码执行。
防护措施:
- 配置struts.ognl.excludedClasses排除危险类
- 设置struts.ognl.allowStaticMethodAccess=false
- 对用户输入进行严格过滤
7. 最佳实践总结
经过多年的Struts2开发实践,我总结了以下OGNL使用经验:
-
明确作用域:始终清楚地知道你要访问的数据在哪个作用域中,ValueStack、request、session还是application。混用作用域是许多bug的根源。
-
适度使用:虽然OGNL很强大,但不应滥用。简单的属性访问可以直接使用,复杂的业务逻辑最好在Action中处理。
-
性能意识:记住$符号的运行时开销,特别是在循环和频繁访问的场景中要谨慎使用。
-
安全第一:永远不要信任用户提供的OGNL表达式,即使是间接提供的。Struts2历史上多次安全漏洞都与OGNL注入有关。
-
测试覆盖:对包含复杂OGNL表达式的部分要编写充分的测试用例,包括边界情况和异常情况。
-
文档注释:对于非直观的OGNL用法,添加清晰的注释说明其意图和预期行为,这对后续维护非常重要。
-
及时升级:关注Struts2的安全公告,及时更新到修复了OGNL相关漏洞的版本。