Servlet简介
一、简介
1、简介
- Servlet
Servlet是使用Java语言编写的Server端程序,它为构建Web应用程序提供了一种基于组件的、平台无关的方法。
从编程角度来看,servlet类似于applet,也是用Java编写的可执行文件,并且通常在响应Web请求时执行。
从功能角度来看,servlet与CGI(通用网关接口)脚本类似,可以响应用户输入(例如单击表单上的按钮),也可以从用户那里收集信息,或者将信息发送回用户,例如:从数据库中检索信息,然后使用Servlet动态构造一个HTML页面,其中包含它收集的信息,最后在用户的浏览器中显示该页面。
Servlet是高效的、可伸缩的,因为它不会在每次执行时都创建新的进程;相反,它是由Web服务器进程中单独的线程处理。Servlet也可以写入服务器日志文件,并且可以利用其他服务器功能的优势,因为它们运行在服务器自己的进程中;而且Servlet有很好的可移植性,不需要修改就可以在任何平台上执行。
- Servlet容器
Servlet容器是Web服务器或应用服务器的一部分,Web服务器或应用服务器提供网络服务,请求和响应通过该服务发送、解码基于MIME的请求并格式化基于MIME的响应;同时,Servlet容器还包含和管理Servlet的整个生命周期。
2、使用场景
上图显示了最常使用servlet的一种方式:
- 用户填写包含指向servlet链接的表单
- 单击提交按钮
- 服务器定位请求的servlet
- servlet收集满足用户请求所需的信息
- 构造一个包含该信息的Web页面
- 此Web页面在用户的浏览器中显示
二、Servlet入门
1、Servlet生命周期
Servlet在Servlet容器中创建到删除的过程称为Servlet的生命周期,包括:加载、实例化、初始化、处理请求、卸载几个阶段,具体如下:
-
加载并初始化
Servlet容器装载Servlet类并实例化一个实例对象,然后调用该对象的
init()
方法初始化。初始化过程主要是读取配置信息、初始化参数,连接数据库等;通常这些都是在Servlet整个生命周期内只执行一次。
-
处理请求
如果Servlet容器收到对该Servlet的请求,则调用此实例对象的
service()
方法处理请求并返回响应结果。Servlet容器接收到客户端的请求时,Servlet引擎会创建一个ServletRequest请求对象和一个ServletResponse响应对象,并将这两个对象做为参数传递给Servlet对象的
service()
方法。 -
卸载
Servlet容器在卸载该实例前调用它的
destory()
方法。当Servlet容器确定某个Servlet实例需要从服务中移除时,容器会调用该实例的
destory()
方法来释放资源。
在整个Servlet的生命周期中,init()
和destory()
方法都只执行一次,当初始化完成后,Servlet容器会将实例对象保存到内存中;通过调用实例对象的service()
方法处理请求。
2、Hello World
- 安装
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
- 创建Servlet
所有Servlet都必须实现javax.servlet.Servlet
接口或继承此接口的实现类;Servlet API中实现Servlet接口的两个类是:GenericServlet和HttpServlet。通常情况下,通过扩展HttpServlet来实现自己的servlet。
实现Servlet接口并重写相关方法:
public class HelloServlet implements Servlet{
public void init(ServletConfig config) throws ServletException {
}
public ServletConfig getServletConfig() {
return null;
}
public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException {
}
public String getServletInfo() {
return null;
}
public void destroy() {
}
}
继承HttpServlet类,按需重写请求处理方法:
public class HelloServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("<html><head><title>Study Servlet</title></head>");
out.println("<body><h3>Hello World! Hello Servlet!</h3></body>");
out.println("</html>");
}
}
- 在web.xml中配置
部署Servlet需要在web.xml中增加<servlet>
和<servlet-mapping>
的配置:
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.study.servlet.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>HelloServlet</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>
- 将工程部署到Tomcat中,访问http://localhost:8080/servlet/hello
三、Servlet开发
1、常用接口&类
Servlet API中的Java代码主要在javax.servlet
软件包中;其中javax.servlet.http
软件包中提供了与HTTP协议有关的接口和Servlet类。
- Servlet接口
javax.servlet.Servlet
接口定义了Servlet必须实现的方法,例如:Servlet生命周期相关方法init()
、service()
、destory()
方法等。
- GenericServlet抽象类
javax.servlet.GenericServlet
类是Servlet接口的通用实现,此类提供了除service()
方法外的其他方法的默认实现;可以通过继承此类重写service()
方法来编写一个基本的Servlet。
- HttpServlet抽象类
javax.servlet.http.HttpServlet
类是专门为HTTP协议而设计的,此类对Servlet接口的所有方法都提供了默认实现;通常情况下,直接继承此类,重写doGet()
和doPost()
方法就可以实现一个Servlet。
HttpServlet类会根据用户的请求类型调用对应的处理方法:doGet
、doPost
、doHead
、doPut
、doDelete
、doOptions
、doTrace
。
- ServletRequest接口
此接口与ServletResponse接口都是service()
方法的参数;javax.servlet.ServletRequest
接口用于向Servlet提供客户端的请求信息,当客户端向Servlet发出请求时,Servlet可以使用此接口获取客户端的请求信息。
- ServletResponse接口
javax.servlet.ServletResponse
接口用于向客户端发出响应信息。
- HttpServletRequest接口
javax.servlet.http.HttpServletRequest
接口继承自ServletRequest接口,提供了处理HTTP请求的方法。
- HttpServletResponse接口
javax.servlet.http.HttpServletResponse
接口继承自ServletResponse接口,提供了响应HTTP请求的方法。
- ServletConfig接口
javax.servlet.ServletConfig
接口负责与Web容器之间的联系;主要是在Servlet初始化时通过Web容器提供的xml文件获取相关的配置。例如:getInitParameter()
方法可以获取在xml文件中配置的初始化变量的值。
web.xml中配置初始化变量:
<servlet>
<servlet-name>HelloServlet</servlet-name>
<servlet-class>com.study.servlet.HelloServlet</servlet-class>
<init-param>
<param-name>test</param-name>
<param-value>I love learning</param-value>
</init-param>
</servlet>
HelloServlet.java:
public class HelloServlet extends HttpServlet{
private String value;
@Override
public void init(ServletConfig config) throws ServletException {
super.init(config);
value = config.getInitParameter("test");
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html");
PrintWriter out = resp.getWriter();
out.println("<html><head><title>Study Servlet</title></head>");
out.println("<body><h3>");
out.println(value);
out.println("</h3></body></html>");
}
}
访问http://localhost:8080/servlet/hello,则会看到输出:I love learning
- ServletContext接口
javax.servlet.ServletContext
接口定义了Servlet对运行Servlet的Web应用程序的视图。容器提供者负责在Servlet容器中提供ServletContext接口的实现。使用ServletContext对象,Servlet可以记录事件、获得对资源的URL引用,设置和存储一些属性以便其他 Servlet在上下文中访问。
例如:通过ServletContext对象的getServerInfo()
方法可以返回服务器类型(eg:Apache Tomcat);通过getAttribute()
方法访问绑定到上下文中的属性。
- RequestDispatcher接口
使用javax.servlet.RequestDispatcher
接口可以将请求的处理转发给另一个Servlet或在响应中包含另一个Servlet的输出。
例如:可以通过include()
方法将请求转发给其他Servlet,转发后的Servlet做出的响应将会并入到原来的响应对象中,原来的Servlet还可以继续输出响应信息;使用forward()
方法可以将请求转发给其他的Servlet,但将由转发后的Servlet对请求做出响应,原Servlet的执行终止。
在ServletRequest接口和ServletContext接口中都提供了getRequestDispatcher(String path)
方法来获取RequestDispatcher对象。这两种获取方式的区别在于:前者通过此方法获取时的参数必须是/
开头,即路径是相对于Web应用的根路径;后者的参数则不仅可以是相对于根目录,也可以相对于当前Servlet的路径。
- HttpSession接口
Web容器使用javax.servlet.http.HttpSession
接口提供了客户与Web服务器的会话关系;通过此接口可以识别用户与请求的关系,建立数据对象和用户的关联,使用户信息可以在此用户的多个请求间共享;还可以记录一些用户的特定信息,例如会话ID(getId()
)、创建时间(getCreationTime()
)、最后访问时间(getLastAccessedTime()
)、会话存活的最大时间(getMaxInactiveInterval()
单位:秒)等。
通过HttpServletRequest接口的getSession()
或getSession(boolean create)
方法可以获取HttpSession对象;如果Web请求中包含会话信息,Web容器会创建或改动HttpSession对象,并管理请求和会话的关系;当会话过期时,Web容器会清理此对象。
HttpSession在以下三种情况下会失效:关闭浏览器时、会话过期时、调用接口提供的终止会话方法invalidate()
时。
可以在web.xml中配置Session超时时间(单位:分):
<session-config>
<session-timeout>15</session-timeout>
</session-config>
HttpSession还可以通过setAttribute(String name, Object value)
和getAttribute(String name)
方法将某个对象绑定到会话中或获取会话中绑定的对象。
2、会话跟踪机制
HTTP协议是一个无状态协议,要构建有效的Web应用程序,来自特定客户机的请求必须彼此关联,因此需要会话跟踪机制。
Servlet支持三种会话跟踪方式:
- Cookies
Cookie是一小段文本信息,随着用户请求在浏览器和Web服务器之间传递。
通过HTTP Cookie进行会话跟踪是最常用的会话跟踪机制:容器向客户端发送一个Cookie,然后,客户端将在后续的每个请求上向服务器返回此Cookie,以此来把请求与会话关联起来。会话跟踪Cookie的标准名称必须是JSESSIONID,所有符合3.0规范的容器都必须支持该名称。容器可能允许通过特定的配置来自定义会话跟踪Cookie的名称。
- SSL Session
SSL(Secure Sockets Layer)即安全套接字层,是HTTPS协议中使用的加密技术,有一个内置的机制,允许来自客户端的多个请求被明确地标识为会话的一部分。Servlet容器可以轻松地使用这些数据来定义会话。
- URL重写
当客户端不接受Cookie时,服务器可以使用URL重写方式来作为会话跟踪的基础。URL重写是将数据(会话ID)添加到URL路径中,Servlet容器会从URL中解析会话ID,从而将请求与会话相关联。
四、Serlvet过滤器
1、简介
过滤器是一段可重用的代码,它可以转换HTTP请求、响应以及头信息的内容。过滤器通常不会像Servlet那样创建响应或响应请求,而是修改或调整对资源的请求或资源的响应。
Servlet过滤器可以对客户端的请求进行处理,处理完成后会交给下一个过滤器处理;请求就在过滤器链中依次被处理,直到发送到目标为止。
2、生命周期
过滤器的生命周期有初始化、响应处理和销毁三个阶段。
- 初始化
在过滤器被创建时会调用init()
方法,此方法仅调用一次。
public void init(FilterConfig filterConfig) throws ServletException {
}
可以通过FilterConfig对象的getServletContext()
获取ServletContext对象;
与ServletConfig类似,FilterConfig对象也可以使用getInitParameter()
等方法获取在web.xml中配置的初始变量。
- 处理
过滤器被调用时会执行doFilter()
方法。
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
}
ServletRequest对象是与请求有关的参数,如果是HTTP请求,将此对象转换为HttpServletRequest对象后就可以获取header、cookies等内容,也可以通过request.getRequestDispatcher()
方法将请求转发;
如果需要阻塞对Servlet或JSP页面的访问,可以通过ServletResponse对象的getWriter()
获取PrintWriter对象直接将响应发送回客户端;
通过FilterChain对象的doFilter()
方法可以让过滤器链中的下一个过滤器继续处理。
- 销毁
过滤器实例被容器移除时会调用destory()
方法,可以在此方法中完成释放资源等清理操作。
3、创建过滤器
过滤器需实现javax.servlet.Filter
接口:
- 样例一
package com.study.servlet;
import java.io.IOException;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
public class LoggingFilter implements Filter{
private FilterConfig filterConfig;
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String motto = filterConfig.getInitParameter("motto");
response.getWriter().println("My motto is " + motto);
}
public void destroy() {
}
}
访问http://localhost:8080/servlet/hello(过滤器配置见第4小节),则会看到过滤器直接将响应内容发送回客户端了:
My motto is Stay hungry,Stay foolish
- 样例二
public class LoggingFilter implements Filter{
public void init(FilterConfig filterConfig) throws ServletException {
}
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
String name = request.getParameter("name");
System.out.println("参数Name的值为:" + name);
chain.doFilter(request, response);
}
public void destroy() {
}
}
访问http://localhost:8080/servlet/hello?name=albert(过滤器配置见第4小节),则会看到过滤器在后台输出:
参数Name的值为:alber
在经过过滤器处理后,最终还是由HelloServlet将内容响应到客户端。
4、配置
<filter>
<filter-name>LoggingFilter</filter-name>
<filter-class>com.study.servlet.LoggingFilter</filter-class>
<init-param>
<param-name>motto</param-name>
<param-value>Stay hungry,Stay foolish</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>LoggingFilter</filter-name>
<servlet-name>HelloServlet</servlet-name>
</filter-mapping>
在<filter-mapping>
中可以使用<servlet-name>
配置具体的servlet或通过<url-pattern>
配置某个路径(支持通配符,例如/home/*.jsp
)。
五、Servlet监听器
事件监听可以让开发者更好地控制ServletContext、HttpSession和ServletRequest的生命周期,并提高了管理Web资源的效率。
Servlet监听器是实现一个或多个Servlet事件监听器接口的类,可以监听一些重要事件的发生,并在这些事件发生前、发生后做相关处理。
1、监听器接口
- 上下文事件
Servlet Context Events:javax.servlet.ServletContextListener
、javax.servlet.ServletContextAttributeListener
- 会话事件
HTTP Session Events:javax.servlet.http.HttpSessionListener
、javax.servlet.http.HttpSessionAttributeListener
、javax.servlet.http.HttpSessionActivationListener
、javax.servlet.http.HttpSessionBindingListener
- 请求事件
Servlet Request Events:javax.servlet.ServletRequestListener
、javax.servlet.ServletRequestAttributeListen
、javax.servlet.AsyncListener
2、监听器样例
使用监听器实现简单的在线用户统计逻辑:其中需要使用HttpSessionAttributeListener接口,当对象添加到Session中或从Session中移除时,分别会触发attributeAdded()
和attributeRemoved()
方法。
- 监听器
import javax.servlet.http.HttpSessionAttributeListener;
import javax.servlet.http.HttpSessionBindingEvent;
public class OnlineUsersListener implements HttpSessionAttributeListener{
public void attributeAdded(HttpSessionBindingEvent event) {
UserList.getInstance().addUser((String) event.getValue());
}
public void attributeRemoved(HttpSessionBindingEvent event) {
UserList.getInstance().removeUser((String) event.getValue());
}
public void attributeReplaced(HttpSessionBindingEvent event) {
}
}
- 在线用户
import java.util.ArrayList;
import java.util.List;
public class UserList {
private List<String> users;
private static final UserList userList = new UserList();
private UserList() {
users = new ArrayList<String>();
}
public static UserList getInstance() {
return userList;
}
public List<String> getUsers() {
return users;
}
public void addUser(String name) {
if(!users.contains(name)) {
users.add(name);
}
}
public void removeUser(String name) {
users.remove(name);
}
public String getOnlineUsers() {
return "当前在线用户数共 " + users.size() + " 人:" + users;
}
}
- 在线用户列表Servlet
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class OnlineUsersServlet extends HttpServlet{
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("<html><head><title>Online Users</title></head>");
out.println("<body><h3>");
out.println(UserList.getInstance().getOnlineUsers());
out.println("</h3></body></html>");
}
}
- 登录页面
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Login</title>
</head>
<body>
<form action="login" method="post">
<input type="text" name="name"/>
<button type="submit">Login</button>
</form>
</body>
</html>
- 登录Servlet
import java.io.IOException;
import java.io.PrintWriter;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
public class LoginServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String name = req.getParameter("name");
req.getSession().setAttribute("username", name);
resp.setContentType("text/html;charset=UTF-8");
PrintWriter out = resp.getWriter();
out.println("<html><head><title>Welcome</title></head><body>");
out.println("<h3>Hi, " + name + "</h3> <a href='logout'>logout</a>");
out.println("</body></html>");
out.close();
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
this.doPost(req, resp);
}
}
- 退出Servlet
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class LogoutServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
doGet(req, resp);
}
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
HttpSession session = req.getSession();
session.removeAttribute("username");
session.invalidate();
resp.sendRedirect("login.html");
}
}
- 运行效果
分别在不同的浏览器(Chrome、Firefox、IE等)中打开登录页面,输入用户名登录后在在线用户页面可以查看当前在线用户:
登录后点击logout链接可以退出登录重新跳转到登录页面,然后再刷新在线用户页面,可以看到此用户已不在在线用户列表中。
3、配置
- 监听器配置:
<listener>
<listener-class>com.study.servlet.OnlineUsersListener</listener-class>
</listener>
- 样例中其他Servlet配置
<servlet>
<servlet-name>LoginServlet</servlet-name>
<servlet-class>com.study.servlet.LoginServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LoginServlet</servlet-name>
<url-pattern>/login</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>LogoutServlet</servlet-name>
<servlet-class>com.study.servlet.LogoutServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>LogoutServlet</servlet-name>
<url-pattern>/logout</url-pattern>
</servlet-mapping>
<servlet>
<servlet-name>OnlineUsersServlet</servlet-name>
<servlet-class>com.study.servlet.OnlineUsersServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>OnlineUsersServlet</servlet-name>
<url-pattern>/online</url-pattern>
</servlet-mapping>