当前位置: 首页>编程语言>正文

springboot selenium容器部署 springboot内置的servlet容器

Springboot默认使用的是嵌入式的Servlet容器(Tomcat);

springboot selenium容器部署 springboot内置的servlet容器,springboot selenium容器部署 springboot内置的servlet容器_spring boot,第1张

问题?

1),如何定制和修改servlet容器的相关配置?

以前使用外置的Tomcat,可以到conf目录下修改配置,进行优化。嵌入式的如何做到呢?

1.修改和server有关的配置(ServerProperties):

server.port=8082
server.servlet.context-path=/crud
server.tomcat.uri-encoding=utf-8
//通用的Servlet容器设置 
server.xxx
//Tomcat的设置
server.tomcat.xxx

2.编写一个EmbeddedServletContainerCustomizer:嵌入式的servlet容器的定制器,来修改servlet容器的配置(Spring Boot2.0以上版本EmbeddedServletContainerCustomizerWebServerFactoryCustomizer替代)

//配置嵌入式的servlet容器
 @Bean
 public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
     return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>(){
         //customize方法就是根据相关配置初始化Servlet容器
         @Override
         public void customize(ConfigurableWebServerFactory configurableWebServerFactory) {
         	//优先级高于配置文件
             configurableWebServerFactory.setPort(8083);
         }
     };
 }
 另一个方法 
//必须要加到容器中才能生效
//    @Bean
//    public ConfigurableServletWebServerFactory configurableServletWebServerFactory(){
//        //修改jetty相关配置,优先级低于配置文件
//        JettyServletWebServerFactory jettyServletWebServerFactory = new JettyServletWebServerFactory();
//        jettyServletWebServerFactory.setPort(8085);
//        return jettyServletWebServerFactory;
//    }

2),SpringBoot能不能支持其他servlet容器
springboot默认使用Tomcat,还支持使用Jetty(更适合长连接)Undertow(不支持JSP)

<!--引入web模块-->
 <dependency>
     <groupId>org.springframework.boot</groupId>
     <artifactId>spring-boot-starter-web</artifactId>
     <!--排除toncat-->
     <exclusions>
         <exclusion>
             <artifactId>spring-boot-starter-tomcat</artifactId>
             <groupId>org.springframework.boot</groupId>
         </exclusion>
     </exclusions>
 </dependency>
 <!--引入其他servlet容器—jetty-->
 <dependency>
     <artifactId>spring-boot-starter-jetty</artifactId>
     <groupId>org.springframework.boot</groupId>
 </dependency>

启动:上面的配置是所有servlet容器通用的

springboot selenium容器部署 springboot内置的servlet容器,springboot selenium容器部署 springboot内置的servlet容器_ide_02,第2张

注册三大组件

springboot是以jar包的方式启动嵌入式的tomcat,,而不是创建一个标准的web应用的目录结构。如果是一个web应用的目录结构,webapp/WEB-INF/web.xml三大组件将注册在web.xml中。然而springboot没有提供web.xml文件,怎么注册呢?
注册三大组件用一下方式:
ServletRegistrationBean:

//注册三大组件
@Bean
public ServletRegistrationBean myServlet(){
  return new ServletRegistrationBean(new MyServlet(),"/myServlet");
}
public class MyServlet extends HttpServlet {
  //处理get请求
  @Override
  protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      doPost(req, resp);
  }
  @Override
  protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      resp.getWriter().write("hello myservlet");
  }
}

FilterRegistrationBean:

@Bean
public FilterRegistrationBean myFilter(){
    FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean();
    filterRegistrationBean.setFilter(new MyFilter());
    filterRegistrationBean.setUrlPatterns(Arrays.asList("/hello","/myServlet"));
    return  filterRegistrationBean;
}
public class MyFilter implements Filter {
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println("MyFilter process...");
        filterChain.doFilter(servletRequest,servletResponse );
    }
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }
    @Override
    public void destroy() {
    }
}

ServletListenerRegistrationBean:

@Bean
public ServletListenerRegistrationBean myListener(){
    ServletListenerRegistrationBean<MyListener> myListenerServletListenerRegistrationBean = new ServletListenerRegistrationBean<>(new MyListener());
    return myListenerServletListenerRegistrationBean;
}
public class MyListener implements ServletContextListener {
    @Override
    public void contextInitialized(ServletContextEvent sce) {
        System.out.println("contextInitialized..web应用启动啦!!");
    }
    @Override
    public void contextDestroyed(ServletContextEvent sce) {
        System.out.println("contextDestroyed..服务器关闭啦!!");
    }
}

springboot selenium容器部署 springboot内置的servlet容器,springboot selenium容器部署 springboot内置的servlet容器_spring_03,第3张

点其他停止的相当于断电,不会打印"contextDestroyed…服务器关闭啦!!"的。

SpringBoot帮我们自动配置SpringMVC的时候,自动注册Springmvc的前端控制器(DispatcherServlet)
DispatcherServletAutoConfiguration

@Bean(name = DEFAULT_DISPATCHER_SERVLET_REGISTRATION_BEAN_NAME)
@ConditionalOnBean(value = DispatcherServlet.class, name = DEFAULT_DISPATCHER_SERVLET_BEAN_NAME)
public DispatcherServletRegistrationBean dispatcherServletRegistration(DispatcherServlet dispatcherServlet,
		WebMvcProperties webMvcProperties, ObjectProvider<MultipartConfigElement> multipartConfig) {
	DispatcherServletRegistrationBean registration = new DispatcherServletRegistrationBean(dispatcherServlet,
			webMvcProperties.getServlet().getPath());
	//默认拦截: / 所有请求;包静态资源,但是不拦截jsp请求; /*会拦截jsp
	//可以通过spring.mvc.servlet.path=来修改SpringMVC前端控制器默认拦截的请求路径
	registration.setName(DEFAULT_DISPATCHER_SERVLET_BEAN_NAME);
	registration.setLoadOnStartup(webMvcProperties.getServlet().getLoadOnStartup());
	multipartConfig.ifAvailable(registration::setMultipartConfig);
	return registration;
}

嵌入式Servlet容器自动配置原理

springboot2.x。
回顾修改servlet容器的相关配置:从下面的代码片,可以看到我们要从容器中获取一个组件ConfigurableWebServerFactory ,然后利用configurableWebServerFactory进行属性的设置,设置完属性,把WebServerFactoryCustomizer注入到容器中。

//配置嵌入式的servlet容器
 @Bean
 public WebServerFactoryCustomizer<ConfigurableWebServerFactory> webServerFactoryCustomizer(){
     return new WebServerFactoryCustomizer<ConfigurableWebServerFactory>(){
         //customize方法就是根据相关配置初始化Servlet容器
         @Override
         public void customize(ConfigurableWebServerFactory configurableWebServerFactory) {
         	//优先级高于配置文件
             configurableWebServerFactory.setPort(8083);
         }
     };
 }

1.猜想自动配置时,要先在容器中注册WebServerFactory,看一下WebServerFactory继承结构:

springboot selenium容器部署 springboot内置的servlet容器,springboot selenium容器部署 springboot内置的servlet容器_spring boot_04,第4张

ServletWebServerFactoryConfiguration:这是一个配置类,Spring容器启动时,添加到容器中,并且如果导入了tomcat,jetty或者Undertow依赖,就会把对应的XXXServletWebServerFactory 导入到容器中。

@Configuration(proxyBeanMethods = false)
class ServletWebServerFactoryConfiguration {
	@Configuration(proxyBeanMethods = false)
	//如果有这三个类就生效,即如果依赖了tomcat,这个就生效,下面的也是如此
	@ConditionalOnClass({ Servlet.class, Tomcat.class, UpgradeProtocol.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	static class EmbeddedTomcat {
		@Bean
		TomcatServletWebServerFactory tomcatServletWebServerFactory(
				ObjectProvider<TomcatConnectorCustomizer> connectorCustomizers,
				ObjectProvider<TomcatContextCustomizer> contextCustomizers,
				ObjectProvider<TomcatProtocolHandlerCustomizer<?>> protocolHandlerCustomizers) {
			TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
			//..省略
			return factory;
		}
	}
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Server.class, Loader.class, WebAppContext.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	static class EmbeddedJetty {
		@Bean
		JettyServletWebServerFactory JettyServletWebServerFactory(
				ObjectProvider<JettyServerCustomizer> serverCustomizers) {
			JettyServletWebServerFactory factory = new JettyServletWebServerFactory();
			factory.getServerCustomizers().addAll(serverCustomizers.orderedStream().collect(Collectors.toList()));
			return factory;
		}
	}
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Servlet.class, Undertow.class, SslClientAuthMode.class })
	@ConditionalOnMissingBean(value = ServletWebServerFactory.class, search = SearchStrategy.CURRENT)
	static class EmbeddedUndertow {
		@Bean
		UndertowServletWebServerFactory undertowServletWebServerFactory(
				ObjectProvider<UndertowDeploymentInfoCustomizer> deploymentInfoCustomizers,
				ObjectProvider<UndertowBuilderCustomizer> builderCustomizers) {
			UndertowServletWebServerFactory factory = new UndertowServletWebServerFactory();
			//....
			return factory;
		}
	}
}

2.继续猜想容器中有了WebServerFactory,那么WebServerFactoryCustomizer是在哪里被注入的呢

WebServerFactoryCustomizer的继承结构:

springboot selenium容器部署 springboot内置的servlet容器,springboot selenium容器部署 springboot内置的servlet容器_servlet容器_05,第5张

EmbeddedWebServerFactoryCustomizerAutoConfiguration:我们导入了那个servlet容器依赖,哪个xxxWebServerFactoryCustomizer 就别注入到容器中。

@Configuration(proxyBeanMethods = false)
@ConditionalOnWebApplication
@EnableConfigurationProperties(ServerProperties.class)
public class EmbeddedWebServerFactoryCustomizerAutoConfiguration {
	@Configuration(proxyBeanMethods = false)
	//当容器中存在Tomcat相关类就生效,下面几个也是如此,也就是说我们导入哪个依赖,哪个就生效
	@ConditionalOnClass({ Tomcat.class, UpgradeProtocol.class })
	public static class TomcatWebServerFactoryCustomizerConfiguration {
		@Bean
		public TomcatWebServerFactoryCustomizer tomcatWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new TomcatWebServerFactoryCustomizer(environment, serverProperties);
		}
	}
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Server.class, Loader.class, WebAppContext.class })
	public static class JettyWebServerFactoryCustomizerConfiguration {
		@Bean
		public JettyWebServerFactoryCustomizer jettyWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new JettyWebServerFactoryCustomizer(environment, serverProperties);
		}
	}
	@Configuration(proxyBeanMethods = false)
	@ConditionalOnClass({ Undertow.class, SslClientAuthMode.class })
	public static class UndertowWebServerFactoryCustomizerConfiguration {
		@Bean
		public UndertowWebServerFactoryCustomizer undertowWebServerFactoryCustomizer(Environment environment,
				ServerProperties serverProperties) {
			return new UndertowWebServerFactoryCustomizer(environment, serverProperties);
		}
	}
}

3.容器中WebServerFactory有了,WebServerFactoryCustomizer也有了,那么是什么时候初始化Servlet容器的呢(就是调用WebServerFactoryCustomizercustomize方法)
先看类ServletWebServerFactoryAutoConfiguration

@Configuration(proxyBeanMethods = false)
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@ConditionalOnClass(ServletRequest.class)
@ConditionalOnWebApplication(type = Type.SERVLET)
@EnableConfigurationProperties(ServerProperties.class)
@Import({ ServletWebServerFactoryAutoConfiguration.BeanPostProcessorsRegistrar.class,
		ServletWebServerFactoryConfiguration.EmbeddedTomcat.class,
		ServletWebServerFactoryConfiguration.EmbeddedJetty.class,
		ServletWebServerFactoryConfiguration.EmbeddedUndertow.class })
public class ServletWebServerFactoryAutoConfiguration {
	//...
	public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
		//...
		//这个类的作用是往容器中添加一些组件
		public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
				BeanDefinitionRegistry registry) {
//往容器中添加了WebServerFactoryCustomizerBeanPostProcessor组件(web服务工厂定制器的后置处理器)。
//这个类实现了BeanPostProcessor,属于bean的后置处理器。作用是在bean初始化前后加一些自己的逻辑处理
			registerSyntheticBeanIfMissing(registry, "webServerFactoryCustomizerBeanPostProcessor",
					WebServerFactoryCustomizerBeanPostProcessor.class);
			registerSyntheticBeanIfMissing(registry, "errorPageRegistrarBeanPostProcessor",
					ErrorPageRegistrarBeanPostProcessor.class);
		}

再看bean的后置处理器WebServerFactoryCustomizerBeanPostProcessor:简单点说就是在bean(xxxServletWebServerFactory )的初始化之前,获取所有的定制器来先定制servlet相关配置。

public class WebServerFactoryCustomizerBeanPostProcessor implements BeanPostProcessor, BeanFactoryAware {
    private List<WebServerFactoryCustomizer<?>> customizers;
    ...
    //bean初始化前调用
	@Override
	public Object postProcessBeforeInitialization(Object bean, String beanName)
			throws BeansException {
		//判断这个bean的类型是WebServerFactory
	    //TomcatServletWebServerFactory继承了WebServerFactory,所以它初始化时,会往下执行
		if (bean instanceof WebServerFactory) {
			postProcessBeforeInitialization((WebServerFactory) bean);
		}
		return bean;
	}
    //bean初始化后调用
	@Override
	public Object postProcessAfterInitialization(Object bean, String beanName)
			throws BeansException {
		return bean;
	}
	@SuppressWarnings("unchecked")
	private void postProcessBeforeInitialization(WebServerFactory webServerFactory) {
		LambdaSafe
		         //获取Web服务工厂定制器(WebServerFactoryCustomizer)
				.callbacks(WebServerFactoryCustomizer.class, getCustomizers(),
						webServerFactory)
				.withLogger(WebServerFactoryCustomizerBeanPostProcessor.class)
				//调用customizer的customize方法,定制嵌入式容器的servlet容器相关的规则,优先级高于配置文件
				.invoke((customizer) -> customizer.customize(webServerFactory));
	}
	private Collection<WebServerFactoryCustomizer<?>> getCustomizers() {
		if (this.customizers == null) {
			// Look up does not include the parent context
			this.customizers = new ArrayList<>(getWebServerFactoryCustomizerBeans());
			this.customizers.sort(AnnotationAwareOrderComparator.INSTANCE);
			this.customizers = Collections.unmodifiableList(this.customizers);
		}
		return this.customizers;
	}
	@SuppressWarnings({ "unchecked", "rawtypes" })
	private Collection<WebServerFactoryCustomizer<?>> getWebServerFactoryCustomizerBeans() {
	    //返回WebServerFactoryCustomizer类型的Customizer(定制器)
	    //上面自动配置类注册的Web服务工厂定制器(xxxWebServerFactoryCustomizer)就是继承了WebServerFactoryCustomizer,
	    //所以这里将那些Customizer(定制器)返回
		return (Collection) this.beanFactory
				.getBeansOfType(WebServerFactoryCustomizer.class, false, false).values();
	}
}

参考:

嵌入式Servlet容器启动原理

前面自动配置将配置在WebServerFactoryCustomizerBeanPostProcessor中定制在xxxWebServerFactoryCustomizer定制器中了。
那么
什么时候创建嵌入式的Servlet容器工厂?(之前自动配置注入到容器的只是工厂的bean定义)
什么时候获取嵌入式的Servlet容器并启动Tomcat的呢?

步骤:
1、Spring Boot启动运行run方法。执行到SpringApplicationrun(String… args)方法。

只截取相关代码:
public class SpringApplication {
public ConfigurableApplicationContext run(String... args) {
	   ...
	   
		ConfigurableApplicationContext context = null;
	    ...
 //调用createApplicationContext方法,返回的是
 //org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext类
 //注意这个类,比较重要,如果是web应用的话,springioc容器用的就是它
context = createApplicationContext();	
	   ...
	   			refreshContext(context);	
	    ...
		return context;
}
protected ConfigurableApplicationContext createApplicationContext() {
		Class<?> contextClass = this.applicationContextClass;
		if (contextClass == null) {
			try {
				switch (this.webApplicationType) {
				case SERVLET:
//DEFAULT_SERVLET_WEB_CONTEXT_CLASS=
//org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext
					contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
					break;
				case REACTIVE:
					contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
					break;
				default:
					contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
				}
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Unable create a default ApplicationContext, "
								+ "please specify an ApplicationContextClass",
						ex);
			}
		}
		return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
	}
}

如果是web应用context的类型就是AnnotationConfigServletWebServerApplicationContext

2、执行refreshContext(context);SpringBoot刷新IOC容器(创建IOC容器对象,并初始化容器,创建容器中的每一
个组件)。一路执行,到refresh(ApplicationContext applicationContext)方法。

protected void refresh(ApplicationContext applicationContext) {
Assert.isInstanceOf(AbstractApplicationContext.class, applicationContext);
//将applicationContext强转成AbstractApplicationContext类型,然后调用refresh()方法。
((AbstractApplicationContext) applicationContext).refresh();
}

AbstractApplicationContextrefresh()方法。执行到refresh,表明了如果是web应用,在创建springIOC容器的同时,也会创建内嵌的servlet容器。

@Override
	public void refresh() throws BeansException, IllegalStateException {
		synchronized (this.startupShutdownMonitor) {
			prepareRefresh();
			ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
			prepareBeanFactory(beanFactory);
			try {
				postProcessBeanFactory(beanFactory);
				invokeBeanFactoryPostProcessors(beanFactory);
				registerBeanPostProcessors(beanFactory);
				initMessageSource();
				initApplicationEventMulticaster();
				onRefresh();
				registerListeners();
				finishBeanFactoryInitialization(beanFactory);
				finishRefresh();
。。。。

之前自动配置的WebServerFactoryCustomizerBeanPostProcessor 后置处理器,已经在registerBeanPostProcessors方法中注册在容器中了。

3,主要是onRefresh()方法,webspringIOC容器AnnotationConfigServletWebServerApplicationContext重写了onRefresh方法。因为AbstractApplicationContext是由applicationContext强转的,而applicationContextAnnotationConfigServletWebServerApplicationContext类型的,所以调用的其实是AnnotationConfigServletWebServerApplicationContextonRefresh()方法,而AnnotationConfigServletWebServerApplicationContextonRefresh()方法是从它的父类继承过来的,也就是ServletWebServerApplicationContextonRefresh()方法。

spring原理的refresh方法调用的就是AbstractApplicationContext的refresh方法。
ServletWebServerApplicationContext也继承自AbstractApplicationContext

@Override
protected void onRefresh() {
	super.onRefresh();
	createWebServer();
	。。。
}

4.webioc容器会创建嵌入式的Servlet容器;createWebServer();

private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
		
5. 获取嵌入式的Servlet容器工厂ServletWebServerFactory
			ServletWebServerFactory factory = getWebServerFactory();
6.使用容器工厂获取嵌入式的Servlet容器,入参为一个匿名的ServletContextInitializer 
			this.webServer = factory.getWebServer(getSelfInitializer());
		}
		else if (servletContext != null) {
			try {
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context",
						ex);
			}
		}
		initPropertySources();
	}
	
protected ServletWebServerFactory getWebServerFactory() {
		//获取ServletWebServerFactory类型的bean名称
		String[] beanNames = getBeanFactory()
				.getBeanNamesForType(ServletWebServerFactory.class);
		。。。
		//根据获取到的bean定义创建xxxServletWebServerFactory对象,在对象的初始化之前会调用自动配置里的
		//后置处理器获取所有的定制器来先定制servlet相关配置
		return getBeanFactory().getBean(beanNames[0], ServletWebServerFactory.class);
	}
	
private org.springframework.boot.web.servlet.ServletContextInitializer getSelfInitializer() {
	return this::selfInitialize;
}
//注意this::selfInitialize,相当于如下写法
return new ServletContextInitializer(){
	@Override
	public void onStartUp(ServletContext servletContext){
		return selfInitialize(servletContext);
	}
}

private void selfInitialize(ServletContext servletContext) throws ServletException {
	prepareWebApplicationContext(servletContext);
	registerApplicationScope(servletContext);
	WebApplicationContextUtils.registerEnvironmentBeans(getBeanFactory(), servletContext);
	
	//getServletContextInitializerBeans方法从Spring IOC容器中获取所有的ServletContextInitializer
	for (ServletContextInitializer beans : getServletContextInitializerBeans()) {
		beans.onStartup(servletContext);
	}
}

问题:
1、什么是ServletContextInitializer
用于往ServletContext容器中注册Servlet,Filter或者EventListener。它的方法和WebApplicationInitializer一模一样,但是它是SpringBoot提供的。
如果只是用于注册Servlet,Filter或者EventListener,我直接是用WebApplicationInitializer接口就行了啊,为什么还需要这个接口。
因为接口WebApplicationInitializer有局限性,只有当SpringbootWar包方式启动时(即使用外置Tomcat),容器在启动时会去每个jar包下找META-INF/services/javax.servlet.ServletContainerInitializer文件,如果有则根据这个文件内容创建ServletContainerInitializer的实现类实例。SpringServletContainerInitializer 进行了实现,为SpringServletContainerInitializerSpringServletContainerInitializer感兴趣的类就是WebApplicationInitializerTomcat启动时会去调用ServletContainerInitializeronstartup方法,SpringServletContainerInitializeronstartup方法,就会调用WebApplicationInitializeronstartup方法。
所以当Springbootjar包方式启动时,WebApplicationInitializer就不会被使用到,但我又想往ServletContext容器中注册Servlet,Filter或者EventListener,这时就可以使用ServletContextInitializer来注入三大组件了。

7,嵌入式的Servlet容器创建对象并启动Servlet容器;
在上文的自动配置原理中我们知道了自动配置往容器中注入了TomcatServletWebServerFactory(以tomcat为例,其他Servlet容器相同),而这个类实际上继承了ServletWebServerFactory。所以在这里会被拿到,执行getWebServer方法。TomcatServletWebServerFactorygetWebServer方法:

//入参为一个匿名的ServletContextInitializer,上面已经传过来了
@Override
	public WebServer getWebServer(ServletContextInitializer... initializers) {
        //创建了tomcat容器对象
		Tomcat tomcat = new Tomcat();
		//配置tomcat相关配置
		File baseDir = (this.baseDirectory != null) ? this.baseDirectory
				: createTempDir("tomcat");
		tomcat.setBaseDir(baseDir.getAbsolutePath());
		Connector connector = new Connector(this.protocol);
		tomcat.getService().addConnector(connector);
		customizeConnector(connector);
		tomcat.setConnector(connector);
		tomcat.getHost().setAutoDeploy(false);
		configureEngine(tomcat.getEngine());
		for (Connector additionalConnector : this.additionalTomcatConnectors) {
			tomcat.getService().addConnector(additionalConnector);
		}
		//重点1:
		prepareContext(tomcat.getHost(), initializers);
		//重点2:
		return getTomcatWebServer(tomcat);
	}
//先看重点2---------------------------------------------------------
TomcatWebServer 类
protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
		return new TomcatWebServer(tomcat, getPort() >= 0);
	}
public class TomcatWebServer implements WebServer {
   ...
   public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
		Assert.notNull(tomcat, "Tomcat Server must not be null");
		this.tomcat = tomcat;
		this.autoStart = autoStart;
		initialize();
	}

	private void initialize() throws WebServerException {
		logger.info("Tomcat initialized with port(s): " + getPortsDescription(false));
		synchronized (this.monitor) {
			try {
				addInstanceIdToEngineName();

				Context context = findContext();
				context.addLifecycleListener((event) -> {
					if (context.equals(event.getSource())
							&& Lifecycle.START_EVENT.equals(event.getType())) {
						// Remove service connectors so that protocol binding doesn't
						// happen when the service is started.
						removeServiceConnectors();
					}
				});

				// Start the server to trigger initialization listeners
				//启动tomcat
				this.tomcat.start();

				// We can re-throw failure exception directly in the main thread
				rethrowDeferredStartupExceptions();

				try {
					ContextBindings.bindClassLoader(context, context.getNamingToken(),
							getClass().getClassLoader());
				}
				catch (NamingException ex) {
					// Naming is not enabled. Continue
				}

				// Unlike Jetty, all Tomcat threads are daemon threads. We create a
				// blocking non-daemon to stop immediate shutdown
				startDaemonAwaitThread();
			}
			catch (Exception ex) {
				stopSilently();
				throw new WebServerException("Unable to start embedded Tomcat", ex);
			}
		}
	}
}
重点1:----------------------------------------------------------------------------------------------------------------
 protected void prepareContext(Host host, ServletContextInitializer[] initializers) {
        File documentRoot = this.getValidDocumentRoot();
        TomcatEmbeddedContext context = new TomcatEmbeddedContext();
        .....
        // (1)把ServletContextInitializer的实现都合并起来
        ServletContextInitializer[] initializersToUse = this.mergeInitializers(initializers);
        // 这个方法最重要的是把TomcatStarter添加到了tomcat容器ContextServletContainerInitializer集合中
      	// 容器创建完成后会遍历调用ContextServletContainerInitializer的onStartUp方法。
        this.configureContext(context, initializersToUse);
        this.postProcessContext(context);
}
 protected void configureContext(Context context, ServletContextInitializer[] initializers) {
	  	// (2)直接new 一个TomcatStarter 
	    TomcatStarter starter = new TomcatStarter(initializers);
	    //把TomcatStarter添加到了tomcat容器ContextServletContainerInitializer集合中
	    context.addServletContainerInitializer(starter, NO_CLASSES);
	    .........
}

TomcatStarter是ServletContainerInitializer的实现类:
class TomcatStarter implements ServletContainerInitializer {

   // ServletContextInitializer是Spring中维护的接口
    private final ServletContextInitializer[] initializers;

    TomcatStarter(ServletContextInitializer[] initializers) {
        this.initializers = initializers;
    }
// tomcat容器启动最后会调用这个方法,这是servlet3.0时添加的
public void onStartup(Set<Class<?>> classes, ServletContext servletContext) throws ServletException {

        ServletContextInitializer[] var3 = this.initializers;
        int var4 = var3.length;
	// 遍历执行ServletContextInitializer#onStartup
        for(int var5 = 0; var5 < var4; ++var5) {
            ServletContextInitializer initializer = var3[var5];
            initializer.onStartup(servletContext);
        }
}

问题:

1、ServletContainerInitializer是什么时候被执行的?

Tomcat启动会调用LifecycleBase类的start方法,start方法又会调用StandardContextstartInternal方法:

会遍历所有的ServletContainerInitializer并执行它的onStartup方法

springboot selenium容器部署 springboot内置的servlet容器,springboot selenium容器部署 springboot内置的servlet容器_spring_06,第6张

外置Servlet容器

使用外置Servlet容器

嵌入式的Servlet的容器用起来简单、便携。

但是也有缺点:默认不支持jsp,优化定制比较复杂。所以在适当场景下,我们还是需要外部的servlet容器。

1、创建war类型的maven项目。创建好web项目的目录结构(必须有web.xml文件)。

springboot selenium容器部署 springboot内置的servlet容器,springboot selenium容器部署 springboot内置的servlet容器_ide_07,第7张

2、将嵌入式的servlet容器依赖的scope指定为provided。

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-tomcat</artifactId>
   <scope>provided</scope>
</dependency>
<packaging>war</packaging>

3、编写一个SpringBootServletInitializer类型的子类,并重写configure方法。

public class ServletInitializer extends SpringBootServletInitializer {
	@Override
	protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
		//传入springboot应用的主程序
		return application.sources(SpringBootWebDemo1Application.class);
	}
}

4、启动容器。

外置Servlet容器启动原理

原理对比;
jar包: 执行SpringBoot主类的main方法,调用SpringApplicationrun方法,启动Springioc容器,创建嵌入式的Servlet容器。
war包:启动服务器(创建嵌入式的Servlet容器),服务器启动SpringBootSpringBootServletInitializer,启动Springioc容器。

Servlet3.0+定义了几个web应用在启动时的规则:
(1)容器在启动时会去每个jar包下找META-INF/services/javax.servlet.ServletContainerInitializer文件,如果有则根据这个文件内容创建ServletContainerInitializer的实现类实例。
(2)可以使用@HandlesTypes注解加载需要的类。

启动原理:

SpringServletContainerInitializer ->spring SCI机制
其实就是servlet3.0的ServletContainerInitializer  SCI机制的扩展延伸

1、启动servlet容器。
2、容器根据Servlet的规则创建SpringServletContainerInitializer。该类在在spring-web-xxx.jar下,这个jar包的META-INF/services/javax.servlet.ServletContainerInitializer文件内容就是org.springframework.web.SpringServletContainerInitializer的全类名。

@HandlesTypes(WebApplicationInitializer.class)
public class SpringServletContainerInitializer implements ServletContainerInitializer {
    @Override
 //@HandlesTypes标注的所有这个类型的类都传入到onStartup方法的Set中
	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());
					}
					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);
		for (WebApplicationInitializer initializer : initializers) {
//每个WebApplicationInitializer调用自己的onStartup方法。
			initializer.onStartup(servletContext);
		}
	}
}

3、SpringBootServletInitializer实现了WebApplicationInitializer接口。所以SpringBootServletInitializer的实现类(上文的ServletInitializer )会被创建对象,并执行onStartup方法。
4、SpringBootServletInitializer实例执行onStartup方法时调用了createRootApplicationContext方法。

protected WebApplicationContext createRootApplicationContext(ServletContext servletContext) {
        SpringApplicationBuilder builder = this.createSpringApplicationBuilder();
        builder.main(this.getClass());
        ApplicationContext parent = this.getExistingRootWebApplicationContext(servletContext);
        if (parent != null) {
            this.logger.info("Root context already created (using as parent).");
            servletContext.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, (Object)null);
            builder.initializers(new ApplicationContextInitializer[]{new ParentContextApplicationContextInitializer(parent)});
        }

        builder.initializers(new ApplicationContextInitializer[]{new ServletContextApplicationContextInitializer(servletContext)});
        builder.contextClass(AnnotationConfigServletWebServerApplicationContext.class);
//调用configure方法,子类(上文的ServletInitializer )重写了这个方法
//并且将SpringBoot的主程序类传入了进来。
        builder = this.configure(builder);
        builder.listeners(new ApplicationListener[]{new SpringBootServletInitializer.WebEnvironmentPropertySourceInitializer(servletContext)});
//使用builder创建一个Spring应用
        SpringApplication application = builder.build();
        if (application.getAllSources().isEmpty() && MergedAnnotations.from(this.getClass(), SearchStrategy.TYPE_HIERARCHY).isPresent(Configuration.class)) {
            application.addPrimarySources(Collections.singleton(this.getClass()));
        }

        Assert.state(!application.getAllSources().isEmpty(), "No SpringApplication sources have been defined. Either override the configure method or add an @Configuration annotation");
        if (this.registerErrorPageFilter) {
            application.addPrimarySources(Collections.singleton(ErrorPageFilterConfiguration.class));
        }
//启动Spring应用,进入就会去到嵌入式Servlet容器启动原理的步骤1
        return this.run(application);
    }

5、Spring应用启动后就创建ioc容器。执行到这一步就和内置Servlet容器启动原理相同了。

遗留问题

如果使用的是外置servlet容器,同时在配置文件做了相关配置,然后又在springboot应用里定制了servlet容器配置,那么谁生效?
自测是外置的生效,那么,为啥自动配置会没生效呢?
是因为内置的Servlet容器根本没启动。
在之前将内置Servlet容器启动原理时,ServletWebServerApplicationContext:使用外置tomcat启动时,一启动servletContext !=nullif (webServer == null && servletContext == null)条件不满足,因此,内置的servlet容器并没有启动。

private void createWebServer() {
		WebServer webServer = this.webServer;
		ServletContext servletContext = getServletContext();
		if (webServer == null && servletContext == null) {
		
5. 获取嵌入式的Servlet容器工厂ServletWebServerFactory
			ServletWebServerFactory factory = getWebServerFactory();
6.使用容器工厂获取嵌入式的Servlet容器
			this.webServer = factory.getWebServer(getSelfInitializer());
		}
		else if (servletContext != null) {
			try {
				getSelfInitializer().onStartup(servletContext);
			}
			catch (ServletException ex) {
				throw new ApplicationContextException("Cannot initialize servlet context",
						ex);
			}
		}
		initPropertySources();
	}

https://www.xamrdz.com/lan/5t31920584.html

相关文章: