spring-boot 外部容器启动流程及原理
本文于
363
天之前发表,文中内容可能已经过时。
外部容器启动流程及原理 author: Tank
前言 springboot 嵌入式容器启动是以jar包形式,那如果采用外部容器启动就需要使用war包。
外部容器启动准备工作 1、准备一个外部容器如 (tomcat)在idea里配置好即可,这里不做多解释 2、需要新增一个servlet初始化时指定主类的一个类 (ServletInitializer 名字随意,这个是模版生成的) 1 2 3 4 5 6 public class ServletInitializer extends SpringBootServletInitializer { @Override protected SpringApplicationBuilder configure (SpringApplicationBuilder application) { return application.sources(SpringBoot04WebJspApplication.class ) ; } }
3、指定打包方式为war tomcat依赖的scope需要改成provided 1 2 3 4 5 6 7 8 9 <packaging > war</packaging > <dependency > <groupId > org.springframework.boot</groupId > <artifactId > spring-boot-starter-tomcat</artifactId > <scope > provided</scope > </dependency >
4、查看servlet3.0规范 Annotations and pluggability -> Shared libraries / runtimes pluggability In addition to supporting fragments and use of annotations one of the requirements is that not only we be able to plug-in things that are bundled in the WEB-INF/lib but also plugin shared copies of frameworks - including being able to plug-in to the web container things like JAX-WS, JAX-RS and JSF that build on top of the web container. The ServletContainerInitializer allows handling such a use case as described below.The ServletContainerInitializer class is looked up via the jar services API. For each application, an instance of the ServletContainerInitializer is created by the container at application startup time. The framework providing an implementation of the ServletContainerInitializer MUST bundle in the META-INF/services directory of the jar file a file called javax.servlet.ServletContainerInitializer , as per the jar services API, that points to the implementation class of the ServletContainerInitializer. In addition to the ServletContainerInitializer we also have an annotation - HandlesTypes. The HandlesTypes annotation on the implementation of the ServletContainerInitializer is used to express interest in classes that may have annotations (type, method or field level annotations) specified in the value of the HandlesTypes or if it extends / implements one those classes anywhere in the class’ super types. The HandlesTypes annotation is applied irrespective of the setting of metadata-complete.
请注意加粗处的说明:大白话的意思就是,启动外部tomcat时就会创建一个ServletContainerInitializer 的实例,这个实例呢必须在META-INF/services下名为javax.servlet.servletcontainerinitializer的文件。他会检查所有jar包。
启动流程原理 javax.servlet.servletcontainerinitializer文件内容如下:
1 org.springframework.web.SpringServletContainerInitializer
从servlet3.0规则来看。外部tomcat启动后会扫描这个文件,并创建SpringServletContainerInitializer实例
SpringServletContainerInitializer 【此类为ServletContainerInitializer子类】 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 @HandlesTypes (WebApplicationInitializer.class ) public class SpringServletContainerInitializer implements ServletContainerInitializer { @Override public void onStartup (@Nullable Set<Class<?>> webAppInitializerClasses, ServletContext servletContext) throws ServletException { List<WebApplicationInitializer> initializers = new LinkedList<>(); if (webAppInitializerClasses != null ) { for (Class<?> waiClass : webAppInitializerClasses) { if (!waiClass.isInterface() && !Modifier.isAbstract(waiClass.getModifiers()) && WebApplicationInitializer.class .isAssignableFrom (waiClass )) { try { initializers.add((WebApplicationInitializer) ReflectionUtils.accessibleConstructor(waiClass).newInstance()); } } } } for (WebApplicationInitializer initializer : initializers) { initializer.onStartup(servletContext); } } }
WebApplicationInitializer 【SpringServletContainerInitializer能处理的类型】 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public interface WebApplicationInitializer { void onStartup (ServletContext servletContext) throws ServletException ; }
SpringBootServletInitializer 【WebApplicationInitializer 的抽象实现】 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 @Override public void onStartup (ServletContext servletContext) throws ServletException { WebApplicationContext rootAppContext = createRootApplicationContext( servletContext); if (rootAppContext != null ) { servletContext.addListener(new ContextLoaderListener(rootAppContext) { @Override public void contextInitialized (ServletContextEvent event) { } }); } } protected WebApplicationContext createRootApplicationContext ( ServletContext servletContext) { SpringApplicationBuilder builder = createSpringApplicationBuilder(); 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(AnnotationConfigServletWebServerApplicationContext.class ) ; builder = configure(builder); builder.listeners(new WebEnvironmentPropertySourceInitializer(servletContext)); SpringApplication application = builder.build(); if (application.getAllSources().isEmpty() && AnnotationUtils .findAnnotation(getClass(), Configuration.class ) ! = null ) { application.addPrimarySources(Collections.singleton(getClass())); } if (this .registerErrorPageFilter) { application.addPrimarySources( Collections.singleton(ErrorPageFilterConfiguration.class )) ; } return run(application); } protected WebApplicationContext run (SpringApplication application) { return (WebApplicationContext) application.run(); }
SpringApplication spring应用的启动入口 无论是外部容器启动还是嵌入式容器启动都必须从此处开始 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 public ConfigurableApplicationContext run (String... args) { StopWatch stopWatch = new StopWatch(); stopWatch.start(); ConfigurableApplicationContext context = null ; Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); configureHeadlessProperty(); SpringApplicationRunListeners listeners = getRunListeners(args); listeners.starting(); try { ApplicationArguments applicationArguments = new DefaultApplicationArguments( args); ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments); configureIgnoreBeanInfo(environment); Banner printedBanner = printBanner(environment); context = createApplicationContext(); exceptionReporters = getSpringFactoriesInstances( SpringBootExceptionReporter.class , new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments, printedBanner); refreshContext(context); afterRefresh(context, applicationArguments); stopWatch.stop(); if (this .logStartupInfo) { new StartupInfoLogger(this .mainApplicationClass) .logStarted(getApplicationLog(), stopWatch); } listeners.started(context); callRunners(context, applicationArguments); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, listeners); throw new IllegalStateException(ex); } try { listeners.running(context); } catch (Throwable ex) { handleRunFailure(context, ex, exceptionReporters, null ); throw new IllegalStateException(ex); } return context; }