zoukankan      html  css  js  c++  java
  • SpringMVC源码:父子容器的创建

    父子容器概念

    父子容器并非 Spring MVC 的专利,在普通的 Spring 环境下 Spring 就已经设计出具有层次结构的容器了,这种设计方式也并非 Spring 独创,其工作方式和 ClassLoader (类加载器)很相似,每个容器有一个自己的父容器,但是与 ClassLoader 不同的是,通过容器查找 bean 时是优先从子容器查找,如果找不到才会从父容器中查找。当应用中存在多个容器时,这种设计方式可以将公共的 bean 放到父容器中,如果父容器中的 bean 不适用,子容器还可以覆盖父容器中的 bean。

    根容器(父容器)通常包含基础设施bean,例如需要在多个Servlet实例之间共享的数据存储库和业务服务。 这些bean被有效地继承,并且可以在特定于Servlet的子容器中被覆盖(即重新声明),该子容器通常包含给定Servlet的本地bean。 下图显示了这种关系:

    image-20211213093927257

    代码实现:

    需要继承AbstractAnnotationConfigDispatcherServletInitializer类,并实现三个抽象方法:

    public class QuickWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    	@Override //spring配置文件
    	protected Class<?>[] getRootConfigClasses() {
    		return new Class<?>[]{AppConfig.class};
    	}
    
    	@Override //springmvc的配置文件
    	protected Class<?>[] getServletConfigClasses() {
    		return new Class<?>[]{MvcConfig.class};
    	}
    
    	@Override //servlet映射
    	protected String[] getServletMappings() {
    		return new String[]{"/"};
    	}
    }
    

    两个配置类:

    @ComponentScan("com.wj.service")
    @Configuration
    public class AppConfig {
    }
    
    @Configuration
    @ComponentScan("com.wj.controller")
    public class MvcConfig {
    }
    

    项目结构:这里MyWebApplicationInitializer不用管。

    image-20211213094219680

    按照上述配置后,就创建了一个父子容器。

    那么如何验证我们创建了父子容器,我们在controller注入applicationContext,然后看applicationContext的parent字段,如果创建成功,则parent字段中一定有父容器:

    image-20211213094630249

    创建父子容器原理

    上面创建父子容器案例中,我们继承AbstractAnnotationConfigDispatcherServletInitializer类后,就自动创建了父子容器。

    我们先来看类的继承关系:

    image-20211213094934473

    该类实现了WebApplicationInitializer,那么势必会调用onStartup方法(具体原理可看前文:https://www.cnblogs.com/wwjj4811/p/15673030.html)

    先来看AbstractContextLoaderInitializer的onStartup方法:

    这里调用registerContextLoaderListener方法,内部会增加一个ContextLoaderListener的监听器。

    	@Override
    	public void onStartup(ServletContext servletContext) throws ServletException {
    		//注册ContextLoaderListener
    		registerContextLoaderListener(servletContext);
    	}
    
    	/**
    	 * Register a {@link ContextLoaderListener} against the given servlet context. The
    	 * {@code ContextLoaderListener} is initialized with the application context returned
    	 * from the {@link #createRootApplicationContext()} template method.
    	 * @param servletContext the servlet context to register the listener against
    	 */
    	protected void registerContextLoaderListener(ServletContext servletContext) {
    		//创建一个根容器
    		WebApplicationContext rootAppContext = createRootApplicationContext();
    		if (rootAppContext != null) {
    			//增加一个ContextLoaderListener
    			ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
                             //添加ApplicationContextInitializer
    			listener.setContextInitializers(getRootApplicationContextInitializers());
    			servletContext.addListener(listener);
    		}
    		else {
    			logger.debug("No ContextLoaderListener registered, as " +
    					"createRootApplicationContext() did not return an application context");
    		}
    	}
    
    	//创建根容器代码在AbstractAnnotationConfigDispatcherServletInitializer中
    	@Override
    	@Nullable
    	protected WebApplicationContext createRootApplicationContext() {
    		//获取根配置类(留给子类实现)
    		Class<?>[] configClasses = getRootConfigClasses();
    		if (!ObjectUtils.isEmpty(configClasses)) {
    			//创建IOC容器
    			AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
    			//注册配置类
    			context.register(configClasses);
    			return context;
    		}
    		else {
    			return null;
    		}
    	}
    

    ContextLoaderListener实际上是一个ServletContextListener

    当Servlet 容器启动或终止Web 应用时,会触发ServletContextEvent 事件,该事件由 ServletContextListener 来处理。在 ServletContextListener 接口中定义了处理ServletContextEvent 事件的两个方法。

    contextInitialized(ServletContextEvent sce) :当Servlet 容器启动Web 应用时调用该方法。在调用完该方法之后,容器再对Filter 初始化,并且对那些在Web 应用启动时就需要被初始化的Servlet 进行初始化。

    contextDestroyed(ServletContextEvent sce) :当Servlet 容器终止Web 应用时调用该方法。在调用该方法之前,容器会先销毁所有的Servlet 和Filter 过滤器。

    再看ContextLoaderListener的contextInitialized方法,看看它在servlet启动时候做了什么事情?

    	@Override
    	public void contextInitialized(ServletContextEvent event) {
    		//初始化根容器
    		initWebApplicationContext(event.getServletContext());
    	}
    

    initWebApplicationContext方法如下:这里是初始化父容器

    	public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
    		if (servletContext.getAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE) != null) {
    			throw new IllegalStateException(
    					"Cannot initialize context because there is already a root application context present - " +
    					"check whether you have multiple ContextLoader* definitions in your web.xml!");
    		}
    
    		servletContext.log("Initializing Spring root WebApplicationContext");
    		Log logger = LogFactory.getLog(ContextLoader.class);
    		if (logger.isInfoEnabled()) {
    			logger.info("Root WebApplicationContext: initialization started");
    		}
    		long startTime = System.currentTimeMillis();
    
    		try {
    			// Store context in local instance variable, to guarantee that
    			// it is available on ServletContext shutdown.
    			if (this.context == null) {
    				this.context = createWebApplicationContext(servletContext);
    			}
    			if (this.context instanceof ConfigurableWebApplicationContext) {
    				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
    				if (!cwac.isActive()) {
    					// The context has not yet been refreshed -> provide services such as
    					// setting the parent context, setting the application context id, etc
                                            //这里cwac.getParent() 默认为null
    					if (cwac.getParent() == null) {
    						// The context instance was injected without an explicit parent ->
    						// determine parent for root web application context, if any.
                                                    //loadParentContext()方法默认返回的也是null
    						ApplicationContext parent = loadParentContext(servletContext);
    						cwac.setParent(parent);
    					}
                                            //配置和刷新web容器,这里不再介绍,详情可看https://www.cnblogs.com/wwjj4811/p/15673030.html
    					configureAndRefreshWebApplicationContext(cwac, servletContext);
    				}
    			}
                            //向servletContext设置根容器
    			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);
    
    			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
    			if (ccl == ContextLoader.class.getClassLoader()) {
    				currentContext = this.context;
    			}
    			else if (ccl != null) {
    				currentContextPerThread.put(ccl, this.context);
    			}
    
    			if (logger.isInfoEnabled()) {
    				long elapsedTime = System.currentTimeMillis() - startTime;
    				logger.info("Root WebApplicationContext initialized in " + elapsedTime + " ms");
    			}
    
    			return this.context;
    		}
    		catch (RuntimeException | Error ex) {
    			logger.error("Context initialization failed", ex);
    			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ex);
    			throw ex;
    		}
    	}
    

    所以说父容器要比子容器先初始化。

    那么,DispatcherServlet在哪里创建的呢?

    这就要看AbstractDispatcherServletInitializer类:

    	@Override
    	public void onStartup(ServletContext servletContext) throws ServletException {
            //调用父类AbstractContextLoaderInitializer的onStartup
            //注册DispatcherServlet
    		super.onStartup(servletContext);
    		registerDispatcherServlet(servletContext);
    	}
    
    	/**
    	 * Register a {@link DispatcherServlet} against the given servlet context.
    	 * <p>This method will create a {@code DispatcherServlet} with the name returned by
    	 * {@link #getServletName()}, initializing it with the application context returned
    	 * from {@link #createServletApplicationContext()}, and mapping it to the patterns
    	 * returned from {@link #getServletMappings()}.
    	 * <p>Further customization can be achieved by overriding {@link
    	 * #customizeRegistration(ServletRegistration.Dynamic)} or
    	 * {@link #createDispatcherServlet(WebApplicationContext)}.
    	 * @param servletContext the context to register the servlet against
    	 */
    	protected void registerDispatcherServlet(ServletContext servletContext) {
    		String servletName = getServletName();
    		Assert.hasLength(servletName, "getServletName() must not return null or empty");
    		//创建Servlet容器(子容器)
    		WebApplicationContext servletAppContext = createServletApplicationContext();
    		Assert.notNull(servletAppContext, "createServletApplicationContext() must not return null");
    
    		//创建DispatcherServlet
    		FrameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
    		Assert.notNull(dispatcherServlet, "createDispatcherServlet(WebApplicationContext) must not return null");
    		dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
    
    		//增加servlet
    		ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
    		if (registration == null) {
    			throw new IllegalStateException("Failed to register servlet with name '" + servletName + "'. " +
    					"Check if there is another servlet registered under the same name.");
    		}
    
    		//设置一些属性
    		registration.setLoadOnStartup(1);
    		//配置映射路径
    		registration.addMapping(getServletMappings());
    		registration.setAsyncSupported(isAsyncSupported());
    
    		Filter[] filters = getServletFilters();
    		if (!ObjectUtils.isEmpty(filters)) {
    			for (Filter filter : filters) {
    				//注册过滤器
    				registerServletFilter(servletContext, filter);
    			}
    		}
    
    		//留给子类实现的方法:用于自定义ServletRegistration
    		customizeRegistration(registration);
    	}
    
    

    这里创建了WebApplicationContext子容器,然后创建DispatcherServlet。创建了DispatcherServlet后,后面在DispatcherServlet初始化时候,会调用它的init()方法,又会走到FrameworkServlet类的initWebApplicationContext方法:

    	protected WebApplicationContext initWebApplicationContext() {
    		WebApplicationContext rootContext =
    				WebApplicationContextUtils.getWebApplicationContext(getServletContext());//获取父容器
    		WebApplicationContext wac = null;//子容器
    		//this.webApplicationContext 是之前createServletApplicationContext()创建的Servlet容器
    		if (this.webApplicationContext != null) {
    			// A context instance was injected at construction time -> use it
    			wac = this.webApplicationContext;//当前web的IOC容器
    			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);
    				}
    			}
    		}
    		if (wac == null) {
    			// No context instance was injected at construction time -> see if one
    			// has been registered in the servlet context. If one exists, it is assumed
    			// that the parent context (if any) has already been set and that the
    			// user has performed any initialization such as setting the context id
    			wac = findWebApplicationContext();
    		}
    		if (wac == null) {
    			// No context instance is defined for this servlet -> create a local one
    			wac = createWebApplicationContext(rootContext);
    		}
    
    		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.
    			synchronized (this.onRefreshMonitor) {
    				//onRefresh方法模板,DispatcherServlet实现了改方法
    				onRefresh(wac);
    			}
    		}
    
    		if (this.publishContext) {
    			// Publish the context as a servlet context attribute.
    			String attrName = getServletContextAttributeName();
    			getServletContext().setAttribute(attrName, wac);
    		}
    
    		return wac;
    	}
    

    至此父子容器关系建立,并且父容器和子容器刷新配置完成。

    这就是SpringMVC父子容器创建的原理。

    父子容器特点

    1. 父容器和子容器是相互隔离的,他们内部可以存在名称相同的bean
    2. 子容器可以访问父容器中的bean,而父容器不能访问子容器中的bean
    3. 调用子容器的getBean方法获取bean的时候,会沿着当前容器开始向上面的容器进行查找,直到找到对应的bean为止
    4. 子容器中可以通过任何注入方式注入父容器中的bean,而父容器中是无法注入子容器中的bean,原因是第2点
  • 相关阅读:
    国庆·生日
    国足
    Eason's concert
    今天的斩获
    The 4400
    闷热
    24
    一直登录不了。。。原来是因为。。。
    黄色暴雨警告
    绝密飞行
  • 原文地址:https://www.cnblogs.com/wwjj4811/p/15682391.html
Copyright © 2011-2022 走看看