#Servlet是什么?
sun公司制订的一种用来扩展web服务器功能的组件规范。
(1)扩展web服务器功能
早期的web服务器只能处理静态资源的请求,即需要事先将html文件准备好,并存放到web服务器上面。不能够处理动态资源的请求(需要计算,动态生成html)。早期使用CGI(CommonGatewayInterface)应用程序来扩展。CGI程序使用perl,c/c++等语言来编写,编写繁琐,不方便移值,性能也不是很好,现在用得很少了。浏览器将请求发送给webserver,如果是动态资源的请求,web server会将请求转交给servlet容器来处理,由容器来处理。
(2)组件规范
- 什么是组件?
符合规范,实现部分功能,并且需要部署到相应的容器里面才能运行的软件模块。
servlet就是一个组件,需要部署到servlet容器里面才能运行。- 什么是容器?
符合规范,提供组件运行环境的程序。
servlet容器为servlet提供运行环境。
#手动部署Servlet
-
step1. 写一个java类,实现Servlet接口或者继承HttpServlet抽象类。
注:建议继承HttpServlet抽象类。 -
step2.编译。
-
step3.打包。
创建一个具有如下结构的文件夹:appname (应用名) WEB-INF classes (.class文件) lib (可选 .jar文件) web.xml (部署描述文件)
-
step4.部署。
将step3创建的文件夹拷贝到容器里面。
可以使用jar命令将step3创建的文件夹压缩成 ".war"结尾的文件,然后再拷贝。 -
step5.启动容器,访问Servlet
http://ip:port/appname/servlet-pattern
注: servlet-pattern在web.xml当中定义。
#Servlet运行过程
比如,在浏览器地址栏输入 http://ip:port/servlet-day01/hello
-
step1.浏览器依据ip,port建立连接(即与web服务器之间建立网络连接)。
-
step2.浏览器需要将相关数据打包(即按照http协议要求,制作一个请求数据包,包含了一些数据,比如请求资源路径),并且将请求
数据包发送出去。 -
step3.web服务器会将请求数据包中数据解析出来,并且将这些数据添加到request对象,同时,还会创建一个response对象。
-
step4.web服务器创建Servlet对象,然后调用该对象的service方法(会将request和response作为参数)。
注:在service方法里面,通过使用request获得请求相关的数据,
比如请求参数值,然后将处理结果写到response。 -
step5.web服务器将response中的数据取出来,制作响应数据包,然后发送给浏览器。
-
step6. 浏览器解析响应数据包,然后展现。
#常见的错误
##500
500是状态码,表示系统错误。
产生的原因:
-
web.xml文件配置错误。比如将类名写错。
-
源代码写错。比如,没有继承HttpServlet。
##404
404是状态码,表示依据请求路径找不到对应的资源。
产生的原因 -
没有部署应用
-
访问地址写错
-
web.xml配置文件写错
##405
405是状态码,表示找不到处理方法。
产生的原因:service方法签名错误(方法名,参数类型,异常类型,返回类型写错)
#HTTP协议
##什么是HTTP协议?
是一种网络应用层协议,规定了浏览器与web服务器之间如何通信以及数据包的结构。
##如何通信?
step1. 先建立连接。
step2. 发送请求数据包。
step3. 发送响应数据包。
step4. 关闭连接。
即 一次请求,一次连接。
##优点
web服务器可以利用有限的连接为尽可能多的客户请求服务。
##两种数据包的结构
###请求数据包
- 请求行 (请求类型 请求资源路径 协议和版本)
GET /hotelmanager/home HTTP/1.1
- 若干消息头:消息头是一些键值对(键和值之间使用": "分隔)
浏览器和服务器之间可以利用消息头传递一些特殊的信息。
比如,浏览器可以发送"user-agent"告诉服务器,浏览器的类型和版本。 - 实体内容:如果请求类型是get,实体内容为空。
###响应数据包
- 状态行 (协议和版本 状态码 状态描述):状态码是一个三位数字,表示服务器处理请求的状态。
HTTP/1.1 200 OK
- 消息头
服务器同样也可以将一些消息头发送给浏览器。
如可以通过设置content-type消息头,告诉浏览器,服务器返回的数据类型。 - 实体内容
程序处理的结果。浏览器会解析实体内容中的数据,然后展现。
#两种请求类型
##get请求
- 哪一些情况下,浏览器会发送get请求?
- 直接输入某个地址
- 点击链接,例如:
pricemodify.jsp?roomtype="单人间"&price=199
- 表单默认提交方式
- 特点
- 会将请求参数放到请求行里面,只能提交少量的数据。
- 因为请求行大约只能存放2k左右的数据。
- 会将请求参数显示在浏览器地址栏,不安全。
- 如有些网络设备(路由器)会记录这些地址。
##post请求
- 哪一些情况下,浏览器会发送post请求,设置表单的method=“post”
- 特点
- 不会将请求参数显示在浏览器地址栏,相对安全,但并不会对请求参数加密
- 会将请求参数添加到实体内容里面,所以,可以提交大量数据给服务器
#Servlet输出乱码问题
- 为什么会乱码?
out在输出时,默认使用iso-8859-1来编码。 - 如何解决?
response.setContentType(“text/html;charset=utf-8”);
这行代码的作用:
- 作用1.返回content-type消息头,告诉浏览器,服务器返回的数据类型。
- 作用2:另外,out在输出时,会使用指定的字符集来编码。
#表单中文参数乱码问题
- 为什么会有乱码?
表单提交时,浏览器会对中文参数值进行编码。服务器端默认会使用iso-8859-1来解码。
会使用表单所在的页面打开时使用的字符集来编码。 - 如何解决?
- 情形1 表单提交方式为post
request.setCharacterEncoding(“utf-8”);
注:要加到所有的request.getParameter方法前面。 - 情形2 表单提交方式为get
设置<Connector URIEncoding="utf-8"/>
此参数在tomcat- service.xml文件中
只针对get请求有效。
#读取请求参数值
form表单post提交,表单控件中都有name值
(1) request提供的getParameter方法。
String getParameter(String paramName);
如果请求参数名写错,会返回null值。
如果不填写任何数据,会获得空字符串。
(2) request提供的getParameterValues
String[] getParameterValues(String paramName);
当有多个请求参数名相同时,用该方法。
多选框,如果不选择任何选项,浏览器不会发送任何数据给服务器。
#重定向
(1) 什么是重定向?
服务器通知浏览器,向一个新地址发送请求。
服务器可以发送一个302状态码以及一个location消息头(值是一个地址,称之为重定向地址)给浏览器,浏览器收到之后,会立即向重定向地址发送请求。
(2)如何重定向?
- response.sendRedirect(String url);:url就是重定向地址。
重定向之前,容器会清空response对象上存放的所有数据。 也就是说,
实体内容里面是没有任何数据的。
(3)特点
- 重定向地址是任意的。
- 重定向之后浏览器地址会发生变化。
#Servlet容器处理请求资源路径
比如 http://ip:port/servlet-day03/abc.html
“/servlet-day03/abc.html”
- step1. 容器默认会认为访问的是一个servlet即查找和"/abc.html"匹配的servlet。
- 匹配规则:
a.精确匹配:
<url-pattern>/abc.html</url-pattern>
b.通配符匹配:
<url-pattern>/*</url-pattern>
<url-pattern>/demo/*</url-pattern>
*:匹配零个或者多个任意的字符。
c.后缀匹配:
<url-pattern>*.do</url-pattern>
*.do 匹配所有以.do结尾的请求。
此方法可以让一个servlet处理多个请求
- step2.如果找不到匹配的servlet,则访问对应的文件。
#Servlet的生命周期
容器如何创建servlet对象,如何对其进行初始化处理,如果调用其方法来处理请求,以及如何销毁该对象的整个过程。
即容器如何管理servlet。
- 生命周期
- 实例化一个Servlet对象,即new一个:执行一次
- 实例化的同时new一个ServletConfig对象
- 调用init()函数初始化对象:执行一次
- 将ServletConfig对象传给init()方法
- 通信组件调用Servlet的service()方法:执行多次
- Tomcat关闭时,销毁Servlet对象:执行一次
注意:Servlet在Tomcat内是单例(单个实例)的默认是首次访问Servlet时实例化它可以通过设置,使得Tomcat启动时实例化。
在web.xml servlet标签中加入
<!-- 配置启动加载 -->
<!-- 值越小,优先级越高(即先被实例化)-->
<load-on-startup>1</load-on-startup></servlet>
##service方法和doGet()&doPost()
容器收到请求之后,会调用servlet实例的service方法。HttpServlet的service方法是如何实现的?依据请求类型,分别调用对应的doXXX方法(比如,get请求就调用doGet方法)。
##相关的几个接口和类
- Servlet接口
- init(ServletConfig config)
- service(ServletRequest req,ServletResponse res)ServletRequest是一个接口,HttpServletRequest是其子接口。ServletResponse与之类似。
- destroy()
- GenericServlet抽象类实现了Servlet接口的init和destroy方法。
- HttpServlet抽象类继承了GenericServlet,实现了service方法。
#ServletConfig对象
为此Servlet预置数据,即初始化参数。
该数据在Tomcat创建ServletConfig后,由Config自动读取config与Servlet是1对1的关系
预置数据标签:web.xml的servlet标签内加入
<init-param>
<param-name>city</param-name>
<param-value>北京</param-value>
</init-param>
- 获取预置数据
ServletConfig cfg=getServletConfig();
System.out.println(cfg.getInitParameter(“city”));
#Servlet上下文
容器启动之后,会为每一个web应用创建一个实现了ServletContext接口要求的对象
该对象可以称之为Servlet上下文。
- 特点
- 唯一性:一个web应用对应一个Servlet上下文。
- 一直存在: 只要容器没有关闭,应用没有被卸载,Servlet上下文就会一直存在。
- 如何获取Servlet上下文?
GenericServlet,ServletConfig,HttpSession,FilterConfig
都提供了一个方法(getServletContext)来 获得Servlet上下文。
ServletContext sc = getServletContext(); - 作用
- 绑订数据
setAttribute,removeAttribute,getAttribute
在满足使用条件的情况下,优先使用生命周期短的
(request < session < Servlet上下文)。 - 读取全局的初始化参数
<!-- 配置全局的初始化参数 -->
<context-param>
<param-name>company</param-name>
<param-value>这而有一个名称</param-value>
</context-param>
//使用继承自GenericServlet的方法来获得上下文
ServletContext sc = getServletContext();
sc.setAttribute("username", "Sally");
//读取全局的初始化参数
String company = sc.getInitParameter("company");
#Servlet线程安全问题
- 为什么说Servlet会有线程安全问题?
- 容器默认情况下,对于某个Servlet,只会创建一个实例。
- 容器收到请求,就会启动一个线程来处理,就有可能有多个线程同时访问同一个Servlet实例(比如,这些线程去修改Servlet的属性值),就有可能产生线程安全问题。
- 如何解决?
a.加锁使用synchronized对有线程安全问题的代码加锁。
b.尽量避免修改属性值
public void service(HttpServletRequest request,HttpServletResponse response)
throws ServletException, IOException {
synchronized(this){
count ++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(
Thread.currentThread()
.getName() + ": " + count);
}
}