SpringMVC父子容器源码分析

这篇具有很好参考价值的文章主要介绍了SpringMVC父子容器源码分析。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

书接上文

一、 Spring整合SpringMVC

1. Spring整合SpringMVC案例分析

Spring框架和Spring MVC框架是Java开发中常用的两个框架,它们可以很好地结合使用,提供了一个强大而灵活的开发环境。下面是一个简单的案例分析,演示如何整合Spring和Spring MVC。

  • 主配置类
// AppConfig.java
@Configuration
@ComponentScan(basePackages = "com.example")
@EnableWebMvc
public class AppConfig {

    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

    // 其他配置项,如数据源、事务管理等

}

  • SpringMVC控制器
// AppConfig.java
@Configuration
@ComponentScan(basePackages = "com.example")
@EnableWebMvc
public class AppConfig {

    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

    // 其他配置项,如数据源、事务管理等

}

  • 启动类,初始化Spring容器
// AppConfig.java
@Configuration
@ComponentScan(basePackages = "com.example")
@EnableWebMvc
public class AppConfig {

    @Bean
    public InternalResourceViewResolver viewResolver() {
        InternalResourceViewResolver resolver = new InternalResourceViewResolver();
        resolver.setPrefix("/WEB-INF/views/");
        resolver.setSuffix(".jsp");
        return resolver;
    }

    // 其他配置项,如数据源、事务管理等

}

  • web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">

    <!--spring 基于web应用的启动-->
    <listener>
        <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    <!--全局参数:spring配置文件-->
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>classpath:spring-core.xml</param-value>
    </context-param>


    <!--配置前端控制器  、核心调度器
    加载spring容器
    -->
    <servlet>
        <servlet-name>springmvc</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>

        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring-mvc.xml</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
        <async-supported>true</async-supported>
    </servlet>
    <!--/ 除了jsp所有请求都会被匹配-->
    <servlet-mapping>
        <servlet-name>springmvc</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

org.springframework.web.context.ContextLoaderListener是Spring框架提供的监听器,用于在Web应用程序启动时加载Spring的根应用上下文(Root ApplicationContext)。它负责初始化和销毁Spring容器,使得在整个Web应用程序的生命周期中,Spring容器能够管理和提供各种Bean,包括服务层、数据访问层等组件。通过该监听器,可以确保在Web应用程序启动时,Spring容器得以正确初始化,以便在整个应用程序中共享和管理Bean实例。

org.springframework.web.servlet.DispatcherServlet是Spring MVC框架的前端控制器,负责接收和分发所有进入应用程序的HTTP请求。作为中央调度器,它根据配置将请求分发给相应的控制器,处理请求的解析、控制器的执行以及视图的渲染,起到了统一调度和管理的作用,是Spring MVC应用程序的核心组件。通过配置,可以灵活指定其行为,包括控制器的位置、视图解析器等,从而实现对请求处理流程的定制和扩展。

2. Spring和SpringMVC父子容器的关系

在Spring框架中,父子容器的概念是常见的,而在Spring MVC中也可以利用父子容器的机制来组织应用程序的架构。以下是关于Spring MVC和Spring父子容器的一些概念:

Spring容器: Spring框架中的核心组件是Spring容器,它负责管理和组织应用程序中的Bean。Spring容器主要分为两种:应用上下文(Application Context)和父上下文(Parent Context)。

Spring MVC容器: 在Spring MVC中,通常会存在一个用于管理Web组件(如Controller、ViewResolver等)的Spring MVC容器。这个容器是WebApplicationContext的实例,负责加载和管理与Web相关的Bean。

父子容器关系: Spring MVC容器可以配置一个父上下文,通常是应用上下文。这种父子容器的关系使得Spring MVC容器能够访问应用上下文中定义的Bean,而应用上下文无法直接访问Spring MVC容器中的Bean。

好处: 使用父子容器的好处在于能够将应用程序的各个模块分开管理,提高了模块之间的解耦性。应用上下文可以包含业务逻辑、数据访问等模块的Bean,而Spring MVC容器专注于管理与Web相关的Bean,例如Controller、ViewResolver等。

配置方式: 在web.xml中配置ContextLoaderListener来加载父上下文,通常是应用上下文。在DispatcherServlet配置中,可以指定其上下文配置文件,该配置文件将会形成Spring MVC容器的上下文。
SpringMVC父子容器源码分析,重温Spring源码系列,Spring,spring

3. SPI机制详解

SPI(Service Provider Interface)是Java中一种用于实现组件扩展的机制,允许第三方实现提供服务,并通过接口被应用程序发现和调用。SPI机制主要涉及到Java的ServiceLoader类以及在META-INF/services目录下的配置文件。下面结合一个案例分析一下SPI如何使用:

  • 定义接口: 定义一个接口,表示服务提供者需要实现的功能。
// ServiceInterface.java
public interface ServiceInterface {
    void execute();
}
  • 编写服务提供者实现类: 编写多个实现了接口的服务提供者类。
// ServiceProviderA.java
public class ServiceProviderA implements ServiceInterface {
    @Override
    public void execute() {
        System.out.println("Service A is executing.");
    }
}
// ServiceProviderB.java
public class ServiceProviderB implements ServiceInterface {
    @Override
    public void execute() {
        System.out.println("Service B is executing.");
    }
}
  • 创建配置文件: 在META-INF/services目录下创建一个以接口全限定名为名字的文件,其中包含服务提供者的类名。
// META-INF/services/com.example.ServiceInterface
com.example.ServiceProviderA
com.example.ServiceProviderB
  • 使用ServiceLoader加载服务: 在应用程序中使用ServiceLoader类加载服务。
// ServiceConsumer.java
import java.util.ServiceLoader;

public class ServiceConsumer {
    public static void main(String[] args) {
        ServiceLoader<ServiceInterface> serviceLoader = ServiceLoader.load(ServiceInterface.class);

        for (ServiceInterface service : serviceLoader) {
            service.execute();
        }
    }
}

SPI的底层实现原理主要依赖于ServiceLoader类的服务发现机制。这个机制通过解析META-INF/services目录下的配置文件,动态加载实现了特定接口的服务提供者,并使得应用程序可以在运行时根据需要发现和调用这些服务提供者。这种机制提高了代码的可扩展性和灵活性,使得系统更容易实现组件的替换和升级。

二、 源码分析

1. 注册父子容器

前面我们在分析XML方式整合SpringMVC的时候,我们需要手动的配置DispatcherServlet,在这里Spring其实会通过前面介绍的SPI机制帮我们自动注册这些东西。

SpringMVC父子容器源码分析,重温Spring源码系列,Spring,spring
上面图片就是Spring-web模块下的META-INF/services模块下提供了一个javax.servlet.ServiletContainerInitializer的文件,这个就是Spi机制中需要配置的一个文件。我们看看这个文件中放了什么实现类:

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
	@Override
	public void onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List<WebApplicationInitializer> initializers = Collections.emptyList();

		if (webAppInitializerClasses != null) {
			initializers = new ArrayList<>(webAppInitializerClasses.size());
			for (Class<?> waiClass : webAppInitializerClasses) {
				// 接口和抽象类servlet容器也会给我们,但是我们不要
				// 排除接口和容器
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						// 实例化,然后添加到集合中
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}
		}

		if (initializers.isEmpty()) {
			servletContext.log("No Spring WebApplicationInitializer types detected on classpath");
			return;
		}

		servletContext.log(initializers.size() + " Spring WebApplicationInitializers detected on classpath");
		AnnotationAwareOrderComparator.sort(initializers);
		// 调用initializer.onStartup  进行扩展
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

可以看见这个类实现了ServletContainerInitializer(Servlet容器初始化接口)接口,然后重写了startup方法,然后这个方法什么时候被调用呢?在Tomcat的启动流程中这个方法会被调用来初始化Servlet容器。

SpringMVC父子容器源码分析,重温Spring源码系列,Spring,spring
在上面的类上面有一个注解@HandlesTypes(WebApplicationInitializer.class),它注入了onstartup所需要的一个参数WebApplicationInitializer.class,这个注解可以理解为Spring感兴趣的一个接口,通过这个注解我们可以传入一个口,Tomcat会自动找到这个接口的实现类, 然后传入到onStartup(@Nullable Set<Class<?>> webAppInitializerClasses, ..) 这个参数上。进入onStartup方法:
SpringMVC父子容器源码分析,重温Spring源码系列,Spring,spring

for (Class<?> waiClass : webAppInitializerClasses) {
				// 接口和抽象类servlet容器也会给我们,但是我们不要
				// 排除接口和容器
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						// 实例化,然后添加到集合中
						initializers.add((WebApplicationInitializer)
								ReflectionUtils.accessibleConstructor(waiClass).newInstance());
					}
					catch (Throwable ex) {
						throw new ServletException("Failed to instantiate WebApplicationInitializer class", ex);
					}
				}
			}

它首先会遍历webAppInitializerClasses的实现类,然后判断当前的实现类不是一个借口和抽象类。如果不是就会将我们这个实现了webAppInitializerClasses的类进行实例化,然后放到一个集合中。 然后执行下面代码:

for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}

上面代码就是循环前面的集合,然后分别调用onStratup方法,即调用webAppInitializerClasses的实现类的onStartup方法。所以我们只需要在应用程序中就可以实现这个类或其子类来注册我们需要的DispatcherServletContextLoaderListener(在XML方式下我们需要去手动配置)。例如下面代码:

public class TulingStarterInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

	/**
	 * 方法实现说明:IOC 父容器的启动类
	 * @author:xsls
	 * @date:2019/7/31 22:12
	 */
	@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class[]{RootConfig.class};
	}

	/**
	 * 方法实现说明 IOC子容器配置 web容器配置
	 * @author:xsls
	 * @date:2019/7/31 22:12
	 */
	@Override
	protected Class<?>[] getServletConfigClasses() {
		return new Class[]{WebAppConfig.class};
	}

	/**
	 * 方法实现说明
	 * @author:xsls
	 * @return: 我们前端控制器DispatcherServlet的拦截路径
	 * @exception:
	 * @date:2019/7/31 22:16
	 */
	@Override
	protected String[] getServletMappings() {
		return new String[]{"/"};
	}
}

SpringMVC父子容器源码分析,重温Spring源码系列,Spring,spring

这里我们进入AbstractAnnotationConfigDispatcherServletInitializer这个父类,来查看它的onStartUp方法:

//AbstractDispatcherServletInitializer类中
@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		//registerContextLoaderListener  ok
		super.onStartup(servletContext);
		// registerDispatcherServlet
		registerDispatcherServlet(servletContext);
	}

上面首先执行super.onStartup(servletContext);,我们进入父类的onStartUp方法。

	@Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		registerContextLoaderListener(servletContext);
	}

上面代码就是注册ContextLoaderListener,回到子类,子类的registerDispatcherServlet(servletContext);就是在注册DispatcherServlet。我们先看是如何注册ContextLoaderListener的。

protected void registerContextLoaderListener(ServletContext servletContext) {
		// 创建父容器 ,
		WebApplicationContext rootAppContext = createRootApplicationContext();
		if (rootAppContext != null) {
			ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
			// 设置初始化器
			listener.setContextInitializers(getRootApplicationContextInitializers());
			servletContext.addListener(listener);
		}
		else {
			logger.debug("No ContextLoaderListener registered, as " +
					"createRootApplicationContext() did not return an application context");
		}
	}

上面代码就是首先创建了一个WebApplicationContext对象,这里就是在创建真正的IOC容器了,这就是创建父容器。然后创建了一个ContextLoaderListener对象。我们进入createRootApplicationContext

@Override
	@Nullable
	protected WebApplicationContext createRootApplicationContext() {
		Class<?>[] configClasses = getRootConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
			context.register(configClasses);
			return context;
		}
		else {
			return null;
		}
	}

前面看过Spring源码的都知道,AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();这句代码就是创建Spring上下文,然后将配置类configClasses加载了进去,但这里并没有refresh容器,而只是创建了一个IOC容器,然后返回。getRootConfigClasses就是获取我们的配置类,这个方法就是在我们前面自定义的TulingStarterInitializer实现了。

@Override
	protected Class<?>[] getRootConfigClasses() {
		return new Class[]{RootConfig.class};
	}

@Configuration
@ComponentScan(basePackages = "com.tuling",excludeFilters = {
		@ComponentScan.Filter(type = FilterType.ANNOTATION,value={Controller.class}),
		@ComponentScan.Filter(type = ASSIGNABLE_TYPE,value =WebAppConfig.class ),
})
public class RootConfig {

}

上面就是注册ContextLoaderListener对象,回到onStartup,看看是如何注册DispatcherServlet的。

protected void registerDispatcherServlet(ServletContext servletContext) {
		String servletName = getServletName();
		Assert.hasLength(servletName, "getServletName() must not return null or empty");
		// 创建子容器
		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());

		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());
		// 设置DispatcherServlet的过滤器
		Filter[] filters = getServletFilters();
		if (!ObjectUtils.isEmpty(filters)) {
			for (Filter filter : filters) {
				registerServletFilter(servletContext, filter);
			}
		}
		// 空方法, 可以再对DispatcherServlet进行定制
		customizeRegistration(registration);
	}

首先上面方法执行了下面语句创建了一个子容器:

WebApplicationContext servletAppContext = createServletApplicationContext();


	@Override
	protected WebApplicationContext createServletApplicationContext() {
		AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
		Class<?>[] configClasses = getServletConfigClasses();
		if (!ObjectUtils.isEmpty(configClasses)) {
			context.register(configClasses);
		}
		return context;
	}

我们发现上面同样创建了一个AnnotationConfigWebApplicationContext容器,这个就是我们的子容器。然后配置类的获取就是调用getServletConfigClasses,这个同样是在前面的TulingStarterInitializer类中配置的。

	@Override
	protected Class<?>[] getServletConfigClasses() {
		return new Class[]{WebAppConfig.class};
	}

@Configuration
@ComponentScan(basePackages = {"com.tuling"},includeFilters = {
		@ComponentScan.Filter(type = FilterType.ANNOTATION,value = {RestController.class, Controller.class})
},useDefaultFilters =false)
@EnableWebMvc   // = <mvc:annotation-driven/>
public class WebAppConfig implements WebMvcConfigurer{
.......
}

上面子容器就创建好了,继续回到registerDispatcherServlet方法,然后创建DispatcherServlet。 设置启动时立即加载,设置过滤器,设置映射等。到这里子容器就创建成功了。

2. 加载父子容器

前面通过Tomcat的SPI机制引出的扩展类,并使用该扩展类创建类父子容器。Tomcat源码继续向下执行,然后Tomcat就会注册监听器。
SpringMVC父子容器源码分析,重温Spring源码系列,Spring,spring

首先它会执行ContextLoaderListenercontextInitializer方法。我们进入该方法:

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

上面代码就调用了一个方法initWebApplicationContext,该方法是ContextLoaderListener的父类ContextLoader的方法。我们进入该方法:

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 {
			// xml会在这里创建,判断当前容器是否为null
			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
					if (cwac.getParent() == null) {
						// The context instance was injected without an explicit parent ->
						// determine parent for root web application context, if any.
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}
			// 在servlet域中设置根容器(在子容器就可以直接拿到了)
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);



			// 获取线程上下文类加载器,默认为WebAppClassLoader
			ClassLoader ccl = Thread.currentThread().getContextClassLoader();
			// 如果spring的jar包放在每个webapp自己的目录中
			// 此时线程上下文类加载器会与本类的类加载器(加载spring的)相同,都是
			if (ccl == ContextLoader.class.getClassLoader()) {
				currentContext = this.context;
			}
			// 如果不同,也就是上面说的那个问题的情况,那么用一个map把刚才创建的
			else if (ccl != null) {
				// 一个webapp对应一个记录,后续调用时直接根据WebAppClassLoader来取出
				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;
		}
	}

首先我们看下面代码:

	if (this.context == null) {
				this.context = createWebApplicationContext(servletContext);
}

这里判断容器是否为空,我们前面知道,在使用Tomcat SPI创建容器的时候,创建的容器是不可能为空的,这句代码的意义就是,我们没有使用SPI,而是使用传统的使用web.xml配置形式创建的是欧,这里就会为空,就需要去创建容器。然后执行下面代码:

if (this.context instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) this.context;
				if (!cwac.isActive()) {
					if (cwac.getParent() == null) {	
						ApplicationContext parent = loadParentContext(servletContext);
						cwac.setParent(parent);
					}
					configureAndRefreshWebApplicationContext(cwac, servletContext);
				}
			}

上面代码首先判断当前容器是否继承了ConfigurableWebApplicationContext类,进入if然后执行ApplicationContext parent = loadParentContext(servletContext);这个loadParentContext固定是返回null的,这个就是提供了一个扩展点,我们可以自己再去在当前容器上实现一个父容器。最后最核心的就是执行configureAndRefreshWebApplicationContext(cwac, servletContext);,我们进入该方法:

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {

			// 设置id
			String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
			if (idParam != null) {
				wac.setId(idParam);
			}
			else {
				// Generate default id...
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(sc.getContextPath()));
			}
		}
		// 设置ServletContext到spring上下文
		wac.setServletContext(sc);
		// 获得servlet容器中的全局参数contextConfigLocation  (xml)
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(sc, null);
		}
		// 在容器加载前 可以通过设置初始化参数contextInitializerClasses、globalInitializerClasses 进行扩展
		customizeContext(sc, wac);
		// 刷新容器
		wac.refresh();
	}

上面代码首先执行wac.setServletContext(sc);将Servlet容器设置到了ApplicationContext中,后续我们就可以拿到这个Servlet容器(通过继承回调接口),将我们需要的Servlet注入到这个容器中。

public class RootConfig extends ServletContextAware{

	@Override
	public void setServletContext(ServletContext servletContext) {
		 //添加Servlet
	}
}

如果是XML方式(web.xml),就会通过下面代码获取全局参数

// 获得servlet容器中的全局参数contextConfigLocation  (xml)
		String configLocationParam = sc.getInitParameter(CONFIG_LOCATION_PARAM);
		if (configLocationParam != null) {
			wac.setConfigLocation(configLocationParam);
		}

最后执行wac.refresh();就刷新了父容器,下面就是执行Spirng的Bean的生命周期流程了,这个前面我们已经分析很多了。到此父容器加载完成。

回到initWebApplicationContext方法,上面方法就是将上面加载的父容器设置到了session域中,后面执行容器绑定的时候就可以拿到容器了。

// 在servlet域中设置根容器(在子容器就可以直接拿到了)
			servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

到此ContextLoaderListener就加载完毕了,下面就可以开始加载子容器DispatcherServlet。当我们设置了loadonStart方法(前面注册ContextLoaderListener的时候设置了),Tomcat就会自动帮我们调用HttpServletBeaninit方法。

@Override
	public final void init() throws ServletException {

		// 解析 init-param 并封装只 pvs 中(xml)
		PropertyValues pvs = new ServletConfigPropertyValues(getServletConfig(), this.requiredProperties);
		if (!pvs.isEmpty()) {
			try {
				// 将当前的这个 Servlet 类转化为一个 BeanWrapper,从而能够以 Spring 的方法来对 init-param 的值进行注入
				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.
		// 初始化Servlet,创建Spring容器
		initServletBean();
	}

上面核心的就是调用initServletBean()方法。

//FrameWorkServlet类中
@Override
	protected final void initServletBean() throws ServletException {
		getServletContext().log("Initializing Spring " + getClass().getSimpleName() + " '" + getServletName() + "'");
		if (logger.isInfoEnabled()) {
			logger.info("Initializing Servlet '" + getServletName() + "'");
		}
		long startTime = System.currentTimeMillis();

		try {
			this.webApplicationContext = initWebApplicationContext();
			// 空方法,无实现·
			initFrameworkServlet();
		}
		catch (ServletException | RuntimeException ex) {
			logger.error("Context initialization failed", ex);
			throw ex;
		}

		if (logger.isDebugEnabled()) {
			String value = this.enableLoggingRequestDetails ?
					"shown which may lead to unsafe logging of potentially sensitive data" :
					"masked to prevent unsafe logging of potentially sensitive data";
			logger.debug("enableLoggingRequestDetails='" + this.enableLoggingRequestDetails +
					"': request parameters and headers will be " + value);
		}

		if (logger.isInfoEnabled()) {
			logger.info("Completed initialization in " + (System.currentTimeMillis() - startTime) + " ms");
		}
	}

上面首先执行了this.webApplicationContext = initWebApplicationContext();这句代码。

protected WebApplicationContext initWebApplicationContext() {
		// 获得ContextLoaderListener存的父容器
		WebApplicationContext rootContext =
				WebApplicationContextUtils.getWebApplicationContext(getServletContext());
		WebApplicationContext wac = null;

		if (this.webApplicationContext != null) {
			// 获得子容器
			wac = this.webApplicationContext;
			if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// 如果没有设置父容器   spring  doGetBean
					if (cwac.getParent() == null) {
						cwac.setParent(rootContext);
					}
					// 配置并且加载子容器
					configureAndRefreshWebApplicationContext(cwac);
				}
			}
		}
		if (wac == null) {
			// 从servlet上下文根据<contextAttribute>名字从域里面获取
			wac = findWebApplicationContext();
		}
		if (wac == null) {
			// xml会在这里创建
			wac = createWebApplicationContext(rootContext);
		}

		//refreshEventReceived 它会在容器加载完设置为true (通过事件onApplicationEvent)
		// springboot在这初始化组件
		if (!this.refreshEventReceived) {
			synchronized (this.onRefreshMonitor) {
				onRefresh(wac);
			}
		}

		if (this.publishContext) {
			// 将当前容器放到servlet域中, 可以再创建子容器
			String attrName = getServletContextAttributeName();
			getServletContext().setAttribute(attrName, wac);
		}

		return wac;
	}

上面代码首先调用getWebApplicationContext从前面Session域中获得的封装的父容器。然后执行下面代码:

if (wac instanceof ConfigurableWebApplicationContext) {
				ConfigurableWebApplicationContext cwac = (ConfigurableWebApplicationContext) wac;
				if (!cwac.isActive()) {
					// 如果没有设置父容器   spring  doGetBean
					if (cwac.getParent() == null) {
						cwac.setParent(rootContext);
					}
					// 配置并且加载子容器
					configureAndRefreshWebApplicationContext(cwac);
				}
			}

上面代码首先就是调用cwac.setParent(rootContext);设置了父容器,这里就完成了父子容器的绑定。然后执行configureAndRefreshWebApplicationContext(cwac);进行配置和加载子容器了。

protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac) {
		if (ObjectUtils.identityToString(wac).equals(wac.getId())) {
			// 设置id
			if (this.contextId != null) {
				wac.setId(this.contextId);
			}
			else {
				// Generate default id...
				wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX +
						ObjectUtils.getDisplayString(getServletContext().getContextPath()) + '/' + getServletName());
			}
		}
		// 设置servlet上下文
		wac.setServletContext(getServletContext());
		wac.setServletConfig(getServletConfig());
		wac.setNamespace(getNamespace());
		// 监听器  委托设计模式
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

		// 将init-param设置到Environment中
		ConfigurableEnvironment env = wac.getEnvironment();
		if (env instanceof ConfigurableWebEnvironment) {
			((ConfigurableWebEnvironment) env).initPropertySources(getServletContext(), getServletConfig());
		}
		// 空方法可扩展
		postProcessWebApplicationContext(wac);
		// 容器启动前初始化
		applyInitializers(wac);
		wac.refresh();
	}

下面这句代码就是添加了一个监听器,这个监听器就会加载我们上篇文章学习到的各种SpringMVC的组件。这个监听器监听的事件是容器加载事件,如果子容器创建完成就会调用监听器的方法。

// 监听器  委托设计模式
		wac.addApplicationListener(new SourceFilteringListener(wac, new ContextRefreshListener()));

最后就是调用refresh方法了。(子容器只有Controller的bean,所以需要和父容器绑定),容器加载完毕就会发布一个加载完毕的事件,这时候就可以调用前面说的的监听器事件了。

private class ContextRefreshListener implements ApplicationListener<ContextRefreshedEvent> {

		@Override
		public void onApplicationEvent(ContextRefreshedEvent event) {
			FrameworkServlet.this.onApplicationEvent(event);
		}
	}

它就会调用onApplicationEvent(event)方法。

public void onApplicationEvent(ContextRefreshedEvent event) {
		this.refreshEventReceived = true;
		synchronized (this.onRefreshMonitor) {
			onRefresh(event.getApplicationContext());
		}
	}

然后调用onRefresh方法。

@Override
	protected void onRefresh(ApplicationContext context) {
		initStrategies(context);
	}

	/**初始化策略,加了s都是多个
	 * Initialize the strategy objects that this servlet uses.
	 * <p>May be overridden in subclasses in order to initialize further strategy objects.
	 */
	protected void initStrategies(ApplicationContext context) {
		initMultipartResolver(context);
		initLocaleResolver(context);
		initThemeResolver(context);
		initHandlerMappings(context);
		initHandlerAdapters(context);
		initHandlerExceptionResolvers(context);
		initRequestToViewNameTranslator(context);
		initViewResolvers(context);
		initFlashMapManager(context);
	}

上面那些SpringMVC得组建就被加载完成了。 在加载这些组件的时候,我们知道组件如果没有它就会从一个属性文件中去取默认的配置去加载,那么这些组件是那里来的呢?这个就得看一个关键的注解@EnableWebMvc 这个注解的作用等同于xml文件配置的

 <mvc:annotation-driven/>

我们进入该注解:

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(DelegatingWebMvcConfiguration.class)
public @interface EnableWebMvc {
}

这个注解上关键的一个注解就是@Import(DelegatingWebMvcConfiguration.class),我们知道在Spring启动过程中会将@Import注解标注的类加载为bean。而这个DelegatingWebMvcConfiguration.class类中就配置了前面的各种SpringMVC组件以及其它一些SpringMVC关键的Bean,就在这里会被加载到容器中(这个类在Springboot中也会讲到)。文章来源地址https://www.toymoban.com/news/detail-804282.html

到了这里,关于SpringMVC父子容器源码分析的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 73.是否可以把我们所需的Bean都放入Spring­mvc子容器里面来管理(springmvc的spring-servlet.xml中配置全局扫描)?

    可以 , 因为父容器的体现无非是为了获取子容器不包含的bean, 如果全部包含在子容器完全用不到父容器了, 所以是可以全部放在springmvc子容器来管理的。 虽然可以这么做不过一般应该是不推荐这么去做的,一般人也不会这么干的。如果你的项目里有用到事物、或者aop记得也

    2024年02月21日
    浏览(47)
  • 【Spring专题】手写简易Spring容器过程分析

    首先必须得声明的是,下面的流程并不代表Spring源码中真正的流程。而是,我们通过现在的Spring提供给我们的某些基础功能,反推过来的流程。所以,并不全面,但是会具有一点参考性。 由于Spring源码分析是一个前后联系比较强的过程,而且这边分析,也是按照代码顺序讲解

    2024年02月13日
    浏览(42)
  • spring高级源码50讲-1-8(spring容器与bean)

    1) 容器接口 BeanFactory 接口,典型功能有: getBean ApplicationContext 接口,是 BeanFactory 的子接口。它扩展了 BeanFactory 接口的功能,如: 国际化 通配符方式获取一组 Resource 资源 整合 Environment 环境(能通过它获取各种来源的配置信息) 事件发布与监听,实现组件之间的解耦 可以

    2024年02月10日
    浏览(31)
  • SpringBoot 底层机制分析【Tomcat 启动+Spring 容器初始化+Tomcat 如何关联Spring 容器】【下】

    😀前言 本篇博文是关于SpringBoot 底层机制分析实现,希望能够帮助你更好的了解SpringBoot 😊 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的文章可以帮助到大家,您的满意是我的动力😉😉 💕欢迎大家:这里是CSDN,我总结知识的地方,欢迎来到我的博客

    2024年02月13日
    浏览(45)
  • Spring源码解析——ApplicationContext容器refresh过程

    正文 在之前的博文中我们一直以BeanFactory接口以及它的默认实现类XmlBeanFactory为例进行分析,但是Spring中还提供了另一个接口ApplicationContext,用于扩展BeanFactory中现有的功能。 ApplicationContext和BeanFactory两者都是用于加载Bean的,但是相比之下,ApplicationContext提供了更多的扩展功

    2024年02月08日
    浏览(59)
  • 【spring源码系列-01】spring底层源码整体概述

    Spring源码系列整体栏目 内容 链接地址 【一】spring源码整体概述 https://blog.csdn.net/zhenghuishengq/article/details/130940885 【二】通过refresh方法剖析IOC的整体流程 https://blog.csdn.net/zhenghuishengq/article/details/131003428 【三】xml配置文件启动spring时refresh的前置工作 https://blog.csdn.net/zhenghuishen

    2024年02月07日
    浏览(43)
  • 【框架源码】手写Spring框架IOC容器核心流程

    要是想要了解Spring IOC底层,首先我们就得先了解什么是IOC。 IOC就是控制反转,把对象创建和对象之间的调用过程交给Spring进行管理。 使用IOC目的就是之前我们创建对象的方式都是用new的方式创建,这个方式有个缺点,被创建对象的类的位置一旦被改动,new就会报错,类与类

    2024年02月06日
    浏览(56)
  • Spring源码系列:初探底层,手写Spring

    在学习Spring框架源码时,记住一句话:源码并不难,只需要给你各种业务场景或者项目经理,你也能实现自己的Spring。虽然你的实现可能无法与开源团队相媲美,但是你肯定可以实现一个0.0.1版本。因此,初次阅读源码时,不要陷入太深的细节中。先了解大体逻辑,再仔细研

    2023年04月12日
    浏览(44)
  • 【Spring源码解读一】IoC容器之AnnotationConfigApplicationContext

    AnnotationConfigApplicationContext构造方法 this() AnnotatedBeanDefinitionReader ClassPathBeanDefinitionScanner register(componentClasses)         根据AnnotationConfigApplicationContext类去阅读其将Bean对象交给IoC容器管理的过程。以下这三个代码块是将配置类注册进IoC容器的例子。下面是关于这个类的继承与

    2024年02月08日
    浏览(45)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包