Servlet总结
Posted LackMemory
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Servlet总结相关的知识,希望对你有一定的参考价值。
接上篇文章,这篇文章主要分析ServletRequest接口。
五、ServletRequest
这个接口代表的是请求,是服务器或容器对浏览器发过来的信息的包装。所以这个接口就是用来获取浏览器发过来的信息的,它的一系列API也证明了这一点。API很多,但是可以通过分类来整理。
(1)获取参数
public String getParameter(String name);
public Enumeration<String> getParameterNames();
public String[] getParameterValues(String name);
public Map<String, String[]> getParameterMap();
这几个方法其实在注释部分已经写的非常明白、清晰了,不得不承认,优秀的程序员也是文档高手。这里再罗嗦一下主要是为了加深自己的理解和记忆,毕竟只有自己能讲出来的东西才算是真正学会的。
这几个方法是第一类,用于获取请求参数。对于一个请求而言,除了正确的url,最重要的就是请求参数了。请求参数是和服务器交换信息的唯一手段,这也凸显了以上几个方法的重要性。不过它们其实蛮简单的:
getParameter方法用来获取一个参数值,当然你要传入参数名才行;
有些参数名可能有多个值,这时候就要用getParameterValues来获取由所有值组成的数组;
想要获取到所有的参数名就要用getParameterNames;
最后是比较重要的getParameterMap,它以Map的形式返回所有的参数,参数名为key,参数值为value。其实请求参数的结构挺适合以Map为介质来存储或转发的,这个方法应该是Spring框架那些便利方法的基础。需要注意的是返回值Map的泛型限制:Map<String,String[]>。
光说不练假把式,直接撸几行代码可能更清楚些:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
String color = req.getParameter("color");
String[] favorates = req.getParameterValues("favorates");
Enumeration<String> params = req.getParameterNames();
Map<String, String[]> map = req.getParameterMap();
System.out.println("color:" + color);
System.out.println("favorates:" + Arrays.toString(favorates));
System.out.print("all param names: ");
while(params.hasMoreElements())
System.out.print(params.nextElement() + " ");
System.out.println("\\n" + "map:");
for(String s : map.keySet())
System.out.println(s + ":" + Arrays.toString(map.get(s)));
在浏览器输入url:http://localhost:8080/learn/test?color=red&favorates=apple&favorates=orange&shape=retangle,结果如下:
color:red
favorates:[apple, orange]
all param names:color favorates shape
map:
color:[red]
favorates:[apple, orange]
shape:[retangle]
(2)属性操作
public Object getAttribute(String name);
public Enumeration<String> getAttributeNames();
public void setAttribute(String name, Object o);
public void removeAttribute(String name);
属性可以看作是向Request对象添加的额外信息。容器对整个链接的处理其实很像一条流水线,刚开始的时候,容器制造了两个盒子,一个叫Request,一个叫Response,分别往两个盒子里塞了一些东西,就交给流水线的下一个工序。盒子里有些东西是固定的,不能随便添加或者删除。那么一个工序想告诉下一个工序一些消息的时候,就只能利用“外挂”,这个“外挂”就是属性,可以随意添加或者删除。
这几个方法整体风格和参数操作很像,除了比较“随便”之外。比如属性就有set和remove方法,而参数只能get。方法注释中也说明了谁会去操作这些属性:
Attributes can be set two ways. The servlet container may set attributes to make available custom information about a request.
Attributes can also be set programatically using @link ServletRequest#setAttribute.需要注意一下setAttribute方法的注释:
If the object passed in is null, the effect is the same as calling @link #removeAttribute.属性操作什么时候用的比较多呢?是当servlet转向一个jsp页面的时候。jsp本质上也是一个servlet,它可能需要上一个servlet提供的属性值来填充页面。这里就不给实例了,等涉及到jsp时再详细分析。
(3)获取浏览器信息
public String getRemoteAddr();
public String getRemoteHost();
public int getRemotePort();
分别用来获取浏览器端的IP地址、名字以及端口号,这个可以直接来例子:
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
System.out.println("ip:" + req.getRemoteAddr());
System.out.println("host:" + req.getRemoteHost());
System.out.println("port: " + req.getRemotePort());
浏览器输入上面的url,结果如下:
ip:0:0:0:0:0:0:0:1
host:0:0:0:0:0:0:0:1
port: 42628
可能是本地访问的原因吧,这ip和host长得很奇怪,难道是ipv6?先不管了,意思到了就行。。
(4)获取服务器信息:
public String getServerName();
public int getServerPort();
public String getLocalName();
public String getLocalAddr();
public int getLocalPort();
直接上代码吧,我也有点傻傻分不清楚。。
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException
System.out.println("serverName: " + req.getServerName());
System.out.println("localName:" + req.getLocalName());
System.out.println("localAddr: " + req.getLocalAddr());
System.out.println("serverPort: " + req.getServerPort());
System.out.println("localPort: " + req.getLocalPort());
结果:
serverName: localhost
localName:ip6-localhost
localAddr: 0:0:0:0:0:0:0:1
serverPort: 8080
localPort: 8080
看一下getServerName的注释:
Returns the host name of the server to which the request was sent.
对比一下getLocalName的:
Returns the host name of the Internet Protocol (IP) interface on which the request was received.我猜这是考虑到集群的问题,一个服务器可能由多台机器组成,所以会有以上的区别。这个问题以后有机会验证。
(5)获取请求头信息
public String getScheme();
public String getProtocol();
public Locale getLocale();
public Enumeration<Locale> getLocales();
public String getCharacterEncoding();
public void setCharacterEncoding(String env) throws UnsupportedEncodingException;
public int getContentLength();
public long getContentLengthLong();
public String getContentType();
public boolean isSecure();
由于其中几个方法涉及到请求体,所以要使用post方法才能测试:
<form method="post" action="test">
test:<input type="text" name="color">
<input type="submit" value="sub">
</form>
浏览器的请求头如下:
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Encoding:gzip, deflate, br
Accept-Language:zh-CN,zh;q=0.8,en;q=0.6,ja;q=0.4,fr;q=0.2,zh-TW;q=0.2,ko;q=0.2
Cache-Control:max-age=0
Connection:keep-alive
Content-Length:11
Content-Type:application/x-www-form-urlencoded
结果:
scheme: http
protocol:HTTP/1.1
Locale: zh_CN
encoding: null
contentType: application/x-www-form-urlencoded
contentLength: 11
isSecure: false
getScheme和getProtocol方法很类似,都是返回协议,只是后者更具体有版本号而已,根据注释上的说法,后者
Returns the name and version of the protocol the request uses
前者
Returns the name of the scheme used to make this request
不太理解外国人的想法。。。
下一组是getLocale和getCharacterEncoding方法,前者获取浏览器偏爱的语言,它针对的是请求头的Accept-Language字段,可以看出这个字段有很多值,按顺序排列,getLocale只返回第一个。而getLocales就可以返回全部的值。
getCharacterEncoding返回浏览器偏爱的编码格式,针对请求头的Accept-Encoding字段,它代表请求体的编码格式。但是这里却返回了null,虽然浏览器已经设置了这个字段的值,以后研究。这个字段的值还可以被设置,使用setCharacterEncoding方法即可,但是如果获取了请求体内容之后再调用这个方法,那显然没用了。
---------2017.4.17,勘误------------
getCharacterEncoding针对的请求头并非Accept-Encoding字段,而是Content-Type;但是现在的浏览器基本上已经不在Content-Type中提供 character encoding了,在上面的例子中可以验证这点。这就是说,字符编码实际上是在Content-Type请求头中作为附加属性给的,这其实很像在ServletResponse接口中的做法,详见我的下一篇文章。
这个勘误也是我在研究ServletResponse接口时发现的,具体的答案来源是这里。
想要getCharacterEncoding不返回null,只能在后台手动调用setCharacterEncoding,但是必须在调用getWriter之前调用。这是为什么呢?为什么不是getInputStream呢?第一,设置字符编码必须在获取请求体之前,因为容器要根据字符编码去解析请求体,如果已经获取到了请求体,那显然再去设置字符编码就没意义了;第二,getInputStream方法获取的是字节流形式的请求体,就是一堆二进制,所以不需要字符编码解析,而getWriter获取的是字符流形式的请求体,这就需要根据字符编码将二进制转化成字符,所以调用setCharacterEncoding要在getWriter之前。
getContentType方法获取的是请求体的MIME type,这里返回值是application/x-www-form-urlencoded,大意是编码之后的form表单数据。其他可能的值还有pdf、img等。而getContentLength方法就是返回请求体的长度,以字节为单位。
isSecure返回一个boolean值,代表请求是否使用了安全通道,比如http请求使用的是https方式的话,该方法会返回true。
(6)获取请求体
public BufferedReader getReader() throws IOException;
public ServletInputStream getInputStream() throws IOException;
这两个方法都是用来获取请求体本身,前者返回的是字符流,后者返回的是字节流。它们都是属于底层的方法,平时基本用不到,因为使用getParameter一类的方法就够了,除非你不信任容器而想自己再解析一遍请求体。看过Tomcat源码的人应该熟悉,getInputStream方法返回的ServletInputStream是由Tomcat来实现的,在Servlet API中这只是个抽象类。也就是说,这两个方法更大可能是被容器使用,容器已经替我们做好了底层的解析。
我能想到的应用场景就是在页面上传文件时,可能会用到这两个方法。
另外,根据注释,这两个方法只能调用其中之一,而不能都被调用。我不知道这一点是如何保证的。。
Either this method or @link #getReader may be called to read the body, not both.
(7)其他
public ServletContext getServletContext();
public RequestDispatcher getRequestDispatcher(String path);
public DispatcherType getDispatcherType();
public boolean isAsyncSupported();
public AsyncContext startAsync() throws IllegalStateException;
public AsyncContext startAsync(ServletRequest servletRequest, ServletResponse servletResponse)
throws IllegalStateException;
public boolean isAsyncStarted();
public AsyncContext getAsyncContext();
只说前两个方法吧,其他几个真是见都没见过。虽然getServletContext和getRequestDispatcher被分到“其他”组,但是这两个方法的重要性并不亚于getParamter。
getServletContext返回一个ServletContext对象,这个对象代表着整个web应用,我在后面的文章中会具体分析。
getRequestDispatcher可以说是极其重要的一个方法了,基本上在纯servlet编程中,由servlet跳转到jsp页面,只有使
用这个方法。常见的形式如下:
RequestDispatcher requestDispatcher = req.getRequestDispatcher("xxx.jsp");
requestDispatcher.forward(req,resp);
getRequestDispatcher参数的名字就是path,一般它的值就是一个jsp页面;同时它的返回值是一个RequestDispatcher
类型的对象,这个类型是一个接口,我后面分析,同时会给出以上两行代码的运作机制。
终于写完这篇了,真他妈累啊,大多数API平时根本永不到的衰。。
以上是关于Servlet总结的主要内容,如果未能解决你的问题,请参考以下文章