Springboot web工程通过内置和外置tomcat启动及其原理

1. 普通的spring web应用(不使用spring boot启动)

需要在web.xml里配置上下文信息。web.xml中通过配置ContextLoaderListener来在tomcat容器启动时创建spring上下文。


    contextConfigLocation
    classpath:applicationContext.xml


    org.springframe.web.context.ContextLoaderListener
ContextLoaderListener实现了ServletContextListener接口,在web.xml配置这个监听器,启动容器时,会执行ContextInitialized方法
@Override
public void contextInitialized(ServletContextEvent event) {
    // 调用父类ContextLoader的方法初始化spring 上下文
    initWebApplicationContext(event.getServletContext());
}

同时ContextLoaderListener继承了ContextLoader,其成员方法initWebApplicationContext()初始化上下文。

如果使用的是spring mvc框架,那么还需要配置前端控制器Servlet,即DispatcherServlet。

  
    DispatcherServlet
    org.springframework.web.servlet.DispatcherServlet  
      
        contextConfigLocation            
          
        classpath:spring/dispatcher-servlet.xml  
      
    1
  
  
    DispatcherServlet  
    /
  

DispatcherSevlet中也维护了一个contextConfigLocation参数,context-param中的设置是全局的,而这里的上下文设置服务于DispatcherServlet,事实上,完全可以在dispatcherServlet中设置上下文,而省略掉context-param和ContextLoaderListener配置:DispatcherServlet在初始化时(tomcat启动时初始化servlet,GenericServlet.init()),最终会调用其父类FrameworkServlet的initWebApplicationContext()方法来初始化web上下文。

2. spring boot web 应用

a. 通过内置tomcat启动

依赖配置:


    org.springframework.boot
    spring-boot-starter-web
    1.5.13.RELEASE

启动类:

@SpringBootApplication
public class App {
    public static void main(String[] args) {
        SpringApplication.run(App.class, args);
    }
}

运行main方法即可。spring会根据classpath下是否存在web相关类来决定启动普通上下文还是web上下文。

private boolean deduceWebEnvironment() {
    // WEB_ENVIRONMENT_CLASSES = { "javax.servlet.Servlet",	            
    //"org.springframework.web.context.ConfigurableWebApplicationContext" };
    for (String className : WEB_ENVIRONMENT_CLASSES) {
        if (!ClassUtils.isPresent(className, null)) {
            return false;
        }
    }
    return true;
}
protected ConfigurableApplicationContext createApplicationContext() {
	Class contextClass = this.applicationContextClass;
	if (contextClass == null) {
		try {
            // 决定起web上下文还是普通上下文
		    contextClass = Class.forName(this.webEnvironment
						? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
		}
		catch (ClassNotFoundException ex) {
			throw new IllegalStateException(
					"Unable create a default ApplicationContext, "
							+ "please specify an ApplicationContextClass",
					ex);
		}
	}
	return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}

b. 通过外置tomcat启动

打包:

war

依赖配置:


    org.springframework.boot
    spring-boot-starter-web
    1.5.13.RELEASE
    
        
            org.springframework.boot
            spring-boot-starter-tomcat
        
    


    javax.servlet
    javax.servlet-api
    provided

新增一个继承自SpringBootServletInitializer的类,并重写configure方法。

public class MyServletInitializer extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        // 参数是之前main方法所在类
        return builder.sources(App.class);
    }
}

根据Servlet3.0规范,web应用启动时会实例化每个jar包中的ServletContainerInitializer提供者(SPI)。实现了ServletContainerInitializer的jar包会在META-INF/services目录下维护一个文件javax.servlet.ServletContainerInitializer,其内容指向ServletContainerInitializer的实现类。如spring-web中:

Springboot web工程通过内置和外置tomcat启动及其原理_第1张图片

该文件内容为: 

org.springframework.web.SpringServletContainerInitializer

看看SpringServletContainerInitializer做了哪些事情

// @HandlesTypes注解表示SpringServletContainerInitializer能处理的类型
@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {

	@Override
    // Set> webAppInitializerClasses是@HandlesTypes标注的类型的所有实例
	public void onStartup(Set> webAppInitializerClasses, ServletContext servletContext)
			throws ServletException {

		List initializers = new LinkedList();

		if (webAppInitializerClasses != null) {
			for (Class waiClass : webAppInitializerClasses) {
				// Be defensive: Some servlet containers provide us with invalid classes,
				// no matter what @HandlesTypes says...
                // 对webAppInitializerClasses做进一步校验,只有符合条件的才添加到            
                // initializers中
				if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) &&
						WebApplicationInitializer.class.isAssignableFrom(waiClass)) {
					try {
						initializers.add((WebApplicationInitializer) 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);
        // 执行所有initializers的onStartup方法
		for (WebApplicationInitializer initializer : initializers) {
			initializer.onStartup(servletContext);
		}
	}

}

我们自定义的MyServletInitializer便是可以被SpringServletContainerInitializer 处理的initiaizer,其onStartup方法会被调用

    @Override
	public void onStartup(ServletContext servletContext) throws ServletException {
		// Logger initialization is deferred in case a ordered
		// LogServletContextInitializer is being used
		this.logger = LogFactory.getLog(getClass());
        // 创建web上下文
		WebApplicationContext rootAppContext = createRootApplicationContext(
				servletContext);
		if (rootAppContext != null) {
			servletContext.addListener(new ContextLoaderListener(rootAppContext) {
				@Override
				public void contextInitialized(ServletContextEvent event) {
					// no-op because the application context is already initialized
				}
			});
		}
		else {
			this.logger.debug("No ContextLoaderListener registered, as "
					+ "createRootApplicationContext() did not "
					+ "return an application context");
		}
	}

	protected WebApplicationContext createRootApplicationContext(
			ServletContext servletContext) {
		SpringApplicationBuilder builder = createSpringApplicationBuilder();
		StandardServletEnvironment environment = new StandardServletEnvironment();
		environment.initPropertySources(servletContext, null);
		builder.environment(environment);
		builder.main(getClass());
		ApplicationContext parent = getExistingRootWebApplicationContext(servletContext);
		if (parent != null) {
			this.logger.info("Root context already created (using as parent).");
			servletContext.setAttribute(
					WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, null);
			builder.initializers(new ParentContextApplicationContextInitializer(parent));
		}
		builder.initializers(
				new ServletContextApplicationContextInitializer(servletContext));
		builder.contextClass(AnnotationConfigEmbeddedWebApplicationContext.class);
        // 配置,我们自定义的initializer重写了该方法,因此会走我们的逻辑
		builder = configure(builder);
        // 创建spring应用
		SpringApplication application = builder.build();
		if (application.getSources().isEmpty() && AnnotationUtils
				.findAnnotation(getClass(), Configuration.class) != null) {
			application.getSources().add(getClass());
		}
		Assert.state(!application.getSources().isEmpty(),
				"No SpringApplication sources have been defined. Either override the "
						+ "configure method or add an @Configuration annotation");
		// Ensure error pages are registered
		if (this.registerErrorPageFilter) {
			application.getSources().add(ErrorPageFilterConfiguration.class);
		}
        // 运行spring应用
		return run(application);
	}

ok,that's all !

你可能感兴趣的:(spring)