
开头JSP的九大内置对象可以说是Java Web入门阶段最绕不开的一块内容。不管你是刚学完Servlet、准备做第一个学生信息管理系统还是在学校课程设计里用JSP写个人展示页面只要你打开一个.jsp文件就会发现里面莫名其妙能用request、session、out这些变量——没人给你new也没人给你赋值直接拿来就能用这就是内置对象。我第一次接触的时候也觉得很神奇后来才明白这其实是JSP为了降低开发门槛把Servlet里那一堆高频对象全部提前准备好了。这篇内容适合两类人看一是刚学完Java基础、正在迈入Web开发门槛的初学者想系统搞懂JSP九大内置对象到底是什么、怎么用二是已经能写简单JSP页面但遇到各种奇怪报错、不知道该用哪个对象存数据、纠结IDEA里JSP文件怎么创建、想看看JSP编译后到底变成什么Java类的朋友。这篇文章会把这些事全部讲透包括每个对象的生命周期、核心方法、典型使用场景以及我在实际项目中踩过的一些坑。我不打算给你背一遍官方文档式的定义而是站在当年我学JSP时最希望有人告诉我的角度把九大内置对象一个一个拆开讲清楚再拿真实场景串一遍。如果你能把这篇内容真正吃透后续去学Spring MVC、Thymeleaf这些框架时会发现很多设计思路其实都是在JSP内置对象这套玩法上演进的。1. 内容整体设计与思路拆解1.1 先搞清楚JSP为什么要有内置对象你可能会问JSP不就是一个写HTML的模板页面吗怎么还有一堆对象这里要把JSP的本质说清楚。JSP文件在第一次被访问时会被容器比如Tomcat翻译成一个Java类这个类本质上就是一个Servlet。也就是说你写在JSP里的所有内容最终都会被放到Servlet的_jspService方法里去执行。问题来了如果一个JSP页面要接收用户提交的表单数据、要把数据存到Session、要输出HTML内容、要拿到应用的全局配置……这些操作在Servlet里统统需要先拿到对应的对象。如果没有内置对象你每写一个JSP页面都得先写一坨HttpServletRequest request ...这种代码页面就会变得非常啰嗦。所以JSP的设计者做了一个很朴实的决定把Servlet里最常用的那几个对象在_jspService方法开头全部初始化好变量名也是固定的你在页面上直接用就行。这就是内置对象的由来。想验证我说的这点很简单找到Tomcat的work目录打开JSP编译出来的xxx_jsp.java文件你会看到_jspService方法里清清楚楚地声明了request、response、pageContext、session、application、out、config、page这几个变量exception则比较特殊只在异常页面里才会赋值。建议你现在就去自己项目的work目录看一眼这不叫以后再说这叫马上动手。1.2 九大内置对象的整体分类九大内置对象听起来很多但分类之后非常清晰。按作用域分pageContext、request、session、application这4个对象负责在不同范围内存取数据这是JSP页面传值、状态保持的核心。按功能分out负责输出内容response负责设置响应头和重定向config读取Servlet配置参数page代表当前JSP页面这个对象实例exception处理页面异常。这里面最核心的其实是4个作用域对象因为90%的JSP页面数据流转都靠它们。我见过很多初学者搞不清request和session的区别写了个登录功能用户数据往request里一存重定向之后就取不到了然后一脸懵。这就是没搞懂作用域生命周期。对象类型作用范围生命周期pageContextPageContext当前页面页面加载到响应结束requestHttpServletRequest一次请求请求发起到响应完成sessionHttpSession一次会话会话建立到超时/销毁applicationServletContext整个应用应用启动到停止outJspWriter当前页面输出页面执行期间responseHttpServletResponse当前响应响应处理期间configServletConfig单个ServletServlet初始化后pageObject当前Servlet当前页面页面执行期间exceptionThrowable异常页面仅isErrorPage页面中这张表建议你保存下来后面写代码拿不准的时候翻一翻。1.3 从学生信息管理系统看内置对象怎么用很多学校课程设计都会让学生用JSP写一个学生信息管理系统。这个项目用到的功能几乎就是九大内置对象的百科全书。登录功能要用request.getParameter接收账号密码验证通过后用session.setAttribute保存登录状态列表页面用request.setAttribute配合request.getRequestDispatcher转发传递查询结果退出登录用session.invalidate销毁会话统计在线人数用application存一个计数器分页查询需要从request里拿页码参数页面异常统一跳转到exception页面显示错误信息。我拿这个项目举例是想说一个道理内置对象不是孤立的语法知识点它们是为了解决Web开发里的真实问题而设计的。你带着登录状态放哪查询结果怎么带过去全局配置怎么读这些问题去学内置对象会轻松很多。后面我会专门写一个小型个人信息展示页面的完整实现让你看看这些对象协同工作的真实样子。2. 九大内置对象逐个拆解2.1 四大作用域对象pageContext、request、session、application先说request。它的本质是客户端请求的一个封装所有表单提交的数据、URL上的参数、请求头信息都在里面。最常用的方法就是getParameter(name)返回的是String类型。如果前端传了多个同名参数用getParameterValues(hobby)拿数组。还有个非常关键的用途是配合request.setAttribute和getRequestDispatcher做服务端转发把数据带到下一个JSP页面。这里必须强调一个转发和重定向的区别。我在新手期就吃过亏登录成功后习惯用response.sendRedirect(index.jsp)结果在index.jsp里用request.getAttribute(user)取登录用户取出来永远是null。原因很简单重定向相当于浏览器重新发了一次新的请求原来的request已经销毁了里面的属性自然没了。如果要用转发代码是这样写的% request.setAttribute(user, user); request.getRequestDispatcher(index.jsp).forward(request, response); %这样index.jsp里request.getAttribute(user)才能拿到值。转发是服务端内部的跳转浏览器地址栏不变重定向是告诉浏览器重新访问新的URL浏览器地址栏会变原请求上下文丢失。然后说session。它是会话级别的对象从用户第一次访问服务器开始创建直到用户关闭浏览器其实不一定立刻销毁、超过超时时间Tomcat默认30分钟、或手动调用invalidate()。最典型的用法是存登录状态、购物车数据、用户偏好设置。session.setAttribute(loginUser, user)存进去其他页面session.getAttribute(loginUser)就能取到不需要每次都传参。我见过不少人滥用session什么数据都往session里塞。用户的选择列表、临时查询条件、页面上的小状态也放进session结果一个session里堆了几十个key。内存负担还是小事更麻烦的是并发场景下用户多开标签页所有标签页共享同一个session数据互相覆盖排查问题的时候欲哭无泪。经验是只有跨页面都需要、且和用户身份强相关的数据才放session其他数据优先用request或者pageContext。再说application。它代表整个Web应用生命周期从应用启动到应用停止所有用户共享同一个实例。典型用途是保存全局计数器比如网站总访问量或者读取全局配置项。注意它天然有并发问题多个用户同时修改application里的数据会导致数据不一致写入的时候最好加同步控制。最后说pageContext。它是当前页面的上下文对象作用域最小。它的一个特殊能力是可以操作其他三个作用域的对象因为它内部持有request、session、application的引用。pageContext.setAttribute(key, value, PageContext.SESSION_SCOPE)可以直接往session里存值。它还提供了一些便捷方法比如pageContext.forward(xxx.jsp)可以直接转发pageContext.include(xxx.jsp)可以包含其他页面。不过实际开发中直接操作pageContext的场景不多了解它能做什么就够了。pageContext与四大作用域的关系理解pageContext相当于一个总代理你通过它能触达当前页面、请求、会话、应用四个层级的存储空间。学习时可以把这四个作用域想象成四个不同大小的盒子页面盒子最小、应用盒子最大数据放哪一个盒子就决定了它能被哪些页面访问到。很多时候你感觉取不到值本质上是放错了盒子。2.2 两大输出对象out与responseout是JspWriter类型负责向客户端输出内容。你写的那些HTML静态内容最后都是通过out.write输出到浏览器的。在JSP脚本里用out.print(hello)就会在响应内容里追加这段字符串。它和response.getWriter()的这个Writer容易弄混这两者输出的目标是一样的都是页面的响应体但JSP容器会先把out的内容缓冲起来最后合并写入响应。如果混用有可能出现顺序错乱的问题所以在一个JSP页面里不要同时用out.print和response.getWriter().write。response是HttpServletResponse类型它的核心职责是设置响应的相关信息除了sendRedirect这个高频方法还有几个实用操作setContentType(text/html;charsetUTF-8)设置响应编码setHeader(Refresh, 3;urlindex.jsp)实现页面定时跳转addCookie(new Cookie(key, value))向浏览器写入Cookie。这些操作如果不用response光靠JSP标签是做不了的。一个常见的实际场景用户提交表单后为了防止刷新页面重复提交你会用response.sendRedirect跳转到另一个页面把上次的POST请求断开。这个思路其实是Post/Redirect/Get模式在早期JSP项目里非常普遍。2.3 三大基础对象page、config、exceptionpage对象代表当前JSP页面编译后的Servlet实例。在JSP页面里page和Java代码里的this基本等价。直接操作page对象的场景非常少我写了好几年JSP几乎没在页面里用过page但需要知道它存在因为它提醒你一个本质JSP页面本质上是一个Servlet对象。config是ServletConfig类型封装了当前Servlet也就是JSP页面在web.xml里的初始化配置。常用的方法是getInitParameter(name)读取配置的初始化参数。如果你的JSP页面需要在运行时读取一段配置又不想硬编码在页面里可以在web.xml里配置init-paramservlet servlet-nameconfigDemo/servlet-name jsp-file/configDemo.jsp/jsp-file init-param param-nameadminEmail/param-name param-valueadminexample.com/param-value /init-param /servlet然后在JSP页面里通过config.getInitParameter(adminEmail)获取值。这种方式把配置和代码分离改配置文件不用重新编译页面是个好习惯。exception是Throwable类型它比较特殊只有当前页面设置了% page isErrorPagetrue %时才可以使用。当一个JSP页面抛出未捕获的异常容器会把请求转发到配置好的异常页面并把异常对象注入到exception变量里。这样你就可以在专门的错误处理页面统一展示异常信息、记录日志而不是让用户看到一大片报错堆栈。最常见的配置方式是在每个JSP页面顶部加% page errorPageerror.jsp %然后在error.jsp里读取exception.getMessage()。要注意生产环境下不要把异常堆栈原样打印给用户看记录在日志里就行页面上给个友好的提示就够了。3. 实操过程与核心环节实现3.1 在IDEA里创建JSP文件并配置运行环境很多朋友问过我怎么在IntelliJ IDEA里创建JSP文件这个话题看着简单但操作不对会走不少弯路。我用的是IDEA 2023版本步骤是这样的第一步新建一个Java Enterprise项目在项目类型里选择Web Application这样IDEA会自动生成web目录和web.xml。如果你建的是普通Java项目也没关系后面可以手动添加Web支持。第二步在src/main/webapp目录上右键选择New你会看到JSP/JSPF这个选项。如果你用的是Jakarta EE版本的IDEA可能显示的是JSP File点它之后输入文件名IDEA就会生成一个带有基础骨架的.jsp文件。这里有个常见问题很多人的IDEA菜单里根本找不到JSP选项。原因一般是项目类型不对——你必须先让IDEA识别出这是一个Web项目。可以打开Project Structure快捷键CtrlAltShiftS在Facets面板里点击加号选择Web然后指定web.xml的路径和Web资源目录。这样IDEA才会把当前模块识别成Web模块右键菜单才会出现JSP相关选项。同样的道理如果你的Color Scheme里没有JSP的配色方案也是因为插件没启用。在Settings的Plugins里搜索Java EE和Web确保这些插件处于启用状态然后重启IDEA配色方案里就会出现JSP了。第三步配置Tomcat。点击右上角的Add Configuration选择Tomcat Server下Local在Application Server那里指向你本地的Tomcat目录然后在Deployment选项卡里添加当前项目的Artifact。这里要特别留意Application context它决定了访问路径如果你填的是/demo那么访问这个JSP页面的URL就是http://localhost:8080/demo/你的页面名.jsp。很多人访问404排查了半天发现是Application Context填的不对或者压根没配置部署。3.2 用九大内置对象搭一个个人信息展示页面光说不练假把式我拿个人信息展示页面这个常见场景来串一遍九大内置对象的用法。假设我们要做一个用户登录后展示个人信息的页面数据不从数据库查而是模拟存储在session里。先做一个登录页面login.jsp核心代码如下% page contentTypetext/html;charsetUTF-8 languagejava % html head title用户登录/title /head body form actionwelcome.jsp methodpost 用户名input typetext nameusernamebr 密码input typepassword namepasswordbr input typesubmit value登录 /form /body /html然后welcome.jsp接收并处理登录存session后展示个人信息% page contentTypetext/html;charsetUTF-8 languagejava % % page importjava.util.Date % % // request接收表单参数 request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); // 模拟登录验证实际项目这里应该查数据库 if (username ! null !username.isEmpty() 123456.equals(password)) { // session保存登录状态 session.setAttribute(loginUser, username); // application统计登录次数模拟全局访问量 Integer count (Integer) application.getAttribute(loginCount); if (count null) { count 0; } count; application.setAttribute(loginCount, count); } else if (username null) { // 直接访问页面说明还没登录 username (String) session.getAttribute(loginUser); } else { out.print(scriptalert(用户名或密码错误);history.back();/script); return; } if (username null) { response.sendRedirect(login.jsp); return; } % html head title个人信息展示/title /head body h2个人中心/h2 p当前登录用户% username %/p pSession ID% session.getId() %/p pSession创建时间% new Date(session.getCreationTime()) %/p p网站模拟登录次数% application.getAttribute(loginCount) %/p p参考路径% application.getRealPath(/) %/p a hreflogout.jsp退出登录/a /body /html这段代码里已经用到了request、session、application、out、response这五个内置对象。另外在页面底部通过application.getRealPath(/)获取项目在服务器上的物理路径这个在生成文件、上传图片时非常常用。至于config如果在web.xml里配置了初始化参数也可以用config.getInitParameter()来读。退出登录页面logout.jsp就一句话session.invalidate(); response.sendRedirect(login.jsp);。跑一遍这个流程你会发现session的会话保持能力、request的一次性传参能力、application的全局共享能力全都体现出来了。搞懂这个流程后面扩展成学生信息管理系统的登录个人中心基本就是复制粘贴改改页面的事。3.3 配置Tomcat后查看JSP编译后的Java类我强烈建议你亲自打开一次JSP编译后的Java类看看这一步的收获比读十篇博客都大。Tomcat在把JSP翻译成Java类之后会存放在work目录下。我用Tomcat 9举例路径一般是[Tomcat安装目录]/work/Catalina/localhost/[应用上下文路径]/org/apache/jsp/找到之后你会发现login_jsp.java和login_jsp.class就躺在里面。用IDEA打开login_jsp.java翻到_jspService方法你会看到这样一段声明代码public void _jspService(final jakarta.servlet.http.HttpServletRequest request, final jakarta.servlet.http.HttpServletResponse response) throws java.io.IOException, jakarta.servlet.ServletException { ... final jakarta.servlet.jsp.PageContext pageContext; jakarta.servlet.http.HttpSession session null; final jakarta.servlet.ServletContext application; final jakarta.servlet.ServletConfig config; jakarta.servlet.jsp.JspWriter out null; final java.lang.Object page this; ... }亲眼看到这段代码之前所有的疑问就都解开了为什么JSP里能直接用request、response因为它们就是这个方法的局部变量。为什么session、application也能直接用因为代码里还有一段根据配置决定是否把session和application赋值给局部变量的逻辑。为什么页面里写的% %脚本片段会被原封不动搬进_jspService方法里因为你写的JSP本质上就是Servlet方法体里的代码。我再多说一句这份编译后的Java类还藏着很多细节。比如你看到的HTML静态内容全部被转成了out.write(...)形式的输出语句。比如你用了JSTL标签会看到它被翻译成了一堆标签处理类的调用。这也是为什么JSP第一次访问会比较慢的原因——它需要经过翻译-编译这个阶段后续再访问因为有缓存就很快了。如果你改了JSP内容Tomcat检测到文件变化会重新翻译编译旧类会被覆盖。理解了编译机制很多奇怪的行为就变得理所当然了。3.4 屏蔽浏览器离开页面的提示搜索热词里有个屏蔽jsp离开页面提示这个需求我记得很清楚。有段时间项目中有一个页面每次切换或关闭浏览器浏览器都会弹出一个系统可能不会保存您的更改的确认对话框。这个提示是前端beforeunload事件触发的常见于表单编辑页面防止误操作。有时候不想弹这个提示了你要做的就是反注册这个事件监听器。在JSP页面里如果你之前写过script window.onbeforeunload function() { return 您确定要离开此页面吗; }; /script那么屏蔽方式有两种。最简单的直接不注册这个事件就行。如果你在父页面注册过、子页面是iframe加载的想屏蔽的话在子页面里执行script window.onbeforeunload null; /script但要注意window.onbeforeunload null只对直接赋值方式有效。如果事件是用addEventListener注册的你需要保留函数引用才能用removeEventListener移除。如果是在不小心打开的情况下最粗暴的办法是直接在当前页面上这样处理script window.onbeforeunload function(e) { e.returnValue undefined; return undefined; }; /script其实这个提示本身不是JSP的功能而是浏览器对页面卸载事件的安全机制。不少初学者把锅甩给服务器端其实是前端的回调逻辑。你自己写页面时按需注册就行。4. 常见问题与排查技巧实录4.1 IDEA的Color Scheme里找不到JSP高亮方案这个问题的本质是IDEA没有识别出.jsp文件应该使用哪种语言模板。正常情况下你新建JSP文件时IDEA会把它和Java EE的JSP方言绑定所以编辑器能高亮HTML标签、JSP指令和Java代码。如果你的Color Scheme里看不到JSP大概率是文件关联出了问题。解决方法是先确认Plugins里的Java EE支持组件已启用然后打开Settings → Editor → File Types在Recognized File Types里找到JSP Files看Registered Patterns里有没有*.jsp。如果没有就手动添加。添加之后再打开Settings → Editor → Color Scheme就能看到JSP选项了。如果你用的是新版IDEA菜单路径略有不同但大方向一样。这个问题十有八九不是配置丢了而是项目类型没被正确识别。4.2 部署JSP时选文件夹选错导致404搜索词里jsp选择文件夹看起来挺简单其实也是新手高频问题。用IDEA部署到Tomcat时Deployment选项卡里要选对Artifact和上下文路径。很多人直接把src/main/webapp这个文件夹添加为External Source然后发现写的JSP访问不到报404。正确的做法是确保项目已经构建出Web Artifact在Project Structure的Artifacts面板里把webapp目录添加到Artifact的Web Resource目录中。部署时在Deployment里选择这个Artifact然后设置Application Context为/项目名。这样访问http://localhost:8080/项目名/xxx.jsp就对了。如果上下文路径设置的是/那URL就不带项目名直接http://localhost:8080/xxx.jsp访问。这块配置不复杂但直接影响你能不能访问到页面值得认真对照一遍自己的配置。4.3 中文乱码问题JSP的中文乱码轻则页面中文变问号重则表单提交后数据全乱。乱码的根源是编码不一致。我常用的排查思路是这样的第一JSP文件本身保存的编码要和页面声明的编码一致。IDEA右下角能看到文件编码建议统一用UTF-8。文件顶部一定要写% page contentTypetext/html;charsetUTF-8 languagejava %第二如果是POST提交在接收参数前调用request.setCharacterEncoding(UTF-8)这样getParameter拿到中文就是正常的。第三如果是GET提交Tomcat 8及以上版本默认URI编码已经是UTF-8一般没问题Tomcat 7及以下需要在server.xml的连接器上配置URIEncodingUTF-8。第四响应输出统一用UTF-8response.setContentType(text/html;charsetUTF-8)或者直接靠page指令声明。乱码问题一旦出现别慌按这个顺序排查。最让人难受的情况就是全是问号那多半是请求处理参数时编码没设置如果是头部乱码、页面里静态中文乱码多半是文件编码保存不对。4.4 内置对象使用的几个典型陷阱第一个陷阱是out和response.getWriter混用导致的输出顺序错乱。JSP页面里out有缓冲response.getWriter写入的内容可能先被刷出去导致页面HTML结构乱掉。我的建议是一个页面只用out输出不要在JSP里手动获取response的Writer。第二个陷阱是response.sendRedirect和request.getAttribute配合使用结果取到null。前面已经说过原理但如果项目里出现这种bug排查时要先判断是转发还是重定向。第三个陷阱是session的空指针。很多人从session.getAttribute拿到对象后不做空判断直接调用它的方法。如果session过期、或者用户没登录这里就会空指针。正确做法是先判断是否为null再使用比如取用户对象时% User user (User) session.getAttribute(loginUser); if (user ! null) { out.print(欢迎 user.getName()); } else { response.sendRedirect(login.jsp); } %第四个陷阱是application的并发问题。用application.setAttribute做的计数器在高并发下会丢数据。比如两个人同时读到count10都加1最后存进去是11不是12。这在学生管理系统里可能无所谓但如果放到生产环境必须对计数操作加同步或者用AtomicInteger这种线程安全类。使用内置对象不要超范围同时也要知道它们的边界在哪。4.5 快速排查速查表现象可能原因排查方向页面404部署上下文路径不对检查IDEA Deployment和Application Contextrequest取到null参数名拼写错误或传参方式不对检查表单name和getParameter参数名重定向后request数据丢失发送了新的请求改用forward或存session中文乱码编码不一致统一页面、请求、响应均为UTF-8高亮样式不正常JSP文件类型未识别检查File Types和插件点击登录没反应form的action路径错误检查action的相对路径5. 从九大内置对象到Servlet/JSP进阶5.1 为什么学JSP内置对象对理解框架有帮助很多人觉得JSP已经是老技术了Spring Boot Thymeleaf才是主流学内置对象没什么用。这话我部分同意但要说明白为什么学内置对象仍然有价值。你去看Spring MVC的Model、ModelAndView这些概念它背后操作的无非还是request作用域你去看Thymeleaf的session表达式本质上访问的还是HttpSession。框架换了一层皮底层的Web容器机制没有任何变化。你要是能说出request、session、application各自的生命周期和作用范围能分清转发和重定向的区别能知道数据该往哪个作用域里放那你在用任何框架时都能快速判断这个数据应该放Model还是Session。这个底层理解能力是内置对象给你的。我面试Java Web岗位时还经常拿request和session有什么区别来考察候选人这题看着基础但能讲透的人对Web开发的基础一定很扎实。5.2 从编译后的Java类里读出的进阶信息我在3.3节建议大家看JSP编译后的Java类这里再补充一些更进阶的观察。你打开xxx_jsp.java会看到JSP页面里的静态内容全部变成了out.write的字符串常量JSP标签被翻译成了Java代码片段。如果你用了% page importjava.util.* %你会发现import语句出现在Java类的头部。还有一个值得关注的点_jspService方法上方的判断逻辑Tomcat会检查session是否启用如果页面配置了% page sessionfalse %就不会把session赋值给局部变量你在页面里用它就会报编译错误。这些细节体现了JSP容器的优化逻辑理解了这些你在纠结为什么我的session不可用为什么out输出顺序不对的时候就能快速定位问题方向。5.3 玩点好玩的JSP烟花模拟器能验证什么热词里有个jsp烟花模拟器听起来挺炫酷。做一个简单的烟花模拟器页面本质就是服务端动态输出HTML、CSS、JavaScript。你可以用out.print输出一串烟花粒子效果的HTML结构用application记录每个用户燃放的次数用session做用户区分用request获取用户的IP地址。这个小项目虽然简单但能把九大内置对象串起来用一遍。说实话JSP做这种页面并不优雅它更适合用来做数据展示和表单交互视觉效果部分还是要靠前端JavaScript。但作为练习烟花模拟器是个很好的综合试炼场它要求你既会用后端对象处理数据、又会输出合适的前端代码还能理解session与application的用户共享模型。从这个角度说做点有趣的小项目比刷一堆死板的练习题有意思得多。我个人在实际操作中的体会是JSP九大内置对象这套东西学会它只需要一个下午用好它却需要在真实项目里反复踩坑、反复总结。内置对象的出现是为了简化Servlet开发它没有多神秘但它是理解Java Web一切上层建筑的基石之一。我建议你把这篇文章里提到的几个代码示例自己敲一遍重点看一下Tomcat work目录里的编译产物再把登录跳转、数据传递这种高频场景的代码逻辑跑通。剩下的事情就是多写多用写多了你会发现当年那些让你挠头的问题慢慢都变成了理所当然的常识。