我在理解 Head First Java Bean 解释时遇到的问题

My problem in understanding Head First's JavaBean explanation

我正在阅读 Head First Servlets 和 JSP,其中一个 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);
}

稍后,他们展示了如何使用 <jsp:useBean><jsp:useProperty> 来获取人名:

我想我明白了 - 如果 request 范围内没有属性,则意味着变量 person 将为空,这也意味着new Person 对象将使用默认构造函数创建。底线:我不会有我的属性。幸运的是,我之前在请求的范围内存储了 Person 对象,这意味着它会找到它。

所以我想尝试一下并稍微更改代码。我输入的不是 <jsp:useBean id="person" class="foo.Person" scope="request"/>,而是 <jsp:useBean id="person" class="foo.Person" scope="application"/>。请记住,我们的属性之前存储在 request 的范围内,而不是 application,这意味着 person 将是无效的。令我惊讶的是,它很容易找到属性并打印 Evan。这是怎么发生的,当书清楚地表明它将在 与我在范围的属性 中所写的相同范围内搜索属性时?

检查 JavaServer Pages™Specification

<jsp:getProperty> 的以下描述

The value of the name attribute in jsp:setProperty and jsp:getProperty will refer to an object that is obtained from the pageContext object through its findAttribute method.

现在,检查同一文档中 findAttribute 的描述:

Searches for the named attribute in page, request, session (if valid), and application scope(s) in order and returns the value associated or null.

因此,如果在 request 范围内找不到属性 person,搜索将扩大到 application 范围。

<jsp:useBean id="person" class="foo.Person" scope="application"/> 将创建一个新的 Person 并将其放入 application 范围(如果不存在),无论此属性是否已存在于 request 范围。

当您使用 <jsp:getProperty name="person" property="name"> 时,搜索属性 person 将在 page > request[=49 上执行=] > session(如果有效)> application scope(s) 按顺序和 find-first 方法,即一旦找到,就返回该值(即不再进行进一步搜索)。

因为你有两个 Person 属性名为 person 的对象:一个在 request 范围内的名称为 Evan,另一个为 new Person()application 范围内,request 范围内的那个作为 findAttribute 工作方式的结果返回。

要点是 <jsp:useBean> 如果在您指定的范围内找不到对象,也可以创建一个对象。这意味着:

  • 如果对象不在您所说的范围内,<jsp:useBean> 将创建一个新对象。
  • 如果对象在您所说的范围内,<jsp:useBean> 将使用该对象。

换句话说,您可以处理一个对象或两个对象,具体取决于您放置该对象的上下文以及您所说的上下文。

我知道,这令人困惑。所以让我们玩一些例子。我将屠杀 Person class 一点以显示对象的创建位置。

package test;

public class Person {
    private String name;

    public Person() {
        try {
            throw new RuntimeException("Just to see what's going on");
        } catch (RuntimeException ex) {
            System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>");
            for (StackTraceElement ste : ex.getStackTrace()) {
                System.out.println(ste.toString());
            }
            System.out.println("<<<<<<<<<<<<<<<<<<<<<<<");
        }
    }
    
    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
    
}

第一个例子:范围匹配

Servlet 内容:

public class TestController extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        
        session.setAttribute("person", new Person());
        
        RequestDispatcher requestDispatcher = request.getRequestDispatcher("/test.jsp");
        requestDispatcher.forward(request, response);
    }
}

test.jsp:

<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
    <body>
        <jsp:useBean id="person" class="test.Person" scope="session" />
    </body>
</html>

访问 servlet 将导致此输出:

>>>>>>>>>>>>>>>>>>>>>>>>>
test.Person.<init>(Person.java:8)
test.TestController.doGet(TestController.java:21)
javax.servlet.http.HttpServlet.service(HttpServlet.java:620)
javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:314)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
java.lang.Thread.run(Thread.java:744)
<<<<<<<<<<<<<<<<<<<<<<<

查看前两行,您将看到在 servlet 中只创建了一个对象。 <jsp:useBean> 只检索了这个。

第二个例子:范围不匹配

Servlet 保持不变:

public class TestController extends HttpServlet {
    private static final long serialVersionUID = 1L;

    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        HttpSession session = request.getSession();
        
        session.setAttribute("person", new Person());
        
        RequestDispatcher requestDispatcher = request.getRequestDispatcher("/test.jsp");
        requestDispatcher.forward(request, response);
    }
}

JSP现在使用应用程序范围,而不是实际人员所在的会话范围:

<%@ page contentType="text/html; charset=UTF-8" %>
<!DOCTYPE html>
<html>
    <body>
        <jsp:useBean id="person" class="test.Person" scope="application" />
    </body>
</html>

现在的结果是这样的:

>>>>>>>>>>>>>>>>>>>>>>>>>
test.Person.<init>(Person.java:8)
test.TestController.doGet(TestController.java:21)
javax.servlet.http.HttpServlet.service(HttpServlet.java:620)
javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:314)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
java.lang.Thread.run(Thread.java:744)
<<<<<<<<<<<<<<<<<<<<<<<

>>>>>>>>>>>>>>>>>>>>>>>>>
test.Person.<init>(Person.java:8)
org.apache.jsp.test_jsp._jspService(test_jsp.java:71)
org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:432)
org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:390)
org.apache.jasper.servlet.JspServlet.service(JspServlet.java:334)
javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:748)
org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:486)
org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:411)
org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:338)
test.TestController.doGet(TestController.java:24)
javax.servlet.http.HttpServlet.service(HttpServlet.java:620)
javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:314)
java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
java.lang.Thread.run(Thread.java:744)
<<<<<<<<<<<<<<<<<<<<<<<

您现在有两个对象。一个由 servlet 创建,如第一个示例,但另一个由 JSP 生成的 servlet 创建,因为 <jsp:useBean> 在应用程序范围内找不到对象,所以它使用class 属性创建一个。

造成混淆的原因是 <jsp:getProperty> 在范围 page > request > session > application 中搜索一个对象,直到找到一个。这与 <jsp:useBean> 所做的不同(如上面两个示例所述)。

如有疑问,请参阅 the JSP specs

话虽这么说,但请注意 <jsp:useBean><jsp:getProperty> 在实践中并没有被广泛使用。人们使用 JSP expression language, JSTL 或其他(更强大的)自定义标签。为了从书中学习并避免使用 scriptlet(这是一种不好的做法),这些可以帮助您入门,但实际上它们并不是人们真正喜欢的访问对象及其属性的方式。