关于Servlet一点总结

Servlet的初始化是从GenericServlet的init方法开始的,调用该方法会将ServletConfig对象保存到Servlet实例中。如果和重写Servlet的init方法一定要调用super.init(config)方法。否则调用getServletContext()得到的是null对象。

参考文章:https://www.cnblogs.com/tuhooo/p/6491903.html


此下为原文:

一、ServletContext对象获取Demo

Servlet容器在启动时会加载Web应用,并为每个Web应用创建唯一的ServletContext对象。

可以把ServletContext看作一个Web应用的服务器端组件的共享内存。在ServletContext中可以存放共享数据,有4个读取或者设置共享数据的方法:

方法名 描述
setAttribute(String name, Object object) 把一个对象和属性名绑定,并将这个对象存放在ServletContext中
getAttribute(String name) 根据给定的属性名返回所绑定的对象
removeAttribute(String name) 根据给定的属性名从ServletContext中删除相应的属性
getAttributeNames() 返回一个Enumeration对象,包含了存储在ServletContext对象中所有的属性名

CounterServlet.java

复制代码
  1 package com.servletContext.demo;
  2 
  3 import java.io.IOException;
  4 
  5 import javax.servlet.ServletConfig;
  6 import javax.servlet.ServletContext;
  7 import javax.servlet.ServletException;
  8 import javax.servlet.http.HttpServlet;
  9 import javax.servlet.http.HttpServletRequest;
 10 import javax.servlet.http.HttpServletResponse;
 11 
 12 public class CounterServlet extends HttpServlet {
 13 
 14     @Override
 15     public void init(ServletConfig config) throws ServletException {
 16         super.init(config);
 17     }
 18 
 19     @Override
 20     protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 21         doPost(req, resp);
 22     }
 23 
 24     @Override
 25     protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
 26 
 27         // 获得ServletContext的引用
 28         ServletContext context = getServletContext();
 29 
 30         // 从ServletContext读取count属性
 31         Integer count = (Integer) context.getAttribute("count");
 32 
 33 
 34         // 如果没有读到count属性,那么创建count属性,并设置初始值为0
 35         if(count == null) {
 36             System.out.println("context中还没有count属性呢");
 37             count = new Integer(0);
 38             context.setAttribute("count", count);
 39         }
 40         count = count + 1;
 41         // count增加之后还要写回去,引用为什么还要重新存回去
 42         context.setAttribute("count", count);
 43         System.out.println("您是第" + count + "个访问的!");
 44 
 45     }
 46 
 47     @Override
 48     public void destroy() {
 49         super.destroy();
 50     }
 51 
 52 }
复制代码

从上述代码中可见通过getServletContext()方法可以直接获得ServletContext的引用。

二、Spring和ServletContext的关系

缘何这两货会扯上关系呢?

在使用Spring的时候想必对如下代码肯定熟悉:

复制代码
  1 // 获取Spring容器
  2 ApplicationContext ctx = new ClassPathXmlApplicationContext("bean.xml");
  3 
  4 // 从Spring容器中根据id获得对象的引用        
  5 User user = (User) ctx.getBean("user");
  6 
  7 // 调用对象的方法        
  8 user.add();
复制代码

这样做是最低级的,也就是通过加载配置文件来获得Spring容器,再来获取对象的应用,在Web项目中,每次都通过加载配置文件显得效率低下,而且繁琐,这里介绍一种另外的方法。想在Web项目启动的时候就把Spring容器也给启动了,不用每次都手动去启动。

这里就用到了上面介绍的ServletContext了,每次Web项目启动的时候都会创建ServletContext对象,而该对象又有一个ServletContextListener的接口,监视ServletContext的创建,这样就可以调用这个接口的回调方法来启动Spring容器了。(但是这里我有个疑问,随着项目启动的不止有ServletContext啊,过滤器好像也随着项目启动,为啥不在过滤器的init()方法里面启动Spring容器呢?)

先来看看这个接口是啥定义:

复制代码
  1 package javax.servlet;
  2 
  3 import java.util.EventListener;
  4 
  5 /**
  6  * Implementations of this interface receive notifications about changes to the
  7  * servlet context of the web application they are part of. To receive
  8  * notification events, the implementation class must be configured in the
  9  * deployment descriptor for the web application.
 10  */
 11 
 12 public interface ServletContextListener extends EventListener {
 13 
 14     /**
 15      ** Notification that the web application initialization process is starting.
 16      * All ServletContextListeners are notified of context initialization before
 17      * any filter or servlet in the web application is initialized.
 18      */
 19     public void contextInitialized(ServletContextEvent sce);
 20 
 21     /**
 22      ** Notification that the servlet context is about to be shut down. All
 23      * servlets and filters have been destroy()ed before any
 24      * ServletContextListeners are notified of context destruction.
 25      */
 26     public void contextDestroyed(ServletContextEvent sce);
 27 }
 28 
复制代码

第一段注释描述的是:这个接口的实现接受和Web应用关联的servlet context的变更的通知。为了接受通知事件,这个类的实现必须在web应用的部署描述符配置。

第二段注释的描述是:通知是在Web应用初始化的时候开始的。所有的ServletContextListeners都会在web应用中任何的filter和servlet初始话之前接收到context初始化的时候通知。

第三段注释的描述是:servlet context将要被关闭的时候的通知。所有的filter和servlet会在任何ServletContextListeners收到context销毁的通知之前就被销毁了。

另外再来看看ServeletContextEvent.java

复制代码
  1 package javax.servlet;
  2 
  3 /**
  4  * This is the event class for notifications about changes to the servlet
  5  * context of a web application.
  6  *
  7  * @see ServletContextListener
  8  * @since v 2.3
  9  */
 10 public class ServletContextEvent extends java.util.EventObject {
 11 
 12     private static final long serialVersionUID = 1L;
 13 
 14     /**
 15      * Construct a ServletContextEvent from the given context.
 16      *
 17      * @param source
 18      *            - the ServletContext that is sending the event.
 19      */
 20     public ServletContextEvent(ServletContext source) {
 21         super(source);
 22     }
 23 
 24     /**
 25      * Return the ServletContext that changed.
 26      *
 27      * @return the ServletContext that sent the event.
 28      */
 29     public ServletContext getServletContext() {
 30         return (ServletContext) super.getSource();
 31     }
 32 }
复制代码

public ServletContextEvent(ServletContext source);这个方法是从一个给定的ServletContext构建一个ServletContextEvent。而public ServletContext getServletContext();则是返回已经改变的ServletContext,暂时不知道有啥用,是不是给监听器塞ServletContext用的啊?

想自己也写一个ServletContextListener呢!

复制代码
  1 package com.servletContext.demo;
  2 
  3 import javax.servlet.ServletContext;
  4 import javax.servlet.ServletContextEvent;
  5 import javax.servlet.ServletContextListener;
  6 
  7 public class MyServletContextListener implements ServletContextListener {
  8 
  9     @Override
 10     public void contextInitialized(ServletContextEvent sce) {
 11 
 12         // 从web.xml中拿出添加的参数
 13         ServletContext ctx = sce.getServletContext();
 14         String initParam = ctx.getInitParameter("myContextListener");
 15         System.out.println("我配置的初始化参数为:" + initParam);
 16 
 17         // 利用初始化参数找到配置文件机型初始化
 18         System.out.println("context初始化了咯");
 19         System.out.println("这里假装初始化Spring容器.....");
 20 
 21     }
 22 
 23     @Override
 24     public void contextDestroyed(ServletContextEvent sce) {
 25 
 26         // 在销毁之前获得ServletContext
 27         ServletContext ctx = sce.getServletContext();
 28 
 29         // 正好刚刚存了一个值进去了,销毁之前拿出来瞅瞅
 30         Integer count = (Integer) ctx.getAttribute("count");
 31 
 32         System.out.println("在销毁之前,count的值为:" + count);
 33 
 34     }
 35 
 36 }
 37 
复制代码

这他喵的居然真的可以!

web.xml为:

复制代码
  1 <?xml version="1.0" encoding="UTF-8"?>
  2 <web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" id="WebApp_ID" version="2.5">
  3   <display-name>ServletContext</display-name>
  4   <welcome-file-list>
  5     <welcome-file>index.html</welcome-file>
  6     <welcome-file>index.htm</welcome-file>
  7     <welcome-file>index.jsp</welcome-file>
  8     <welcome-file>default.html</welcome-file>
  9     <welcome-file>default.htm</welcome-file>
 10     <welcome-file>default.jsp</welcome-file>
 11   </welcome-file-list>
 12 
 13     <!-- 假装为Spring监听器提供启动参数,其实是给ServletContext提供的 -->
 14     <context-param>
 15         <param-name>myContextListener</param-name>
 16         <!-- 这里如果bean.xml在包cn.ssh下,那么就应该写为:cn/ssh/bean.xml -->
 17         <param-value>这是我设置的值</param-value>
 18     </context-param>
 19 
 20     <!-- 配置Spring的监听器 -->
 21     <listener>
 22         <listener-class>com.servletContext.demo.MyServletContextListener</listener-class>
 23     </listener>
 24 
 25   <servlet>
 26     <servlet-name>count</servlet-name>
 27     <servlet-class>com.servletContext.demo.CounterServlet</servlet-class>
 28   </servlet>
 29   <servlet-mapping>
 30     <servlet-name>count</servlet-name>
 31     <url-pattern>/counter</url-pattern>
 32   </servlet-mapping>
 33 
 34 </web-app>
复制代码

测试结果为:

image

看来真的是可以了,这里关闭服务器的时候Console中的内容也被清除了,暂时没有看到ServletContext销毁时的消息。

Spring提供的是ContextLoaderListener,这个监听器实现了ServletContextListener接口,可以作为Listener使用,它会在创建的时候自动查找WEB-INF/下的applicationContext.xml文件,因此,如果只有一个配置文件,并且文件名为applicationContext.xml,则只需要在web.xml中加入对Listener的配置就可以。

如果有多个配置文件需要加载,则要考虑使用<context-param.../>元素来确定配置文件的文件名。ContextLoaderListener加载的时候,会查找名为contextConfigLocation的初始化参数。因此<context-param.../>时应该指定参数名为contextConfigLocation。

复制代码
  1 <!-- 为Spring监听器提供启动参数 -->
  2 <context-param>
  3     <param-name>contextConfigLocation</param-name>
  4     <!-- 这里如果bean.xml在包cn.ssh下,那么就应该写为:cn/ssh/bean.xml -->
  5     <param-value>classpath:bean.xml</param-value>
  6 </context-param>
  7 
  8 <!-- 配置Spring的监听器 -->
  9 <listener>
 10     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
 11 </listener>
复制代码

如果没有使用contextConfigLocation指定配置文件,则Spring自动查找applicationContext.xml配置文件;如果有contextConfigLocation,则利用该参数确定配置文件。如果无法找到适合的配置文件,Spring将无法初始化。

Spring根据指定的文件创建WebApplicationContext对象,并将其保存在Web应用的ServletContext中。大部分情况下,应用中的Bean无需感受到ApplicationContext的存在,只要用ApplicationContext中的IoC即可。

这个监听器所在的jar包为:

image

如果需要利用ApplicationContext的实例,可以通过如下代码获取:

复制代码
  1 package com.ssh.domain;
  2 
  3 import javax.servlet.ServletContext;
  4 import javax.servlet.http.HttpServletRequest;
  5 
  6 import org.apache.struts2.ServletActionContext;
  7 import org.springframework.web.context.WebApplicationContext;
  8 import org.springframework.web.context.support.WebApplicationContextUtils;
  9 
 10 import com.opensymphony.xwork2.ActionSupport;
 11 import com.ssh.test.TestAdd;
 12 
 13 public class TestAction extends ActionSupport {
 14 
 15     @Override
 16     public String execute() throws Exception {
 17 
 18         HttpServletRequest request = ServletActionContext.getRequest();
 19         ServletContext servletContext = request.getServletContext();
 20         // 这里不是通过依赖注入,而是直接从容器中拿
 21         WebApplicationContext ctx = WebApplicationContextUtils.getWebApplicationContext(servletContext);
 22 
 23         // 也可以是下面这样的
 24         WebApplicationContext ctx1 = (WebApplicationContext)
 25                 servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE);
 26 
 27         if(ctx == ctx1) {
 28             System.out.println("两次获得对象是一样的");
 29         }
 30 
 31         TestAdd testAdd = (TestAdd) ctx.getBean("testAdd");
 32         testAdd.add();
 33 
 34         return NONE;
 35     }
 36 }
复制代码

TestAdd.java

复制代码
  1 package com.ssh.test;
  2 
  3 public class TestAdd {
  4 
  5     public void add( ) {
  6         System.out.println("通过WebContext获得的而打印....");
  7     }
  8 
  9 }
复制代码

测试结果为:http://localhost:8080/spring_struts2/testAction

image

三、瞧瞧Spring提供的ContextLoaderListener代码是咋写的

打开源码,就蛋疼了,有封装了一下:

复制代码
  1 package org.springframework.web.context;
  2 
  3 import javax.servlet.ServletContextEvent;
  4 import javax.servlet.ServletContextListener;
  5 
  6 /**
  7  * Bootstrap listener to start up and shut down Spring's root {@link WebApplicationContext}.
  8  * Simply delegates to {@link ContextLoader} as well as to {@link ContextCleanupListener}.
  9  *
 10  * <p>This listener should be registered after {@link org.springframework.web.util.Log4jConfigListener}
 11  * in {@code web.xml}, if the latter is used.
 12  *
 13  * <p>As of Spring 3.1, {@code ContextLoaderListener} supports injecting the root web
 14  * application context via the {@link #ContextLoaderListener(WebApplicationContext)}
 15  * constructor, allowing for programmatic configuration in Servlet 3.0+ environments.
 16  * See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
 17  *
 18  * @author Juergen Hoeller
 19  * @author Chris Beams
 20  * @since 17.02.2003
 21  * @see #setContextInitializers
 22  * @see org.springframework.web.WebApplicationInitializer
 23  * @see org.springframework.web.util.Log4jConfigListener
 24  */
 25 public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
 26 
 27     /**
 28      * Create a new {@code ContextLoaderListener} that will create a web application
 29      * context based on the "contextClass" and "contextConfigLocation" servlet
 30      * context-params. See {@link ContextLoader} superclass documentation for details on
 31      * default values for each.
 32      * <p>This constructor is typically used when declaring {@code ContextLoaderListener}
 33      * as a {@code <listener>} within {@code web.xml}, where a no-arg constructor is
 34      * required.
 35      * <p>The created application context will be registered into the ServletContext under
 36      * the attribute name {@link WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE}
 37      * and the Spring application context will be closed when the {@link #contextDestroyed}
 38      * lifecycle method is invoked on this listener.
 39      * @see ContextLoader
 40      * @see #ContextLoaderListener(WebApplicationContext)
 41      * @see #contextInitialized(ServletContextEvent)
 42      * @see #contextDestroyed(ServletContextEvent)
 43      */
 44     public ContextLoaderListener() {
 45     }
 46 
 47     /**
 48      * Create a new {@code ContextLoaderListener} with the given application context. This
 49      * constructor is useful in Servlet 3.0+ environments where instance-based
 50      * registration of listeners is possible through the {@link javax.servlet.ServletContext#addListener}
 51      * API.
 52      * <p>The context may or may not yet be {@linkplain
 53      * org.springframework.context.ConfigurableApplicationContext#refresh() refreshed}. If it
 54      * (a) is an implementation of {@link ConfigurableWebApplicationContext} and
 55      * (b) has <strong>not</strong> already been refreshed (the recommended approach),
 56      * then the following will occur:
 57      * <ul>
 58      * <li>If the given context has not already been assigned an {@linkplain
 59      * org.springframework.context.ConfigurableApplicationContext#setId id}, one will be assigned to it</li>
 60      * <li>{@code ServletContext} and {@code ServletConfig} objects will be delegated to
 61      * the application context</li>
 62      * <li>{@link #customizeContext} will be called</li>
 63      * <li>Any {@link org.springframework.context.ApplicationContextInitializer ApplicationContextInitializer}s
 64      * specified through the "contextInitializerClasses" init-param will be applied.</li>
 65      * <li>{@link org.springframework.context.ConfigurableApplicationContext#refresh refresh()} will be called</li>
 66      * </ul>
 67      * If the context has already been refreshed or does not implement
 68      * {@code ConfigurableWebApplicationContext}, none of the above will occur under the
 69      * assumption that the user has performed these actions (or not) per his or her
 70      * specific needs.
 71      * <p>See {@link org.springframework.web.WebApplicationInitializer} for usage examples.
 72      * <p>In any case, the given application context will be registered into the
 73      * ServletContext under the attribute name {@link
 74      * WebApplicationContext#ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE} and the Spring
 75      * application context will be closed when the {@link #contextDestroyed} lifecycle
 76      * method is invoked on this listener.
 77      * @param context the application context to manage
 78      * @see #contextInitialized(ServletContextEvent)
 79      * @see #contextDestroyed(ServletContextEvent)
 80      */
 81     public ContextLoaderListener(WebApplicationContext context) {
 82         super(context);
 83     }
 84 
 85 
 86     /**
 87      * Initialize the root web application context.
 88      */
 89     @Override
 90     public void contextInitialized(ServletContextEvent event) {
 91         initWebApplicationContext(event.getServletContext());
 92     }
 93 
 94 
 95     /**
 96      * Close the root web application context.
 97      */
 98     @Override
 99     public void contextDestroyed(ServletContextEvent event) {
100         closeWebApplicationContext(event.getServletContext());
101         ContextCleanupListener.cleanupAttributes(event.getServletContext());
102     }
103 
104 }
复制代码

源码果然是个好东西,平时敲代码那会注意到这么多细节

这个类不复杂,两个构造方法,外加一个初始化的时候创建Spring容器和服务关闭的时候对容器的清理,封装了之后还要看其他的类,哎。

首先第一段注释是对这个类的描述:

这个启动监听器是用开启和关闭Spring的root的,这里他用了root而不是容器。简单的代理给了ContextLoader和ContextCleanupListener这两个类来处理。

如果这个org.springframework.web.util.Log4jConfigListener被用到了,那么ContextLoaderListener应该在它之后注册。

在Spring3.1中,ContextLoaderListener支持通过ContextLoaderListener(WebApplicationContext)这个构造方法向应用上下文中注入root(也就是Spring的容器),这样可以以编程的方式来配置Servlet 3.0+的环境。

第二段注释是,新建一个ContextLoaderListener的类将会基于Servlet的"contextClass"和"contextCofigLocation"这两个参数来创建web应用的上下文。

翻译的好累啊,反正意思差不多就是这样5555....

来看这段代码:

复制代码
  1 /**
  2 * Initialize the root web application context.
  3 */
  4 @Override
  5 public void contextInitialized(ServletContextEvent event) {
  6     initWebApplicationContext(event.getServletContext());
  7 }
复制代码

这个initWebApplicationContext方法是ContextLoad.java这个类里面的方法。

复制代码
  1 /**
  2  * Initialize Spring's web application context for the given servlet context,
  3  * using the application context provided at construction time, or creating a new one
  4  * according to the "{@link #CONTEXT_CLASS_PARAM contextClass}" and
  5  * "{@link #CONFIG_LOCATION_PARAM contextConfigLocation}" context-params.
  6  * @param servletContext current servlet context
  7  * @return the new WebApplicationContext
  8  * @see #ContextLoader(WebApplicationContext)
  9  * @see #CONTEXT_CLASS_PARAM
 10  * @see #CONFIG_LOCATION_PARAM
 11  */
 12 public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
 13     if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
 14             throw new IllegalStateException(
 15                     "Cannot initialize context because there is already a root application context present - " +
 16                     "check whether you have multiple ContextLoader* definitions in your web.xml!");
 17         }
 18 
 19     Log logger = LogFactory.getLog(ContextLoader.class);
 20     servletContext.log("Initializing Spring root WebApplicationContext");
 21     if (logger.isInfoEnabled()) {
 22         logger.info("Root WebApplicationContext: initialization started");
 23     }
 24     long startTime = System.currentTimeMillis();
 25 
 26     try {
 27         // Store context in local instance variable, to guarantee that
 28         // it is available on ServletContext shutdown.
 29         if (this.context == null) {
 30             this.context = createWebApplicationContext(servletContext);
 31         }
 32         if (this.context instanceof ConfigurableWebApplicationContext) {
 33             ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
 34             if (!cwac.isActive()) {
 35                 // The context has not yet been refreshed -> provide services such as
 36                 // setting the parent context, setting the application context id, etc
 37                 if (cwac.getParent() == null) {
 38                     // The context instance was injected without an explicit parent ->
 39                     // determine parent for root web application context, if any.
 40                     ApplicationContext parent = loadParentContext(servletContext);
 41                     cwac.setParent(parent);
 42                 }
 43                 configureAndRefreshWebApplicationContext(cwac, servletContext);
 44             }
 45         }
 46         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
 47 
 48         ClassLoader ccl = Thread.currentThread().getContextClassLoader();
 49         if (ccl == ContextLoader.class.getClassLoader()) {
 50             currentContext = this.context;
 51         }
 52         else if (ccl != null) {
 53             currentContextPerThread.put(ccl, this.context);
 54         }
 55 
 56         if (logger.isDebugEnabled()) {
 57             logger.debug("Published root WebApplicationContext as ServletContext attribute with name [" +
 58                         WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE + "]");
 59         }
 60         if (logger.isInfoEnabled()) {
 61             long elapsedTime = System.currentTimeMillis() - startTime;
 62             logger.info("Root WebApplicationContext: initialization completed in " + elapsedTime + " ms");
 63         }
 64 
 65         return this.context;
 66     }
 67     catch (RuntimeException ex) {
 68         logger.error("Context initialization failed", ex);
 69         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
 70         throw ex;
 71     }
 72     catch (Error err) {
 73         logger.error("Context initialization failed", err);
 74         servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, err);
 75         throw err;
 76     }
 77 }
复制代码

ContextLoad.initWebApplicationContext是为给定的servlet context来初始化web应用的上下文的。

业务逻辑解读:

首先从ServletContext中看看有没有Spring创建的这个容器;

然后为ContextLoader存一份实例变量,使得在ServletContext关闭之后仍可以访问;

  1 this.context = createWebApplicationContext(servletContext);

这句就是创建一个WebApplicationContext相当于我们自己加载配置文件的那个类。

  1 configureAndRefreshWebApplicationContext(cwac, servletContext);

这句话也很明显,就是配置并且刷新WebAppCtx的。

  1 servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

这句将创建的Spring的context作为属性放到servletContext中。

  1 return this.context;

然后就返回了Spring的容器了.....是不是简洁(装逼装不下去了),调用链好长。

暂时只能分析到这里!



发布了35 篇原创文章 · 获赞 14 · 访问量 14万+

猜你喜欢

转载自blog.csdn.net/U___U/article/details/79274228
今日推荐