为什么我不能在没有 <jsp:useBean> 的情况下使用 <jsp:getProperty>?

Why I can't use <jsp:getProperty> without <jsp:useBean>?

假设 servlet 的代码为:

protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
    foo.Person p = new foo.Person("Evan");
    req.setAttribute("person", p);

    RequestDispatcher view = req.getRequestDispatcher("/result.jsp");
    view.forward(req, resp);
}

,转到 result.jsp 打印名字 (Evan)。这是它的外观图片(来源 Head First Servlets 和 JSP):

我通过调用 getAttribute() 知道 <jsp:useBean> returns 相同的 Person 对象,因为它们在相同的 请求范围 中。而在另一边,<jsp:getProperty> 将调用 findAttribute() 从字面上尝试查找值“person”的属性..并最终打印 Evan.

但是如果我不使用 <jsp:useBean> 怎么办?这是否意味着我无法在 request 范围内访问“person”属性?我的意思是它仍然会在那里,即使我没有使用 <jsp:useBean>. 所以为什么我必须在两个 id 中有相同的 value("person") <jsp:useBean> 中的 <jsp:getProperty> 中的 name?简单删除 <jsp:useBean> 会破坏我的程序。

知道 <jsp:getProperty> 调用 findAttribute(),如果只有一个属性(比如 attribute-name)不是更合乎逻辑吗?将用作在范围 page>request>session>application 中查找属性的参数?为什么我必须“绑定”这两个标签:<jsp:useBean><jsp:getProperty>?

你觉得下面的代码怎么样?

public class Main {
    public static void main(String[] args) {
        System.out.println(person);
    }
}

你一定猜对了不会编译成功.

现在,下面的代码呢?

public class Main {
    public static void main(String[] args) {
        Person person = new Person();// Declaring person
        System.out.println(person);
    }
}

当然会编译成功1因为现在编译器明白了person是什么

同样,使用

<jsp:getProperty name="person" property="name">

没有将 person 声明为

<!-- Declaring person -->
<jsp:useBean id="person" class="foo.Person" scope="request" />

编译不成功.


1 假设 Person.class 在那里。

TL;DR: 你应该记住你需要使用 <jsp:getProperty><jsp:useBean> 因为规范是这样说的。 <jsp:useBean> 需要将 bean 引入 JSP 处理器,然后 <jsp:getProperty> 才能使用它。

较长的解释:

Why I can't use <jsp:getProperty> without <jsp:useBean>?

因为它们“在某种程度上”旨在协同工作。我不知道为什么会这样决定(只有 JSP 规范的设计者才能回答这个问题),但是 the spec 本身对 <jsp:getProperty> 有这样的说法:

The object named by the name must have been “introduced” to the JSP processor using either the jsp:useBean action or a custom action with an associated VariableInfo entry for this name. If the object was not introduced in this manner, the container implementation is recommended (but not required) to raise a translation error, since the page implementation is in violation of the specification.

我说“有点”是为了一起工作而设计的,因为在某些情况下你可以使用 <jsp:getProperty> 而不使用 <jsp:useBean>,但是你必须配置 JSP 处理器来忽略 JSP.5.3 规范规则(对于允许的服务器)。

这不是很清楚,所以让我们看看一些代码会发生什么。

我有以下 JSP:

-------------------------------------------------------------------
<jsp:useBean id="person" class="test.Person" scope="application" />
+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
<jsp:getProperty name="person" property="name" />
-------------------------------------------------------------------

我使用了这些定界符,以便稍后可以在 JSP 生成的 servlet 中找到它们,它们在其中产生以下代码:

  out.write("\t\t-------------------------------------------------------------------\r\n");
  out.write("\t\t");
  test.Person person = null;
  synchronized (application) {
    person = (test.Person) _jspx_page_context.getAttribute("person", javax.servlet.jsp.PageContext.APPLICATION_SCOPE);
    if (person == null){
      person = new test.Person();
      _jspx_page_context.setAttribute("person", person, javax.servlet.jsp.PageContext.APPLICATION_SCOPE);
    }
  }
  out.write("\r\n");
  out.write("\t\t+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\r\n");
  out.write("\t\t");
  out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString((((test.Person)_jspx_page_context.findAttribute("person")).getName())));
  out.write("\r\n");
  out.write("\t\t-------------------------------------------------------------------\r\n");

如果您查看 <jsp:getProperty>,您会发现它转换为 test.Person:

org.apache.jasper.runtime.JspRuntimeLibrary.toString((((test.Person)_jspx_page_context.findAttribute("person")).getName()))

但是那是从哪里来的呢?在您的 <jsp:getProperty> 中,您指定了一个 bean 名称 (person) 和一个 属性 名称 (name),但没有 class。所以这些属性只会导致 findAttribute("person"),然后导致 getName()。 class 是从哪里拿来的?答案是,之前对 <jsp:useBean> 的调用在 JSP 处理器中引入了这个。

所以你必须调用 <jsp:useBean> 来在 JSP 处理器中引入 bean,这样当处理器看到 <jsp:getProperty> 它就知道它在处理什么。所以基本上,<jsp:useBean> 定义它,然后 <jsp:getProperty> 使用它。如果你不调用 <jsp:useBean><jsp:getProperty> 会尝试使用未定义的东西,JSP 处理器会报错,你会得到一个异常:

jsp:getProperty for bean with name 'person'. Name was not previously introduced as per JSP.5.3

但是如果你阅读规格,它会说:

[...] the container implementation is recommended (but not required) to raise a translation error [...]

如果您使用 Tomcat,例如,有一个 org.apache.jasper.compiler.Generator.STRICT_GET_PROPERTY 系统 属性 控制要求 <jsp:getProperty> 中使用的对象是以前 "引入”到 JSP 处理器(基本上,执行或不执行 JSP.5.3 规则)。例如,参见 Tomcat documentation page.

因此,如果我使用以下系统变量启动 Tomcat 服务器:

-Dorg.apache.jasper.compiler.Generator.STRICT_GET_PROPERTY=false

然后我可以在没有 <jsp:useBean> 的情况下使用 <jsp:getProperty>,前提是我以其他方式在范围内引入 person BEAN(比如从带有 request.setAttribute() 的 servlet,session.setAttribute()application.setAttribute() 以便 <jsp:getProperty> 可以执行 pageContext.findAttribute() 并查找名为 person 的 bean 并找到它。

如果您使用该系统 属性,则 <jsp:getProperty> 标记生成的输出会发生变化。它不再依赖于 <jsp:useBean> 并且删除了演员表:

out.write("\t\t+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++\r\n");
      out.write("\t\t");
      out.write(org.apache.jasper.runtime.JspRuntimeLibrary.toString(org.apache.jasper.runtime.JspRuntimeLibrary.handleGetProperty(_jspx_page_context.findAttribute("person"), "name")));
      out.write("\r\n");
      out.write("\t\t-------------------------------------------------------------------\r\n");

如果有人对所有这些混乱的展开方式感兴趣,class要查看的(对于 Tomcat 服务器)是:org.apache.jasper.compiler.Validatororg.apache.jasper.compiler.Generator .