1.Servlet简介
sun公司制定的一种用来扩展web服务器功能的组件规范.
(1)扩展web服务器功能
早期的很多web服务器(apache http server,iis等等)只能够处理静态资源的请求,不能够处理动态资源的请求.
静态资源:指的是需要事先将网页写好.
动态资源:通过计算,生成网页.
(了解)CGI(Common Gateway Interace通用网关接口):可以使用perl,c/c++等语言来开发(比如复杂,并且可移植性差),现在用的少了.
(2)组件规范
组件:符合规范,实现部分功能,并且需要部署到相应的容器里面才能运行的软件模块.
容器:符合规范,提供组件运行环境的程序.
Servlet就是一个组件,需要部署到相应的Servlet容器当中才能运行(比如部署到Tomcat上运行)
案例:
2.Serlvet Hello World
(1).Servlet是Oralce(SUN)定义的开发规范:
webapp
|-- WEB-INF
| |-- web.xml (部署描述文件)配置请求与Serlvet的映射关系
| | /hello -> cn.tedu.day01.HelloServlet
| |-- lib 放置第三方的库 如:数据库驱动程序等
| |-- classes 放置自己写的,编译后的类
| | |-- cn.tedu.day01.HelloServlet.class
|-- index.html
|-- logo.png
(2).固定的接口名
- Servlet 接口
- HelloServlet 类必须实现Servlet接口(还可以继承HttpServlet)
(3).固定的配置文件规则 web.xml
>cn.tedu.day01.HelloServletclass> hello class hello /hello
使用Maven项目创建Web项目步骤:
- 创建Maven项目,选择war方式
- 利用右键菜单生成部署描述文件
- 导入 Targeted Runtimes
- 创建Servlet类
- 部署测试
案例: 使用Servlet接口创建Servlet
1.创建类
public class HelloServlet implements Servlet{
public void init(ServletConfig cfg)
throws ServletException {
}
public void destroy() {
}
public ServletConfig getServletConfig() {
return null;
}
public String getServletInfo() {
return null;
}
public void service(ServletRequest req,ServletResponse res) throws ServletException, IOException {
res.setContentType("text/html");
PrintWriter out=res.getWriter();
out.println(
"Hello World
");
}
}
2.配置web.xml
>cn.tedu.day01.HelloServletclass> hello class hello /hello
3.部署测试
http://localhost:8080/Servlet01/hello
三.Servlet的运行过程
Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
①Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
②装载并创建该Servlet的一个实例对象。
③调用Servlet实例对象的init()方法。
④创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
⑤WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
运行流程图:
step1. 浏览器根据ip和port建立与服务器之间连接.
step2. 浏览器将相关数据打包成请求数据包,然后发送给服务器.
step3. 服务器解析请求数据包,并将解析到的数据存放到request对象里面,同时创建一个respose对象.
step4. 服务器创建Servlet对象,然后调用该对象的service方法来处理请求.
注:在调用service方法时,会将request对象和response对象作为参数传过来.所以,可以在service方法里面,调用request对象来获取请求数据包中的数据,并且将处理结果写到response对象里面.
step5. web服务器会将response对象中的数据取出来,打包(即按照 http协议创建响应数据包),然后发送给浏览器。
step6. 浏览器拆包(按照http协议要求,将响应数据包中的数据解析 出来),然后生成相应的页面。
四.常见的问题及解决方式
(1)404
404是一个状态码,表示服务器依据请求路径找不到对应的资源.
状态码:是一个三位数字,由http协议规定,表示服务器处理请求的一种状态.
(2)解决方式:
a.检查请求路径是否正确
b.检查是否正确部署该应用
(2)500
500也是一个状态码,表示服务器处理出错.
解决方式:
a.代码错误
b.代码不严谨
c.配置出错
(3)405
405也是一个状态码,表示服务器找不到处理方法
解决方法:
检查service方法
(包括方法名,返回类型,参数类型,异常类型)
HttpServlet
实现Servlet接口:
- 直接Servlet接口编程繁琐
- Servlet 接口有两个实现类实现 HttpServlet 更加简便
- GenericServlet
- HttpServlet
- HttpServlet区分了Http请求类型
- get 请求 被 doGet方法处理
- post 请求被 doPost处理
- doGet方法中调用一下doPost,就可以一起处理get和Post
案例2 :GenericServlet
1.编写类
/**
* 相对于实现 Servlet接口,继承 GenericServlet 更简单
*/
public class DemoServlet
extends GenericServlet{
public void service(ServletRequest req,
ServletResponse res)
throws ServletException, IOException {
res.setContentType("text/html");
PrintWriter out = res.getWriter();
out.print("HI GenericServlet");
}
}
2.配置 web.xml
>cn.tedu.day01.DemoServletclass> demo1 class demo1 /demo1
实例3:使用 HttpServlet创建Servlet
1.创建类
/**
* 继承 HttpServlet,比实现Servlet接口简单 相比 GenericServlet 可以区别 get、post请求重写 doGet 处理 get 请求
* get请求: 浏览器地址栏直接请求是get请求
* a标签连接请求是get请求
* img标签中的src是get请求
* 重写 doPost处理 post 请求
* post请求: 表单method=post时候的请求
*/
public class TestServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req,
HttpServletResponse resp)
throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter out=resp.getWriter();
out.println("HI HttpServlet doGet()");
}
}
2.配置 web.xml
>cn.tedu.day01.TestServletclass> demo2 class demo2 /demo2
案例4 :利用网页处理get请求
1.编写网页 webapp/demo.html
Insert title here
测试get请求
test
测试post请求
客户端向服务器发起post请求,但是服务器只能处理
get请求,此时服务器端会出现 405错误!
案例5 利用HttpServlet处理post请求
1.编写Servlet类
/**
* 处理Post请求,就需要重写doPost方法
*/
public class DoPostServlet extends HttpServlet{
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("Hi doPost");
}
}
2.配置
>cn.tedu.day01.DoPostServletclass> demo3 class demo3 /demo3
3.编写网页
正确处理post请求案例
案例6: 既能处理get也能处理post请求的Servlet
1.编写Servlet
/**
* 既能处理get请求也能处理post请求
* 1. get请求-> doGet() -> doPost()
* 2. post请求 -> doPost()
*/
public class GetPostServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doPost(req, resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter out=resp.getWriter();
out.print("get & post");
}
}
2.配置
>cn.tedu.day01.GetPostServletclass> demo4 class demo4 /demo4
3.编写 html
即能处理get也能处理post请求
test
HttpServletRequest
客户端浏览器发起的请求消息,经过Tomcat解析以后,封装到Request对象中。
利用Request提供API可以获取请求中的信息。
案例7 利用Request获取请求信息
-
编写Servlet
/**
* 演示 Request 对象的功能
* Request 代表用户浏览器发送的请求信息
*/
public class RequestDemoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//读取请求行上的 Method 信息
String method=request.getMethod();
//读取请求行上的 Request-URI
String uri = request.getRequestURI();
//读取请求行上的协议版本
String protocol = request.getProtocol();
//读取请求头信息
// User-Agent 用户代理,就是浏览器的信息, 类型版本等
// 获取请求头中的用户浏览器相关信息
String ua=request.getHeader("User-Agent");
String host = request.getHeader("Host");
//设置服务器发送端的编码规则
//response.setCharacterEncoding("UTF-8");
//设置浏览器接收时候 的解码规则
response.setContentType(
"text/html; charset=UTF-8");
//设置 contentType 时候,response会自动
//设置CharacterEncoding
PrintWriter out = response.getWriter();
out.print("");
out.print("");
out.print("");
out.print("- "+method+"
");
out.print("- "+uri+"
");
out.print("- "+protocol+"
");
out.print("- "+ua+"
");
out.print("- "+host+"
");
out.print("- 试试
");
out.print("
");
out.print("");
out.print("");
}
}
2.配置
RequestDemoServlet RequestDemoServlet class>cn.tedu.day01.RequestDemoServlet class>RequestDemoServlet /reqdemo
HttpServletResponse:
Response对象代表服务器向客户端发送的信息
案例8 利于Response对象向客户端发送信息
-
Servlet类
/**
* 演示Response对象的功能
*/
public class ResponseDemoServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置响应行状态码
response.setStatus(404);
//添加一个自定义的响应消息头:
response.addHeader("Demo", "Hello World!");
response.setContentType(
"text/html; charset=UTF-8");
//一定在设置编码以后,获取out对象!!否则有编码错误
PrintWriter out = response.getWriter();
//设置响应的实体Body
out.println("");
out.println("");
out.println("404,没有找到妹子呀!
");
out.println("");
out.println("");
}
}
2.配置
>cn.tedu.day01.ResponseDemoServletclass> ResponseDemoServlet ResponseDemoServlet class ResponseDemoServlet /respdemo
五.http协议(了解)
1.什么是http协议?
是w3c制定的一个网络应用层协议,规定了浏览器如何与web服务器之间进行通信以及相应的数据包格式.
# 2.如何通信?
ste1.建立连接
step2.发送请求
step3.发送响应
step4.关闭连接
注:如果要再次发送请求,需要重新建立新的连接.即"一次请求,一次连接."这样做,优点是,服务器可以利用有限的连接为尽可能多的请求服务.
# 3.数据格式
1.请求数据包
请求行(请求类型 请求资源路径 协议类型和版本)
若干消息头:
消息头是一些键值对,由w3c定义,浏览器和服务器可以利用消息头传递一些特定的信息,比如浏览器可以通过
"user-agent"消息头告诉浏览器,浏览器的类型和版本.
实体内容 :
如果请求类型是get,则内容为空
如果是请求类型是post,则内容为请求参数.
GET /servlet-day01/date HTTP/1.1
Host: localhost:8080
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3724.8 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9
2.响应数据包
状态行(协议类型和版本 状态码 状态描述)
若干消息头:
服务器也可以发送一些消息头给浏览器,比如发送content-type消息头,告诉浏览器,服务器返回的
数据的类型和编码
实体内容:
程序的处理结果,浏览器解析之后,生成相应的页面
HTTP/1.1 200 OK
Server: Apache-Coyote/1.1
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 10
Date: Wed, 18 Sep 2019 13:55:20 GMT
3.get请求
特点:
a.会将请求参数添加到请求行,只能提交少量的数据给服务器 请求行只有一行,大约能存放4k左右的数据.
b.会将请求参数显示在浏览器地址栏,不安全.
4.post请求
a.会将请求参数添加到实体类型里面,可以提交大量的数据给服务器.
b.不会将请求参数显示 在浏览器地址栏,相对安全(注意,并不会加密,对于敏感数据 需要加密处理)
6.Servlet输出中文,要注意什么?
1.为什么会有乱码?
out.println方法 默认会使用ISO-8859-1来编码.
如何解决?
response.setContentType("text/html;charset=utf-8");
注:这句话有两个作用
a.设置content-type消息头的值(告诉浏览器,服务器返回的数据类型和编码)
b.设置out.println方法使用哪种字符集
2.表单包含有中文参数值,如何处理?
为什么会有乱码?
表单提交时,浏览器会对中文参数值进行编码(比如使用utf-8来编码),服务器端,默认会使用iso-8859-1来解码,所以会产生编码.
注:使用打开该表单所在的页面时的字符集来编码.
如何解决?
a.post请求
request.setCharacterEncoding("utf-8");
b.get请求
在server.xml添加代码 URIEncoding="utf-8":
Servlet与普通Java类的区别
Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。
针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才会销毁。
在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。
如果在
举例:
> org.apache.catalina.servlets.InvokerServlet class> invoker class 1
用途:为web应用写一个InitServlet,这个servlet配置为启动时装载,为整个web应用创建必要的数据库表和数据。
六.Servlet的线程安全问题
当多个客户端并发访问同一个Servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。例如下面的代码:
不存在线程安全问题的代码:
package gacl.servlet.study;
2
3 import java.io.IOException;
4
5 import javax.servlet.ServletException;
6 import javax.servlet.http.HttpServlet;
7 import javax.servlet.http.HttpServletRequest;
8 import javax.servlet.http.HttpServletResponse;
9
10 public class ServletDemo3 extends HttpServlet {
11
12
13 public void doGet(HttpServletRequest request, HttpServletResponse response)
14 throws ServletException, IOException {
15
16 /**
17 * 当多线程并发访问这个方法里面的代码时,会存在线程安全问题吗
18 * i变量被多个线程并发访问,但是没有线程安全问题,因为i是doGet方法里面的局部变量,
19 * 当有多个线程并发访问doGet方法时,每一个线程里面都有自己的i变量,
20 * 各个线程操作的都是自己的i变量,所以不存在线程安全问题
21 * 多线程并发访问某一个方法的时候,如果在方法内部定义了一些资源(变量,集合等)
22 * 那么每一个线程都有这些东西,所以就不存在线程安全问题了
23 */
24 int i=1;
25 i++;
26 response.getWriter().write(i);
27 }
28
29 public void doPost(HttpServletRequest request, HttpServletResponse response)
30 throws ServletException, IOException {
31 doGet(request, response);
32 }
33
34 }
存在线程安全问题的代码:
package gacl.servlet.study;
2
3 import java.io.IOException;
4
5 import javax.servlet.ServletException;
6 import javax.servlet.http.HttpServlet;
7 import javax.servlet.http.HttpServletRequest;
8 import javax.servlet.http.HttpServletResponse;
9
10 public class ServletDemo3 extends HttpServlet {
11
12 int i=1;
13 public void doGet(HttpServletRequest request, HttpServletResponse response)
14 throws ServletException, IOException {
15
16 i++;
17 try {
18 Thread.sleep(1000*4);
19 } catch (InterruptedException e) {
20 e.printStackTrace();
21 }
22 response.getWriter().write(i+"");
23 }
24
25 public void doPost(HttpServletRequest request, HttpServletResponse response)
26 throws ServletException, IOException {
27 doGet(request, response);
28 }
29
30 }
把i定义成全局变量,当多个线程并发访问变量i时,就会存在线程安全问题了,如下图所示:同时开启两个浏览器模拟并发访问同一个Servlet,本来正常来说,第一个浏览器应该看到2,而第二个浏览器应该看到3的,结果两个浏览器都看到了3,这就不正常。
线程安全问题只存在多个线程并发操作同一个资源的情况下,所以在编写Servlet的时候,如果并发访问某一个资源(变量,集合等),就会存在线程安全问题,那么该如何解决这个问题呢?
先看看下面的代码:
package gacl.servlet.study;
2
3 import java.io.IOException;
4
5 import javax.servlet.ServletException;
6 import javax.servlet.http.HttpServlet;
7 import javax.servlet.http.HttpServletRequest;
8 import javax.servlet.http.HttpServletResponse;
9
10
11 public class ServletDemo3 extends HttpServlet {
12
13 int i=1;
14 public void doGet(HttpServletRequest request, HttpServletResponse response)
15 throws ServletException, IOException {
16 /**
17 * 加了synchronized后,并发访问i时就不存在线程安全问题了,
18 * 为什么加了synchronized后就没有线程安全问题了呢?
19 * 假如现在有一个线程访问Servlet对象,那么它就先拿到了Servlet对象的那把锁
20 * 等到它执行完之后才会把锁还给Servlet对象,由于是它先拿到了Servlet对象的那把锁,
21 * 所以当有别的线程来访问这个Servlet对象时,由于锁已经被之前的线程拿走了,后面的线程只能排队等候了
22 *
23 */
24 synchronized (this) {//在java中,每一个对象都有一把锁,这里的this指的就是Servlet对象
25 i++;
26 try {
27 Thread.sleep(1000*4);
28 } catch (InterruptedException e) {
29 e.printStackTrace();
30 }
31 response.getWriter().write(i+"");
32 }
33
34 }
35
36 public void doPost(HttpServletRequest request, HttpServletResponse response)
37 throws ServletException, IOException {
38 doGet(request, response);
39 }
40
41 }
现在这种做法是给Servlet对象加了一把锁,保证任何时候都只有一个线程在访问该Servlet对象里面的资源,这样就不存在线程安全问题了,如下图所示:
这种做法虽然解决了线程安全问题,但是编写Servlet却万万不能用这种方式处理线程安全问题,假如有9999个人同时访问这个Servlet,那么这9999个人必须按先后顺序排队轮流访问。
针对Servlet的线程安全问题,Sun公司是提供有解决方案的:让Servlet去实现一个SingleThreadModel接口,如果某个Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用其service方法。
查看Sevlet的API可以看到,SingleThreadModel接口中没有定义任何方法和常量,在Java中,把没有定义任何方法和常量的接口称之为标记接口,经常看到的一个最典型的标记接口就是"Serializable",这个接口也是没有定义任何方法和常量的,标记接口在Java中有什么用呢?主要作用就是给某个对象打上一个标志,告诉JVM,这个对象可以做什么,比如实现了"Serializable"接口的类的对象就可以被序列化,还有一个"Cloneable"接口,这个也是一个标记接口,在默认情况下,Java中的对象是不允许被克隆的,就像现实生活中的人一样,不允许克隆,但是只要实现了"Cloneable"接口,那么对象就可以被克隆了。
让Servlet实现了SingleThreadModel接口,只要在Servlet类的定义中增加实现SingleThreadModel接口的声明即可。
对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立的Servlet实例对象。
实现SingleThreadModel接口并不能真正解决Servlet的线程安全问题,因为Servlet引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过时的)。
2019-12-25 22:26:33