ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

request对象详解:从参数获取到请求转发,彻底搞懂JSP内置对象

request对象详解:从参数获取到请求转发,彻底搞懂JSP内置对象 做Java Web前端的同学尤其是刚接触JSP的初学者十有八九第一个真正用熟的内置对象就是request。它和你每天打交道最多用户提交了一个表单你想知道填了什么页面之间要跳转你想带点数据过去甚至你想看看用户用的什么浏览器、从哪里访问统统都得靠它。可以说弄懂了requestJava Web前端的入门之路就打通了一半。这篇内容不打算把JSP九大内置对象挨个念一遍而是只针对request这一个对象从原理到常用方法从取参到转发从乱码到坑点全部掰开揉碎讲清楚。适合刚接触Servlet和JSP的后端初学者也适合做Java Web课程设计、毕业设计时需要快速上手页面交互的同学。看完之后你至少能独立完成一个带表单提交、参数获取、请求转发和个人信息展示的完整JSP页面。1. JSP内置对象与request的定位为什么它排在第一个1.1 九大内置对象里request和response是最核心的一对JSP内置对象不需要你手动new就能直接用因为它们都是容器在页面运行时自动创建好的。九大内置对象分别是request、response、out、session、application、pageContext、config、page、exception其中request和response是所有请求处理的两条主线request代表浏览器发过来的问话response代表服务器回给浏览器的应答。其他对象要么是围绕请求过程中的数据保存session、application、pageContext要么是输出辅助out要么是配置信息config、page、exception。这里想强调一个容易被忽略的点JSP页面本质上就是一个Servlet。Tomcat会把JSP编译成对应的Java类然后这个类的_jspService(HttpServletRequest request, HttpServletResponse response)方法就是整个页面的执行入口。你在JSP里能直接用的request就是这个方法参数里的HttpServletRequest对象。所以研究JSP内置对象本质上就是在研究Servlet API里的HttpServletRequest和HttpServletResponse这个定位搞清楚了很多为什么能直接用的疑问自然解开。1.2 request对象到底封装了什么你可以把request理解成一个快递包裹里面装了浏览器在发出请求那一刻的所有信息主要分成三大类。第一类是请求行信息就是请求行里那一行的内容包括请求方式GET还是POST、请求的URL路径、使用的HTTP协议版本。这些信息直接决定了你页面后面怎么处理。第二类是请求头信息也就是HTTP头那一堆键值对比如User-Agent浏览器类型、Referer从哪个页面跳过来的、Cookie浏览器本地保存的数据、Accept-Language用户语言偏好等。这些Header信息平时用得相对少但在防爬虫、做下载统计、判断客户端类型时非常有用。第三类是请求参数也就是用户真正提交的业务数据。无论用户是在URL后面用?namexxx拼接的查询参数还是在表单里通过POST提交的字段到了服务器端都被解析成一组一组的键值对。request对象把这些参数放到了一个类似Map的结构里通过getParameter系列方法就可以随时取出来。记住一句话凡是客户端能告诉服务器的信息基本都在request里。你在Servlet或者JSP里处理的业务逻辑绝大多数都是从从request里拿东西开始的。2. request核心方法拆解从取参数到传值一网打尽2.1 获取请求参数getParameter、getParameterValues与getParameterMap你写页面的第一件事大概率就是接收上一个页面传过来的参数。request获取参数最常用的方法是getParameter(String name)它接收一个参数名返回对应的参数值格式是字符串。举个最简单的例子。用户在一个注册页输入了用户名提交到一个名为reg.jsp的处理页这个页面想拿到用户名代码就是% String username request.getParameter(username); out.print(你输入的用户名是 username); %看起来很容易但有几个隐藏的细节需要特别注意。第一getParameter一次只能拿一个值。如果页面上有多个同名控件比如一组同名的checkbox多选框那用getParameter只能拿到第一个值后面的全丢了。这时候得换getParameterValues(String name)它返回String数组% String[] hobbies request.getParameterValues(hobby); if (hobbies ! null) { for (String hobby : hobbies) { out.print(选中爱好 hobby br/); } } %第二如果你既不想一个字段一个字段地取又想知道这次请求一共传了哪些参数可以直接用getParameterMap()方法把整个参数集合当成一个Map来遍历% MapString, String[] paramMap request.getParameterMap(); for (Map.EntryString, String[] entry : paramMap.entrySet()) { String paramName entry.getKey(); String[] paramValues entry.getValue(); out.print(参数名 paramName 参数值 Arrays.toString(paramValues) br/); } %这个方法在做通用日志记录、参数自动封装的时候特别方便比如你想把前端传上来的所有参数原样打出来调试一行遍历就全出来了。第三也是最常见的坑参数永远是字符串。用户提交的是18你用getParameter拿到的是字符串18不是数字18。想比较年龄大小、做数值运算必须手动转成int或double。转的时候建议用Integer.parseInt但要先做好判空否则参数没传时getParameter返回null直接parse会抛NumberFormatException。2.2 获取请求行信息method、URI与协议很多新人不理解为什么要看请求方式总觉得接口能通就行。实际上GET和POST在业务语义上有明显区别GET通常用于查数据、带参跳转参数会暴露在URL和浏览器历史里POST通常用于提交数据、修改状态参数放在请求体里相对隐蔽且能承载更大的数据量。在页面里判断用户是GET还是POST过来的是getMethod()方法% String method request.getMethod(); if (GET.equals(method)) { out.print(这是GET请求); } else if (POST.equals(method)) { out.print(这是POST请求); } %需要知道的还有这几个getRequestURI()返回请求的资源路径比如/jsp_learn/reg.jsp通常用于记录日志或做权限判定。getRequestURL()返回URL全地址比如http://localhost:8080/jsp_learn/reg.jsp包含协议、域名、端口和路径适合做跳转地址拼装。getRemoteAddr()返回发起请求的客户端IP地址比如192.168.1.100。做访问日志的时候必用。getProtocol()返回协议和版本号比如HTTP/1.1。一个典型的场景是你做了个后台管理页面不想让别的机器随便访问可以先用getRemoteAddr()拿IP做个校验不是内网IP就拒绝。虽然真实项目里会把这层逻辑放到Filter里做但在JSP学习阶段用内置对象直接判断也是个很好的练手方式。2.3 获取请求头User-Agent与防盗链HTTP请求头里藏着很多隐形的信息其中日常开发里最常用的两个请求头是User-Agent和Referer。浏览器访问你的页面时会自动带上自己的User-Agent标明自己是Chrome、Firefox还是Safari。你可以用getHeader(User-Agent)把它读取出来。常见的应用场景是下载页面判断客户端类型手机访问就提示下载AppPC访问就提供安装包再比如有些老项目需要兼容IE浏览器发现UA里包含MSIE或者Trident就自动跳到一个低版本兼容页面。Referer则是记录用户是从哪个页面链接跳转过来的。图片防盗链就是利用这个头做的如果Referer不是你自己域名的页面就返回一张替换的提示图。注意getHeader(Referer)可能为null比如用户直接在浏览器地址栏输入网址访问就不会带Referer所以用的时候也要判空。遍历所有请求头的方法也很有用% EnumerationString headerNames request.getHeaderNames(); while (headerNames.hasMoreElements()) { String headerName headerNames.nextElement(); String headerValue request.getHeader(headerName); out.print(headerName headerValue br/); } %新手调试的时候把这段代码丢到一个临时页面里跑一下就能看见浏览器发来的全部请求头信息对理解HTTP协议非常有帮助。2.4 请求作用域setAttribute与getAttributerequest对象不只是接数据它也能带数据。在JSP里如果你想把一些计算好的结果传递给另一个页面可以使用setAttribute(String name, Object value)往request里放数据然后在目标页面用getAttribute(String name)把数据取出来。% // 页面A把用户信息放入request User user new User(张三, 25); request.setAttribute(userInfo, user); request.getRequestDispatcher(show.jsp).forward(request, response); %%-- 页面Bshow.jsp 取出数据 --% % User user (User) request.getAttribute(userInfo); out.print(用户名 user.getName() 年龄 user.getAge()); %这就是典型的请求转发 request域传值组合。注意setAttribute放的是Object取出来的时候需要强转成原来的类型。如果request里没有对应的keygetAttribute返回的是null所以取出来之后最好先判空再使用。这里要重点区分两个概念setAttribute/getAttribute操作的是服务器端内存中的数据而getParameter读取的是客户端传过来的请求参数。两者都通过request调用但完全是两回事。初学者经常混看到getParameter取不到数据就以为是setAttribute没放进去其实是搞混了数据来源。2.5 请求转发getRequestDispatcher的配合用法request.getRequestDispatcher(String path)返回一个RequestDispatcher对象调用它的forward(request, response)方法可以把当前请求转发到另一个Servlet、JSP或HTML页面。% request.getRequestDispatcher(/login.jsp).forward(request, response); %forward执行后浏览器地址栏的URL不会变化整个过程发生在服务器内部对用户来说就像访问了一个页面。这种做法在MVC模式中非常常见Servlet处理完业务逻辑后把结果数据放进request然后forward到JSP做展示。由于request域在这次请求范围内是共享的因此转发过去的页面能拿到之前setAttribute的数据。很多人会把forward和response.sendRedirect搞混。forward是服务器内部跳转只发一次请求sendRedirect是浏览器重新发一次请求跳转后地址栏会变。需要强调的是forward之后不要在代码里再往response里写内容否则可能报IllegalStateException或者页面内容重复输出。3. 实操用request对象完成一个完整的用户信息展示页面3.1 需求拆解与页面设计光讲方法不落地等于白看。这里带着完整做一个个人信息展示页面把request对象的多数知识点串起来。需求很简单一个表单页让用户填写姓名、年龄、性别、爱好、备注提交后跳到一个展示页把刚才填的信息全部回显出来同时在展示页把这次请求的IP、浏览器信息和请求方式也一并显示出来。这个需求覆盖了getParameter单值取值、getParameterValues多值取值、getHeader请求头读取、getRemoteAddrIP获取、getMethod请求方式判断以及表单POST提交的中文乱码处理。本就是热搜词里jsp个人信息展示页面的典型场景做完这个你对request的日常用法就有了完整的肌肉记忆。3.2 表单页实现form.jsp% page contentTypetext/html;charsetUTF-8 languagejava % html head title个人信息填写/title meta charsetUTF-8 /head body h2请填写你的个人资料/h2 form actionshow.jsp methodpost 姓名input typetext namename/br/br/ 年龄input typetext nameage/br/br/ 性别 input typeradio namegender value男 checked/男 input typeradio namegender value女/女 br/br/ 爱好 input typecheckbox namehobby valueJava/Java input typecheckbox namehobby value前端/前端 input typecheckbox namehobby value数据库/数据库 input typecheckbox namehobby value算法/算法 br/br/ 备注 textarea nameremark rows3 cols40/textarea br/br/ input typesubmit value提交信息/ /form /body /html这里有几个设计细节要说一下。性别用单选框设置namegender四个爱好共用namehobby这样在服务器端就能对应上单值参数和多值参数的不同取法。表单的method设置为post是为了演示POST请求参数的获取方式同时避开GET方式下中文参数在URL里的编码问题。表单提交目标show.jsp的action写成相对路径是因为两个页面在同一个目录下。3.3 展示页实现show.jsp% page contentTypetext/html;charsetUTF-8 languagejava % % page importjava.util.Arrays % html head title个人信息展示/title meta charsetUTF-8 /head body % // 处理POST请求的中文乱码必须在获取任何参数之前调用 request.setCharacterEncoding(UTF-8); // 1. 取单个参数 String name request.getParameter(name); String age request.getParameter(age); String gender request.getParameter(gender); // 2. 取多个同名参数复选框 String[] hobbies request.getParameterValues(hobby); String hobbyStr (hobbies ! null) ? Arrays.toString(hobbies) : 无; // 3. 取文本框内容 String remark request.getParameter(remark); // 4. 取请求行信息 String method request.getMethod(); String ip request.getRemoteAddr(); String uri request.getRequestURI(); // 5. 取请求头信息 String userAgent request.getHeader(User-Agent); % h2你提交的个人信息如下/h2 table border1 cellpadding8 cellspacing0 tr td姓名/td td% name %/td /tr tr td年龄/td td% age %/td /tr tr td性别/td td% gender %/td /tr tr td爱好/td td% hobbyStr %/td /tr tr td备注/td td% remark %/td /tr /table h3本次请求的技术信息/h3 table border1 cellpadding8 cellspacing0 tr td请求方式/td td% method %/td /tr tr td客户端IP/td td% ip %/td /tr tr td请求URI/td td% uri %/td /tr tr tdUser-Agent/td td% userAgent %/td /tr /table a hrefform.jsp返回重新填写/a /body /html代码的逻辑很直观但有两处需要特别提示一下。第一request.setCharacterEncoding(UTF-8)必须在获取任何参数之前执行放到页面的最前面。如果页面已经调用了getParameter取过值再调用这个方法就来不及了。第二复选框的hobby参数可能为null用户一个都没选所以在拼字符串时做了判空处理否则Arrays.toString(null)会直接抛NullPointerException。把这两个页面部署到Tomcat的webapps目录下启动服务器浏览器访问http://localhost:8080/你的项目名/form.jsp填写信息后提交就能在show.jsp看到完整的回显内容。这个页面虽然简单但已经把request对象最常用的API都走了一遍初学者在做课程设计时照搬这套模式就能应付大部分表单类需求。3.4 业务扩展登录判定与页面间数据传递上面的表单演示只是取数据更接近真实项目的是存数据再传递。这里扩展一个场景用户登录成功后把用户名和登录时间放进request里转发到欢迎页展示。login.jsp模拟一个极简登录表单% page contentTypetext/html;charsetUTF-8 languagejava % html head title登录/title meta charsetUTF-8 /head body form actionlogin_check.jsp methodpost 用户名input typetext nameusername/br/br/ 密码input typepassword namepassword/br/br/ input typesubmit value登录/ /form /body /htmllogin_check.jsp做校验和数据传递% page contentTypetext/html;charsetUTF-8 languagejava % % request.setCharacterEncoding(UTF-8); String username request.getParameter(username); String password request.getParameter(password); // 模拟校验用户名和密码不为空就算登录成功 if (username ! null !username.isEmpty() password ! null !password.isEmpty()) { // 把数据放入request作用域 request.setAttribute(username, username); request.setAttribute(loginTime, new java.text.SimpleDateFormat(yyyy-MM-dd HH:mm:ss).format(new java.util.Date())); // 请求转发到欢迎页 request.getRequestDispatcher(welcome.jsp).forward(request, response); } else { out.print(scriptalert(用户名或密码不能为空);location.hreflogin.jsp;/script); } %welcome.jsp取出数据% page contentTypetext/html;charsetUTF-8 languagejava % % String username (String) request.getAttribute(username); String loginTime (String) request.getAttribute(loginTime); % html body h2欢迎% (username ! null ? username : 未知用户) %/h2 p登录时间% (loginTime ! null ? loginTime : 无) %/p /body /html这个过程体现的就是JSP里非常标准的请求转发request域传值模式。因为用的是forward而不是sendRedirectwelcome.jsp和login_check.jsp之间共享同一个request对象所以setAttribute进去的数据在welcome.jsp里能原样取出来。等以后学到MVC或者Servlet做控制器时你会发现这套玩法其实就是Spring MVC里Model传值的底层原型。4. 乱码、空指针与请求响应的边界request使用问题排查实录4.1 中文乱码POST和GET的处理方式不一样中文乱码是初学者用request时遇到最多的坑没有之一。要理解乱码的根源得先明白编码和解码的过程浏览器在表单页使用UTF-8编码把中文参数发给服务器服务器端默认却用ISO-8859-1来解码两边的码表不一致数据自然就变成乱码了。POST请求的处理方式最简单在取任何参数之前加一行% request.setCharacterEncoding(UTF-8); %这行代码的作用是告诉服务器本次请求的body内容使用UTF-8进行解码。注意它只对POST请求体里带的参数生效而且必须在首次调用getParameter之前执行晚了就无效。GET请求的乱码是另一条路GET参数放在URL上URL的编码行为由服务器端的连接器配置决定。Tomcat 8及以上版本默认就将URI解码设置为UTF-8所以多数情况下GET中文不会出问题。但如果你用老版本Tomcat7及以下就需要在conf/server.xml里的Connector节点加一个属性URIEncodingUTF-8。如果项目里既有GET也有POST而且Tomcat版本不统一稳妥的做法是在代码里对GET参数手动做一次转码% String name request.getParameter(name); if (name ! null) { name new String(name.getBytes(ISO-8859-1), UTF-8); } %这是一种以毒攻毒的做法先把拿到的参数按服务器的错误解码结果还原成字节数组再用UTF-8重新解码。能解决部分老环境下的GET乱码但有局限性新项目不建议主动使用了解原理即可。最简单的排查思路是先确认页面ContentType里是否设置了UTF-8再确认Tomcat版本最后看请求方式是POST还是GET。90%的乱码问题都出在前两环节挨个排查就好。4.2 参数为null导致的空指针问题新手在展示页里写% name %没报错但一旦代码变成if (name.equals(张三))就会抛NullPointerException。常见场景是你从页面A跳到页面B但B里用了页面A才有的参数名或者复选框一个都没勾取出来的数组就是null。规避空指针的核心习惯有两个第一取出来的参数先用是否为null做一次判断再进入业务逻辑。判断空和判断空字符串还不一样表单里用户什么都没输入提交上来的是空串但字段根本没传才是null。很多业务逻辑要求既不为null也不为空串写法就是if (param null || param.isEmpty())。第二做类型转换时要加try-catch或者用工具方法。比如年龄字段用户手滑输入了abcInteger.parseInt会直接抛NumberFormatException。比较稳妥的写法是% String ageStr request.getParameter(age); int age 0; if (ageStr ! null !ageStr.isEmpty()) { try { age Integer.parseInt(ageStr); } catch (NumberFormatException e) { age 0; } } %这种防御式取参的写法确实啰嗦但它在生产环境的可靠性很高。等你后面用框架时框架自带的参数绑定和类型转换会帮你省掉大部分模板代码但底层原理仍然是这一套。4.3 请求转发vs重定向最容易选错的跳转方式很多初学者把request.getRequestDispatcher(xxx).forward(request, response)和response.sendRedirect(xxx)混用在同一个项目里结果要么数据取不到要么URL莫名其妙变来变去。这两者的核心区别用一句话概括forward一次请求redirect两次请求。forward是服务器内部转发浏览器地址栏不变整个过程共享同一个request和response对象所以在forward之后的页面里能从request里getAttribute到数据。redirect是服务器告诉浏览器你去另一个地址找我地址栏会变化浏览器会重新发起一次全新的请求原来的request对象已经销毁用setAttribute放进去的数据自然取不到。实际场景里往一个正在编辑的页面提交数据后为了防止用户刷新页面导致重复提交通常用redirect做提交后跳转地址栏变成结果页URL刷新不会重复提交。而在MVC里Servlet处理完业务需要把数据交给JSP渲染一定用forward。还有一种坑是forward的路径问题。getRequestDispatcher的路径分为两种以/开头的是相对当前Web应用的根路径不加/则是相对当前请求路径的路径。同一个项目里稍微一混就会出现404。建议统一使用以/开头的写法比如request.getRequestDispatcher(/welcome.jsp)这样路径的起点就是项目根目录不容易错。4.4 request对象的生命周期与内存注意点request对象的生命周期很短一次请求开始就创建请求结束就被回收。这意味着你不能在request里存太多数据更不能把request存到session或者application里长期保存。一些新手为了图省事把大对象比如一个列表查询结果直接setAttribute进去再forward会造成不必要的内存占用尤其在并发量高的时候压力明显。正确的做法是request域适合存一次请求内需要传递的轻量数据比如表单校验结果、查询条件、用户ID等。全局共享的数据放application跨请求但同会话的数据放session。三者各有明确的分工按数据的使用范围选对存储位置是Java Web开发的常识也是面试常问的基础问题。4.5 其他容易被忽略的小细节request.getParameter取到的值HTML标签里的特殊字符会原样输出存在XSS风险。在JSP页面输出用户提交的数据时最好用c:out标签或者手动对、、等字符做转义不然用户提交一段script代码别人的浏览器就会中招。用户快速双击提交按钮可能导致同一请求发送两次页面上的处理逻辑要么做好幂等重复执行结果相同要么用JavaScript在提交后禁用按钮。这个问题和request本身没直接关系但排查为什么数据插了两条时经常发现是这里的问题。最后提一个调试小技巧临时在JSP页面里写一段遍历getParameterMap()的循环把请求里的所有参数名和值打出来定位参数丢失问题比挨个getParameter快得多。排查HTTP头信息就遍历getHeaderNames()看User-Agent、Referer、Cookie到底有没有传过来。掌握这两个调试手段很多页面传参问题的排查效率能提升一大截。我在最初上手JSP的时候对request也是一知半解总觉得反正能拿到参数就行。直到有一次被一个诡异的中文乱码问题折磨了大半天才老老实实地把HttpServletRequest的API文档从头翻了一遍又把netstat抓包和浏览器开发者工具里的请求报文对照着看了一场才算真正把这套机制吃透。对这个曾经最不起眼的内置对象我的体会是它没有框架里那些高级组件那样光鲜却是整个Java Web请求链路的起点。你把request的取参、传值、转发和编码这四件事练扎实了后面学Servlet、Filter、Spring MVC时到处都能看到它的影子。如果这篇内容对你有一点点帮助建议你把文中的案例手敲一遍跑通了再往真实项目里延伸——亲自踩过坑和只在文章里看懂完全是两种体验。
返回列表