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平时根本永不到的衰。。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值