从Servlet开始谈Spring框架的启动原理

Posted Dream_it_possible!

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了从Servlet开始谈Spring框架的启动原理相关的知识,希望对你有一定的参考价值。

        

目录

目录

一、web应用启动原理分析

1.  Web应用的启动监听器ContextLoaderListener

2.  Web应用初始化入口init()

3. Spring-webmvc核心实现DispatcherServlet

二、Spring IOC容器的启动原理分析

二、Spring Boot 应用启动原理分析

 

在工作中我们往往会用到了Spring框架做后端开发,如果想寻求技术的突破,光学会用是远远不够的,我学习Spring框架有一段时间了,我慢慢地认识了Spring, 例如Spring的核心容器IOC是如何实现的,Aop原理,Web应用启动原理,如何与其他框架整合,包括Spring的一些高级用法,在此记录下来,学习的道路漫长,学的越多不懂的越多,fighting!     

        学习web容器前,我们先看一个Spring框架的UML类图, 主要是展现DispatcherServlet、FramworkServlet、HttpServletBean和ApplicationContextAware的关系,DispatcherServlet最终实现了Servlet接口。

        接着看另外一个核心接口WebApplicationContext的UML类图

        由UML类图可以发现WebApplicationContext有一个父接口ApplicationContext和一个子接口ConfigurableWebApplicationContext子接口, 其中ConfigurableWebApplicationContext有4个实现GenericWebApplicationContext、GroovyWebApplicationContext、StaticWebApplicationContext和XmlWebApplicationContext。

        此处实现用到了策略模式,Spring容器默认使用的XmlWebApplicationContext,可以在FrameworkServlet里找到一个静态熟悉DEFAULT_CONTEXT_CLASS, 在没有找到web容器的情况下,那么就使用XmlWebApplicationContext容器。

	public static final Class<?> DEFAULT_CONTEXT_CLASS = XmlWebApplicationContext.class;

         上述的类和接口基本都是Spring框架的核心类,为了掌握启动原理,不光要熟悉这些类,我们可以从Servlet开始追踪溯源,其中ContextLoaderListener是Spring框架提供的一个web应用的实现类, 实现了ServletContextListener接口。

一、web应用启动原理分析

1.  Web应用的启动监听器ContextLoaderListener

        早期的应用开发者在利用Spring框架开发web应用时,我们需要在web.xml文件里添加一个web监听器的配置 org.springframework.web.context.ContextLoaderListener, 为什么要添加ContextLoaderListener这个配置呢? 

        我们可以从Spring源码中找到这个ContextLoaderListener类,查看源码: 

        由上图可以发现ContextLoaderListener类继承了ContextLoader类并实现了ServletContextListener接口,如果对servlet比较熟悉的话,该接口也是sevlet的监听器,主要的作用在于监听容器的启动,初始化容器,tomcat就是一个很好的例子,可以从javax.servlet-api-3.1.0.jar里找到源代码,ServletContextListener接口包含2个方法 ContextInitialized(ServletContextEvent sce)和contextDestroyed(ServletContextEvent sce),主要的功能是初始化ServletContext 容器和销毁ServletContext容器。

public interface ServletContextListener extends EventListener 

    /**
     * Receives notification that the web application initialization
     * process is starting.
     *
     * <p>All ServletContextListeners are notified of context
     * initialization before any filters or servlets in the web
     * application are initialized.
     *
     * @param sce the ServletContextEvent containing the ServletContext
     * that is being initialized
     */
    public void contextInitialized(ServletContextEvent sce);

    /**
     * Receives notification that the ServletContext is about to be
     * shut down.
     *
     * <p>All servlets and filters will have been destroyed before any
     * ServletContextListeners are notified of context
     * destruction.
     *
     * @param sce the ServletContextEvent containing the ServletContext
     * that is being destroyed
     */
    public void contextDestroyed(ServletContextEvent sce);

       contextInitialized放发在Servlet容器启动时,就会执行我们可以在ContextLoaderListener类的源代码里找到实现,找到了initWebApplicationContext(event.getServletContext())方法

	@Override
	public void contextInitialized(ServletContextEvent event) 
		initWebApplicationContext(event.getServletContext());
	

         包含了Spring容器的初始化逻辑:

2. Web应用初始化入口init()

        如果对servlet足够熟悉,那么你一定知道servlet接口里的init()方法是servlet容器初始化的入口,也就是说我们可以在Servlet接口里定义我们自己实现的初始化逻辑。 接着看Spring框架里的org.springframework.web.servlet.HttpServletBean, HttpServletBean是一个抽象类,实现了servlet接口里的Init()方法, 源码如下:

public abstract class HttpServletBean extends HttpServlet implements EnvironmentCapable, EnvironmentAware 


@Override
	public final void init() throws ServletException 
		if (logger.isDebugEnabled()) 
			logger.debug("Initializing servlet '" + getServletName() + "'");
		

		// Set bean properties from init parameters.
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) 
			try 
				BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
				ResourceLoader resourceLoader = new ServletContextResourceLoader(getServletContext());
				bw.registerCustomEditor(Resource.class, new ResourceEditor(resourceLoader, getEnvironment()));
				initBeanWrapper(bw);
				bw.setPropertyValues(pvs, true);
			
			catch (BeansException ex) 
				if (logger.isErrorEnabled()) 
					logger.error("Failed to set bean properties on servlet '" + getServletName() + "'", ex);
				
				throw ex;
			
		

		// Let subclasses do whatever initialization they like.
        // web 应用的启动入口 initServletBean()
		initServletBean();

		if (logger.isDebugEnabled()) 
			logger.debug("Servlet '" + getServletName() + "' configured successfully");
		
	

    ...

        该方法的作用是容器初始化的入口,从代码里可以找到一个方法initServletBean(),该方法在FrameWorkServlet类里实现,代码如下:

@SuppressWarnings("serial")
public abstract class FrameworkServlet extends HttpServletBean implements ApplicationContextAware 
	/**
	 * Overridden method of @link HttpServletBean, invoked after any bean properties
	 * have been set. Creates this servlet's WebApplicationContext.
	 *  web 容器启动入口
	 */
	@Override
	protected final void initServletBean() throws ServletException 
		getServletContext().log("Initializing Spring FrameworkServlet '" + getServletName() + "'");
		if (this.logger.isInfoEnabled()) 
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization started");
		
		long startTime = System.currentTimeMillis();

		try 
			// 初始化web容器
			this.webApplicationContext = initWebApplicationContext();
			initFrameworkServlet();
		
		catch (ServletException ex) 
			this.logger.error("Context initialization failed", ex);
			throw ex;
		
		catch (RuntimeException ex) 
			this.logger.error("Context initialization failed", ex);
			throw ex;
		

		if (this.logger.isInfoEnabled()) 
			long elapsedTime = System.currentTimeMillis() - startTime;
			this.logger.info("FrameworkServlet '" + getServletName() + "': initialization completed in " +
					elapsedTime + " ms");
		
	
// 其他代码用...表示
...

        如果足够细心,我们就会发现FrameworkServlet类实现了一个ApplicationContextAware接口,该接口里只包含一个方法setApplicationContext(ApplicationContext applicationContext),在Spring中,我们知道如果有类实现了ApplicationContextAware接口,那么我们可以通过setApplicationContext(AppicationContext applicationContext)方法拿到applicationContext容器, WebApplicationContext接口是ApplicationContext的子接口,他们俩是一个父子容器的关系。

	@Override
	public void setApplicationContext(ApplicationContext applicationContext) 
		if (this.webApplicationContext == null && applicationContext instanceof WebApplicationContext) 
			this.webApplicationContext = (WebApplicationContext) applicationContext;
			this.webApplicationContextInjected = true;
		
	

        从上述代码,我们可以发现webApplicationContext容器先通过setApplicationContext方法初始化赋值。

        为什么在FramworkServlet的initServletBean方法里又做了一次初始webApplicationContext

的动作呢?

        接着看initWebApplicationContext()方法,里面判断了一下wac能否转为ConfigurableWebApplciationContext, 如果能的话,那就继续判断是否需要设置父容器,如果需要的父容器那么设置父容器WebApplicationContext。

	if (this.webApplicationContext != null) 
			// A context instance was injected at construction time -> use it
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) 
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) 
					// The context has not yet been refreshed -> provide services such as
					// setting the parent context, setting the application context id, etc
					if (cwac.getParent() == null) 
						// The context instance was injected without an explicit parent -> set
						// the root application context (if any; may be null) as the parent
						cwac.setParent(rootContext);
					
					configureAndRefreshWebApplicationContext(cwac);
				
			
		

        从上述代码中我们可以找到问题的答案,Web应用需要通过parent属性将各容器之间关联起来,如果出现找不到容器的情况下,那么会去创建一个默认的web容器, 该默认的容器是XmlWebApplicationContext。

if (wac == null) 
			// No context instance is defined for this servlet -> create a local one
			wac = createWebApplicationContext(rootContext);
		

         一切初始化好后,那么就开始初始化mvc所有相关的策略,前提是没有收到refresh事件

	if (!this.refreshEventReceived) 
			// Either the context is not a ConfigurableApplicationContext with refresh
			// support or the context injected at construction time had already been
			// refreshed -> trigger initial onRefresh manually here.
			onRefresh(wac);
		

        接着看DispatcherServlet! 

3. Spring-webmvc核心实现DispatcherServlet

        MVC是现在非常流行一种web应用架构,Spring框架在启动IOC容器时在DispatcherSevlet类里的Onrefresh()方法实现了MVC的九大组件的初始化策略:

public class DispatcherServlet extends FrameworkServlet 	

@Override
	protected void onRefresh(ApplicationContext context) 
		initStrategies(context);
	
....

        初始化策略如下: 多文件上传、国际化、主题、处理器映射、处理器适配器、处理器异常解决方案、请求转换为视图转换器、视图解析器、Flashmap管理器。

	protected void initStrategies(ApplicationContext context) 
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	

        上述流程执行完毕后,Web应用的启动流程就基本结束了,最后把WebApplicationContext放入到ServletContext上下文里使用。

	if (this.publishContext) 
			// Publish the context as a servlet context attribute.
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
			if (this.logger.isDebugEnabled()) 
				this.logger.debug("Published WebApplicationContext of servlet '" + getServletName() +
						"' as ServletContext attribute with name [" + attrName + "]");
			
		

        也就是在初始化的时候,先去找从ServletContext里去拿WebApplicationContext,如果没有拿到,那么再去createWebApplicationContext。

web容器初始化完毕后,WebApplicationContext也具有了Servlet相关的特性。 

二、 Spring IOC容器的启动原理分析

        从上述代码中可以发现,在启动web应用时,会伴随着IOC容器的启动,我们都知道Spring框架的核心是IOC,IOC简单的讲就是讲对象的管理和依赖交给Spring去管理,其实Spring的IOC容器真正启动的时候是在调用AbstractApplicationContext.refresh()方法结束后,refresh()方法包含了Spring IOC容器启动的所有流程。

        refresh()方法在AbstractApplicationContext类里有具体的实现:

        refresh()方法源代码: 

public void refresh() throws BeansException, IllegalStateException 
		synchronized (this.startupShutdownMonitor) 
			// Prepare this context for refreshing.
			prepareRefresh();

			// Tell the subclass to refresh the internal bean factory.
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();

			// Prepare the bean factory for use in this context.
			prepareBeanFactory(beanFactory);

			try 
				// Allows post-processing of the bean factory in context subclasses.
				postProcessBeanFactory(beanFactory);

				// Invoke factory processors registered as beans in the context.
				invokeBeanFactoryPostProcessors(beanFactory);

				// Register bean processors that intercept bean creation.
				registerBeanPostProcessors(beanFactory);

				// Initialize message source for this context.
				initMessageSource();

				// Initialize event multicaster for this context.
				initApplicationEventMulticaster();

				// Initialize other special beans in specific context subclasses.
				onRefresh();

				// Check for listener beans and register them.
				registerListeners();

				// Instantiate all remaining (non-lazy-init) singletons.
				finishBeanFactoryInitialization(beanFactory);

				// Last step: publish corresponding event.
				finishRefresh();
			

			catch (BeansException ex) 
				if (logger.isWarnEnabled()) 
					logger.warn("Exception encountered during context initialization - " +
							"cancelling refresh attempt: " + ex);
				

				// Destroy already created singletons to avoid dangling resources.
				destroyBeans();

				// Reset 'active' flag.
				cancelRefresh(ex);

				// Propagate exception to caller.
				throw ex;
			

			finally 
				// Reset common introspection caches in Spring's core, since we
				// might not ever need metadata for singleton beans anymore...
				resetCommonCaches();
			
		
	

        IOC容器的启动是也就意味着Spring应用的启动成功,我们才能从IOC容器中拿到Bean。 

二、Spring Boot 应用启动原理分析

        Spring本身是一个低侵入、可扩展的轻量级框架,现在最流行的Springboot 框架也是基于Spring实现的。

        可以从main()方法找到boot的启动路线以及原理, 进入到SpringApplication.run(String... args)方法:

        我们可以发现最终调用到了AbstractApplicationContext.refresh()方法, 如下:

         由此可见,Springboot一方面帮我们的开发简化了很多配置上的工作,另一方面具备Spring 框架所有的功能。

以上是关于从Servlet开始谈Spring框架的启动原理的主要内容,如果未能解决你的问题,请参考以下文章

spring学习--DispatcherServlet工作原理

Spring配置文件

spring boot启动加载tomcat原理深度剖析

spring boot启动加载tomcat原理深度剖析

#yyds干货盘点# Spring核心原理分析之MVC九大组件

spring boot启动加载tomcat原理深度剖析