3.Servlet
3.1 Servlet介绍
Servlet是基于Java语言的Web服务器端编程技术,按照Java EE规范定义,Servlet是运行在Servlet容器中的Java类,它能处理Web客户的HTTP请求,并产生HTTP响应。
- Servlet对请求的处理和响应过程分为如下几个步骤:
- 接收HTTP请求;
- 取得请求信息,包括请求头和请求参数数据;
- 调用其他Java类方法,完成具体的业务功能;
- 实现到其他Web组件的跳转(包括重定向或请求转发);
- 生成HTTP响应(包括HTML或非HTML响应)。
3.2 创建Servlet
- 创建类继承自HttpServlet
- 添加maven支持 servlet-api、 jsp-api
- 在pom.xml 的dependencies
<!-- https://mvnrepository.com/artifact/javax.servlet/javax.servlet-api -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.0.1</version>
<!--作用域 provided 参与编译,运行时抛弃 -->
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/javax.servlet.jsp/jsp-api -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.1</version>
<scope>provided</scope>
</dependency>
- 重写doGet或者doPost发方法
package com.neuedu.servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 项目: javaweb1206
* 类名: FirstServlet
* 创建时间: 2020/12/6 13:46
* 描述 : 第一个Servlet
* 作者 : 张金山
* QQ : 314649444
* Site: https://jshand.gitee.io
*/
public class FirstServlet extends HttpServlet {
/**
* 接受的 GET请求
* 1 浏览器地址栏直接访问
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
/**
* 接受POST请求
* @param request 请求对象
* @param response 响应对象
* @throws ServletException
* @throws IOException
*/
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
ServletOutputStream os = response.getOutputStream();
os.write("hello servlet".getBytes());
os.flush();
os.close();
}
}
- 声明配置Servlet
- 2.5版本规范中采用web.xml配置文件声明配置
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<display-name>Archetype Created Web Application</display-name>
<servlet>
<servlet-name>FirstServlet</servlet-name>
<servlet-class>com.neuedu.servlet.FirstServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>FirstServlet</servlet-name>
<url-pattern>/first</url-pattern>
</servlet-mapping>
</web-app>
- 3.x版本以上规范中使用注解声明配置
package com.neuedu.servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 项目: javaweb1206
* 类名: ${NAME}
* 创建时间: 2020/12/6 14:42
* 描述 : ${dc}
* 作者 : 张金山
* QQ : 314649444
* Site: https://jshand.gitee.io
*
*
* http://127.0.0.1:8080/web/second
*
*
*/
@WebServlet(name = "SecondServlet",urlPatterns="/second")
public class SecondServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//响应浏览器一个内容
ServletOutputStream os = resp.getOutputStream();
os.write("Second servlet".getBytes());
os.flush();
os.close();
}
}
部署运行Servlet
添加Tomcat的 jvm
- sh
-Dfile.encoding=utf-8
3.3 Servlet体系结构
Servlet是使用Servlet API(应用程序设计接口)及相关类和方法的Java程序。
Servlet API包含两个软件包:
- javax.servlet包
- 定义了所有Servlet类都必须实现或继承的通用接口和类
- javax.servlet.http包
- 定义了采用HTTP协议通信的HttpServlet类
3.3.1 Servlet接口
Servlet接口规定了必须由Servlet类实现并且由Servlet引擎识别和管理的方法集;
Servlet接口的基本目标是提供与Servlet生命周期相关的方法,如:init()、service()和destroy()等。
方法:
方法名称 | 方法描述 |
---|---|
init(ServletConfig config) | Servlet初始化方法,在Servlet实例化后,容器调用该方法进行Servlet的初始化,init()方法只能被调用一次,如果此方法没有正常结束,就会抛出一个ServletException异常,一旦抛出该异常,Servlet将不再执行,随后对其进行再次调用会,容器会重新载入并再次运行init()方法。 |
service(ServletRequest req,ServletResponse resp) | Servlet的服务方法。当用户对Servlet发出请求时容器会调用该方法处理用户的请求 |
destroy() | Servlet的销毁方法。容器在终止Servlet服务前调用此方法,容器调用此方法前必须给service()线程足够时间来结束执行,因此接口规定当service()正在执行时,destroy()不被执行 |
getServletConfig() | 此方法可以让Servlet在任何时候获得ServletConfig对象 |
getServletInfo() | 此方法返回一个String对象,该对象包含Servlet的信息,例如:开发者、创建日期、描述信息等。该方法也可用于容器 |
3.3.2 GenericServlet抽象类
GenericServlet是一个通用的协议无关的Servlet,它实现了Servlet和ServletConfig接口。
继承自GenericServlet的Servlet应该要覆盖service()方法。
常用方法:
方法名称 | 功能描述 |
---|---|
public void init(ServletConfig config) | 调用Servlet接口中的init()方法。此方法还有一无参的重载方法,其功能与此方法相同 |
public String getInitParameter(Stringname) | 返回名称为name的初始化参数的值 |
public ServletContext getServletContext() | 返回ServletContext对象的引用 |
3.3.3 HttpServlet抽象类
HttpServlet指能够处理HTTP请求的Servlet,它在原有Servlet接口上添加了对HTTP协议的处理,它比Servlet接口的功能更为强大。
HttpServlet类中的主要方法及描述:
方法 | 方法描述 |
---|---|
service(HttpServletRequest req,HttpServletResponse resp) | HttpServlet在实现Servlet接口时,重写了service()方法,该方法会自动判断用户的请求方式;若为GET请求,则调用HttpServlet的doGet()方法;若为POST请求,则调用doPost()方法。因此,开发人员在编写Servlet时,通常只需要重写doGet()或doPost()方法,而不要去重写service方法。如果Servlet收到一个HTTP请求而你没有重载相应的do方法,它就返回一个说明此方法对本资源不可用的标准HTTP错误。 |
doGet(HttpServletRequest req,HttpServletResponse resp) | 此方法被本类的service()方法调用,用来处理一个HTTP GET 操作。 |
doPost(HttpServletRequest req,HttpServletResponse resp) | 此方法被本类的service()方法调用,用来处理一个HTTP POST 操作。 |
HttpServlet作为HTTP请求的分发器,除了提供对GET和POST请求的处理外,对于其他请求类型也提供了相应的处理方法(如:doHead()、doOptions()、doDelete()、doPut()、doTrace())
3.4 Servlet声明配置
Servlet的声明配置信息主要包括Servlet的描述、名称、初始参数、类路径以及访问地址等。
3.4.1 注解式声明
注解@WebServlet会在程序部署时被Servlet容器处理,容器将根据具体的属性配置把相应的类部署为Servlet;
注解@WebServlet的属性及描述
属性名 | 类型 | 描述 |
---|---|---|
name | String | 指定Servlet的名字,可以为任何字符串,一般与Servlet的类名相同,如果没有显式指定,则该Servlet的取值即为类的全限定名 |
urlPatterns | String[] | 指定一组Servlet的URL匹配模式,可以是匹配地址映射(如:/SimpleServlet)、匹配目录映射(如:/servlet/)和匹配扩展名映射(如:.action) |
value | String[] | 该属性等价于urlPatterns属性。两个属性不能同时使用 |
loadOnStartup | int | 指定Servlet的加载顺序。当此选项没有指定时,表示容器在该Servlet第一次被请求时才加载;当值为0或者大于0时,表示容器在应用启动时就加载这个Servlet。值越小,启动该servlet的优先级越高。原则上不同的Servlet应该使用不同的启动顺序数字 |
initParams | WebInitParam[] | 指定一组Servlet初始化参数,为可选项 |
asyncSupported | boolean | 声明Servlet是否支持异步操作模式,默认为false |
description | String | 指定该Servlet的描述信息 |
displayName | String | 指定该Servlet的显示名,通常配合工具使用 |
Servlet的映射路径
urlPatterns= {"/hello","/hello.do","/hello/hello.html"}
urlPatterns= {"/hello","*.do"}*
- 精确匹配: /hello
访问路径:http://localhost:8080/HelloServlet/hello
/hello/hello.do 访问路径:http://localhost:8080/HelloServlet/hello/hello.do
- 模糊匹配: /* 访问路径:
http://localhost:8080/HelloServlet/任意路径
/hello/*
访问路径:http://localhost:8080/HelloServlet/hello/任意路径
.do、.action、*.后缀名
访问路径:http://localhost:8080/HelloServlet/任意路径.do
3.4.2 Servlet2.5版本声明配置
在web.xml中通过<servlet> </servlet>
元素声明Servlet,其子元素及其描述如下表:
属性名 | 类型 | 描述 |
---|---|---|
<description> | String | 指定该Servlet的描述信息,等价于@WebServlet 的description属性 |
<display-name> | String | 指定该Servlet的显示名,通常配合工具使用,等价于@WebServlet的displayName属性 |
<servlet-name> | String | 指定Servlet的名称,一般与Servlet的类名相同,要求在一个web.xml文件内名字唯一,等价于@WebServlet的name属性 |
<servlet-class> | String | 指定Servlet类的全限定名,即:包名.类名 |
<init-param> | 指定Servlet初始化参数,等价于@WebServlet的initParams属性,若有多个参数可重复定义此元素。此元素为可选配置 | |
<param-name> | String | 指定初始参数名 |
<param-value> | String | 指定初始参数名对应的值 |
<load-on-startup> | int | 指定Servlet的加载顺序,等价于@WebServlet的loadOnStartup属性 |
<async-supported> | boolean | 指定Servlet是否支持异步操作模式,默认为false,等价于@WebServlet的asyncSupported属性 |
web.xml中<servlet-mapping> </servlet-mapping>
元素用于指定Servlet的URL映射,其子元素及其描述如下表: servlet-mapping 可以有多个
属性名 | 类型 | 描述 |
---|---|---|
<servlet-name> | String | 用来指定要映射的Servlet名称,要与<servlet> 声明中的<servlet-name> 值一致 |
<url-pattern> | String | 指定Servlet的URL匹配模式,等价于@WebServlet的urlPatterns属性或value属性 |
3.4.3 Servlet2.5 API配置 完整实例
3.4.3.1 路径映射
package com.neuedu.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 项目: javaweb1206
* 类名: PathPatternsServlet
* 创建时间: 2020/12/7 8:50
* 描述 : 路径的配置
* 作者 : 张金山
* QQ : 314649444
* Site: https://jshand.gitee.io
*/
public class PathPatternsServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println("被请求了:"+req.getRequestURL().toString());
}
}
package com.neuedu.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 项目: javaweb1206
* 类名: InitParameterServlet
* 创建时间: 2020/12/7 9:24
* 描述 : 初始化携带参数的
* 作者 : 张金山
* QQ : 314649444
* Site: https://jshand.gitee.io
*/
public class InitParameterServlet extends HttpServlet {
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request,response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String url = super.getInitParameter("url");
String username = super.getInitParameter("username");
String password = super.getInitParameter("password");
System.out.println("InitParameterServlet.doPost");
System.out.println("url:"+url);
System.out.println("username:"+username);
System.out.println("password:"+password);
}
}
3.4.3.2 web.xml
<!DOCTYPE web-app PUBLIC
"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
"http://java.sun.com/dtd/web-app_2_3.dtd" >
<web-app>
<servlet>
<servlet-name>PathPatternsServlet</servlet-name>
<servlet-class>com.neuedu.servlet.PathPatternsServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>FirstServlet</servlet-name>
<servlet-class>com.neuedu.servlet.FirstServlet</servlet-class>
</servlet>
<servlet>
<servlet-name>InitParameterServlet</servlet-name>
<servlet-class>com.neuedu.servlet.InitParameterServlet</servlet-class>
<init-param>
<param-name>url</param-name>
<param-value>jdbc:mysql://127.0.0.1:3306/数据库2222222?userUnicode=true</param-value>
</init-param>
<init-param>
<param-name>username</param-name>
<param-value>admin</param-value>
</init-param>
<init-param>
<param-name>password</param-name>
<param-value>123456</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>FirstServlet</servlet-name>
<!--http://127.0.0.1:8080/web/first-->
<url-pattern>/first</url-pattern>
</servlet-mapping>
<!--http://127.0.0.1:8080/web/pathPatterns-->
<servlet-mapping>
<servlet-name>PathPatternsServlet</servlet-name>
<url-pattern>/pathPatterns</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>PathPatternsServlet</servlet-name>
<url-pattern>/path/pathPatterns</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>PathPatternsServlet</servlet-name>
<url-pattern>/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>PathPatternsServlet</servlet-name>
<url-pattern>/path2/*</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>PathPatternsServlet</servlet-name>
<url-pattern>*.do</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>InitParameterServlet</servlet-name>
<!--http://127.0.0.1:8080/web/init-->
<url-pattern>/init</url-pattern>
</servlet-mapping>
</web-app>
3.4.4 注解形式配置Servlet完整实例
package com.neuedu.servlet;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebInitParam;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.io.PrintWriter;
/**
* 项目: javaweb1206
* 类名: ${NAME}
* 创建时间: 2020/12/7 10:49
* 描述 : ${dc}
* 作者 : 张金山
* QQ : 314649444
* Site: https://jshand.gitee.io
*/
@WebServlet(
name = "AnnotationServlet",
urlPatterns = {
"/anno",
"/annotation"
},
loadOnStartup = 5,
initParams={
@WebInitParam(name="abc1",value="2021年12月7日10:57:07"),
@WebInitParam(name="abc2",value="2022年12月7日10:57:07"),
@WebInitParam(name="abc3",value="2023年12月7日10:57:07"),
}
)
public class AnnotationServlet extends HttpServlet {
public AnnotationServlet() {
System.out.println("AnnotationServlet.AnnotationServlet");
}
@Override
public void init() throws ServletException {
super.init();
String abc1 = getInitParameter("abc1");
String abc2 = getInitParameter("abc2");
String abc3 = getInitParameter("abc3");
System.out.println("abc1:"+abc1);
System.out.println("abc2:"+abc2);
System.out.println("abc3:"+abc3);
System.out.println("AnnotationServlet.init");
}
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
this.doPost(request, response);
}
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
PrintWriter out = response.getWriter();
out.print("welcome AnnotationServlet");
out.flush();
out.close();
}
}
3.5 Servlet生命周期
Servlet生命周期是指Servlet实例从创建到响应客户请求,直至销毁的过程。Servlet程序本身不直接在Java虚拟机上运行,由Servlet容器负责管理其整个生命周期。
Servlet生命周期可分为四个阶段:实例化、初始化、处理请求、销毁。
Servlet容器在如下时刻加载和实例化一个Servlet:
在Servlet容器启动后,客户首次向Servlet发出请求,Servlet容器会判断内存中是否存在指定的Servlet对象,如果没有则创建它,然后根据客户的请求创建HttpRequest、HttpResponse对象,从而调用Servlet 对象的service方法。
在为Servlet配置了自动装入选项(load-on-startup)时,服务器在启动时会自动装入此Servlet。
Servlet初始化
- Servlet实例化后,Servlet容器将调用Servlet的init方法来对Servlet实例进行初始化,初始化成功,Servlet在Web容器中会处于服务可用状态;如果初始化失败,Servlet容器会销毁该实例;
- 当Servlet运行出现异常时,Servlet容器会使该实例变为服务不可用状态。
- 请求处理
- 服务器接收到客户端请求,会为该请求创建“请求”对象和“响应”对象,并调用service()方法,service()方法再调用其他方法来处理请求.
- 在Servlet生命周期中,service()方法可能被多次调用。当多个客户端同时访问某个Servlet的service()方法时,服务器会为每个请求创建一个线程,这样可以并行处理多个请求,减少请求处理的等待时间,提高服务器的响应速度。但同时也要注意对同一对象的并发访问问题。
- 服务终止
- 当Servlet容器需要终止Servlet(如Web服务器被关闭或需要出让资源),它会先调用Servlet的destroy()方法使其释放正在使用的资源。
- 在调用destroy()方法之前,必须让当前正在执行service()方法的任何线程完成执行,或者超过了服务器定义的时间限制。
- 在destroy()方法完成后,Servlet容器必须释放Servlet实例以便被垃圾回收。
package com.neuedu.servlet;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/**
* 项目: javaweb1206
* 类名: PathPatternsServlet
* 创建时间: 2020/12/7 8:50
* 描述 : 路径的配置
* 作者 : 张金山
* QQ : 314649444
* Site: https://jshand.gitee.io
*/
public class PathPatternsServlet extends HttpServlet {
public PathPatternsServlet() {
System.out.println("创建Servlet对象");
}
@Override
public void init() throws ServletException {
super.init();
System.out.println("PathPatternsServlet.init");
}
/**
* 尽量不重写service 方法
* @param req
* @param resp
* @throws ServletException
* @throws IOException
*/
@Override
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
super.service(req, resp);
System.out.println("处理请求 的 service");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req,resp);
}
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
System.out.println(this);
System.out.println("被请求了:"+req.getRequestURL().toString());
}
@Override
public void destroy() {
super.destroy();
System.out.println("PathPatternsServlet.destroy");
}
}
3.6 Servlet的多线程并发问题
- 由于servlet在Tomcat中是以单例模式存在的,所以当多个servlet的线程同时访问了servlet的共享数据,如成员变量,可能会引发线程安全问题。
- 多线程下每个线程对局部变量都会有自己的一份copy,这样对局部变量的修改只会影响到自己的copy而不会对别的线程产生影响,线程安全的。
解决办法:
把使用到共享数据的代码块进行同步(使用synchronized关键字进行同步)
建议在servlet类中尽量不要使用成员变量。如果确实要使用成员,必须同步。而且尽量缩小同步代码块的范围。
