Servlet生命周期以及工作原理

「爱情、让人受尽委屈。」 2022-07-13 23:07 321阅读 0赞

  最近感觉到用久了SpringMVC、Struts2等框架,反而对它们的底层实现,即Servlet,的相关知识有了许多遗忘。现在参考了网上的一些博客,来进行一次知识点总结。   

Servlet响应客户端请求的过程

image\_1b205lk8c1f881cu01s401jn01b6n9.png-54kB

Servlet生命周期

  1. init方法:当Servlet容器第一次加载并创建Servlet实例时,在调用该Servlet的构造函数后立即调用init方法对该Servlet对象进行初始化。构造器和init方法都只会被调用一次,这说明Servlet是单实例的(需要考虑线程安全的问题,不推荐在其中写全局变量)。
  2. service方法:每次请求都会调用service方法,它是实际用于响应请求的方法,可以被多次调用。
  3. destroy方法:在服务器端停止且卸载Servlet时执行该方法,用于释放当前Servlet所占用的资源,只会被调用一次。

以上方法都由Servlet容器(例如Tomcat)负责调用。    

ServletConfig

  Servlet接口的init方法会接收一个ServletConfig对象作为参数,即init(ServletConfig servletConfig),ServletConfig对象封装了该Serlvet的配置信息,并且可以获取ServletContext对象。例如:
  
在web.xml中配置Servlet的初始化参数:

  1. <servlet>
  2. <!-- Servlet注册的名字 -->
  3. <servlet-name>helloServlet</servlet-name>
  4. <!-- Servlet全类名 -->
  5. <servlet-class>servlet.HelloServlet</servlet-class>
  6. <!-- 配置该Servlet的初始化参数 -->
  7. <init-param>
  8. <param-name>username</param-name>
  9. <param-value>root</param-value>
  10. </init-param>
  11. <init-param>
  12. <param-name>password</param-name>
  13. <param-value>123456</param-value>
  14. </init-param>
  15. </servlet>

可以在HelloServlet的init方法中获取这些参数的信息(在实际开发中,通常使用的Servlet都继承了HttpServlet类,可以通过getServletConfig()在该Servlet对象中获取到ServletConfig对象,不一定只能在init方法中使用,下面的getServletContext()也是一样):

  1. @Override
  2. public void init(ServletConfig servletConfig) throws ServletException {
  3. //获取Servlet初始化参数相关的信息
  4. String username = servletConfig.getInitParameter("username");
  5. System.out.println("username is "+username);
  6. Enumeration<String> names = servletConfig.getInitParameterNames();
  7. while(names.hasMoreElements()){
  8. String name = names.nextElement();
  9. System.out.println("name is "+name);
  10. System.out.println("value is "+servletConfig.getInitParameter(name));
  11. }

当有请求发送到该Servlet后可以在控制台看到输出:

image\_1b207k93m1f0knmd1vej8pcqlsm.png-17.4kB    

ServletContext

  ServletContext对象代表着当前的WEB应用。可以认为SerlvetContext是当前 WEB应用的一个大管家,可以从中获取到当前WEB应用的各个方面的信息。
  ServletContext对象可以由SerlvetConfig获取:

  1. ServletContext servletContext = servletConfig.getServletContext();

  ServletContext主要的作用有:
  
① 获取当前WEB应用的初始化参数:
  
在web.xml中配置当前WEB应用的初始化参数:(这些参数可以被所有的 Servlet通过ServletContext获取,而上面配置在Servlet中的参数只能由对应的Servlet获取)

  1. <context-param>
  2. <param-name>driver</param-name>
  3. <param-value>com.mysql.jdbc.Driver</param-value>
  4. </context-param>
  5. <context-param>
  6. <param-name>jdbcUrl</param-name>
  7. <param-value>jdbc:mysql:///xiangwanpeng</param-value>
  8. </context-param>

在init方法中获取到这些参数:

  1. @Override
  2. public void init(ServletConfig servletConfig) throws ServletException {
  3. //获取ServletContext对象,它包含了当前WEB应用的各种信息
  4. ServletContext servletContext = servletConfig.getServletContext();
  5. //获取WEB应用的初始化信息,方法和servletConfig类似
  6. String driver = servletContext.getInitParameter("driver");
  7. System.out.println("driver is "+driver);
  8. Enumeration<String> names2 = servletContext.getInitParameterNames();
  9. while(names2.hasMoreElements()){
  10. String name2 = names2.nextElement();
  11. System.out.println("name2 is "+name2);
  12. System.out.println("value2 is "+servletContext.getInitParameter(name2));
  13. }
  14. }

当有请求发送到该Servlet后可以在控制台看到输出:

image\_1b207q5r788mo0hf6m1gj1omo13.png-9.8kB

② 获取当前WEB应用的某一个文件在服务器上的绝对路径,而不是部署前的路径。

例如现在在根目录下新建一个note.txt:

image\_1b209obula7s169r1501pn71a7k1t.png-5kB

在Servlet中获取:

  1. String realPath = servletContext.getRealPath("/note.txt");
  2. System.out.println("realPath is "+realPath);

获取请求后在控制台输出:

realPath is G:\Eclipse\J2EE\workspace.metadata.plugins\org.eclipse.wst.server.core\tmp0\wtpwebapps\JavaWeb\note.txt

③ 获取当前 WEB应用的名称:

  1. String contextPath = servletContext.getContextPath();
  2. System.out.println(contextPath);

④ 获取当前 WEB应用的某一个文件对应的输入流:

在src目录下新建一个jdbc.properties:

image\_1b20aapc615gg2qd4iv1ofebah2a.png-6.3kB

在Servlet中获取该文件的输入流:

  1. InputStream is = servletContext.getResourceAsStream("/WEB-INF/classes/jdbc.properties");
  2. System.out.println("is is "+is);

输出:

image\_1b20acpqjehe1b8lsrk1if4dtr2n.png-7.1kB

注意区别另一种方法,即通过getClassLoader获取,这种方法的路径是没有/WEB-INF/classes/前缀的:

  1. InputStream is2 = this.getClass().getClassLoader().getResourceAsStream("jdbc.properties");
  2. System.out.println("is2 is "+is2);

⑤ 和attribute相关的几个方法。    

GenericServlet

  GenericServlet是Servlet接口和ServletConfig接口的实现类,是一个抽象类,因为其中的service()方法为抽象方法。它的作用是:如果新建的 Servlet程序直接继承GenericSerlvet会使开发更简洁。
  
具体实现:
① 在GenericServlet中声明了一个SerlvetConfig类型的成员变量,在init(ServletConfig)方法中对其进行了初始化。
② 利用servletConfig成员变量的方法实现了 ServletConfig接口的方法。
③ 还定义了一个 init()方法,在init(SerlvetConfig)方法中对其进行调用,子类可以直接覆盖init()在其中实现对Servlet的初始化。
④ 不建议直接覆盖 init(ServletConfig),因为如果忘记编写super.init(config),而还是用了SerlvetConfig接口的方法,则会出现空指针异常。
⑤ 新建的 init(){}并非Serlvet的生命周期方法,而init(ServletConfig)是生命周期相关的方法。

以下是源码:

  1. public abstract class GenericServlet implements Servlet, ServletConfig, java.io.Serializable {
  2. private transient ServletConfig config;
  3. public GenericServlet() { }
  4. public void destroy() {
  5. }
  6. public String getInitParameter(String name) {
  7. return getServletConfig().getInitParameter(name);
  8. }
  9. public Enumeration getInitParameterNames() {
  10. return getServletConfig().getInitParameterNames();
  11. }
  12. public ServletConfig getServletConfig() {
  13. return config;
  14. }
  15. public ServletContext getServletContext() {
  16. return getServletConfig().getServletContext();
  17. }
  18. public String getServletInfo() {
  19. return "";
  20. }
  21. public void init(ServletConfig config) throws ServletException {
  22. this.config = config;
  23. this.init();
  24. }
  25. public void init() throws ServletException {
  26. }
  27. public void log(String msg) {
  28. getServletContext().log(getServletName() + ": "+ msg);
  29. }
  30. public void log(String message, Throwable t) {
  31. getServletContext().log(getServletName() + ": " + message, t);
  32. }
  33. public abstract void service(ServletRequest req, ServletResponse res)
  34. throws ServletException, IOException;
  35. public String getServletName() {
  36. return config.getServletName();
  37. }
  38. }

HttpServlet

  HttpServlet是一个继承自 GenericServlet的Servlet,而且它是针对于 HTTP协议所定制。
  HttpServlet在service(ServletRequest,ServletResponse)方法中直接把ServletReuqest和 ServletResponse强制转换为HttpServletRequest和HttpServletResponse。并调用了重载的service(HttpServletRequest, HttpServletResponse)方法。
  在service(HttpServletRequest, HttpServletResponse)中,通过request.getMethod()获取请求方式,并根据请求方式创建了doXxx()方法(xxx为具体的请求方式,比如 doGet,doPost)。
  实际开发中, 我们通常直接继承HttpServlet,这样做的好处是可以直接有针对性的覆盖doXxx()方法;直接使用HttpServletRequest和HttpServletResponse,而不再需要进行类型的强制转换。
  
主要源码如下:

  1. public void service(ServletRequest req, ServletResponse res)
  2. throws ServletException, IOException
  3. {
  4. HttpServletRequest request;
  5. HttpServletResponse response;
  6. try {
  7. request = (HttpServletRequest) req;
  8. response = (HttpServletResponse) res;
  9. } catch (ClassCastException e) {
  10. throw new ServletException("non-HTTP request or response");
  11. }
  12. service(request, response);
  13. }
  14. protected void service(HttpServletRequest req, HttpServletResponse resp)
  15. throws ServletException, IOException
  16. {
  17. String method = req.getMethod();
  18. if (method.equals(METHOD_GET)) {
  19. long lastModified = getLastModified(req);
  20. if (lastModified == -1) {
  21. // servlet doesn't support if-modified-since, no reason
  22. // to go through further expensive logic
  23. doGet(req, resp);
  24. } else {
  25. long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
  26. if (ifModifiedSince < (lastModified / 1000 * 1000)) {
  27. // If the servlet mod time is later, call doGet()
  28. // Round down to the nearest second for a proper compare
  29. // A ifModifiedSince of -1 will always be less
  30. maybeSetLastModified(resp, lastModified);
  31. doGet(req, resp);
  32. } else {
  33. resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
  34. }
  35. }
  36. } else if (method.equals(METHOD_HEAD)) {
  37. long lastModified = getLastModified(req);
  38. maybeSetLastModified(resp, lastModified);
  39. doHead(req, resp);
  40. } else if (method.equals(METHOD_POST)) {
  41. doPost(req, resp);
  42. } else if (method.equals(METHOD_PUT)) {
  43. doPut(req, resp);
  44. } else if (method.equals(METHOD_DELETE)) {
  45. doDelete(req, resp);
  46. } else if (method.equals(METHOD_OPTIONS)) {
  47. doOptions(req,resp);
  48. } else if (method.equals(METHOD_TRACE)) {
  49. doTrace(req,resp);
  50. } else {
  51. //
  52. // Note that this means NO servlet supports whatever
  53. // method was requested, anywhere on this server.
  54. //
  55. String errMsg = lStrings.getString("http.method_not_implemented");
  56. Object[] errArgs = new Object[1];
  57. errArgs[0] = method;
  58. errMsg = MessageFormat.format(errMsg, errArgs);
  59. resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
  60. }
  61. }

发表评论

表情:
评论列表 (有 0 条评论,321人围观)

还没有评论,来说两句吧...

相关阅读