一、简介

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的一种方式:

  1. 用户填写包含指向servlet链接的表单
  2. 单击提交按钮
  3. 服务器定位请求的servlet
  4. servlet收集满足用户请求所需的信息
  5. 构造一个包含该信息的Web页面
  6. 此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

  • 安装

Maven配置

<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>

三、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类会根据用户的请求类型调用对应的处理方法:doGetdoPostdoHeaddoPutdoDeletedoOptionsdoTrace

  • 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.ServletContextListenerjavax.servlet.ServletContextAttributeListener

  • 会话事件

HTTP Session Events:javax.servlet.http.HttpSessionListenerjavax.servlet.http.HttpSessionAttributeListenerjavax.servlet.http.HttpSessionActivationListenerjavax.servlet.http.HttpSessionBindingListener

  • 请求事件

Servlet Request Events:javax.servlet.ServletRequestListenerjavax.servlet.ServletRequestAttributeListenjavax.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>
附: