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中出现的先后顺序,来决定过滤器的运行顺序。