Servlet、GenericServlet、HttpServlet
Servlet
对于web容易来说,所有servlet都必须有的行为,规范在servlet接口中:
package javax.servlet;import java.io.IOException;public interface Servlet { public void init(ServletConfig config) throws ServletException; public ServletConfig getServletConfig(); public void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public String getServletInfo(); public void destroy();}复制代码
当我们所实例化的一个servlet被容器载入后,容器会建立该servlet的接口的实例,根据servlet设定的一些初始化和其他信息(一般在web.xml中),创建一个servletConfig(稍后会介绍servletConfig接口)的实例,然后调用servlet接口的init方法并且传入一个servletconfig实例,完成servlet的初始化。
servlet初始化完成之后,如果这时候收到了对于某个servlet的请求,那么这时候容器就会先通过urlPattern找到这个servlet,然后调用这个servlet的service方法,传入ServletRequest、ServletResponse的实例,注意这两个接口并不是http开头的,这好似因为最初设计的时候,并没有限制servlet只能用于http协议上,但实际上大到现在servlet还是只用在了http上哈哈。
当一个servlet已经没用了,容器准备回收它之前,容器会呼叫它的destroy方法,来执行一些资源的回收与释放。
GenericServlet
GenericServlet是一个抽象类,它实现了servlet接口
package javax.servlet;import java.io.IOException;import java.util.Enumeration;public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable { private transient ServletConfig config; public GenericServlet() {} public void destroy() {} public String getInitParameter(String name) { return getServletConfig().getInitParameter(name); } public EnumerationgetInitParameterNames() { return getServletConfig().getInitParameterNames(); } public ServletConfig getServletConfig() { return config; } public ServletContext getServletContext() { return getServletConfig().getServletContext(); } public String getServletInfo() { return ""; } public void init(ServletConfig config) throws ServletException { this.config = config; this.init(); } public void init() throws ServletException {} public void log(String msg) { getServletContext().log(getServletName() + ": "+ msg); } public void log(String message, Throwable t) { getServletContext().log(getServletName() + ": " + message, t); } public abstract void service(ServletRequest req, ServletResponse res) throws ServletException, IOException; public String getServletName() { return config.getServletName(); }}复制代码
GenericServlet实现了servlet的接口,其中有一个接受servletconfig参数的init方法,会传入servletconfig的实例,之后才会调用无参数的init方法,所以** 如果我们重新定义servlet的初始化,不建议直接重定义接受参数的init方法,而是建议重新定义无参数的init方法 ** GenericServlet對Servlet介面的service()方法沒有實作,僅標示為abstract,service()方法的實作由子類別
HttpServlet
我们看到GenericServlet对于servlet接口的service方法没有具体实现,标记为抽象方法,这是因为service方法交给了它的子类去实现,比如具体的httpservlet:
package javax.servlet.http;略...public abstract class HttpServlet extends GenericServlet { public HttpServlet() {} 略... @Override 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); } protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException { String method = req.getMethod(); if (method.equals(METHOD_GET)) { long lastModified = getLastModified(req); if (lastModified == -1) { doGet(req, resp); } else { 略... }}复制代码
HttpServlet实现了javax.servlet.http接口,同时又继承实现了GenericServlet未实现的service方法。在web容器的环境下,实际上传入的请求和响应对象的是HttpServletRequest、HttpServletResponse接口。他们其实都是ServletRequest、ServletResponse的子接口,增加了一些关于http的操作。
ServletConfig
在web容易启动后,会读取(Annotation)或web.xml里面的设定,根据其中对每个servlet的设定将servlet载入并且实例化,并未每个servlet的配置产生一个servletconfig的对象,随后调用servlet的接口的init方法,并且将产生的servletconfig的对象当作参数传入。
servletconfig其实就是将(Annotation)或web.xml中为servlet配置的信息整合保存到一个对象中,容器会为每个servlet产生一个servlet对象和一个servletconfig对象。genericservlet同时实现了这两个接口,servlet和servletconfig。
GenericServlet主要的目的,就是将servlet调用init方法传入的config对象封装起来:
private transient ServletConfig config;public void init(ServletConfig config) throws ServletException { this.config = config; this.init();}public void init() throws ServletException {}复制代码
GenericServlet在實作Servlet的init()方法時,也呼叫了另一個無參數的init()方法,基本上你在撰寫Servlet時,如果有一些初始時所要執行的動作,可以重新定義這個無參數的init()方法,而不是直接重新定義有ServletConfig參數的init()方法。
对于一般的对象,我们如果想在初始化之后,执行一些要执行的动作,我们一般需要重新定义构造函数,在构造函数中完成后续的动作即可。但在写servlet的时候,却不一样,如果我们想要执行一些与web资源相关的初始化操作时,我们要重新定义init方法。举个例子,如果我们想要使用servletconfig来做一些事情,我们不能在构造函数中定义,因为我们实例化servlet的时候,容器还没有执行init方法传入servletconfig对象,所以我们这时候还没有servletconfig的实例
GenericServlet也包括了Servlet與ServletConfig所定義方法的簡單實作,實作內容主要是透過ServletConfig來取得一些相關資訊,例如:
public ServletConfig getServletConfig() { return config;}public String getInitParameter(String name) { return getServletConfig().getInitParameter(name);}public Enumeration getInitParameterNames() { return getServletConfig().getInitParameterNames();} public ServletContext getServletContext() { return getServletConfig().getServletContext();}复制代码
所以当我们继承httpservlet实现一个servlet的时候,就可以直接利用这些方法取得相关的servletconfig的信息,而不用意识到servletconfig的存在。 若要使用標註設定個別Servlet的初始參數,可以在@WebServlet中使用@WebInitParam設定initParams。例如:
...@WebServlet(name="ServletConfigDemo", urlPatterns={ "/conf"}, initParams={ @WebInitParam(name = "PARAM1", value = "VALUE1"), @WebInitParam(name = "PARAM2", value = "VALUE2") })public class ServletConfigDemo extends HttpServlet { private String PARAM1; private String PARAM2; public void init() throws ServletException { super.init(); PARAM1 = getServletConfig().getInitParameter("PARAM1"); PARAM2 = getServletConfig().getInitParameter("PARAM2"); } ....}复制代码
若要在web.xml中設定個別Servlet的初始參數,可以在標籤之中,使用進行設定,web.xml中的設定會覆蓋標註的設定。例如:
......复制代码 ServletConfigDemo cc.openhome.ServletConfigDemo PARAM1 VALUE1 PARAM2 VALUE2
由於ServletConfig必須在Web容器將Servlet實例化後,呼叫有參數的init()方法再將之傳入,是與Web應用程式資源相關的物件,所以在繼承HttpServlet後,通常會重新定義無參數的init()方法以進行Servlet初始參數的取得。GenericServlet定義了一些方法,將ServletConfig封裝起來,便於取得設定資訊,所以取得Servlet初始參數的程式碼也可以改寫為:
...@WebServlet(name="ServletConfigDemo", urlPatterns={ "/conf"}, initParams={ @WebInitParam(name = "PARAM1", value = "VALUE1"), @WebInitParam(name = "PARAM2", value = "VALUE2") })public class AddMessage extends HttpServlet { private String PARAM1; private String PARAM2; public void init() throws ServletException { super.init(); PARAM1 = getInitParameter("PARAM1"); PARAM2 = getInitParameter("PARAM2"); } ….}复制代码
下面這個範例簡單地示範如何設定、使用Servlet初始參數,其中登入成功與失敗的網頁,可以由初始參數設定來決定:
import java.io.*;import javax.servlet.*;import javax.servlet.annotation.*;import javax.servlet.http.*;@WebServlet(name = "LoginServlet", urlPatterns = { "/login.do"}, initParams = { @WebInitParam(name = "SUCCESS", value = "success.view"), @WebInitParam(name = "ERROR", value = "error.view") })public class LoginServlet extends HttpServlet { private String SUCCESS_VIEW; private String ERROR_VIEW; @Override public void init() throws ServletException { super.init(); SUCCESS_VIEW = this.getInitParameter("SUCCESS"); ERROR_VIEW = this.getInitParameter("ERROR"); } protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { response.setContentType("text/html;charset=UTF-8"); String name = request.getParameter("name"); String passwd = request.getParameter("passwd"); if ("caterpillar".equals(name) && "123456".equals(passwd)) { request.getRequestDispatcher(SUCCESS_VIEW) .forward(request, response); } else { request.getRequestDispatcher(ERROR_VIEW) .forward(request, response); } }}复制代码
ServletContext
ServletContext接口定义了Servlet所运行程序的一个总体的行为与信息,你可以使用ServletContext实例对象來取得所请求资源的URL、应用程序的初始化參數,甚至动态的设定Servlet实例。
ServletContext本身的名字让人产生误解,因为它以Servlet作为开头,容易被误认为只是一个单独的Servlet的代表对象。但实际上,当整个Web应用程序加载进入Web容器之后,容器会生成一个ServletContext对象作为整个应用程序的代表,并设定给ServletConfig,你只要通过调用ServletConfig的getServletContext()方法就可以取得ServletContext对象。 以下我们先介绍几个容易搞错,需要注意的方法:
- getRequestDispatcher()方法可以取得RequestDispatcher实例对象,使用时路徑的指定必须以"/"作为开头,这个斜杠代表应用程序的根目录(Context Root)。
context.getRequestDispatcher("/pages/some.jsp").forward(request, response);复制代码
以"/"作为开头有时候也可以理解为程序环境的初始化路径(Context-relative),沒有以"/"作为开头的(Request-relative)路径则成为(Request-relative)请求相对路径。事实上,HttpServletRequest 的getRequestDispatcher()方法在实例化的时候,若是环境相对路径,就直接交给ServletContext的 getRequestDispatcher();若是请求相对路径,则会先转换为环境相对路径再转发给ServletContext的 getRequestDispatcher()來取得RequestDispatcher。
- 如果想要知道Web程序中的某个目录中有哪些档案,则可以使用getResourcePaths()方法。例如: for(String avatar : getServletContext().getResourcePaths("/")) { // 顯示 avatar 文字... }
使用時指定路徑必須以"/"作為開頭,表示相對於應用程式環境根目錄,傳回的路徑會像是:
/welcome.html/catalog//catalog/index.html/catalog/products.html/customer//customer/login.jsp/WEB-INF//WEB-INF/web.xml/WEB-INF/classes/com.acme.OrderServlet.class复制代码
可以看到,这个方法会将WEB-INF的目录信息文件信息也列出来。如果是目录信息,会以“/”作为结尾。
如果想在Web程序中读取程序目录中某个文档的内容,则可以使用getResourceAsStream()方法,使用时指定路径以"/"作为开头,表示相对于应用程序的环境根目录,或者相对是/WEB-INF/lib中JAR档案里META-INF/resources的路徑(Web程序中,JAR檔案必須放在/WEB-INF/lib中,這是規定),执行结果会返回一个InputStream对象,接下来我们就可以用这个对象来读取文档的内容。底下是個讀取PDF並傳送給客戶端的範例:
import java.io.*;import javax.servlet.*;import javax.servlet.annotation.*;import javax.servlet.http.*;@WebServlet(name="Ebook", urlPatterns={ "/ebook.view"}, initParams={ @WebInitParam(name="PDF_FILE", value="/WEB-INF/jdbc.pdf") })public class Ebook extends HttpServlet { private String PDF_FILE; @Override public void init() throws ServletException { super.init(); PDF_FILE = getInitParameter("PDF_FILE"); } protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { String passwd = request.getParameter("passwd"); if("123456".equals(passwd)) { response.setContentType("application/pdf"); InputStream in = this.getServletContext() .getResourceAsStream(PDF_FILE); OutputStream out = response.getOutputStream(); byte[] buffer = new byte[1024]; int length = -1; while((length = in.read(buffer)) != -1) { out.write(buffer, 0, length); } in.close(); out.close(); } }}复制代码
每个Web程序都会有一个相对应的ServletContext,针对「应用程序」初始化时所需用到的一些参数的信息,你可以在web.xml中摄制应用程序的初始化参数,設定時使用标签来定义。例如:
复制代码 … MESSAGE /WEB-INF/messages.txt
在程序中可以通过调用ServletContext的getInitParameter()方法來读取初始化参数。因此Web應用程式初始參數常被稱為ServletContext初始參數。
在整個Web程序的生命周期中,Servlet所需共用的信息参数,則可以设定为ServletContext属性。由于ServletContext在Web应用程序存活的时候会一直存在,所以設定為ServletContext属性的信息,除非你主动移除,否则也是一直存在web程序中的。
可以透過ServletContext的setAttribute()方法設定物件為ServletContext屬性,之後可透過ServletContext的getAttribute()方法取出該屬性。若要移除屬性,則透過ServletContext的removeAttribute()方法。
注意!ServletContext不是线程安全的,所以必要时我们还需要注意存取变量属性时的线程安全问题。