Tomcat&Servlet基础&Request对象
Servlet、Tomcat基础知识点,仅供参考
19年黑马JavaWeb
课程的笔记
Servlet? Tomcat?
在讲解Servlet
之前,先补充一些web
服务器的知识,所谓服务器,通俗上的理解就是安装了服务器软件的计算机。这些服务器软件通过接受用户请求、处理用户请求,回传响应来实现提供某一种服务。
而Tomcat
就可以理解为是一种服务器软件。
Servlet
全名是:server applet
,也就是运行在服务端的小程序,这些小程序配合服务器软件处理具体的业务。因此通常服务器接收到具体的请求的时候,服务器会将请求分配给相应的servlet
进行处理,再将servlet
的响应结果回传给用户。
Tomcat服务器安装
下载地址:Apache Tomcat® - Welcome!
解压压缩包,建议不带中文,可以看到Tomcat服务器的如下目录结构:
在bin
目录下双击startup.bat
即可启动tomcat
Tomcat部署配置
Servlet配置
环境配置——IDEA
首先新建web项目,这个在不同版本的idea中都不同,通常都是选择:
1 | new --> project -->java Enterprise -->Web project | Servlet |
然后就是配置Tomcat了,顺带一提,下载tomcat之后,不要乱改bin
里面的catalina.bat
,我当时就是因为乱码问题作死改了这个配置里面的 JAVA_OPTS(set JAVA_OPTS = -Dfile = utf-8)
,然后idea就一直报错了,错误如下:
1 | unable to ping server localhost:1099 |
idea
会帮你配置JAVA_OPTS
, 如果你配置了,那么你的配置将会覆盖掉idea
的(坑爹啊,艹)
配置可以参考下面的图:
环境配置——Eclipse
基于XML的配置
创建
JavaEE
项目,项目名称(虚拟目录)为:/servletTest
,就是上图的那个Application context
定义一个类,实现Servlet接口
实现接口中的抽象方法
1 | public class ServletDemo1 implements Servlet{ |
- 配置Servlet, 在web.xml中配置:
1 | <!--配置Servlet --> |
- Link Start
1 | http://localhost:8080/servletTest/demo1 |
执行原理
- 当服务器接受到客户端浏览器的请求后,会解析请求
URL
路径,获取访问的Servlet
的资源路径 - 查找
web.xml
文件,是否有对应的<url-pattern>
标签体内容。 - 如果有,则在找到对应的
<servlet-class>
全类名 tomcat
会将字节码文件加载进内存,并且创建其对象- 调用其方法
基于注解的配置
- 创建
JavaEE
项目,选择Servlet
的版本3.0以上,可以不创建web.xml
- 定义一个类,实现
Servlet
接口 - 复写方法
1
2
3
4
5
6
7
8public class ServletDemo1 implements Servlet{
...
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("Hello World!");
}
...
} - 在类上使用
@WebServlet
注解,进行配置
1 |
这个注解的源代码如下:
1 |
|
1 |
|
Servlet 生命周期
一个servlet
程序要想在Web
服务器上运行,一般需要四个步骤:
- 装入
- 初始化
- 提供服务
- 销毁
其中程序员能决定,从步骤2开始,到步骤4,装入一般交给web
服务器,准确点来说是服务器软件,如Tomcat
负责。
初始化
初始化,也就是实例化Servlet
的具体实现类的工作,这个动作会调用Servlet
接口的init()
,在整个servlet
生命周期中只会执行一次。
那这里就产生了两个问题
Servlet什么时候初始化?
默认情况下,第一次被访问时,Servlet类就被初始化,可以配置执行Servlet的创建时机。
在<servlet>
标签下配置
- 第一次被访问时创建,
<load-on-startup>
的值为负数 - 在服务器启动时创建,
<load-on-startup>
的值为0或正整数
Servlet是单例的
Servlet的初始化在整个生命周期过程中只执行一次,这就说明对于每一个Servlet
的具体实现类被加载之后,必须要走完整个生命周期,才能再次创建相同实现类的实例,因此在内存中只存在唯一对象,Servlet
是单例的,一旦涉及到单例,就可能存在线程安全问题。
因此尽量不要在Servlet
中定义成员变量。即使定义了成员变量,也不要修改值
提供服务
该过程会执行service()
,执行多次,每次访问Servlet时,Service方法都会被调用一次。
由于对Servlet
接口的进一步封装,现在也很少直接在service()
进行编码,一般使用的是HttpServlet
的doGet()
、doPost()
等
销毁
执行destroy方法,只执行一次,Servlet
被销毁时执行。服务器关闭时,Servlet被销毁
- 只有服务器正常关闭时,才会执行destroy方法。
- destroy方法在
Servlet
被销毁之前执行,一般用于释放资源
Servlet API及体系结构
一般自定义的servlet都是派生于:HttpServlet
其中GenericServlet
和HttpServlet
都是抽象类,GenericServlet
只提供一个抽象方法service()
,对Servlet中的其他方法都做了默认实现。因此如果需要继承GenericServlet,只需要实现一个方法:service()
而对于HttpServlet
来说,Http
有7中请求方式,将这7种方式都封装在HttpServlet
,它们都是:doXXX()
这样的方法!
实际上观察HttpServlet
的service()
方法可以看到:
首先getMethod()
返回请求方式,如果是post
,调用doPost()
,如果是get
,调用doGet()
,以此类推。
实际上对于HttpServlet
,常用的两个方法是:doGet()
和doPost()
,因此继承自HttpServlet
的Servlet
类只需要重写这两个方法即可。
分类
Servlet实现相关
- javax.servlet.Servlet
- javax.servlet.GenericServlet
- javax.servlet.http.HttpServlet
Servlet配置文件相关(Web.xml解析结果)
- javax.servlet.ServletConfig
响应和请求
- javax.servlet.ServletResponse
- javax.servlet.ServletRequest
- javax.servlet.http.HttpServletResponse
- javax.servlet.http.HttpServletRequest
会话跟踪
- javax.servlet.http.HttpSession
- javax.servlet.http.HttpSessionBindingListener
- javax.servlet.http.HttpSessionBindingEvent
Servlet上下文环境
- javax.servlet.ServletContext
Servlet协作
- javax.servlet.RequestDispatcher
Cookie
- javax.servlet.http.Cookie
Utils
- javax.servlet.http.HttpUtils
再谈URLPartten
在前面的Servlet配置中,我们详细介绍对于servlet
中的URL
如何配置,本小节我们继续深入探讨配置URL
的几种方式。
首先在WebServlet里面,可以看到:
1 | String[] urlPatterns() default {}; |
urlPatterns
实际上却可以接受一个数组,这说明对于一个servlet
它可以有多个访问的URL
,如:
1 | // Application Context: servletDemo |
这样当你本地的tomcat服务器启动后,你在浏览器输入下面三个地址都会链接到该servlet
:
1 | localhost:8080/servletDemo/demo1 |
当然对于一个servlet,你也可以配置多层访问URL,如:
1 | // Application Context: servletDemo |
这样当你本地的tomcat服务器启动后,你需要这样才能够访问这个servlet
:
1 | localhost:8080/servletDemo/user/demo1 |
如果你只写这样:
1 | localhost:8080/servletDemo/user |
相信我,绝对会404
同时URL
也是支持通配符*
的,如:
1 | // Application Context: servletDemo |
后面的*
无论你输入什么都可以跳转到该servlet
,如:
1 | localhost:8080/servletDemo/user/askia |
这些URL
都会跳转到这个Servlet里面,当然:如果你只是输入:
1 | localhost:8080/servletDemo/Askia |
那也会404
,要看准链接!
Request
HTTP请求消息数据格式
关于HTTP的具体内容这里不具体阐述,可以看这篇文章,这里只放出关于HTTP的请求消息数据格式的解析。
我们浏览器一旦有处理一次HTTP请求,都会发一次类似这样的消息:
1 | POST /login.html HTTP/1.1 |
请求行
请求行数据就是HTTP请求数据的第一行,也就是:
1 | POST /login.html HTTP/1.1 |
这里面包含了请求方式,请求URL,请求协议/版本
,如上面的请求消息解析出来就是:
1 | 请求方式:Post |
请求头
请求头通常是键值对存在,包含非常多的信息:
1 | Host: localhost |
其中比较重要的是:
- User-Agent:浏览器告诉服务器,我访问你使用的浏览器版本信息,可以在服务器端获取该头的信息,解决浏览器的兼容性问题
- Referer: 告诉服务器,我(当前请求)从哪里来?,如:
http://localhost/login.html
,这个东西的作用有两个:防盗链和统计工作。
请求空行
就是Upgrade-Insecure-Requests: 1
和username=zhangsan
之间的那个空行,用于分割POST请求的请求头和请求体。
请求体(正文)
封装POST请求消息的请求参数的,只有POST请求方式,才有请求体,如:
1 | username=zhangsan |
Request对象继承体系结构
可以看到上面的继承体系,和Servlet的继承体系如出一辙,其中HttpServletRequest
只能在HttpServlet
中见到。在GenericServlet中常见到的是ServletRequest
Request功能
获取请求行信息的方法
对于请求行数据:
1 | GET /day14/demo1?name=zhangsan&user=lisi |
1 | String getMethod() // 获取请求方式 :GET |
获取请求头信息的方法
之前说过请求头信息是一组键值对,因此:
1 | String getHeader(String name); // 通过请求头的名称获取请求头的值 |
1 | request.getHeader("User-Agent"); |
获取请求体信息的方法
1 | BufferedReader getReader() // 获取字符输入流,只能操作字符数据(文本) |
对于这个请求体:
1 | username=zhangsan |
获取它的方法:
1 | String str = request.getReader().readLine(); // str = username=zhangsan |
Request额外功能
更加通用的获取请求参数方法
前面在请求头里面介绍过获取请求参数的方法:String getQueryString()
这个方法获取到的参数是:XXXXX=XXXX&XXXX=XXX
格式的,并且只能是Get方式的请求参数,有没有更加通用的方法,既能获取Get请求参数也能获取Post请求参数,有:
1 | String getParameter(String name); // 根据参数名称获取参数值 |
上面四个方法,对于Post、Get都通用,对于下面的请求参数:
1 | /day14/demo1?name=zhangsan&user=lisi |
只需要使用:
1 | String s = request.getParameter("name"); // s = zhangsan |
同时有时候需要使用到多个参数的时候,比如说:
1 | hobby=xx&hobby=game |
两个Hobby,那就需要使用:
1 | String[] ss = request.getParameterValues("hobby"); // ss = {"xx", "game"} |
防止乱码现象
乱码现象在Get
方式中一般不会出现,tomcat 8
已经将Get
方式乱码问题解决了,当然如果你使用tomcat 7
,或者你使用的是Post
方式你可以在获取请求参数之前使用:
1 | request.setCharacterEncoding("utf-8"); |
来防止乱码现象
请求转发
Request可以实现服务器内部的资源跳转,例如,你现在有两个Servlet,URL什么的已经配置好了,如:
1 |
|
我希望在Servlet1
处理完之后能够跳转到Servlet2
,做一个请求的转发,要做到这点需要两个步骤:
- 通过request对象获取请求转发器对象:
1 | RequestDispatcher getRequestDispatcher(String path) |
- 使用
RequestDispatcher
的forward()
进行转发:
1 | void forward(ServletRequest request, ServletResponse response) |
如上面的例子,我们可以这样写转发:
1 |
|
请求转发有三个特点:
- 浏览器地址栏路径不发生变化
调用了request.getRequestDispatcher("/demo2").forward(request, response);
之后浏览器的URL还是/demo1
,不会因为调用请求转发而改变。 - 只能转发到当前服务器内部资源中。
request.getRequestDispatcher("www.baidu.com")
是没法完成请求转发的! - 转发是一次请求
实际上细心的同学还会发现,请求转发还有一种方式,那就是RequestDispatcher
的include()
:
1 | void include(ServletRequest request, ServletResponse response) |
include方式的转发和forward方式的转发实现的功能是一样的,区别在于请求URL的变化。
还有include方式的转发,不能设置response对象的HTTP头和状态码,而forward可以。
include方式的转发,转发之后可以使用out.println(),forward不可以
数据共享
域对象:一个有作用范围的对象,可以在范围内共享数据。
request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据
1 | void setAttribute(String name,Object obj) // 存储数据 |
结合请求转发来使用,可以实现多个Servlet之间的数据共享!
获取ServletContext(Web.xml上的内容)
1 | ServletContext getServletContext() |