Spring与SpringMvc启动流程详解

Spring与SpringMvc启动流程详解,第1张

Spring与SpringMvc启动流程详解 文章目录​​

目录

文章目录​​

前言

一、ServletContainerInitializer 接口是什么?

二、@HandlesTypes注解是什么

三、SpringServletContainerInitializer 是什么

四、WebApplicationInitializer 接口

总结




前言

本文不是SpringBoot的启动流程,是基于Tomcat,Spring,SpringMVC整合的纯注解启动流程,在我们启动Tomcat的时候Spring和SpringMvc是怎么加载启动的呢?ServletContainerInitializer 离不开这个关键的接口,围绕此接口在下文详细叙述。





一、ServletContainerInitializer 接口是什么?

在Tomcat容器启动的时候会扫描所有jar包中 meta-INF/services 目录下存不存在一个名为javax.servlet.ServletContainerInitializer的文件,此文件的内容是实现了ServletContainerInitializer接口的实现类的全类名, 若存在则tomcat容器会将该实现类加载和实例化并调用该类的onStartup方法

public interface ServletContainerInitializer {
     void onStartup(Set> c, ServletContext ctx) throws ServletException;
}

可以看到onStartup方法有两个参数 

  1.  Set> webAppInitializerClasses 是一个Class对象的Set集合,Set集合中的Class对象是从何而来呢?请带着问题向下接着看.
  2. ServletContext servletContex 
    

解答刚才提出的疑问,  参数一 webAppInitializerClasses 从何而来@HandlesTypes(WebApplicationInitializer.class), tomcat容器会扫描所有实现了注解标注的WebApplicationInitializer.class这个接口的实现类, 并把所有实现类的Class对象在调用onStartup方法时以参数的形式传递过来

二、@HandlesTypes注解是什么
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface HandlesTypes {
    Class[] value();
}

 这个注解标注接受一个Class的数组,这个数组会被当作参数传递给onStartup方法, 对应此方法的第一个参数, 这就解答了上面的问题,Set集合中的Class对象是从何而来的





三、SpringServletContainerInitializer 是什么

  在Spring中 (Spring的spi文件)org.springframework.web.SpringServletContainerInitializer 类就是实现了ServletContainerInitializer接口并重写了onStartup方法的实现类
​这时,当tomcat启动时就会调用SpringServletContainerInitializer 类重写的onStartup方法, Spring重写的onStartup方法就做了三件事

  1. 见图片2号方框判断条件, 找出所有非接口, 非抽象类 且是WebApplicationInitializer.class的子类的Class对象,
  2. 见图片3号方框, 把满足条件的所有Class生成的对象, 添加到List集合存储起来
  3. 见图片4号方框, 遍历第二步的集合中的对象 调用对象的onStartup方法,注意此处的onStartup方法是WebApplicationInitializer.class 这个接口定义的
    public interface WebApplicationInitializer {
    	void onStartup(ServletContext servletContext) throws ServletException;
    }
四、WebApplicationInitializer 接口

方便以编程方式配置ServletContext, Spring和SpringMvc容器的启动也依赖此接口及其子类, 主要依赖的子类有三个

  1. AbstractContextLoaderInitializer
  2. AbstractDispatcherServletInitializer
  3. AbstractAnnotationConfigDispatcherServletInitializer

类的继承结构如下:

 当调用onStartup方法时由其子类AbstractDispatcherServletInitializer#onStartup方法实现

//此处代码在AbstractDispatcherServletInitializer类中
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
//此处调用父类的AbstractContextLoaderInitializer#onStartup方法
	super.onStartup(servletContext);//第一步
	registerDispatcherServlet(servletContext);//第二步
}

 第一步先调用了AbstractContextLoaderInitializer#onStartup方法, 调用了registerContextLoaderListener方法

此处代码在AbstractContextLoaderInitializer类中
@Override
public void onStartup(ServletContext servletContext) throws ServletException {
	registerContextLoaderListener(servletContext);
}

protected void registerContextLoaderListener(ServletContext servletContext) {
	WebApplicationContext rootAppContext = createRootApplicationContext();
	if (rootAppContext != null) {
//创建一个监听器, 在Tomcat容器初始化完成时调用, ContextLoaderListener同时继承了ContextLoader类
//创建ContextLoaderListener对象的同时 为ContextLoader的congtext属性赋值
		ContextLoaderListener listener = new ContextLoaderListener(rootAppContext);
//第一次设置 初始化容器时的 加载配置容器的类,接下来还有一次
		listener.setContextInitializers(getRootApplicationContextInitializers());
		servletContext.addListener(listener);
	}
}

 createRootApplicationContext 方法创建了一个Spring空的容器对象, 

//在AbstractAnnotationConfigDispatcherServletInitializer类中
protected WebApplicationContext createRootApplicationContext() {
//getRootConfigClasses是抽象方法需要子类实现,该方法的返回值会以FULL bean方式注册到SpringIoc容器中成为bean, 被SpringIOC容器感知接管
	Class[] configClasses = getRootConfigClasses();
	if (!ObjectUtils.isEmpty(configClasses)) {
        AnnotationConfigWebApplicationContext context ;
        //创建注解驱动的IOC容器
        context = new AnnotationConfigWebApplicationContext();
        //以FULL 方式注册bean到IOC容器
		context.register(configClasses);
		return context;
	}
	else {
		return null;
	}
}

 并为注册了一个监听器对象, 在Tomcat容器启动完成时调用 initWebApplicationContext方法初始化IOC容器,初始化IOC容器的关键方法是 configureAndRefreshWebApplicationContext方法 此方法主要是 激活IOC容器,加载bean到容器内, 并且在刷新容器之前 提供配置 IOC容器的方法,至此父子容器中 父容器SpringIOC容器创建并初始化完成.

//在ContextLoader类中
public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
//之前已经为this.context 赋值此if判断条件为false
    if (this.context == null) {
	    this.context = createWebApplicationContext(servletContext);
    }

    if (this.context instanceof ConfigurableWebApplicationContext) {
    	ConfigurableWebApplicationContext cwac;
        cwac = (ConfigurableWebApplicationContext) this.context;
        //判断当前容器是否激活
	    if (!cwac.isActive()) {
            //获取当前容器父容器,此时容器为根容器即Spring容器,所以为null ,if条件为true
            //SpringMvc子容器还未创建
		    if (cwac.getParent() == null) {
                //此方法返回null
			    ApplicationContext parent = loadParentContext(servletContext);
                //这时parent为null
			    cwac.setParent(parent);
	    	}
            //配置并刷新SpringIoc容器,在此方法之前IOC容器虽然创建但是是个空容器并没有bean
            //此方法执行之后就会为激活IOC容器 ,并加载bean
		    configureAndRefreshWebApplicationContext(cwac, servletContext);
	    }
    }

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

}


protected void configureAndRefreshWebApplicationContext(ConfigurableWebApplicationContext wac, ServletContext sc) {
    if (ObjectUtils.identityToString(wac).equals(wac.getId())) {	
		String idParam = sc.getInitParameter(CONTEXT_ID_PARAM);
		if (idParam != null) {
			wac.setId(idParam);
		}
		else {
	        wac.setId(ConfigurableWebApplicationContext.APPLICATION_CONTEXT_ID_PREFIX                     
                                     +ObjectUtils.getDisplayString(sc.getContextPath()));
		}
	}

	wac.setServletContext(sc);
	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);
	}
    //容器刷新之前 第二次加载配置容器实现类并调用初始化方法
	customizeContext(sc, wac);
	wac.refresh();
}

 第二步紧接着调用AbstractDispatcherServletInitializer#registerDispatcherServlet

//此处代码在AbstractDispatcherServletInitializer类中
protected void registerDispatcherServlet(ServletContext servletContext) {
    String servletName = getServletName();
	WebApplicationContext servletAppContext = createServletApplicationContext();
	frameworkServlet dispatcherServlet = createDispatcherServlet(servletAppContext);
    dispatcherServlet.setContextInitializers(getServletApplicationContextInitializers());
    ServletRegistration.Dynamic registration = servletContext.addServlet(servletName, dispatcherServlet);
    registration.setLoadonStartup(1);
	registration.addMapping(getServletMappings());
	registration.setAsyncSupported(isAsyncSupported());
    Filter[] filters = getServletFilters();
	if (!ObjectUtils.isEmpty(filters)) {
		for (Filter filter : filters) {
			registerServletFilter(servletContext, filter);
		}
	}

	customizeRegistration(registration);
}





总结

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/zaji/5573485.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-14
下一篇 2022-12-14

发表评论

登录后才能评论

评论列表(0条)

保存