如何让JSP表达式语言(EL)在表达式无效时抛出明确错误
默认JSP EL采用容错设计:当表达式中的标识符找不到、对象不存在对应属性、类型转换失败时,不会抛出异常,只会返回null,页面渲染时null会被输出为空串,这就是你测试时只看到"Hello,"的原因。
可以通过以下配置关闭容错机制,让错误直接抛出:
1. Tomcat容器全局配置(最常用)
修改Tomcat目录下conf/catalina.properties,增加以下配置项:
# 解析不到bean/属性时直接抛出PropertyNotFoundException,不返回null org.apache.el.BeanELResolver.IGNORE_PROPERTY_NOT_FOUND=false # 类型转换失败时抛出异常,不返回0、false这类默认值 org.apache.el.parser.COERCE_TO_ZERO=false
修改后重启Tomcat即可全局生效,所有EL写错变量名、属性名的情况都会直接抛出明确异常,堆栈里会标注出错的EL表达式位置和找不到的属性/变量名。
2. 应用级配置
在你项目的WEB-INF/web.xml中添加JSP配置,确保EL没有被全局禁用:
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0"> <!-- 其他现有配置 --> <jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <el-ignored>false</el-ignored> </jsp-property-group> </jsp-config> </web-app>
如果是单个JSP页面需要单独控制EL开关,可以在页面顶部加page指令:
<%@ page isELIgnored="false" %>
3. 跨容器通用方案
如果你使用的是Jetty、Undertow等其他Servlet容器,没有提供上述配置开关,可以自定义一个ELResolver实现,在解析对象/属性返回null时主动抛出异常,再将该解析器注册到应用的EL上下文即可,这种方案不依赖特定容器,所有符合Servlet规范的容器都能生效。
JSP EL本身没有"form bean"的内置概念,所有能在EL里访问的对象,都是存放在四大作用域(page、request、session、application)中的属性,EL会按照作用域从小到大依次查找匹配名称的属性,名称的确定规则如下:
- 手动存入作用域的对象:名称完全和你调用
setAttribute(key, value)时传入的key一致。比如你在Servlet里写request.setAttribute("loginForm", formObj),EL里就用${loginForm.xxx}访问,key是什么就写什么。 - Spring MVC自动暴露的对象:默认规则是类名首字母小写,比如类名是
UserRegisterForm,默认名称就是userRegisterForm;如果类名前两个字母连续大写(比如OSSConfigForm),名称和类名一致为OSSConfigForm。如果用@ModelAttribute("customName")指定了名称,就用你指定的customName访问。 - Struts2框架的对象:框架会自动把Action的属性、ModelDriven的模型对象压入值栈,EL访问时不需要加bean前缀,直接写
${propertyName}就能取到对应属性。
如果不确定当前页面有哪些可访问的bean,可以临时在JSP页面加以下调试代码,运行后会列出所有作用域内的合法属性名,直接对着用就不会写错:
<% out.println("<h3>Page域属性:</h3>"); java.util.Enumeration<String> pageAttrs = pageContext.getAttributeNamesInScope(PageContext.PAGE_SCOPE); while(pageAttrs.hasMoreElements()) out.println(pageAttrs.nextElement() + "<br>"); out.println("<h3>Request域属性:</h3>"); java.util.Enumeration<String> reqAttrs = request.getAttributeNames(); while(reqAttrs.hasMoreElements()) out.println(reqAttrs.nextElement() + "<br>"); out.println("<h3>Session域属性:</h3>"); java.util.Enumeration<String> sessionAttrs = session.getAttributeNames(); while(sessionAttrs.hasMoreElements()) out.println(sessionAttrs.nextElement() + "<br>"); out.println("<h3>Application域属性:</h3>"); java.util.Enumeration<String> appAttrs = application.getAttributeNames(); while(appAttrs.hasMoreElements()) out.println(appAttrs.nextElement() + "<br>"); %>
你最开始写的formBeanBame本身就是其他示例里的占位符,还有拼写错误(Bame应为Name),你没有手动存入对应名称的对象,自然是取不到的。
内容的提问来源于stack exchange,提问作者Ian Boyd

