Servlet读书笔记系列文《Servlet就是这样》第三篇:Servlet的一些细节
细节一:Servlet简介
l Servlet是sun公司提供的一门用于开发动态web资源的技术。
l Sun公司在其API中提供了一个servlet接口,用户若想开发一个动态web资源(即开发一个Java程序向浏览器输出数据),需要完成以下2个步骤:
1) 编写一个Java类,实现servlet接口。
2) 把开发好的Java类部署到web服务器中。
细节二:Servlet的运行过程
l Servlet程序是由WEB服务器调用,web服务器收到客户端的Servlet访问请求后:
1) Web服务器首先检查是否已经装载并创建了该Servlet的实例对象。如果是,则直接执行第④步,否则,执行第②步。
2) 装载并创建该Servlet的一个实例对象。
3) 调用Servlet实例对象的init()方法。
4) 创建一个用于封装HTTP请求消息的HttpServletRequest对象和一个代表HTTP响应消息的HttpServletResponse对象,然后调用Servlet的service()方法并将请求和响应对象作为参数传递进去。
5) WEB应用程序被停止或重新启动之前,Servlet引擎将卸载Servlet,并在卸载之前调用Servlet的destroy()方法。
细节三:Servlet的生命周期
l 生命周期的概念:一件事物,什么时候生,什么时候死,以及在其生存阶段的某一时点会触发的事件,统称为该事物的生命周期。
l Servlet的生命周期:
1. 通常情况下,服务器会在Servlet第一次被调用时创建该Servlet类的实例对象(servlet出生);一旦被创建出来,该Servlet实例就会驻留在内存中,为后续请求服务;直至web容器退出,servlet实例对象才会被销毁(servlet死亡)。
2. 在Servlet的整个生命周期内,Servlet的init方法只有在servlet被创建时被调用一次。
3. 而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。
4. servlet被销毁前,会调用destroy()方法。
细节四:Servlet接口实现类
l 当编写一个servlet时,必须直接或间接实现servlet接口,Servlet接口SUN公司定义了两个默认实现类,分别为:GenericServlet、HttpServlet。要实现javax.servlet.Servlet接口(即写自己的Servlet应用),你可以写一个继承自javax.servlet.GenericServletr的Generic Servlet ,也可以写一个继承自java.servlet.http.HttpServlet的HTTP Servlet(这就是为什么我们自定义的Servlet通常是extentdsHttpServlet的)
l 查看Servlet的官方API,Servlet有5个要实现的方法,如下:
1) init(servletconfig config)
2) service(servletrequest req,servletresponse resp)
3) destroy()
4) getservletconfig()
5) getservletinfo()
l 我们查看下ServletAPI,其中是这样描述 GenericServlet 的:
GenericServlet 定义了一个通用的,无关协议的的Servlet 。如果要在 Web 应用中使用 Http 进行 Servlet 通信,请扩展 HttpServlet (即继承 HttpServlet )
l 我们再查看下GenericServlet、HttpServlet它们的源码:
GenericServlet
public abstractclass GenericServlet implements Servlet,ServletConfig, Serializable {
……
……
public void destroy(){}
public String getInitParameter(String name){}
public Enumeration getInitParameterNames(){}
public ServletConfig getServletConfig(){}
public ServletContext getServletContext(){}
public String getServletInfo(){}
public void init(ServletConfig config){}
public void init(){}
public void log(String msg){}
public void log(String message, Throwable t){}
public String getServletName(){}
public abstractvoid service(ServletRequest paramServletRequest,ServletResponse paramServletResponse) throws ServletException, IOException;
}
HttpServlet
public abstract class HttpServlet extendsGenericServlet implements Serializable {
……
……
protected long getLastModified(HttpServletRequestreq) {
protected void doHead(HttpServletRequest req,HttpServletResponse resp)
protected void doPost(HttpServletRequest req,HttpServletResponse resp)
throws ServletException, IOException {
protected void doGet(HttpServletRequest req,HttpServletResponse resp)
throws ServletException, IOException {
protected void doPut(HttpServletRequest req,HttpServletResponse resp)
throws ServletException, IOException {
protected void doDelete(HttpServletRequest req,HttpServletResponse resp)
throws ServletException, IOException {
private static Method[] getAllDeclaredMethods(Classc) {
protected void doOptions(HttpServletRequest req,HttpServletResponse resp)
throws ServletException, IOException {
protected void doTrace(HttpServletRequest req,HttpServletResponse resp)
throws ServletException, IOException {
protectedvoid service(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
Stringmethod = req.getMethod();
if (method.equals("GET")) {
long lastModified = getLastModified(req);
if (lastModified == -1L) {
doGet(req, resp);
} else {
long ifModifiedSince =req.getDateHeader("If-Modified-Since");
if (ifModifiedSince < lastModified /1000L * 1000L) {
maybeSetLastModified(resp,lastModified);
doGet(req, resp);
} else {
resp.setStatus(304);
}
}
} else if (method.equals("HEAD")){
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals("POST")){
doPost(req, resp);
} else if (method.equals("PUT")){
doPut(req, resp);
} else if (method.equals("DELETE")){
doDelete(req, resp);
} else if(method.equals("OPTIONS")) {
doOptions(req, resp);
} else if(method.equals("TRACE")) {
doTrace(req, resp);
} else {
String errMsg =lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg,errArgs);
resp.sendError(501, errMsg);
}
}
private void maybeSetLastModified(HttpServletResponseresp,
long lastModified) {
public void service(ServletRequest req,ServletResponse res)
throws ServletException, IOException {
HttpServletRequest request;
HttpServletResponse response;
try{
request = (HttpServletRequest) req;
response = (HttpServletResponse) res;
}catch (ClassCastException e) {
throw new ServletException("non-HTTP request or response");
}
service(request, response);
}
}
l 请注意查看上面这两个类的源码(PS:这两个类中的大部分方法我只是写出了它们的方法头,简短,方便查看),我们注意到,这两个类是抽象类,不能直接进行实例化,必须给出子类才能实例化(注意这一块),它们的类中分别有一个抽象方法,就是service()方法,这个是要我们手动实现的。
l 总得来看,GenericServlet给出了设计 servlet的一些骨架,定义了 servlet 生命周期,还有一些得到名字、配置、初始化参数的方法,其设计的是和应用层协议无关的,也就是说你有可能用非 http 协议实现它(其实目前 Java Servlet 还是只有 Http 一种)。
l HttpServlet 是采用 Http 协议进行通信的,所以它也实现 Http 协议中的多种方法,HttpServlet 的 service() 方法比较特殊,带 public 关键字的 service() 方法明显是继承自父类,它只接收 HTTP 请求,这里把相应的 request 和 response 转换为了基于 HTTP 协议的相应对象,最终将请求转到带 protected 关键字的 service() 方法中。protected service() 方法根据请求的类型将请求转发到相应的 doDelete() 、 doGet() 、 doOptions() 、 doPost() 、 doPut() 等方法中。 所以开发自己的 Servlet 时,不需要覆盖HttpServlet 的 service() 方法,因为该方法最终将请求转发相相应的 doXXX 方法中,只需要覆盖相应的 doXXX 方法进行请求处理即可。如果重写了该方法,那么就不会根据方法名调用其他具体的方法了。
细节五:Servlet映射URL
l 由于客户端是通过URL地址访问web服务器中的资源,所以Servlet程序若想被外界访问,必须把servlet程序映射到一个URL地址上,这个工作在web.xml文件中使用<servlet>元素和<servlet-mapping>元素完成。
l <servlet>元素用于注册Servlet,它包含有两个主要的子元素:<servlet-name>和<servlet-class>,分别用于设置Servlet的注册名称和Servlet的完整类名。
l 一个<servlet-mapping>元素用于映射一个已注册的Servlet的一个对外访问路径,它包含有两个子元素:<servlet-name>和<url-pattern>,分别用于指定Servlet的注册名称和Servlet的对外访问路径。
细节六:Servlet的调用和生命周期
l Servlet是一个供其他Java程序(Servlet引擎)调用的Java类,它不能独立运行,它的运行完全由Servlet引擎来控制和调度。
l 针对客户端的多次Servlet请求,通常情况下,服务器只会创建一个Servlet实例对象,也就是说Servlet实例对象一旦创建,它就会驻留在内存中,为后续的其它请求服务,直至web容器退出,servlet实例对象才会销毁。
l 在Servlet的整个生命周期内,Servlet的init方法只被调用一次。而对一个Servlet的每次访问请求都导致Servlet引擎调用一次servlet的service方法。对于每次访问请求,Servlet引擎都会创建一个新的HttpServletRequest请求对象和一个新的HttpServletResponse响应对象,然后将这两个对象作为参数传递给它调用的Servlet的service()方法,service方法再根据请求方式分别调用doXXX方法。
细节七:Servlet的默认映射
l 如果某个Servlet的映射路径仅仅为一个正斜杠(/),那么这个Servlet就成为当前Web应用程序的缺省Servlet。
l 凡是在web.xml文件中找不到匹配的<servlet-mapping>元素的URL,它们的访问请求都将交给缺省Servlet处理,也就是说,缺省Servlet用于处理所有其他Servlet都不处理的访问请求。
l 在<tomcat的安装目录>\conf\web.xml文件中,注册了一个名称为org.apache.catalina.servlets.DefaultServlet的Servlet,并将这个Servlet设置为了缺省Servlet。
l 当访问Tomcat服务器中的某个静态HTML文件和图片时,实际上是在访问这个缺省Servlet。
细节八:线程安全
l 当多个客户端并发访问同一个Servlet时,web服务器会为每一个客户端的访问请求创建一个线程,并在这个线程上调用Servlet的service方法,因此service方法内如果访问了同一个资源的话,就有可能引发线程安全问题。
l 如果某个Servlet实现了SingleThreadModel接口,那么Servlet引擎将以单线程模式来调用其service方法。
l SingleThreadModel接口中没有定义任何方法,只要在Servlet类的定义中增加实现SingleThreadModel接口的声明即可。
l 对于实现了SingleThreadModel接口的Servlet,Servlet引擎仍然支持对该Servlet的多线程并发访问,其采用的方式是产生多个Servlet实例对象,并发的每个线程分别调用一个独立的Servlet实例对象。
l 实现SingleThreadModel接口并不能真正解决Servlet的线程安全问题,因为Servlet引擎会创建多个Servlet实例对象,而真正意义上解决多线程安全问题是指一个Servlet实例对象被多个线程同时调用的问题。事实上,在Servlet API 2.4中,已经将SingleThreadModel标记为Deprecated(过时的)。
细节九:ServletContext
l WEB容器在启动时,它会为每个WEB应用程序都创建一个对应的ServletContext对象,它代表当前web应用。
l ServletConfig对象中维护了ServletContext对象的引用,开发人员在编写servlet时,可以通过ServletConfig.getServletContext方法获得ServletContext对象。
l 由于一个WEB应用中的所有Servlet共享同一个ServletContext对象,因此Servlet对象之间可以通过ServletContext对象来实现通讯。ServletContext对象通常也被称之为context域对象。
l 查看ServletContextAPI文档,了解ServletContext对象的功能。