你不知道的PageContext

你不知道的PageContext

最近在文艺复兴,学习JSP和Servlet,此文为笔者学习记录。

本文分为以下几个部分:

前言

环境搭建

正文

总结

前言

在我们使用的项目中,存储数据最常用的非 SessionCookie 莫属了,但实际上还有 PageContext、ServletContext

  • PageContext :作用于每个 page 中
  • ServletContext :作用于服务器中,保存数据在服务器中,例如用户 A 在当前页面设置了值,用户 B 可以在其他页面获取该值,可以看成 共享 Session。

一些题外话:

​ jsp 最后会被转义成 class 文件进行输出,html代码会被 out.print()出去,java 代码会原封不动的编译。

环境搭建

笔者使用的是 idea 2021.2 版本(版本不一样编译后class文件位置可能不一样),JDK 1.8。

使用 idea 创建一个maven javaweb项目

1

目录结构如下

image-20210806134744595

修改index.jsp,写入java代码

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<%
    String a = "";
%>
</body>
</html>

添加tomcat,启动项目,访问首页,查看被 tomcat 编译过得 java、class文件(只有访问过该jsp文件,才会有编译过得class文件,访问首页就是访问index)

我(当前版本:idea 2021.02版本)的被编译的文件路径:C:\用户\当前用户\AppData\Local\JetBrains\idea\tomcat\项目\work\

C:\Users\yb\AppData\Local\JetBrains\IntelliJIdea2021.2\tomcat\06562583-39ad-4361-959e-eedb21db4b9a\work\Catalina\localhost\page_context_war\org\apache\jsp

image-20210806135323340

老版本的可能在 C:\用户\当前用户\.IntelliJIdea版本\system\tomcat\项目\work\

C:\Users\yb\.IntelliJIdea2018.3\system\tomcat\Unnamed_test\work\Catalina\localhost\test_war\org\apache\jsp

image-20210809140548241

打开index_jsp.java文件,找到 _jspService 中会存在String a = "";正是上方写的java代码。

正文

首先pom文件引入jar包

<dependency>
    <groupId>javax.servlet</groupId>
    <artifactId>javax.servlet-api</artifactId>
    <version>4.0.1</version>
</dependency>

<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>jsp-api</artifactId>
    <version>2.2</version>
</dependency>

<dependency>
    <groupId>javax.servlet.jsp</groupId>
    <artifactId>javax.servlet.jsp-api</artifactId>
    <version>2.3.3</version>
</dependency>

回归正题,我们一般Session存的,会用Session取值,ServletContext存值,会用ServletContext取,就像这样

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<%
    pageContext.setAttribute("page", "pageContext");
    request.setAttribute("req", "request");
    session.setAttribute("session", "session");
    application.setAttribute("app", "servletContext");
%>
<%=pageContext.getAttribute("page")%>
<br>
<%=request.getAttribute("req")%>
<br>
<%=session.getAttribute("session")%>
<br>
<%=application.getAttribute("app")%>
<br>
</body>
</html>

image-20210806140504508

但是,我们可以通过PageContext获取所有值,使用pageContext.findAttribute

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<%
    pageContext.setAttribute("page", "pageContext");
    request.setAttribute("req", "request");
    session.setAttribute("session", "session");
    application.setAttribute("app", "servletContext");
%>
<%=pageContext.findAttribute("page")%>
<br>
<%=pageContext.findAttribute("req")%>
<br>
<%=pageContext.findAttribute("session")%>
<br>
<%=pageContext.findAttribute("app")%>
<br>
</body>
</html>

image-20210806140504508

我们查看对应的jsp编译的class文件

response.setContentType("text/html;charset=UTF-8");
PageContext pageContext = _jspxFactory.getPageContext(this, request, response, (String)null, true, 8192, true);
_jspx_page_context = pageContext;
ServletContext application = pageContext.getServletContext();
pageContext.getServletConfig();
session = pageContext.getSession();
out = pageContext.getOut();
//...
pageContext.setAttribute("page", "pageContext");
request.setAttribute("req", "request");
session.setAttribute("session", "session");
application.setAttribute("app", "servletContext");
out.write(10);
out.print(pageContext.findAttribute("page"));
out.write("\n");
out.write("<br>\n");
out.print(pageContext.findAttribute("req"));
out.write("\n");
out.write("<br>\n");
out.print(pageContext.findAttribute("session"));
out.write("\n");
out.write("<br>\n");
out.print(pageContext.findAttribute("app"));
out.write("\n");
out.write("<br>\n");

我们看到 session、servletContext 是从pageContext中获得,这就比较容易解释pageContext为什么能得到session的东西了,我们再看看pageContext.findAttribute究竟是什么。

ctrl 按进去,并不能看到实现类,只能看到接口,查阅tomcat官方api,可以找到其实现类

org.apache.jasper.runtime.PageContextImpl

我们解压tomcat目录下lib文件夹中的 jasper.jar 文件,找到 PageContextImpl.class 文件

image-20210806165633723

我们找到findAttribute方法

public Object findAttribute(String name) {
    //如果name不存在,抛出异常
    if (name == null) {
        throw new NullPointerException(Localizer.getMessage("jsp.error.attribute.null_name"));
    } else {
        //首先自己 pageContext 查看自己有没有名字为 name 的东西,有则返回
        Object o = this.attributes.get(name);
        if (o != null) {
            return o;
        } else {
            //没有再查看request的,然后session的,最后ServletContext
            o = this.request.getAttribute(name);
            if (o != null) {
                return o;
            } else {
                if (this.session != null) {
                    try {
                        o = this.session.getAttribute(name);
                    } catch (IllegalStateException var4) {
                    }

                    if (o != null) {
                        return o;
                    }
                }

                return this.context.getAttribute(name);
            }
        }
    }
}

我们发现 pageContext 是一步一步往上寻找的,首先从 pagecontext 中获取,没有再从request中获取,再session,最后servletContext获取,是一步一步往上寻找,我们可以测试一下。

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<%
    session.setAttribute("test", "测试session");
    request.setAttribute("test", "测试request");
%>
<%=pageContext.findAttribute("test")%>
<br>
<%=pageContext.findAttribute("test")%>
</body>
</html>

image-20210806170233996

我们看到符合我们的预期,那我们怎么从这里面获取session的值呢?(不通过getsession方法)

我们查看Structure发现有个 getAttribute(String name, int scope)

public Object getAttribute(String name, int scope) {
    if (name == null) {
        throw new NullPointerException(Localizer.getMessage("jsp.error.attribute.null_name"));
    } else {
        //不同scope对应不同的方法
        switch(scope) {
            case 1:
                return this.attributes.get(name);
            case 2:
                return this.request.getAttribute(name);
            case 3:
                if (this.session == null) {
                    throw new IllegalStateException(Localizer.getMessage("jsp.error.page.noSession"));
                }

                return this.session.getAttribute(name);
            case 4:
                return this.context.getAttribute(name);
            default:
                throw new IllegalArgumentException(Localizer.getMessage("jsp.error.page.invalid.scope"));
        }
    }
}

通过第二个参数来修改来源,那我们修改index.jsp文件

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<body>
<%
    session.setAttribute("test", "测试session");
    request.setAttribute("test", "测试request");
%>
<%=pageContext.getAttribute("test",2)%>
<br>
<%=pageContext.getAttribute("test",3)%>
</body>
</html>

image-20210806171445707

成功获取到了值。

里面还有一些方法,读者感兴趣可以自行阅读。

回顾之前jsp编译成class文件的代码

PageContext pageContext = _jspxFactory.getPageContext(this, request, response, (String)null, true, 8192, true);

我们看到pageContext是 jspFactory 获取到的,jspFactoryImpl 也是和PageContext实现类在同一个目录下,一步步查看最后调用了 pageContext.initialize 方法,部分参数是传递进来的,所以 pageContext 的request、session等不是传进来的,就是携带进来的,这就解释了为什么用的request等都是一个东西了。

public void initialize(Servlet servlet, ServletRequest request, ServletResponse response, String errorPageURL, boolean needsSession, int bufferSize, boolean autoFlush) throws IOException {
        this.servlet = servlet;
        this.config = servlet.getServletConfig();
        this.context = this.config.getServletContext();
        this.errorPageURL = errorPageURL;
        this.request = request;
        this.response = response;
        this.applicationContext = JspApplicationContextImpl.getInstance(this.context);
        if (request instanceof HttpServletRequest && needsSession) {
            this.session = ((HttpServletRequest)request).getSession();
        }

        if (needsSession && this.session == null) {
            throw new IllegalStateException(Localizer.getMessage("jsp.error.page.sessionRequired"));
        } else {
            this.depth = -1;
            if (bufferSize == -1) {
                bufferSize = 8192;
            }

            if (this.baseOut == null) {
                this.baseOut = new JspWriterImpl(response, bufferSize, autoFlush);
            } else {
                this.baseOut.init(response, bufferSize, autoFlush);
            }

            this.out = this.baseOut;
            this.setAttribute("javax.servlet.jsp.jspOut", this.out);
            this.setAttribute("javax.servlet.jsp.jspRequest", request);
            this.setAttribute("javax.servlet.jsp.jspResponse", response);
            if (this.session != null) {
                this.setAttribute("javax.servlet.jsp.jspSession", this.session);
            }

            this.setAttribute("javax.servlet.jsp.jspPage", servlet);
            this.setAttribute("javax.servlet.jsp.jspConfig", this.config);
            this.setAttribute("javax.servlet.jsp.jspPageContext", this);
            this.setAttribute("javax.servlet.jsp.jspApplication", this.context);
        }
    }

其中有个servlet参数,明明在 jsp 编译后的class文件并没有找到servlet,为什么传个this能传servlet,其实 extends HttpJspBase (org.apache.jasper.runtime.HttpJspBase),我们查看HttpJspBase就可以找到他是继承 HttpServlet的,这就不难解释为何传 this 是servlet了,因为这个jsp本身就是servlet鸭。

image-20210809085309946

总结

PageContext 为页面上下文对象,jsp编译成java再编译成class文件中,获取request等就是从 PageContext 中获取的,可以把PageContext看成一个容器。

PageContext 中含有 session、request 等,可以利用 getAttribute(String name, int scope)、setAttribute(String name, Object o, int scope) 等方法,根据 scope 不同获取 request、session等。

posted @ 2021-08-09 09:55  抱糖果彡  阅读(371)  评论(0编辑  收藏  举报