Tomcat&Servlet基础&Request对象

Servlet、Tomcat基础知识点,仅供参考

19年黑马JavaWeb课程的笔记

Servlet? Tomcat?

在讲解Servlet之前,先补充一些web服务器的知识,所谓服务器,通俗上的理解就是安装了服务器软件的计算机。这些服务器软件通过接受用户请求、处理用户请求,回传响应来实现提供某一种服务。

Tomcat就可以理解为是一种服务器软件。

Servlet全名是:server applet,也就是运行在服务端的小程序,这些小程序配合服务器软件处理具体的业务。因此通常服务器接收到具体的请求的时候,服务器会将请求分配给相应的servlet进行处理,再将servlet的响应结果回传给用户。

image-20210809184109992

Tomcat服务器安装

下载地址:Apache Tomcat® - Welcome!

解压压缩包,建议不带中文,可以看到Tomcat服务器的如下目录结构:

image-20210809184145766

tomcat目录结构

bin目录下双击startup.bat即可启动tomcat

image-20210809184319851

Tomcat部署配置

image-20210809184429573

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(坑爹啊,艹)

配置可以参考下面的图:

image-20211123115635964

image-20211123115718686

环境配置——Eclipse

基于XML的配置

  1. 创建JavaEE项目,项目名称(虚拟目录)为:/servletTest,就是上图的那个Application context

  2. 定义一个类,实现Servlet接口

  3. 实现接口中的抽象方法

1
2
3
4
5
6
7
8
public class ServletDemo1 implements Servlet{
...
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("Hello World!");
}
...
}
  1. 配置Servlet, 在web.xml中配置:
1
2
3
4
5
6
7
8
9
10
<!--配置Servlet -->
<servlet>
<servlet-name>demo1</servlet-name>
<servlet-class>cn.itcast.web.servlet.ServletDemo1</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>demo1</servlet-name>
<url-pattern>/demo1</url-pattern>
</servlet-mapping>
  1. Link Start
1
2
http://localhost:8080/servletTest/demo1
// http://ip:port/Application context/servlet URI

执行原理

  1. 当服务器接受到客户端浏览器的请求后,会解析请求URL路径,获取访问的Servlet的资源路径
  2. 查找web.xml文件,是否有对应的<url-pattern>标签体内容。
  3. 如果有,则在找到对应的<servlet-class>全类名
  4. tomcat会将字节码文件加载进内存,并且创建其对象
  5. 调用其方法

基于注解的配置

  1. 创建JavaEE项目,选择Servlet的版本3.0以上,可以不创建web.xml
  2. 定义一个类,实现Servlet接口
  3. 复写方法
    1
    2
    3
    4
    5
    6
    7
    8
    public class ServletDemo1 implements Servlet{
    ...
    @Override
    public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
    System.out.println("Hello World!");
    }
    ...
    }
  4. 在类上使用@WebServlet注解,进行配置
1
@WebServlet("资源路径")

这个注解的源代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface WebServlet {
String name() default "";//相当于<Servlet-name>
String[] value() default {};//代表urlPatterns()属性配置
String[] urlPatterns() default {};//相当于<url-pattern>
int loadOnStartup() default -1;//相当于<load-on-startup>
WebInitParam[] initParams() default {};
boolean asyncSupported() default false;
String smallIcon() default "";
String largeIcon() default "";
String description() default "";
String displayName() default "";
}
1
2
3
4
5
6
7
8
9
@WebServlet(name = "demo1", urlPatterns = "/demo1")
public class ServletDemo1 implements Servlet{
...
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("Hello World!");
}
...
}

Servlet 生命周期

一个servlet程序要想在Web服务器上运行,一般需要四个步骤:

  1. 装入
  2. 初始化
  3. 提供服务
  4. 销毁

其中程序员能决定,从步骤2开始,到步骤4,装入一般交给web服务器,准确点来说是服务器软件,如Tomcat负责。

初始化

初始化,也就是实例化Servlet的具体实现类的工作,这个动作会调用Servlet接口的init(),在整个servlet生命周期中只会执行一次。

那这里就产生了两个问题

Servlet什么时候初始化?

默认情况下,第一次被访问时,Servlet类就被初始化,可以配置执行Servlet的创建时机。

<servlet>标签下配置

  1. 第一次被访问时创建,<load-on-startup>的值为负数
  2. 在服务器启动时创建,<load-on-startup>的值为0或正整数

Servlet是单例的

Servlet的初始化在整个生命周期过程中只执行一次,这就说明对于每一个Servlet的具体实现类被加载之后,必须要走完整个生命周期,才能再次创建相同实现类的实例,因此在内存中只存在唯一对象,Servlet是单例的一旦涉及到单例,就可能存在线程安全问题

因此尽量不要在Servlet中定义成员变量。即使定义了成员变量,也不要修改值

提供服务

该过程会执行service(),执行多次,每次访问Servlet时,Service方法都会被调用一次。

由于对Servlet接口的进一步封装,现在也很少直接在service()进行编码,一般使用的是HttpServletdoGet()doPost()

销毁

执行destroy方法,只执行一次,Servlet被销毁时执行。服务器关闭时,Servlet被销毁

  • 只有服务器正常关闭时,才会执行destroy方法。
  • destroy方法在Servlet被销毁之前执行,一般用于释放资源

Servlet API及体系结构

一般自定义的servlet都是派生于:HttpServlet

Servlet API类图

其中GenericServletHttpServlet都是抽象类,GenericServlet只提供一个抽象方法service(),对Servlet中的其他方法都做了默认实现。因此如果需要继承GenericServlet,只需要实现一个方法:service()

而对于HttpServlet来说,Http有7中请求方式,将这7种方式都封装在HttpServlet,它们都是:doXXX()这样的方法!

实际上观察HttpServletservice()方法可以看到:

image-20211123125340201

首先getMethod()返回请求方式,如果是post,调用doPost(),如果是get,调用doGet(),以此类推。

实际上对于HttpServlet,常用的两个方法是:doGet()doPost(),因此继承自HttpServletServlet类只需要重写这两个方法即可。

分类

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
  • javax.servlet.http.Cookie

Utils

  • javax.servlet.http.HttpUtils

再谈URLPartten

在前面的Servlet配置中,我们详细介绍对于servlet中的URL如何配置,本小节我们继续深入探讨配置URL的几种方式。

image-20211123125816380

首先在WebServlet里面,可以看到:

1
String[] urlPatterns() default {};

urlPatterns实际上却可以接受一个数组,这说明对于一个servlet它可以有多个访问的URL,如:

1
2
3
4
5
6
7
8
9
// Application Context: servletDemo
@WebServlet({"/demo1", "/demo2", "/demo3"})
public class ServletDemo1 implements Servlet {
... // 省略其他方法
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("Hello World!");
}
}

这样当你本地的tomcat服务器启动后,你在浏览器输入下面三个地址都会链接到该servlet

1
2
3
localhost:8080/servletDemo/demo1
localhost:8080/servletDemo/demo2
localhost:8080/servletDemo/demo3

当然对于一个servlet,你也可以配置多层访问URL,如:

1
2
3
4
5
6
7
8
9
// Application Context: servletDemo
@WebServlet({"/user/demo1", "/user/Askia/demo2", "/user/argento/askia/Hello/demo3"})
public class ServletDemo1 implements Servlet {
... // 省略其他方法
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("Hello World!");
}
}

这样当你本地的tomcat服务器启动后,你需要这样才能够访问这个servlet

1
2
3
localhost:8080/servletDemo/user/demo1
localhost:8080/servletDemo/user/Askia/demo2
localhost:8080/servletDemo/user/argento/askia/Hello/demo3

如果你只写这样:

1
2
localhost:8080/servletDemo/user
localhost:8080/servletDemo/user/argento

相信我,绝对会404

同时URL也是支持通配符*的,如:

1
2
3
4
5
6
7
8
9
// Application Context: servletDemo
@WebServlet("/user/*")
public class ServletDemo1 implements Servlet {
... // 省略其他方法
@Override
public void service(ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException {
System.out.println("Hello World!");
}
}

后面的*无论你输入什么都可以跳转到该servlet,如:

1
2
3
localhost:8080/servletDemo/user/askia
localhost:8080/servletDemo/user/Jewo
localhost:8080/servletDemo/user/newHappy

这些URL都会跳转到这个Servlet里面,当然:如果你只是输入:

1
2
localhost:8080/servletDemo/Askia
localhost:8080/servletDemo/Jewo

那也会404,要看准链接!

Request

HTTP请求消息数据格式

关于HTTP的具体内容这里不具体阐述,可以看这篇文章,这里只放出关于HTTP的请求消息数据格式的解析。

我们浏览器一旦有处理一次HTTP请求,都会发一次类似这样的消息:

1
2
3
4
5
6
7
8
9
10
11
POST /login.html	HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost/login.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1

username=zhangsan

请求行

请求行数据就是HTTP请求数据的第一行,也就是:

1
POST /login.html	HTTP/1.1

这里面包含了请求方式,请求URL,请求协议/版本,如上面的请求消息解析出来就是:

1
2
3
请求方式:Post
请求URL:/login.html
请求协议/版本: HTTP/1.1

请求头

请求头通常是键值对存在,包含非常多的信息:

1
2
3
4
5
6
7
8
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:60.0) Gecko/20100101 Firefox/60.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate
Referer: http://localhost/login.html
Connection: keep-alive
Upgrade-Insecure-Requests: 1

其中比较重要的是:

  1. User-Agent:浏览器告诉服务器,我访问你使用的浏览器版本信息,可以在服务器端获取该头的信息,解决浏览器的兼容性问题
  2. Referer: 告诉服务器,我(当前请求)从哪里来?,如:http://localhost/login.html,这个东西的作用有两个:防盗链和统计工作。

请求空行

就是Upgrade-Insecure-Requests: 1username=zhangsan之间的那个空行,用于分割POST请求的请求头和请求体。

请求体(正文)

封装POST请求消息的请求参数的,只有POST请求方式,才有请求体,如:

1
username=zhangsan	

Request对象继承体系结构

image-20211129114632092

可以看到上面的继承体系,和Servlet的继承体系如出一辙,其中HttpServletRequest只能在HttpServlet中见到。在GenericServlet中常见到的是ServletRequest

Request功能

获取请求行信息的方法

对于请求行数据:

1
GET /day14/demo1?name=zhangsan&user=lisi HTTP/1.1
1
2
3
4
5
6
7
8
String getMethod()				// 获取请求方式 :GET
String getContextPath() // 获取虚拟目录:/day14
String getServletPath() // 获取Servlet路径: /demo1
String getQueryString() // 获取get方式请求参数:name=zhangsan&user=lisi
String getRequestURI() // 获取请求URI:/day14/demo1
StringBuffer getRequestURL() // 获取请求URL:http://localhost/day14/demo1
String getProtocol() // 获取协议及版本:HTTP/1.1
String getRemoteAddr() // 获取客户机的IP地址

获取请求头信息的方法

之前说过请求头信息是一组键值对,因此:

1
2
String getHeader(String name);			// 通过请求头的名称获取请求头的值
Enumeration<String> getHeaderNames() //获取所有的请求头名称
1
2
request.getHeader("User-Agent");
request.getHeader("Accept");

获取请求体信息的方法

1
2
BufferedReader getReader()				// 获取字符输入流,只能操作字符数据(文本)
ServletInputStream getInputStream() // 获取字节输入流,可以操作所有类型数据(字节)

对于这个请求体:

1
username=zhangsan	

获取它的方法:

1
String str = request.getReader().readLine();	// str = username=zhangsan	

Request额外功能

更加通用的获取请求参数方法

前面在请求头里面介绍过获取请求参数的方法:String getQueryString() 这个方法获取到的参数是:XXXXX=XXXX&XXXX=XXX格式的,并且只能是Get方式的请求参数,有没有更加通用的方法,既能获取Get请求参数也能获取Post请求参数,有:

1
2
3
4
String getParameter(String name);			// 根据参数名称获取参数值
String[] getParameterValues(String name); // 根据参数名称获取参数值的数组:hobby=xx&hobby=game
Enumeration<String> getParameterNames(); // 获取所有请求的参数名称
Map<String,String[]> getParameterMap(); // 获取所有参数的map集合

上面四个方法,对于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
2
3
4
5
6
7
8
@WebServlet("/demo1")
public class Servlet1 implements HttpServlet{
...
}
@WebServlet("/demo2")
public class Servlet2 implements HttpServlet{
...
}

我希望在Servlet1处理完之后能够跳转到Servlet2,做一个请求的转发,要做到这点需要两个步骤:

  1. 通过request对象获取请求转发器对象:
1
RequestDispatcher getRequestDispatcher(String path)
  1. 使用RequestDispatcherforward()进行转发:
1
void forward(ServletRequest request, ServletResponse response) 

如上面的例子,我们可以这样写转发:

1
2
3
4
5
6
7
8
9
10
11
@WebServlet("/demo1")
public class Servlet1 implements HttpServlet{
protected void doGet(HttpServletRequest request, HttpServletResponse response){
... // 其他代码省略
request.getRequestDispatcher("/demo2").forward(request, response);
}
}
@WebServlet("/demo2")
public class Servlet2 implements HttpServlet{
...
}

请求转发有三个特点:

  1. 浏览器地址栏路径不发生变化
    调用了request.getRequestDispatcher("/demo2").forward(request, response);之后浏览器的URL还是/demo1,不会因为调用请求转发而改变。
  2. 只能转发到当前服务器内部资源中。
    request.getRequestDispatcher("www.baidu.com")是没法完成请求转发的!
  3. 转发是一次请求

实际上细心的同学还会发现,请求转发还有一种方式,那就是RequestDispatcherinclude()

1
void include(ServletRequest request, ServletResponse response) 

include方式的转发和forward方式的转发实现的功能是一样的,区别在于请求URL的变化。

还有include方式的转发,不能设置response对象的HTTP头和状态码,而forward可以。

include方式的转发,转发之后可以使用out.println(),forward不可以

数据共享

域对象:一个有作用范围的对象,可以在范围内共享数据。

request域:代表一次请求的范围,一般用于请求转发的多个资源中共享数据

1
2
3
void setAttribute(String name,Object obj)		// 存储数据
Object getAttitude(String name) // 通过键获取值
void removeAttribute(String name) // 通过键移除键值对

结合请求转发来使用,可以实现多个Servlet之间的数据共享!

获取ServletContext(Web.xml上的内容)

1
ServletContext getServletContext()