NOTE
Embedded Tomcat Auto-Configuration Principle
1. Enable ServletContainer auto-configuration. 2. Inject the Tomcat container factory. 3. How custom configuration takes effect. 4. How to modify Tomcat configuration. 5. Summary.
This is a historical learning note and may contain outdated or incomplete understanding.
1. Enable ServletContainer Auto-Configuration
@EmbeddedServletContainerAutoConfiguration
EmbeddedServletContainerAutoConfiguration is the auto-configuration class through which Spring Boot automatically configures an embedded servlet container.
@AutoConfigureOrder(Ordered.HIGHEST_PRECEDENCE)
@Configuration
@ConditionalOnWebApplication
@Import(BeanPostProcessorsRegistrar.class)// Imports BeanPostProcessorsRegistrar. This class is key to how custom Tomcat configuration takes effect
public class EmbeddedServletContainerAutoConfiguration {
@Configuration
@ConditionalOnClass({ Servlet.class, Tomcat.class })// Takes effect when Tomcat.class exists -- use Tomcat
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class,
search = SearchStrategy.CURRENT)// Takes effect when the user has not customized EmbeddedServletContainerFactory
public static class EmbeddedTomcat {
// First inject a Tomcat factory into the Spring container
@Bean
public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory();
}
}
@Configuration
@ConditionalOnClass({ Servlet.class, Server.class, Loader.class,
WebAppContext.class })// Takes effect when Loader.class exists -- use Jetty
@ConditionalOnMissingBean(value = EmbeddedServletContainerFactory.class,
search = SearchStrategy.CURRENT)
public static class EmbeddedJetty {
@Bean
public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
return new JettyEmbeddedServletContainerFactory();
}
}
}
It determines which servlet container to create based on whether Tomcat or Jetty exists on the classpath.
We use Tomcat’s TomcatEmbeddedServletContainerFactory as an example.
2. Inject the Tomcat Container Factory
2.1. What the Tomcat Container Factory Looks Like
- Create Tomcat
public class TomcatEmbeddedServletContainerFactory
extends AbstractEmbeddedServletContainerFactory implements ResourceLoaderAware {
@Override
public EmbeddedServletContainer getEmbeddedServletContainer(
ServletContextInitializer... initializers) {
// Create Tomcat
Tomcat tomcat = new Tomcat();
// The following configures Tomcat's basic properties
// Base directory: if not configured, create a temporary directory
File baseDir = (this.baseDirectory != null) ? this.baseDirectory
: createTempDir("tomcat");
tomcat.setBaseDir(baseDir.getAbsolutePath());
// HTTP connector
Connector connector = new Connector(this.protocol);
tomcat.getService().addConnector(connector);
// Key method for custom configuration
customizeConnector(connector);
tomcat.setConnector(connector);
tomcat.getHost().setAutoDeploy(false);
configureEngine(tomcat.getEngine());
for (Connector additionalConnector : this.additionalTomcatConnectors) {
tomcat.getService().addConnector(additionalConnector);
}
prepareContext(tomcat.getHost(), initializers);
//new TomcatEmbeddedServletContainer(tomcat, getPort() >= 0);
return getTomcatEmbeddedServletContainer(tomcat);
}
protected void customizeConnector(Connector connector) {
int port = (getPort() >= 0) ? getPort() : 0;
connector.setPort(port);
if (StringUtils.hasText(this.getServerHeader())) {
connector.setAttribute("server", this.getServerHeader());
}
if (connector.getProtocolHandler() instanceof AbstractProtocol) {
customizeProtocol((AbstractProtocol<?>) connector.getProtocolHandler());
}
if (getUriEncoding() != null) {
connector.setURIEncoding(getUriEncoding().name());
}
// If ApplicationContext is slow to start we want Tomcat not to bind to the socket
// prematurely...
connector.setProperty("bindOnInit", "false");
if (getSsl() != null && getSsl().isEnabled()) {
customizeSsl(connector);
}
if (getCompression() != null && getCompression().getEnabled()) {
customizeCompression(connector);
}
// Get all TomcatConnectorCustomizers and call customize to configure them
// ServerProperties is one of them
for (TomcatConnectorCustomizer customizer : this.tomcatConnectorCustomizers) {
customizer.customize(connector);
}
}
}
The role of TomcatEmbeddedServletContainerFactory is to create the Tomcat container. Its key method is getEmbeddedServletContainer. So when is this method called?
2.2. When Is Tomcat Created?
2.2.1. Refresh the Container in a Web Environment
We know that one step during Spring context refresh is onRefresh. It is a template method implemented by subclasses.
In a web environment, EmbeddedWebApplicationContext.onRefresh is called.
EmbeddedWebApplicationContext.onRefresh
protected void onRefresh() {
super.onRefresh();
try {
// Create the servlet container
createEmbeddedServletContainer();
}
catch (Throwable ex) {
throw new ApplicationContextException("Unable to start embedded container",
ex);
}
}
private void createEmbeddedServletContainer() {
// The first time this is entered, both are null
EmbeddedServletContainer localContainer = this.embeddedServletContainer;
ServletContext localServletContext = getServletContext();
if (localContainer == null && localServletContext == null) {
// Get the servlet-container factory; this calls @Bean TomcatEmbeddedServletContainerFactory
EmbeddedServletContainerFactory containerFactory = getEmbeddedServletContainerFactory();
// Create the servlet container through the factory
this.embeddedServletContainer = containerFactory
// Call TomcatEmbeddedServletContainerFactory#getEmbeddedServletContainer
.getEmbeddedServletContainer(getSelfInitializer());
}
else if (localServletContext != null) {
try {
getSelfInitializer().onStartup(localServletContext);
}
catch (ServletException ex) {
throw new ApplicationContextException("Cannot initialize servlet context",
ex);
}
}
initPropertySources();
}
2.3. Tomcat Creation Process
TomcatEmbeddedServletContainer
public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer {
public TomcatEmbeddedServletContainer(Tomcat tomcat, boolean autoStart) {
Assert.notNull(tomcat, "Tomcat Server must not be null");
this.tomcat = tomcat;
this.autoStart = autoStart;
// Initialize Tomcat
initialize();
}
private void initialize() throws EmbeddedServletContainerException {
TomcatEmbeddedServletContainer.logger
.info("Tomcat initialized with port(s): " + getPortsDescription(false));
synchronized (this.monitor) {
try {
addInstanceIdToEngineName();
try {
final Context context = findContext();
context.addLifecycleListener(new LifecycleListener() {
@Override
public void lifecycleEvent(LifecycleEvent 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 Tomcat server
// Start the server to trigger initialization listeners
this.tomcat.start();
// We can re-throw failure exception directly in the main thread
rethrowDeferredStartupExceptions();
try {
ContextBindings.bindClassLoader(context, getNamingToken(context),
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) {
containerCounter.decrementAndGet();
throw ex;
}
}
catch (Exception ex) {
stopSilently();
throw new EmbeddedServletContainerException(
"Unable to start embedded Tomcat", ex);
}
}
}
}
Besides injecting the servlet container, it also imports a BeanPostProcessorsRegistrar class, which is key to custom configuration.
3. Principle Behind Custom Configuration Taking Effect
Besides creating and starting the Tomcat container, EmbeddedServletContainerAutoConfiguration also imports a key class, BeanPostProcessorsRegistrar.
3.1. Inject BeanPostProcessorsRegistrar for Custom Tomcat Configuration
public static class BeanPostProcessorsRegistrar
implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata,
BeanDefinitionRegistry registry) {
if (this.beanFactory == null) {
return;
}
registerSyntheticBeanIfMissing(registry,
"embeddedServletContainerCustomizerBeanPostProcessor",
// Import this BeanPostProcessor
// After bean instantiation and property assignment, when custom initialization methods are called,
// postProcessBeforeInitialization and postProcessAfterInitialization are invoked
EmbeddedServletContainerCustomizerBeanPostProcessor.class);
registerSyntheticBeanIfMissing(registry,
"errorPageRegistrarBeanPostProcessor",
ErrorPageRegistrarBeanPostProcessor.class);
}
}
3.1.1. After the Tomcat Bean Is Instantiated, Call postProcessBeforeInitialization for Custom Configuration
EmbeddedServletContainerCustomizerBeanPostProcessor
public class EmbeddedServletContainerCustomizerBeanPostProcessor
implements BeanPostProcessor, BeanFactoryAware {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName)
throws BeansException {
if (bean instanceof ConfigurableEmbeddedServletContainer) {
// Pass in ConfigurableEmbeddedServletContainer
postProcessBeforeInitialization((ConfigurableEmbeddedServletContainer) bean);
}
return bean;
}
private void postProcessBeforeInitialization(
ConfigurableEmbeddedServletContainer bean) {
// Get all customizers and call customize
for (EmbeddedServletContainerCustomizer customizer : getCustomizers()) {
customizer.customize(bean);
}
}
private Collection<EmbeddedServletContainerCustomizer> getCustomizers() {
if (this.customizers == null) {
// Get EmbeddedServletContainerCustomizer classes from the bean factory by type, and sort them
// This is the key to our custom Tomcat container configuration
this.customizers = new ArrayList<EmbeddedServletContainerCustomizer>(
this.beanFactory
.getBeansOfType(EmbeddedServletContainerCustomizer.class,
false, false)
.values());
Collections.sort(this.customizers, AnnotationAwareOrderComparator.INSTANCE);
this.customizers = Collections.unmodifiableList(this.customizers);
}
return this.customizers;
}
// Not very useful; simply return the bean
@Override
public Object postProcessAfterInitialization(Object bean, String beanName)
throws BeansException {
return bean;
}
}
4. How to Modify Tomcat Configuration
- Configuration file
server:
port: 8000
- Configuration class
@Bean
public EmbeddedServletContainerCustomizer embeddedServletContainerCustomizer()
{
return new EmbeddedServletContainerCustomizer()
{
@Override
public void customize(ConfigurableEmbeddedServletContainer configurableEmbeddedServletContainer)
{
configurableEmbeddedServletContainer.setPort(8000);
}
};
}
4.1. Principle Behind the Configuration Taking Effect After Modification
- Configuration-file approach
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties
// ServerProperties corresponding to the configuration file is essentially an EmbeddedServletContainerCustomizer
implements EmbeddedServletContainerCustomizer, EnvironmentAware, Ordered
- Configuration-class approach
The principle is the same.
5. Summary
- During Spring startup, one step of refreshing the context is invokeBeanFactoryPostProcessors, which calls the
postProcessBeanDefinitionRegistrymethods of allBeanFactoryPostProcessorinstances. - One of them is
ConfigurationClassPostProcessor. ItspostProcessBeanDefinitionRegistrymethod parses the@Importannotations of all@Configurationclasses, calls theselectImportmethods ofImportSelectorinstances, and calls theregisterBeanDefinitionsmethods ofImportBeanDefinitionRegistrarinstances. BeanPostProcessorsRegistrar, imported by@Configuration EmbeddedServletContainerAutoConfiguration, is one of them. It registers theEmbeddedServletContainerCustomizerBeanPostProcessorBeanPostProcessor in the container.- The
EmbeddedServletContainerAutoConfigurationclass used by Spring Boot to auto-configure Tomcat will: - One step during Spring context refresh is
onRefresh, which calls theonRefreshmethod of the subclassEmbeddedWebApplicationContext. - This
onRefreshmethod calls@Bean TomcatEmbeddedServletContainerFactoryandEmbeddedServletContainerFactory.getEmbeddedServletContainer. getEmbeddedServletContainercreates the Tomcat container, configures it (thecustomizemethods of allTomcatConnectorCustomizerinstances, includingServerProperties), and starts Tomcat.- One step during Spring context refresh is
finishBeanFactoryInitialization. When the Tomcat container is instantiated, its properties are assigned, and custom methods are called, thepostProcessBeforeInitializationmethod ofEmbeddedServletContainerCustomizerBeanPostProcessoris called. It calls thecustomizemethods of allEmbeddedServletContainerCustomizerinstances to customize Tomcat.
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub