Servlet--Servlet进阶API、过滤器、监听器

Servlet初始化过程、ServletConfig

每个Servlet都必须由Web容器读取Servlet设置的信息,初始化等,才能生成对应的Servlet实例。对于每个Servlet的设置信息,Web容器都会为其生成一个ServletConfig作为代表对象。

在Servlet接口上,定义了与Servlet生命周期及请求服务相关的init,service,destroy三个方法。每一次请求来到容器时,会产生HttpServletRequest,HttpServletResponse对象,并调用service方法时当做参数传入。

在Web容器启动后,根据上面所说,会产生一个ServletConfig对象,而后调用init方法并将产生的ServletConfig对象传入其中,这个过程在创建Servlet实例之后只会发生一次,之后每次请求到来,就只调用Servlet的service方法进行服务。

Servlet类架构图:

在Java中,当我们想要在对象实例化后做一些操作,必须定义构造器,然而在JavaWeb中则不然,当我们想要使用ServletConfig来做一些事情的时候,我们需要重新定义init方法,因为在Web中,对象实例化之后,容器还没有调用init方法传入ServletConfig,所以我们如果在构造器中使用ServletConfig的话,是没有这个对象的。

在之前我们要得到我们在Servlet中设置的参数的有关信息(WebServlet中设置的东西),我们是直接使用getInitParameterNames, getInitParameterName等方法,其实这些方法都是经过封装的,目的就是不让我们意识到ServletConfig这一API,实际上,GenericServlet中包括了Servlet和ServletConfig所定义方法的简单实现,如:

public String getInitParameter(String name){
    return getServletConfig.getInitParameter(name);
}

等等。所以一般我们在取得Servlet的初始参数时候,都会直接将代码写成getInitParameter。

根据上面我所说的,因为ServletConfig是在容器调用有参数的init方法的时候才被调用的,所以我们如果要使用ServletConfig进行一些初始化的动作,我们需要在无参数的init方法中进行定义,我们来看一下源码:

private transient ServletConfig config;
public void init(ServletConfig config) throw ServletException {
    this.config = config;
    this.init();
}

我们可以清楚的看到在init中定义的一系列初始化动作,在得到ServletConfig的实例之后都会得到执行。
至于在Servlet中什么时候使用初始化参数,我觉得它的作用是和C语言的#define相当的。

ServletContext

ServletContext对象可以用来取得请求资源的URL,设置与存储属性,应用程序初始化参数,动态设置Servlet实例。

ServletContext事实上不是单一Servlet的代表对象,当整个Web应用程序加载Web容器之后,容器会产生一个ServletContext对象作为整个应用程序的代表,并设置给ServletConfig,通过ServletConfig的getServletContext方法可以取得ServletContext对象。

getRequestDispatcher()

用来取得RequestDispatcher实例,使用时的路径必须指定为“/”作为开头,这个“/”代表应用程序环境跟目录。

以“/”作为开头称为环境相对路径,没有以“/”作为开头称为请求相对路径。实际上getRequestDispatcher在实现的时候,若是环境相对路径,则直接委托给ServletContext的getRequestDispatcher,若是请求相对路径,则直接转换为环境相对路径,在委托给ServletContext的getRequestDispatcher方法。

getResourcePaths()

它可以帮助我们知道Web应用程序的某个目录中有哪些文件,使用时的路径必须指定为“/”作为开头,表示相对于应用程序环境根目录。

getResourceAsStream()

如果想在Web应用程序中读取某个文件的内容,则可以使用getResourceAsStream(),它的指定路径也是以“/”开头,运行结果会返回InputStream实例。

ServletContext事件,监听器

监听器:当我们想要在HttpServletRequest,HttpSession,ServletContext对象生成,销毁或相关属性发生的时间点做一些我们想要做的事情,就要实现相对应的监听器。

ServletContextListener

ServletContextListener是“生命周期监听器”,如果想要知道Web应用程序何时已经初始化,或即将销毁,可以实现这个类。

在这个接口中,定义了两个方法,分别是:

public interface ServletContextListen extends EventListener {
    public void contextInitialized(ServletContextEvent sce);
    public void contextDestroyed(ServletContextEvent sce);
}

在Web应用程序初始化后或即将结束销毁之前,会调用ServletContextListener实现类相对应的contextInitialized方法或contextDestroyed方法。可以在contextInitialized中实现应用程序初始化后资源的准备动作,在contextDestroyed中实现应用程序资源的释放动作。

ServletContextListener可以直接使用@WebListener标注,而且必须实现ServletContextListener接口。当容器调用这个接口中的方法时,会传入ServletContextEvent,其封装了ServletContext,可以通过ServletContextEvent的getServletContext方法取得ServletContext,通过ServletContext的getInitParameter方法来读取初始参数。

在整个Web应用程序生命周期期间,Servlet需共享的资料可以设置为ServletContext属性。由于ServletContext在Web应用程序存活期间都会一直存在,所以设置为ServletContext属性的数据,除非主动移除,否则也是一直存活与Web应用程序中。

因为@WebListener没有设置初始参数的属性,所以仅适用与无须设置初始参数的情况。如果需要设置初始参数,可以再Web.xml中设置。

<context-param>
    <param-name>AVATAR</param-name>
    <param-value>/avatars</param-value>
</context-param>

ServletContextAttributeListener

此类是属性改变监听器,如果想要对象被设置,移除,或替换ServletContext属性,可以收到通知以进行一些操作,可以实现这个类。

HttpSession事件、监听器

HttpSessionListener

生命周期监听器,与ServletContext生命周期监听器功能相近,如果想在HttpSession对象创建或结束时,做一些相对应的动作,可以实现此类。

public interface HttpSessionListener extends EventListener {
    public void sessionCreated(HttpSessionEvent se);
    public void sessionDestroyed(HttpSessionEvent se);
}

方法的具体功能我不在细说,对照ServletContextListener就行,我们可以通过传入的HttpSessionEvent,使用getSession得到HttpSession,以针对会话对象做出相对应的创建或结束处理操作 。

现如今有一个真实的应用场景,有些网站为了防止用户重复登录,会在数据库中以某个字段代表用户是否登录,用户登录之后在数据库中设置该字段信息,代表用户已经登录,而用户注销之后则重置该字段。如果用户已经登录,在注销之前尝试再用另一个浏览器进行登录,应用程序会检查数据中代表登录与否的字段,如果发现已被设置为登录,则拒绝重复登录。现在有一个问题,如果用户在进行注销之前不小心直接关闭了浏览器,没有确实运行注销的操作,那么数据库中代表登录与否的字段就不会被重置。为此可以实现HttpSessionListener,由于HttpSession有存活期限,当容器销毁某个HttpSession时,就会调用sessionDestoryed,我们就可以在其中重置数据库的登录字段。

HttpSessionAttributeListener

这个类的功能和ServletContextAttributeListener是非常相近的,我们直接来看一下该接口的源码:

public interface HttpSessionAttributeListener extends EventListener {
    public void attributeAdded (HttpSessionBindEvent se);
    public void attributeRemoved (HttpSessionBindEvent se);
    public void attributeReplaced (HttpSessionBindEvent se);
}

HttpSessionBindListener

对象绑定监听器,如果有个即将加入HttpSession的属性对象,希望在设置给HttpSession成为属性或从HttpSession中一处时,可以收到HttpSession的通知,则可以让该对象实现HttpSessionBindListener接口。看到这,大家有没有对这个类和上一个类产生混淆,乍一看,这两个类实现的是一样的功能啊。我也是在网上搜索了一些资料后,才解开了心中的迷惑。我们在使用HttpSessionBindListener的时候,想要将一个对象设置为HttpSession属性并对其进行“监听”,那么我们必须在实现这个类的时候让其实现HttpSessionBindListener这个接口,然后绑定到HttpSession上才能实现监听。然而当我们使用HttpSessionAttributeListener接口时,无论任何对象,只要我们将其绑定到HttpSession上,就可以对其进行“监听”。

package SessionListenerDemo2;

import javax.servlet.http.HttpSessionBindingEvent;
import javax.servlet.http.HttpSessionBindingListener;

/** * Created by hg_yi on 17-6-5. */
public class User implements HttpSessionBindingListener {
    private String name;
    private String data;

    public User(String name) {
        this.name = name;
    }

    public void valueBound(HttpSessionBindingEvent event) {
        this.data = name + "来自数据库的操作...";
    }

    public void valueUnbound(HttpSessionBindingEvent event) { }

    public String getData() {
        return data;
    }

    public String getName() {
        return name;
    }
}

当我们想要使用HttpSessionBindListener对User进行监听,那么我们在设置其为HttpSession属性的时候,就需要让其实现HttpSessionBindListener接口。

HttpServletRequest事件、监听器

ServletRequestListener

生命周期监听器,听到这个名字,我想大家都已经知道它是干什么的了,直接来看一下源码:

public interface ServletRequestListener extends EventListener {
    public void requestDestroyed(ServletRequestEvent sre);
    public void requestInitialized(ServletRequestEvent sre);
}

ServletRequestAttributeListener

属性改变监听器,其作用跟上面所说没有什么区别,方法中传入的参数是ServletRequestAttributeEvent。
ServletRequestAttributeEvent有个getName方法,其作用是可以取得属性设置或移除时指定的名称,而getValue则可以取得属性设置或移除时的对象。

生命周期监听器,属性改变监听器,都必须使用@WebListener或在web.xml中设置,容器才会知道要加载,读取监听器相关设置。

过滤器

在说过滤器之前,我们先明确一个概念:
在容器调用Servlet的service方法之前,Servlet并不知道有请求的到来,调用service方法之后,在对浏览器进行响应之前,浏览器也不知道其真正的响应是什么。基于这种特性,过滤器就是介于Servlet之前,可拦截浏览器对Servlet的请求,也可改变Servlet对浏览器的响应。

实现与设置过滤器

在Servlet/JSP中要实现过滤器,必须实现Filter接口,并使用@WebFilter标注或在web.xml中定义过滤器。filter源码:

public interface Filter {
    public void init(FilterConfig filterConfig) throws ServletException;
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 
    throws IOException, ServletException;
    public void destroy();
}

可以发现其中有三个待实现的方法。下来我们来说一下这三个方法:

  • init:当过滤器类被载入容器时并实例化后,容器会运行其init方法并传入FilterConfig对象作为参数,这个对象的作用和ServletConfig的作用是很一样的,得到过滤器的设置信息代表对象。
  • doFilter:容器在调用Servlet的service方法方法前,可以应用某过滤器,就会调用该过滤器的doFilter方法。在doFilter方法中可以进行service方法的前置处理,之后决定是否调用FilterChain的doFilter方法。如果调用了FilterChain的doFilter方法,就会运行下一个过滤器,如果没有下一个过滤器,就调用请求目标的Servlet的service方法,如果因为某个情况没有调用FilterChain的doFilter方法,则请求就不会继续交给接下来的过滤器或目标Servlet,这就是拦截请求,从Servlet的观点来看,根本不知道浏览器有发出请求。
  • 如果想在过滤器销毁之前做一些处理,则可以将动作定义到destroy方法中。

我们可以看一下FilterChain的doFilter的实现:

Filter filter = filterIterator.next();
if (filter != NULL) {
    filter.doFilter(request, response, this);
} else {
    targetServlet.service(request, response);
}

它的实现方式是递归,也就是说,service的后续处理会以堆栈的顺序返回。

触发过滤器的时机,默认是浏览器直接发出请求,如果是那些通过RequestDispatcher的forward或include的请求,设置@WebFilter的DispatcherTypes。例如:

@WebFilter(
    filterName = "some",
    urlPattern = {"/some"},
    dispatcherTypes = {
        DispatcherType.FORWARD,
        DispatcherType.INCLUDE,
        DispatcherType.REQUEST,
        DispatcherType.ERROR
    }

如果不设置任何dispatcherTypes,则默认为REQUEST。FORWARD就是指通过RequestDispatcher的forward而来的请求可以套用过滤器,其他的概念和这个基本相似。

如果有某个URL或Servlet会应用多个过滤器,则根据在web.xml中出现的先后顺序,来决定过滤器的运行顺序。

你可能感兴趣的:(servlet过滤器,ServletAPI,Servle监听器)