1、Servlet详解
1.1 Servlet核心接口和类
在Servlet体系结构中,除了实现Servlet接口,还可以通过继承GenericServlet 或 HttpServlet类,完成编写。
1.1.1 Servlet接口
在Servlet API中最重要的是Servlet接口,所有Servlet都会直接或间接的与该接口发生联系,或是直接实现该接口,或间接继承自实现了该接口的类。 该接口包括以下五个方法:
init(ServletConfig config)
ServletConfig getServletConfig()
service(ServletRequest req,ServletResponse res)
String getServletInfo()
destroy( )
1.1.2 GenericServlet抽象类
GenericServlet 使编写 Servlet 变得更容易。它提供生命周期方法 init 和 destroy 的简单实现,要编写一般的 Servlet,只需重写抽象 service 方法即可。
1.1.3 HttpServlet类
HttpServlet是继承GenericServlet的基础上进一步的扩展。
HttpServlet是GenericServlet的子类,重写了service方法 ,转发到doGet或doPost方法中 默认get请求一般情况下要么重写doGet或doPost方法,要么重写service方法提供将要被子类化以创建适用于 Web 站点的 HTTP servlet 的抽象类。HttpServlet 的子类至少必须重写一个方法,该方法通常是以下这些方法之一: doGet,如果 servlet 支持 HTTP GET 请求 doPost,用于 HTTP POST 请求 doPut,用于 HTTP PUT 请求 doDelete,用于 HTTP DELETE 请求
1.2 Servlet两种创建方式
1.2.1 实现接口Servlet
/**
* Servlet创建的第一种方式:实现接口Servlet
* */
public class HelloServlet2 implements Servlet{
@Override
public void destroy() {
}
@Override
public ServletConfig getServletConfig() {
return null;
}
@Override
public String getServletInfo() {
return null;
}
@Override
public void init(ServletConfig arg0) throws ServletException {
}
@Override
public void service(ServletRequest request, ServletResponse response) throws ServletException, IOException {
System.out.println("OK");
response.getWriter().println("welcome use servlet");
}
}
1.2.2 继承HttpServlet(推荐)
/**
* Servlet implementation class HelloServlet
* Servlet的第二种创建方式,继承HttpServlet.也是开发中推荐的
* 一般情况下:要么重写doGet/doPost;要么重写service方法
*/
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().print("welcome use servlet");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
1.2.3 常见错误
HTTP Status 404 资源找不到 。
第一种情况:地址书写错误。
第二种情况:地址没有问题,把IDEA项目中out目录删除,然后重新运行。
Serlvet地址配置重复。both mapped to the url-pattern [/helloservlet] which is not permitted。
Serlvet地址配置错误。比如没有写/ Invalid <url-pattern> [helloservlet2] in servlet mapping。
1.3 Servlet两种配置方式
1.3.1 使用web.xml(Servlet2.5之前使用)
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://xmlns.jcp.org/xml/ns/javaee" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd" version="3.1">
<display-name>Web_Day11</display-name>
<!--Servlet的第二种配置 -->
<!--Servlet配置 -->
<servlet>
<!--名称 -->
<servlet-name>hello2</servlet-name>
<!--Servlet的全称类名 -->
<servlet-class>com.qf.web.servlet.HelloServlet</servlet-class>
<!--启动的优先级,数字越小越先起作用 -->
<load-on-startup>1</load-on-startup>
</servlet>
<!--映射配置 -->
<servlet-mapping>
<!--名称 -->
<servlet-name>hello2</servlet-name>
<!--资源的匹配规则:精确匹配 -->
<url-pattern>/hello2</url-pattern>
</servlet-mapping>
<welcome-file-list>
<welcome-file>login.html</welcome-file>
</welcome-file-list>
</web-app>
1.3.2 配置属性
url-pattern定义匹配规则,取值说明: 精确匹配 /具体的名称 只有url路径是具体的名称的时候才会触发Servlet 后缀匹配 *.xxx 只要是以xxx结尾的就匹配触发Servlet 通配符匹配 /* 匹配所有请求,包含服务器的所有资源 通配符匹配 / 匹配所有请求,包含服务器的所有资源,不包括.jsp load-on-startup 1元素标记容器是否应该在web应用程序启动的时候就加载这个servlet。 2它的值必须是一个整数,表示servlet被加载的先后顺序。 3如果该元素的值为负数或者没有设置,则容器会当Servlet被请求时再加载。 4如果值为正整数或者0时,表示容器在应用启动时就加载并初始化这个servlet,值越小,servlet的优先级越高,就越先被加载。值相同时,容器就会自己选择顺序来加载。
1.3.3 使用注解 (Servlet3.0后支持,推荐)
/**
* Servlet implementation class HelloServlet
* 演示Servlet注解式配置
*/
@WebServlet("/hello")
public class HelloServlet extends HttpServlet {
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.getWriter().print("OK");
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
1.3.4 @WebServlet注解常用属性
name: Serlvet名字 (可选)
value: 配置url路径,可以配置多个
urlPatterns:配置url路径 ,和value作用一样,不能同时使用
loadOnStartup:配置Servlet的创建的时机, 如果是0或者正数 启动程序时创建,如果是负数,则访问时创建。 数子越小优先级越高。
2、Servlet应用【重点】
2.1 request对象
在Servlet中用来处理客户端请求需要用doGet或doPost方法的request对象
| request |
|---|
![]() |
2.1.1 get和post区别
get提交的数据会放在URL之后,以?分割URL和传输数据,参数之间以&相连
get方式明文传递,数据量小,不安全
效率高,浏览器默认请求方式为GET请求
对应的Servlet的方法是doGet
post方法是把提交的数据放在HTTP包的Body中
密文传递数据,数据量大,安全
效率相对没有GET高
对应的Servlet的方法是doPost
2.1.2 request主要方法
| 方法名 | 说明 |
|---|---|
| String getParameter(String name) | 根据表单组件名称获取提交数据 |
| void setCharacterEncoding(String charset) | 指定每个请求的编码 |
2.1.3 request应用
HTML页面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>欢迎页面</title>
</head>
<body>
<h1>欢迎你</h1>
<div>
<form action="HelloServlet" method="post">
<label>姓名:</label><input type="text" name="name"><br/>
<label>年龄:</label><input type="text" name="age"><br/>
<input type="submit" value="提交">
</form>
</div>
</body>
</html>
Servlet代码
@WebServlet("/HelloServlet")
public class HelloServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取表单提交的姓名
String name=request.getParameter("name");
//获取年龄
String age=request.getParameter("age");
//服务端输出打印
System.out.println(request.getRemoteAddr()+"发来信息:姓名:"+name+"---->年龄:"+age);
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
}
2.1.4 get请求收参问题
产生乱码是因为服务器和客户端沟通的编码不一致造成的,因此解决的办法是:在客户端和服务器之间设置一个统一的编码,之后就按照此编码进行数据的传输和接收
2.1.5 get中文乱码(在Tomcat7及以下版本会出现)
在Tomcat7及以下版本,客户端以UTF-8的编码传输数据到服务器端,而服务器端的request对象使用的是ISO8859-1这个字符编码来接收数据,服务器和客户端沟通的编码不一致因此才会产生中文乱码的。
解决办法:在接收到数据后,先获取request对象以ISO8859-1字符编码接收到的原始数据的字节数组,然后通过字节数组以指定的编码构建字符串,解决乱码问题。
Tomcat8的版本中get方式不会出现乱码了,因为服务器对url的编码格式可以进行自动转换。
/**
* Servlet implementation class HelloServlet
* 演示Servlet的GET请求,中文乱码的问题
*
*/
@WebServlet("/GETServlet")
public class GetServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//获取表单提交的姓名
String name=request.getParameter("name");
name=new String(name.getBytes("ISO8859-1"),"UTF-8");
//获取年龄
String age=request.getParameter("age");
//服务端输出打印
System.out.println(request.getRemoteAddr()+"发来信息:姓名:"+name+"---->年龄:"+age);
}
}
2.1.6 post中文乱码
由于客户端是以UTF-8字符编码将表单数据传输到服务器端的,因此服务器也需要设置以UTF-8字符编码进行接收。
解决方案:使用从ServletRequest接口继承而来的setCharacterEncoding(charset)方法进行统一的编码设置。
/**
* Servlet implementation class HelloServlet
* 演示Servlet的GET请求,中文乱码的问题
*
*/
@WebServlet("/GETServlet")
public class GetServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//设置请求参数的编码格式--对GET无效
request.setCharacterEncoding("UTF-8");
//获取表单提交的信息
String name=request.getParameter("msg");
//服务端输出打印
System.out.println(request.getRemoteAddr()+"发来信息:"+msg);
}
}
2.2 response对象
response对象用于响应客户请求并向客户端输出信息。
| response |
|---|
![]() |
2.2.1 response主要方法
| 方法名称 | 作用 |
|---|---|
| setHeader(name,value) | 设置响应信息头 |
| setContentType(String) | 设置响应文件类型、响应式的编码格式 |
| setCharacterEncoding(String) | 设置服务端响应内容编码格式 |
| getWriter() | 获取字符输出流 |
2.2.2 response应用
//获取请求参数代码省略
//获取输出流
PrintWriter out=response.getWriter();
out.println("<html>");
out.println("<head><title>login</title></head>");
out.println("<body>");
out.println("<h1>Login Success!!!</h1>");
//out.println("<h1>登录成功!!!</h1>");
out.println("</body>");
out.println("</html>");
2.2.3 解决输出中文乱码
设置服务器端响应的编码格式
设置客户端响应内容的头内容的文件类型及编码格式
//不推荐
response.setCharacterEncoding("utf-8");//设置响应编码格式为utf-8
response.setHeader("Content-type","text/html;charset=UTF-8");
同时设置服务端的编码格式和客户端响应的文件类型及响应时的编码格式
response.setContentType("text/html;charset=UTF-8");
3、 转发与重定向
3.1 现有问题
在之前案例中,调用业务逻辑和显示结果页面都在同一个Servlet里,就会产生设计问题
不符合单一职能原则、各司其职的思想
不利于后续的维护
应该将调用业务逻辑的控制层和显示结果分离开
| 现阶段问题 |
|---|
![]() |
3.1.1 业务、显示分离
| 业务与显示分离 |
|---|
![]() |
3.2 转发
转发的作用在服务器端,将请求发送给服务器上的其他资源,以共同完成一次请求的处理。
3.2.1 页面跳转
在调用业务逻辑的Servlet中,编写以下代码
request.getRequestDispatcher("/目标URL-pattern").forward(request, response);
| forward |
|---|
![]() |
3.2.2 数据传递
forward表示一次请求,是在服务器内部跳转,可以共享同一次request作用域中的数据
request作用域:拥有存储数据的空间,作用范围是一次请求有效(一次请求可以经过多次转发)
可以将数据存入request后,在一次请求过程中的任何位置进行获取
可传递任何数据(基本数据类型、对象、数组、集合等)
存数据:request.setAttribute(key,value);
以键值对形式存储在request作用域中。key为String类型,value为Object类型
取数据:request.getAttribute(key);
通过String类型的key访问Object类型的value
3.2.3 转发特点
转发是服务器行为
转发是浏览器只做了一次访问请求
转发浏览器地址不变
转发两次跳转之间传输的信息不会丢失,所以可以通过request进行数据的传递、
转发只能将请求转发给同一个Web应用中的组件
3.3 重定向
重定向作用在客户端,客户端将请求发送给服务器后,服务器响应给客户端一个新的请求地址,客户端重新发送新请求。
3.3.1 页面跳转
在调用业务逻辑的Servlet中,编写以下代码
response.sendRedirect("目标URI");
| redirect |
|---|
![]() |
3.3.2 数据传递
sendRedirect跳转时,地址栏改变,代表客户端重新发送的请求。属于两次请求
response没有作用域,两次request请求中的数据无法共享
传递数据:通过URI的拼接进行数据传递("/WebProject/b?username=tom");
获取数据:request.getParameter("username");
3.3.3 重定向特点
重定向是客户端行为。
重定向是浏览器做了至少两次的访问请求。
重定向浏览器地址改变。
重定向两次跳转之间传输的信息会丢失(request范围)。
重定向可以指向任何的资源,包括当前应用程序中的其他资源、同一个站点上的其他应用程序中的资源、其他站点的资源。
3.4 转发、重定向总结
当两个Servlet需要传递数据时,选择forward转发。不建议使用sendRedirect进行传递
4. Servlet生命周期
4.1 生命周期四个阶段
4.1.1 实例化
当用户第一次访问Servlet时,由容器调用Servlet的构造器创建具体的Servlet对象。也可以在容器启动之后立刻创建实例。使用如下代码可以设置Servlet是否在服务器启动时就创建。 <load-on-startup>1</load-on-startup>
注意:只执行一次
4.1.2 初始化
在初始化阶段,init()方法会被调用。这个方法在javax.servlet.Servlet接口中定义。其中,方法以一个ServletConfig类型的对象作为参数。
注意:init方法只被执行一次
4.1.3 服务
当客户端有一个请求时,容器就会将请求ServletRequest与响应ServletResponse对象转给Servlet,以参数的形式传给service方法。
此方法会执行多次
4.1.4 销毁
当Servlet容器停止或者重新启动都会引起销毁Servlet对象并调用destroy方法。
destroy方法执行一次
4.1.5 Servlet执行流程
| Servlet执行流程 |
|---|
![]() |
/**
* Servlet implementation class LifeServlet
* 演示Servlet的生命周期:
* 1、实例化
* 2、init:初始化
* 3、service:服务
* 4、destory:销毁
*/
@WebServlet("/lifeservlet")
public class LifeServlet extends HttpServlet {
public LifeServlet() {
super();
System.out.println("1、完成了实例化");
}
@Override
public void init() throws ServletException {
super.init();
System.out.println("2、完成了初始化");
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
System.out.println("3、就绪中");
response.getWriter().append("Served at: ").append(request.getContextPath());
}
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
doGet(request, response);
}
@Override
public void destroy() {
super.destroy();
System.out.println("4、销毁了");
}
}
5、Servlet特性
5.1 线程安全问题
Servlet在访问之后,会执行实例化操作,创建一个Servlet对象。而我们Tomcat容器可以同时多个线程并发访问同一个Servlet,如果在方法中对成员变量做修改操作,就会有线程安全的问题。
5.2 如何保证线程安全
synchronized
将存在线程安全问题的代码放到同步代码块中
实现SingleThreadModel接口
servlet实现SingleThreadModel接口后,每个线程都会创建servlet实例,这样每个客户端请求就不存在共享资源的问题,但是servlet响应客户端请求的效率太低,所以已经淘汰。
尽可能使用局部变量
public class SafeServlet extends HttpServlet implements SingleThreadModel {
//private String message = "";
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String message = "";
//假设1、接收参数
//2、调用业务逻辑 得到登录结果
message = "登录成功";//登录失败!
PrintWriter printWriter = resp.getWriter();
printWriter.println(message);
}
}







浙公网安备 33010602011771号