你不知道的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. HDC2021技术分论坛:吐司盒子?芝士码?HarmonyOS音视频测试来啦

    作者:lifusheng,用户体验技术专家 当下,音视频无处不在,很多设备和应用都涉及音视频.因而,对于HarmonyOS开发者们来说,如何对鸿蒙生态产品进行音视频测试是一个非常重要的问题. 华为Ha ...

  2. Spring Cloud Sleuth:分布式请求链路跟踪

    Spring Cloud Sleuth:分布式请求链路跟踪 SpringCloud学习教程 SpringCloud Spring Cloud Sleuth 是分布式系统中跟踪服务间调用的工具,它可以直 ...

  3. mm系列权重文件瘦身

    瘦身脚本: (会在resnet50.pth文件的同级目录下生成一个resnet50_thin.pth) import os import torch root_dir = os.getcwd() de ...

  4. webpack中常见的Plugin?解决了什么问题?

    一.是什么 Plugin(Plug-in)是一种计算机应用程序,它和主应用程序互相交互,以提供特定的功能 是一种遵循一定规范的应用程序接口编写出来的程序,只能运行在程序规定的系统下,因为其需要调用原纯 ...

  5. 深入了解PBKDF2:密码学中的关键推导函数

    title: 深入了解PBKDF2:密码学中的关键推导函数 date: 2024/4/20 20:37:35 updated: 2024/4/20 20:37:35 tags: 密码学 对称加密 哈希 ...

  6. three.js使用Instanced Draw+Frustum Cull+LOD来渲染大场景(开源)

    大家好,本文使用three.js实现了渲染大场景,在移动端也有较好的性能,并给出了代码,分析了关键点,感谢大家~ 关键词:three.js.Instanced Draw.大场景.LOD.Frustum ...

  7. 力扣383(java&python)-赎金信(简单)

    题目: 给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成. 如果可以,返回 true :否则返回 false . m ...

  8. 深度解析|基于 eBPF 的 Kubernetes 一站式可观测性系统

    ​简介:阿里云 Kubernetes 可观测性是一套针对 Kubernetes 集群开发的一站式可观测性产品.基于 Kubernetes 集群下的指标.应用链路.日志和事件,阿里云 Kubernete ...

  9. DataX在数据迁移中的应用

    简介: DataX在数据迁移中的应用 1. DataX定义 首先简单介绍下datax是什么.DataX是阿里巴巴集团内被广泛使用的离线数据同步工具/平台,实现包括 MySQL.Oracle.SqlSe ...

  10. 一年增加 1.2w 星,Dapr 能否引领云原生中间件的未来?

    简介: 虽然 Dapr 在国外有很高的关注度,但在国内知名度非常低,而且现有的少量 Dapr 资料也偏新闻资讯和简单介绍,缺乏对 Dapr 的深度解读.在 Dapr v1.0 发布之际,我希望可以通过 ...