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

白昼怎懂夜的黑 提交于 2021-02-05 07:25:29

问题


Say there is servlet that has code:

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);
}

, that goes to result.jsp to print given name (Evan). Here is a picture of how it would look (source Head First Servlets and JSP):

I know that <jsp:useBean> returns same Person object by calling getAttribute()- since they are in same request scope. While on the other side, <jsp:getProperty> will call findAttribute() to literally try to find attribute of value "person".. and eventually print Evan.

But what if I didn't use <jsp:useBean>? Does that mean I couldn't access "person" attribute at scope request? I mean it would still be there, even if I didn't use <jsp:useBean>. So why I must have same value("person") inside both id in <jsp:useBean> and name inside <jsp:getProperty>? Simple removing <jsp:useBean> breaks my program.

Knowing that <jsp:getProperty> calls findAttribute(), wouldn't it be more logical if there was a single attribute (like attribute-name), that will be used as an argument to find attributes in scopes page>request>session>application? Why I must "tie" those two tags: <jsp:useBean> and <jsp:getProperty>?


回答1:


What do you think of the following code?

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

You must have already correctly guessed that it won't be compiled successfully.

Now, what about the following code?

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

Of course, it will be compiled successfully1 because now the compiler understands what person is.

Similarly, using

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

without declaring person as

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

won't be compiled successfully.


1 Assuming Person.class is there.




回答2:


TL;DR: You should just remember that you need to use <jsp:getProperty> with <jsp:useBean> because the specification says so. <jsp:useBean> needs to introduce the bean to the JSP processor, before <jsp:getProperty> can use it.

The longer explanation:

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

Because they were "somewhat" designed to work together. I don't know why it was decided like that (only the designers of the JSP specification can answer that), but the spec itself has this to say about <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.

I say "somewhat" designed to work together, because in some cases you can use <jsp:getProperty> without <jsp:useBean>, but you have to configure the JSP processor to ignore the JSP.5.3 specification rule (for servers that allow that).

This is not very clear, so let's see what happens with some code.

I have the following JSP:

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

I used those delimiters so that I can later find them in the JSP generated servlet, where they result in this code:

  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");

If you look at the <jsp:getProperty> you can see that it does a cast to test.Person:

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

But where did that came from? In your <jsp:getProperty> you specify a bean name (person) and a property name (name), but no class. So those attributes only result in findAttribute("person") and then in getName(). Where was the class taken from? And the answer is, the previous call to <jsp:useBean> introduced this in the JSP processor.

So you have to call <jsp:useBean> to introduce the bean in the JSP processor so that when the processor sees the <jsp:getProperty> it knows what it's dealing with. So basically, <jsp:useBean> defines it, then <jsp:getProperty> uses it. If you don't call <jsp:useBean>, the <jsp:getProperty> will try to use something undefined, the JSP processor will complain, and you get back an exception of:

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

But if you read the specs, it says:

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

If you use Tomcat, for example, there is a org.apache.jasper.compiler.Generator.STRICT_GET_PROPERTY system property that controls the requirement to have the object used in <jsp:getProperty> to be previously "introduced" to the JSP processor (basically, enforcing or not the JSP.5.3 rule). See for example this Tomcat documentation page.

So, if I start my Tomcat server with a system variable of:

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

Then I can use <jsp:getProperty> without <jsp:useBean>, PROVIDED THAT I INTRODUCE THE person BEAN IN SCOPE SOME OTHER WAY (like from a servlet with request.setAttribute(), session.setAttribute() or application.setAttribute() so that <jsp:getProperty> can do a pageContext.findAttribute() and look for a bean named person and find it.

If you use that system property, then the output generated by the <jsp:getProperty> tag changes. It no longer depends on <jsp:useBean> and the cast is removed:

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");

If someone is interested in how all of this mess unfolds, the classes to look at (for a Tomcat server) are: org.apache.jasper.compiler.Validator and org.apache.jasper.compiler.Generator.



来源:https://stackoverflow.com/questions/65524361/why-i-cant-use-jspgetproperty-without-jspusebean

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!