你不知道的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. 如何保存/同步多架构容器 Docker 镜像

    前言 随着容器.芯片技术的进一步发展,以及绿色.节能.信创等方面的要求,多 CPU 架构的场景越来越常见.典型的应用场景包括: 信创:x86 服务器 + 鲲鹏 ARM 等信创服务器: 个人电脑:苹果 ...

  2. k8s 深入篇———— 守护容器[九]

    前言 守护容器,也叫做deamonset, 只做整理 正文 顾名思义,DaemonSet 的主要作用,是让你在 Kubernetes 集群里,运行一个 Daemon Pod. 所以,这个 Pod 有如 ...

  3. docker 应用篇————swarm[二十]

    前言 简单介绍一下swarm. 正文 前提,docker 安装. 有3台机器,全部按照了docker. 现在开始搭建集群. 首先需要初始化: 然后需要注入: 注入之后,那么需要就是启动节点加入进来,那 ...

  4. sweetviz工具避坑和简单使用

    网上关于sweetviz的文章比较少,有些坑这里说一下给大家避坑. 使用sweetviz遇到的错误如下: KeyError: "None of ['index'] are in the co ...

  5. vue3.0体验版生命周期

    使用方法:cnpm install --save @vue/composition-api在组件内引入 把上图的 onMounted 换成(2.6->3.0) beforeCreate-> ...

  6. 第三課:信道学习Source Connect Reader & Destinations File Writer

    第一步: 切换到主信道(Channels)界面,右键点击新建信道(New Channel) 第二步 : 下面是设置一些信道概要(Summary)信息 其中summary(概要) 界面主要包含 信道名称 ...

  7. Schedulerx2.0支持应用级别资源管理和任务优先级

    1. 前言 Schedulerx2.0是一套分布式的任务调度+计算框架.作为一套分布式计算引擎,用户经常需要资源管理的需求,当前schedulerx仅仅支持单个任务实例的管控(比如单机子任务并发数.拉 ...

  8. KubeVela:标准化的云原生平台构建引擎

    简介: 本文由"GO 开源说"第三期 KubeVela 直播内容修改整理而成,视频内容较长,本文内容有所删减和重构. KubeVela 的背景 KubeVela 是一个基于 Go ...

  9. 基于 Scheduled SQL 对 VPC FlowLog 实现细粒度时间窗口分析

    简介: 针对VPC FlowLog的五元组和捕获窗口信息,在分析时使用不同时间窗口精度,可能得到不一样的流量特征,本文介绍一种方法将原始采集日志的时间窗口做拆分,之后重新聚合为新的日志做分析,达到更细 ...

  10. Go语言入门分享

    ​简介: Go语言出自Ken Thompson.Rob Pike和Robert Griesemer之手,起源于2007年,并在2009年正式对外发布.Go的主要目标是"兼具Python等动态 ...