你不知道的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项目

目录结构如下

修改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

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

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

打开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>

但是,我们可以通过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>

我们查看对应的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 文件

我们找到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>

我们看到符合我们的预期,那我们怎么从这里面获取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>

成功获取到了值。

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

回顾之前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鸭。

总结

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等。

你不知道的PageContext的更多相关文章

  1. MySQL 系列(三)你不知道的 视图、触发器、存储过程、函数、事务、索引、语句

    第一篇:MySQL 系列(一) 生产标准线上环境安装配置案例及棘手问题解决 第二篇:MySQL 系列(二) 你不知道的数据库操作 第三篇:MySQL 系列(三)你不知道的 视图.触发器.存储过程.函数 ...

  2. MySQL 系列(二) 你不知道的数据库操作

    第一篇:MySQL 系列(一) 生产标准线上环境安装配置案例及棘手问题解决 第二篇:MySQL 系列(二) 你不知道的数据库操作 本章内容: 查看\创建\使用\删除 数据库 用户管理及授权实战 局域网 ...

  3. 《你不知道的JavaScript》整理(二)——this

    最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,这次研究了一下“this”. 当一个函数被调用时,会创建一个活动记录(执行上下文). 这个记录会包含函 ...

  4. 《你不知道的JavaScript》整理(一)——作用域、提升与闭包

    最近在读一本进阶的JavaScript的书<你不知道的JavaScript(上卷)>,里面分析了很多基础性的概念. 可以更全面深入的理解JavaScript深层面的知识点. 一.函数作用域 ...

  5. 你不知道的Javascript(上卷)读书笔记之一 ---- 作用域

    你不知道的Javascript(上卷)这本书在我看来是一本还不错的书籍,这本书用比较简洁的语言来描述Js的那些"坑",在这里写一些博客记录一下笔记以便消化吸收. 1 编译原理 在此 ...

  6. JSP中的 HttpSession、pageContext对象

    pageContext 隐含对象对应javax.servlet.jsp.PageContext,都自动的被加入至pageContext中, 您可以由它来取得与JSP相关的对应之Servlet对象,像是 ...

  7. page、pageContext、servletContext的区别

    ServletContext是容器上下文,指当前的一个web应用的上下文 JSP网页本身,page对象是当前页面转换后的Servlet类的实例.从转换后的Servlet类的代码中,可以看到这种关系:O ...

  8. PageContext

    pageContext对象 pageContext对象是JSP中很重要的一个内置对象,不过在一般的JSP程序中,很少用到它,所以知道request对象.response对象的人比较多,知道pageCo ...

  9. [No00002E]关于大数据,你不知道的6个迷思

    还是那个观点:计算机,编程语言,互联网,大数据等等都只是工具! 导语:看过美剧<纸牌屋>没?知道这部"白宫甄嬛传"为什么会火吗?靠的是大!数!据! 过去两年,在 Net ...

  10. ${pageContext.request.contextPath}无效

    发现在Tomcat7.0.58,在jsp页面使用${pageContext.request.contextPath}获取不到项目名称,网上找了很多答案试了都无效: 把Tomcat版本换成Tomcat7 ...

随机推荐

  1. Spring框架之IOC和AOP底层原理

    ​1.1简介 Spring:春天-->软件行业的春天 2002,首次推出了Spring框架的雏:interface21框架! Spring框架即以interface21框架为基础,经过重新设计, ...

  2. Mysql之innodb架构

    Innodb存储引擎的架构 内存结构 Bufer Pool 缓冲池是主内存中的一个区域,里面可以缓存磁盘上经常操作的真实数据,在执行增删改查操作时,先操作缓冲池中的数据(若缓冲池没有数据,则从磁盘加载 ...

  3. Tomcat启动闪退的10个解决小技巧

    引言 大家好!在我们日常开发中,使用Tomcat作为Web服务器是相当常见的. 然而,遇到Tomcat启动后立即闪退的问题也不是什么稀罕事. 这种情况可能会让人感到困惑和沮丧,特别是当你急需完成一个项 ...

  4. redis 简单整理——主从拓扑图[二十二]

    前言 Redis的复制拓扑结构可以支持单层或多层复制关系,根据拓扑复杂性 可以分为以下三种:一主一从.一主多从.树状主从结构,下面分别介绍. 正文 一主一从结构 一主一从结构是最简单的复制拓扑结构,用 ...

  5. Taurus.MVC 性能压力测试(ap 压测 和 linux 下wrk 压测):.NET 版本

    前言: 上次发布了:Taurus.MVC 性能压力测试(ap 压测 和 linux 下wrk 压测):.NET Core 版本 今天计划准备压测一下 .NET 版本,来测试并记录一下 Taurus.M ...

  6. 深度解读《深度探索C++对象模型》之拷贝构造函数

    接下来我将持续更新"深度解读<深度探索C++对象模型>"系列,敬请期待,欢迎关注!也可以关注公众号:iShare爱分享,自动获得推文. 写作不易,请有心人到我的公众号上 ...

  7. 如何实现数据库数据到Abp vnext实体对象的同步?以及代码生成工具

    在采用了EF Core的Code First方式下,如果你在数据库中直接添加了新表或存储过程,你需要在项目代码中手动反向工程这些数据库的更改,以保持Code First的代码与数据库同步.这种情况可以 ...

  8. 【2021.6.26 NOI模拟】Problem B. 简单题 another solution

    Problem Description Input 从文件 b.in 中读入数据. 一个正整数 n. Output 输出到文件 b.out 中. 一个整数表示答案. Sample Data Input ...

  9. 剑指offer42(Java)-连续子数组的最大和(简单)

    题目: 输入一个整型数组,数组中的一个或连续多个整数组成一个子数组.求所有子数组的和的最大值. 要求时间复杂度为O(n). 示例1: 输入: nums = [-2,1,-3,4,-1,2,1,-5,4 ...

  10. 力扣532(java&python)-数组中的 k-diff 数对(中等)

    题目: 给你一个整数数组 nums 和一个整数 k,请你在数组中找出 不同的 k-diff 数对,并返回不同的 k-diff 数对 的数目. k-diff 数对定义为一个整数对 (nums[i], n ...