前面我们已经了解到Spring IoC容器初始化是调用AbstractApplicationContext.refresh()方法
1 public void refresh() throws BeansException, IllegalStateException { 2 synchronized (this.startupShutdownMonitor) { 3 // Prepare this context for refreshing. 4 prepareRefresh(); 5 6 // Tell the subclass to refresh the internal bean factory. 7 ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); 8 9 // Prepare the bean factory for use in this context. 10 prepareBeanFactory(beanFactory); 11 12 try { 13 // Allows post-processing of the bean factory in context subclasses. 14 postProcessBeanFactory(beanFactory); 15 16 // Invoke factory processors registered as beans in the context. 17 invokeBeanFactoryPostProcessors(beanFactory); 18 19 // Register bean processors that intercept bean creation. 20 registerBeanPostProcessors(beanFactory); 21 22 // Initialize message source for this context. 23 initMessageSource(); 24 25 // Initialize event multicaster for this context. 26 initApplicationEventMulticaster(); 27 28 // Initialize other special beans in specific context subclasses. 29 onRefresh(); 30 31 // Check for listener beans and register them. 32 registerListeners(); 33 34 // Instantiate all remaining (non-lazy-init) singletons. 35 finishBeanFactoryInitialization(beanFactory); 36 37 // Last step: publish corresponding event. 38 finishRefresh(); 39 } 40 41 catch (BeansException ex) { 42 if (logger.isWarnEnabled()) { 43 logger.warn("Exception encountered during context initialization - " + 44 "cancelling refresh attempt: " + ex); 45 } 46 47 // Destroy already created singletons to avoid dangling resources. 48 destroyBeans(); 49 50 // Reset 'active' flag. 51 cancelRefresh(ex); 52 53 // Propagate exception to caller. 54 throw ex; 55 } 56 } 57 }
那么,spring是在什么时候初始化BeanFactory容器呢?
一,当使用tomcat时,在web.xml中会配置servlet
1 <servlet> 2 <servlet-name>service</servlet-name> 3 <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> 4 <load-on-startup>1</load-on-startup> 5 </servlet>
其中 tomcat启动时加载web.xml中servlet,
对spring DispatcherServlet来说,类层次如下
在HttpServletBean.init()方法中
1 @Override 2 public final void init() throws ServletException { 3 if (logger.isDebugEnabled()) { 4 logger.debug("Initializing servlet '" + getServletName() + "'"); 5 } 6 7 // Set bean properties from init parameters. 8 try { 9 PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties); 10 BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this); 11 ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext()); 12 bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment())); 13 initBeanWrapper(bw); 14 bw.setPropertyValues(pvs, true); 15 } 16 catch (BeansException ex) { 17 logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex); 18 throw ex; 19 } 20 21 // Let subclasses do whatever initialization they like. 22 initServletBean(); 23 24 if (logger.isDebugEnabled()) { 25 logger.debug("Servlet '" + getServletName() + "' configured successfully"); 26 } 27 }
在FrameWorkServlet.initServletBean()方法和initWebApplicationContext()中,用到了refresh创建BeanFactory
1 @Override 2 protected final void initServletBean() throws ServletException { 3 getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'"); 4 if (this.logger.isInfoEnabled()) { 5 this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started"); 6 } 7 long startTime = System.currentTimeMillis(); 8 9 try { 10 this.webApplicationContext = initWebApplicationContext(); 11 initFrameworkServlet(); 12 } 13 catch (ServletException ex) { 14 this.logger.error("Context initialization failed", ex); 15 throw ex; 16 } 17 catch (RuntimeException ex) { 18 this.logger.error("Context initialization failed", ex); 19 throw ex; 20 } 21 22 if (this.logger.isInfoEnabled()) { 23 long elapsedTime = System.currentTimeMillis() - startTime; 24 this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " + 25 elapsedTime + " ms"); 26 } 27 } 28 29 /** 30 * Initialize and publish the WebApplicationContext for this servlet. 31 * <p>Delegates to {@link #createWebApplicationContext} for actual creation 32 * of the context. Can be overridden in subclasses. 33 * @return the WebApplicationContext instance 34 * @see #FrameworkServlet(WebApplicationContext) 35 * @see #setContextClass 36 * @see #setContextConfigLocation 37 */ 38 protected WebApplicationContext initWebApplicationContext() { 39 WebApplicationContext rootContext = 40 WebApplicationContextUtils.getWebApplicationContext(getServletContext()); 41 WebApplicationContext wac = null; 42 43 if (this.webApplicationContext != null) { 44 // A context instance was injected at construction time -> use it 45 wac = this.webApplicationContext; 46 if (wac instanceof ConfigurableWebApplicationContext) { 47 ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac; 48 if (!cwac.isActive()) { 49 // The context has not yet been refreshed -> provide services such as 50 // setting the parent context, setting the application context id, etc 51 if (cwac.getParent() == null) { 52 // The context instance was injected without an explicit parent -> set 53 // the root application context (if any; may be null) as the parent 54 cwac.setParent(rootContext); 55 } 56 configureAndRefreshWebApplicationContext(cwac); 57 } 58 } 59 } 60 if (wac == null) { 61 // No context instance was injected at construction time -> see if one 62 // has been registered in the servlet context. If one exists, it is assumed 63 // that the parent context (if any) has already been set and that the 64 // user has performed any initialization such as setting the context id 65 wac = findWebApplicationContext(); 66 } 67 if (wac == null) { 68 // No context instance is defined for this servlet -> create a local one 69 wac = createWebApplicationContext(rootContext); 70 } 71 72 if (!this.refreshEventReceived) { 73 // Either the context is not a ConfigurableApplicationContext with refresh 74 // support or the context injected at construction time had already been 75 // refreshed -> trigger initial onRefresh manually here. 76 onRefresh(wac); 77 } 78 79 if (this.publishContext) { 80 // Publish the context as a servlet context attribute. 81 String attrName = getServletContextAttributeName(); 82 getServletContext().setAttribute(attrName, wac); 83 if (this.logger.isDebugEnabled()) { 84 this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() + 85 "' as ServletContext attribute with name [" + attrName + "]"); 86 } 87 } 88 89 return wac; 90 }
当不配置在web.xml中时,只有当调用到相应servlet时,才会加载servlet
二, 当使用spring-boot时,
1 public class HelloApplication { 2 3 public static void main(String[] args) { 4 SpringApplication.run(HelloApplication.class, args); 5 } 6 7 }
1 public ConfigurableApplicationContext run(String... args) { 2 StopWatch stopWatch = new StopWatch(); 3 stopWatch.start(); 4 ConfigurableApplicationContext context = null; 5 configureHeadlessProperty(); 6 SpringApplicationRunListeners listeners = getRunListeners(args); 7 listeners.started(); 8 try { 9 ApplicationArguments applicationArguments = new DefaultApplicationArguments( 10 args); 11 context = createAndRefreshContext(listeners, applicationArguments); 12 afterRefresh(context, applicationArguments); 13 listeners.finished(context, null); 14 stopWatch.stop(); 15 if (this.logStartupInfo) { 16 new StartupInfoLogger(this.mainApplicationClass) 17 .logStarted(getApplicationLog(), stopWatch); 18 } 19 return context; 20 } 21 catch (Throwable ex) { 22 handleRunFailure(context, listeners, ex); 23 throw new IllegalStateException(ex); 24 } 25 }
创建bean容器
1 private ConfigurableApplicationContext createAndRefreshContext( 2 SpringApplicationRunListeners listeners, 3 ApplicationArguments applicationArguments) { 4 ConfigurableApplicationContext context; 5 // Create and configure the environment 6 ConfigurableEnvironment environment = getOrCreateEnvironment(); 7 configureEnvironment(environment, applicationArguments.getSourceArgs()); 8 listeners.environmentPrepared(environment); 9 if (isWebEnvironment(environment) && !this.webEnvironment) { 10 environment = convertToStandardEnvironment(environment); 11 } 12 13 if (this.bannerMode != Banner.Mode.OFF) { 14 printBanner(environment); 15 } 16 17 // Create, load, refresh and run the ApplicationContext 18 context = createApplicationContext(); 19 context.setEnvironment(environment); 20 postProcessApplicationContext(context); 21 applyInitializers(context); 22 listeners.contextPrepared(context); 23 if (this.logStartupInfo) { 24 logStartupInfo(context.getParent() == null); 25 logStartupProfileInfo(context); 26 } 27 28 // Add boot specific singleton beans 29 context.getBeanFactory().registerSingleton("springApplicationArguments", 30 applicationArguments); 31 32 // Load the sources 33 Set<Object> sources = getSources(); 34 Assert.notEmpty(sources, "Sources must not be empty"); 35 load(context, sources.toArray(new Object[sources.size()])); 36 listeners.contextLoaded(context); 37 38 // Refresh the context 39 refresh(context); 40 if (this.registerShutdownHook) { 41 try { 42 context.registerShutdownHook(); 43 } 44 catch (AccessControlException ex) { 45 // Not allowed in some environments. 46 } 47 } 48 return context; 49 }
这时候,项目可以不是web servlet项目,不依赖tomcat,一个纯的spring bean容器,通过dubbo netty实现调用
销毁
Servlet销毁前会调用destroy()方法,tomcat如下情况会调用destroy()
1) 修改 Servlet,触发重新编译加载,调用栈如下
1 public Servlet getServlet() throws ServletException { 2 /* 3 * DCL on 'reload' requires that 'reload' be volatile 4 * (this also forces a read memory barrier, ensuring the new servlet 5 * object is read consistently). 6 * 7 * When running in non development mode with a checkInterval it is 8 * possible (see BZ 62603) for a race condition to cause failures 9 * if a Servlet or tag is reloaded while a compile check is running 10 */ 11 if (getReloadInternal()) { 12 synchronized (this) { 13 // Synchronizing on jsw enables simultaneous loading 14 // of different pages, but not the same page. 15 if (getReloadInternal()) { 16 // This is to maintain the original protocol. 17 destroy(); 18 19 final Servlet servlet; 20 21 try { 22 InstanceManager instanceManager = InstanceManagerFactory.getInstanceManager(config); 23 servlet = (Servlet) instanceManager.newInstance(ctxt.getFQCN(), ctxt.getJspLoader()); 24 } catch (Exception e) { 25 Throwable t = ExceptionUtils 26 .unwrapInvocationTargetException(e); 27 ExceptionUtils.handleThrowable(t); 28 throw new JasperException(t); 29 } 30 31 servlet.init(config); 32 33 if (!firstTime) { 34 ctxt.getRuntimeContext().incrementJspReloadCount(); 35 } 36 37 theServlet = servlet; 38 reload = false; 39 // Volatile 'reload' forces in order write of 'theServlet' and new servlet object 40 } 41 } 42 } 43 return theServlet; 44 }
- when the container shuts down or the application shuts down;
- when the container decides that there is a shortage of memory;
- when this servlet hasn't got a request in a long time.
Servlet destory()
1 /** 2 * Called by the servlet container to indicate to a servlet that the servlet 3 * is being taken out of service. This method is only called once all 4 * threads within the servlet's <code>service</code> method have exited or 5 * after a timeout period has passed. After the servlet container calls this 6 * method, it will not call the <code>service</code> method again on this 7 * servlet. 8 * 9 * <p> 10 * This method gives the servlet an opportunity to clean up any resources 11 * that are being held (for example, memory, file handles, threads) and make 12 * sure that any persistent state is synchronized with the servlet's current 13 * state in memory. 14 */ 15 public void destroy();
注:还可以通过ContextLoaderListener方式实现spring ioc启动
<context-param> <param-name>contextConfigLocation</param-name> <param-value>classpath:spring.xml</param-value> </context-param> <listener> <listener-class> org.springframework.web.context.ContextLoaderListener </listener-class> </listener>