你不知道的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. HarmonyOS多媒体框架介绍

    原文:https://mp.weixin.qq.com/s/_2LHv7s7X4IJMCPU8hcCeg,点击链接查看更多技术内容.   随着科技进步,我们的生活发生了翻天覆地的变化.过去几年音视频技 ...

  2. Spring-Cloud 组件之 Spring Cloud Eureka:服务注册与发现

    Spring Cloud Eureka:服务注册与发现 SpringCloud学习教程 SpringCloud Spring Cloud Eureka是Spring Cloud Netflix 子项目 ...

  3. VScode 使用emmet

    背景 在很多的编辑场合,很多时候回出现很多逻辑性的问题.可能觉得html是一门没有逻辑的语言,实际上,它是有一定的思想编辑的.后来出现了emmet,这个不仅仅是一种快捷方式,同时也是一种思考方式. 解 ...

  4. 技术人如何"结构化"高效思考?

    1 前言 你是否在解决问题时,常常觉得脑子很乱,很多零散的信息迸发出来,但就是无法形成解决方案. 之所以这样,原因就在于,我们的大脑处理不了太多零散而复杂的信息.人类大脑在处理信息的时候,有两个规律: ...

  5. 力扣71(java)-简化路径(中等)

    题目: 给你一个字符串 path ,表示指向某一文件或目录的 Unix 风格 绝对路径 (以 '/' 开头),请你将其转化为更加简洁的规范路径. 在 Unix 风格的文件系统中,一个点(.)表示当前目 ...

  6. OceanBase初体验之从MySQL迁移数据到OceanBase集群

    前置条件 MySQL 环境 OceanBase 环境 测试用的表结构和一些数据 先在源端 MySQL 用如下脚本创建测试表,以及写入10000条数据用于迁移测试. use test; CREATE T ...

  7. [FAQ] Fontconfig error: Cannot load default config file

      在使用一些第三方库时(比如生成图片),如果出现此提示,说明系统里缺少字体. 在 Ubuntu 上可以运行:$ apt-get install fontconfig 在 Centos 上可以运行:$ ...

  8. dotnet 修复多框架 TargetFrameworks 包含不受支持平台导致构建失败

    本文将告诉大家如何修复 dotnet 项目里的多框架 TargetFrameworks 如果包含了当前系统无法支持的平台时,如何进行跳过.解决在 Linux 平台构建时提示 Mac Catalyst ...

  9. WPF 通过 WindowsAppSDK 使用 WinRT 的手写识别功能

    本文告诉大家如何在基于 .NET 6 的 WPF 使用 WinRT 的手写识别功能 在开始之前需要先创建 WPF 项目,创建完成之后,可替换 csproj 项目文件为以下代码,用来安装初始化环境 &l ...

  10. 【爬虫GUI】YouTube评论采集软件,突破反爬,可无限爬取!

    目录 一.背景介绍 1.1 软件说明 1.2 效果演示 二.科普知识 2.1 关于视频id 2.2 关于评论时间 三.爬虫代码 3.1 界面模块 3.2 爬虫模块 3.3 日志模块 四.获取源码及软件 ...