一、生命周期 Servlet 生命周期可被定义为从创建直到毁灭的整个过程。以下是 Servlet 遵循的过程:
Servlet 初始化后调用init ()
方法。
Servlet 调用service()
方法来处理客户端的请求。
Servlet 销毁前调用destroy()
方法。
最后,Servlet 是由 JVM 的垃圾回收器进行垃圾回收的。
现在让我们详细讨论生命周期的方法。
1、初始化(init) init()
方法被设计成只调用一次。它在第一次创建 Servlet 时被调用,在后续每次用户请求时不再调用。
1 2 3 4 @Override public void init (ServletConfig servletConfig) throws ServletException { }
默认情况下Servlet在第一次被访问时创建,但可以指定Servlet的创建时机,只需在web.xml
文件中的<servlet/>
标签内使用<load-on-startup/>
标签来指定即可,配置如下所示:
1 2 3 4 5 6 7 8 9 <servlet > <servlet-name > demo</servlet-name > <servlet-class > cn.frankfang.servlet.ServletDemo</servlet-class > <load-on-startup > -1</load-on-startup > </servlet >
下面对<load-on-startup/>
标签的值进行详细介绍:
创建时机
值
第一次被访问时
负数,默认为-1
服务器启动时
零或正整数
注:Servlet 的 init()
方法只执行一次,说明一个 Servlet 在内存中只存在一个对象,Servlet 是单例的。当多个用户同时访问时可能存在线程安全问题,因此尽量不要在 Servlet 中定义成员变量。
2、提供服务(service) service()
方法是执行实际任务的主要方法。Servlet 容器(即 Web 服务器)调用service()
方法来处理来自客户端(浏览器)的请求,并把格式化的响应写回给客户端。
每次服务器接收到一个 Servlet 请求时,服务器会产生一个新的线程并调用服务。service()
方法检查 HTTP 请求类型(GET、POST、PUT、DELETE 等),并在适当的时候调用doGet()
、doPost()
、doPut()
,doDelete()
等方法。
每次访问 Servlet 时,service()
方法都会被调用一次。
1 2 3 4 @Override public void service (ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { }
3、销毁(destroy) destroy()
方法只会在 Servlet 生命周期结束时被调用一次。destroy()
方法可以让 Servlet 关闭数据库连接、停止后台线程、把 Cookie 列表或点击计数器写入到磁盘,并执行其他类似的清理活动。
在调用destroy()
方法之后,Servlet 对象被标记为垃圾回收。destroy()
方法定义如下所示:
1 2 3 4 @Override public void destroy () { }
注:只有服务器正常关闭时,才会执行destroy()
方法,destroy()
方法在 Servlet 被销毁之前执行,一般用于释放资源。
二、过滤器 1、概述 当访问服务器的资源时,过滤器可以将请求拦截下来,完成一些功能,如登录验证、统一编码处理等。
2、使用 (1)实现接口 实现javax.servlet.Filter
接口:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package cn.frankfang.filter;import javax.servlet.*;import java.io.IOException;public class FilterDemo1 implements Filter { @Override public void init (FilterConfig filterConfig) throws ServletException { } @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { } @Override public void destroy () { } }
(2)复写方法 在 Filter 接口中定义了以下三个方法:
public void init(FilterConfig filterConfig)
web 应用程序启动时,web 服务器将创建Filter 的实例对象,并调用其init方法,读取web.xml配置,完成对象的初始化功能,从而为后续的用户请求作好拦截的准备工作(filter对象只会创建一次,init方法也只会执行一次)。开发人员通过init方法的参数,可获得代表当前filter配置信息的FilterConfig对象。
下面将介绍如何使用其中的FilterConfig
类型参数。首先在web.xml
文件中添加以下内容:
1 2 3 4 5 6 7 8 <filter > <filter-name > FilterDemo</filter-name > <filter-class > cn.frankfang.filter.FilterDemo</filter-class > <init-param > <param-name > param1</param-name > <param-value > value1</param-value > </init-param > </filter >
在 init 方法使用 FilterConfig 对象获取参数:
1 2 3 4 5 6 public void init (FilterConfig config) throws ServletException { String value1 = config.getInitParameter("param1" ); System.out.println("参数值: " + value1); }
public void doFilter (ServletRequest request, ServletResponse response, FilterChain filterChain)
该方法完成实际的过滤操作,当客户端请求方法与过滤器设置匹配的URL时,Servlet容器将先调用过滤器的doFilter方法。FilterChain用户访问后续过滤器。
下面是复写 doFilter 方法的一个例子:
1 2 3 4 5 6 7 8 9 @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("拦截请求" ); filterChain.doFilter(servletRequest, servletResponse); System.out.println("已放行" ); }
注意:在执行拦截操作之后在没有出现异常或检查通过的情况下需要把请求传回过滤链中,否则客户端将无法接收到正确的响应信息。
Servlet容器在销毁过滤器实例前调用该方法,在该方法中释放Servlet过滤器占用的资源。
下面是复写 destroy 方法的一个例子:
1 2 3 4 @Override public void destroy () { }
(3)配置拦截路径 在web.xml
中添加以下内容:
1 2 3 4 5 6 7 8 9 <filter > <filter-name > demo1</filter-name > <filter-class > cn.frankfang.filter.FilterDemo1</filter-class > </filter > <filter-mapping > <filter-name > demo1</filter-name > <url-pattern > /*</url-pattern > </filter-mapping >
自 Servlet 3.0 起开始支持注解配置方式,如果使用注解方式进行配置,则需在自定义的过滤器类上加上@WebFilter
注解,并将其value
属性或urlPatterns
属性赋值为需要进行拦截的路径。若需获取更多关于该注解的信息,请参阅:javax.servlet.annotation.WebFilter 。
下面对拦截路径的类型进行说明:
具体资源路径:如/index.jsp
表示只有访问index.jsp
资源时,过滤器才会被执行
拦截目录:如/user/*
表示访问/user下的所有资源时,过滤器都会被执行
后缀名拦截:如*.jsp
表示访问所有后缀名为.jsp
资源时,过滤器都会被执行
拦截所有资源:/*
表示访问所有资源时,过滤器都会被执行
(4)配置拦截方式 除了需要配置拦截路径之外,还需要配置拦截方式。所谓拦截方式,就是指资源被访问的方式,下面将通过表格对所有拦截方式进行介绍:
方式
说明
FORWARD
转发访问资源
INCLUDE
包含访问资源
REQUEST
直接请求资源(默认值)
ASYNC
异步访问资源
ERROR
错误跳转资源
如果使用XML配置方式,需要在web.xml
配置文件中添加以下内容:
1 2 3 4 5 <filter-mapping > <filter-name > demo1</filter-name > <url-pattern > /*</url-pattern > <dispatcher > REQUEST</dispatcher > </filter-mapping >
如果使用注解方式,则需将@WebFilter
注解的dispatcherTypes
为枚举类型DispatcherType
中的值,下面给出该枚举的源代码:
1 2 3 4 5 6 7 8 9 10 11 12 package javax.servlet;public enum DispatcherType { FORWARD, INCLUDE, REQUEST, ASYNC, ERROR; private DispatcherType () { } }
3、生命周期
在服务器启动后会创建 Filter 对象,然后调用 init 方法,只执行一次,用于加载资源
在服务器关闭后 Filter 对象被销毁,如果服务器正常关闭,则会执行 destroy 方法,只执行一次,用于释放资源
每一次请求被拦截资源时会执行 doFilter 方法,执行多次
4、过滤器链 Web 应用程序可以根据特定的目的定义若干个不同的过滤器。假设现在有AuthFilter
和LogFilter
两个过滤器,都是对/demo1
路径的请求进行拦截,下面将通过一张图来说明这两个过滤器的执行顺序:
客户端首先向服务器发送请求,Web服务器首先对请求的访问路径进行判断,当访问路径为/demo1
时,Web容器将多个 Filter 组合为一个过滤器链,过滤器链中各个 Filter 的拦截顺序与它们在web.xml
中映射的顺序一致(就是按照<filter-mapping/>
定义的顺序,此外如果采用注解的方式,则是按照@WebFilter
注解中filterName
属性值的字符串比较规则比较,值小的先执行),下面给出相关代码。
LogFilter 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package cn.frankfang.filter;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import java.io.IOException;@WebFilter(urlPatterns = "/demo1", filterName = "0_LogFilter", dispatcherTypes = DispatcherType.REQUEST) public class LogFilter implements Filter { @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("0_LogFilter已拦截请求" ); filterChain.doFilter(servletRequest, servletResponse); servletResponse.getWriter().print("<h1>Hello " ); System.out.println("0_LogFilter已放行" ); } }
AuthFilter 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 package cn.frankfang.filter;import javax.servlet.*;import javax.servlet.annotation.WebFilter;import java.io.IOException;@WebFilter(urlPatterns = "/demo1", filterName = "1_AuthFilter", dispatcherTypes = DispatcherType.REQUEST) public class AuthFilter implements Filter { @Override public void doFilter (ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException { System.out.println("1_AuthFilter已拦截请求" ); filterChain.doFilter(servletRequest, servletResponse); servletResponse.getWriter().print("World!</h1>" ); System.out.println("1_AuthFilter已放行" ); } }
ServletDemo1 类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package cn.frankfang.servlet;import javax.servlet.*;import java.io.IOException;@WebServlet("/demo1") public class ServletDemo1 implements Servlet { @Override public void init (ServletConfig servletConfig) throws ServletException { } @Override public ServletConfig getServletConfig () { return null ; } @Override public void service (ServletRequest servletRequest, ServletResponse servletResponse) throws ServletException, IOException { servletResponse.setContentType("text/html;charset=utf-8" ); System.out.println("Hello Servlet!" ); } @Override public String getServletInfo () { return null ; } @Override public void destroy () { } }
启动服务器并访问/demo1
,则会在控制台中输出以下内容:
此外在浏览器中会展示以下内容:
看到这里或许你就明白了 Filter 的大致原理:Filter 是通过 Java 中的动态代理来实现的,过滤器链本质上就是对 Servlet 实现类进行增强。结合下面这张图可更好的进行理解:
三、异常处理 1、声明式 当一个 Servlet 抛出一个异常时,Web容器在使用了exception-type
元素的web.xml
中搜索与抛出异常类型相匹配的配置。下面将举例说明如何在web.xml
文件中采用声明式的异常处理方式。
假如有一个名为ExceptionHandler
的 Servlet 在已定义的异常或错误出现时被调用,则在web.xml
中需添加以下内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 <servlet > <servlet-name > ExceptionHandler</servlet-name > <servlet-class > cn.frankfang.exception.ExceptionHandler</servlet-class > </servlet > <servlet-mapping > <servlet-name > ExceptionHandler</servlet-name > <url-pattern > /error</url-pattern > </servlet-mapping > <error-page > <error-code > 403</error-code > <location > /error</location > </error-page > <error-page > <error-code > 404</error-code > <location > /error</location > </error-page > <error-page > <exception-type > javax.servlet.ServletException</exception-type > <location > /error</location > </error-page > <error-page > <exception-type > java.io.IOException</exception-type > <location > /error</location > </error-page >
如果需要对所有的异常有一个通用的错误处理程序,可采用如下写法:
1 2 3 4 <error-page > <exception-type > java.lang.Throwable</exception-type > <location > /error</location > </error-page >
上面介绍了声明式异常处理中web.xml
的写法,下面来完善ExceptionHandler
这个异常处理的 Servlet 的内容:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package cn.frankfang.exception;import javax.servlet.ServletException;import javax.servlet.http.HttpServlet;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.IOException;public class ExceptionHandler extends HttpServlet { @Override protected void doGet (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { Throwable throwable = (Throwable) req.getAttribute("javax.servlet.error.exception" ); Integer statusCode = (Integer) req.getAttribute("javax.servlet.error.status_code" ); String servletName = (String) req.getAttribute("javax.servlet.error.servlet_name" ); String requestUri = (String) req.getAttribute("javax.servlet.error.request_uri" ); resp.setContentType("text/html;charset=utf-8" ); resp.getWriter().println( "<!DOCTYPE html>\n" + "<html>\n" + "<head><title>Error Page</title></head>\n" + "<body>\n" + "<h1>ERROR INFO</h1>\n" + "<h2>HTTP STATUS CODE: " + statusCode + "</h2>\n" + "<h2>EXCEPTION TYPE: " + throwable.getClass().getName() + "</h2>\n" + "<h2>SERVLET NAME: " + servletName + "</h2>\n" + "<h2>REQUEST URI: " + requestUri + "</h2>\n" + "</body>\n" + "</html>" ); } @Override protected void doPost (HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { this .doGet(req, resp); } }
上面的代码使用了ServletRequest
接口中的getAttribute(String name)
方法来获取Servlet的相关属性,下面将通过表格对可获取的属性进行介绍:
属性
解释
javax.servlet.error.status_code
该属性给出状态码,状态码可被存储,并在存储为java.lang.Integer
数据类型后可被分析。
javax.servlet.error.exception_type
该属性给出异常类型的信息,异常类型可被存储,并在存储为java.lang.Class
数据类型后可被分析。
javax.servlet.error.message
该属性给出确切错误消息的信息,信息可被存储,并在存储为java.lang.String
数据类型后可被分析。
javax.servlet.error.request_uri
该属性给出有关 URL 调用 Servlet 的信息,信息可被存储,并在存储为java.lang.String
数据类型后可被分析。
javax.servlet.error.exception
该属性给出异常产生的信息,信息可被存储,并在存储为java.lang.Throwable
数据类型后可被分析。
javax.servlet.error.servlet_name
该属性给出 Servlet 的名称,名称可被存储,并在存储为java.lang.String
数据类型后可被分析。
2、程序式 程序式的异常处理最常用的就是使用try-catch
语句块进行异常的捕获并处理,例如在用户上传文件时出现异常时可采用如下写法:
1 2 3 4 5 6 7 8 try { } catch (IOException e) { this .getServletContext().log("文件上传失败! 异常类型: " + e.toString()); resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "IO异常" ); }
除了可以在try-catch
语句块中直接处理异常,还可以使用RequestDispatcher
处理异常。下面将举例介绍如何使用RequestDispatcher
进行异常处理:
1 2 3 4 5 6 7 8 9 10 11 12 13 try { int i = 1 / 0 ; } catch (ArithmeticException e) { req.setAttribute("javax.servlet.error.exception" ,e); req.setAttribute("javax.servlet.error.status_code" , HttpServletResponse.SC_INTERNAL_SERVER_ERROR); req.setAttribute("javax.servlet.error.servlet_name" , "servletDemo" ); req.setAttribute("javax.servlet.error.request_uri" ,req.getRequestURI()); RequestDispatcher requestDispatcher = req.getRequestDispatcher("error" ); requestDispatcher.forward(req, resp); }
可以看到,在代码中使用了RequestDispatcher
对象的forward
方法进行请求转发,将Servlet信息以及错误信息保存在Request中,并将请求转发到ExceptionHandler
这个 Servlet 进行处理,该 Servlet 的内容与上文相同,这里不再赘述。