Spring Boot内嵌的Tomcat的定制和优化

SpringBoot默认集成了tomcat、jetty、undertow 三种容器,本文讲解内置Tomcat的定制和优化

修改配置文件application.properties

org.springframework.boot.autoconfigure.web.ServerProperties类中有Server的相关配置,源码如下:

@ConfigurationProperties(
    prefix = "server",
    ignoreUnknownFields = true
)
public class ServerProperties {
    private Integer port;
    private InetAddress address;
    @NestedConfigurationProperty
    private final ErrorProperties error = new ErrorProperties();
    private Boolean useForwardHeaders;
    private String serverHeader;
    private DataSize maxHttpHeaderSize = DataSize.ofKilobytes(8L);
    private Duration connectionTimeout;
    @NestedConfigurationProperty
    private Ssl ssl;
    @NestedConfigurationProperty
    private final Compression compression = new Compression();
    @NestedConfigurationProperty
    private final Http2 http2 = new Http2();
    private final ServerProperties.Servlet servlet = new ServerProperties.Servlet();
    //tomcat
    private final ServerProperties.Tomcat tomcat = new ServerProperties.Tomcat();
    //jetty
    private final ServerProperties.Jetty jetty = new ServerProperties.Jetty();
    //undertow 
    private final ServerProperties.Undertow undertow = new ServerProperties.Undertow();

    public ServerProperties() {
    }
}

其中port和address是三个容器的公共配置
server.port = 8001是绑定端口号
server.address = 192.168.0.1是绑定IP地址
Tomcat的日志配置在ServerProperties.Tomcat.Accesslog内部类中,
Tomcat的配置在ServerProperties.Tomcat内部类中,具体源码如下:

 //Tomcat的配置
 public static class Tomcat {
        private final ServerProperties.Tomcat.Accesslog accesslog = new ServerProperties.Tomcat.Accesslog();
        private String internalProxies = "10\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|192\\.168\\.\\d{1,3}\\.\\d{1,3}|169\\.254\\.\\d{1,3}\\.\\d{1,3}|127\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}|172\\.1[6-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.2[0-9]{1}\\.\\d{1,3}\\.\\d{1,3}|172\\.3[0-1]{1}\\.\\d{1,3}\\.\\d{1,3}|0:0:0:0:0:0:0:1|::1";
        private String protocolHeader;
        private String protocolHeaderHttpsValue = "https";
        private String portHeader = "X-Forwarded-Port";
        private String remoteIpHeader;
        private File basedir;
        @DurationUnit(ChronoUnit.SECONDS)
        private Duration backgroundProcessorDelay = Duration.ofSeconds(10L);
        private int maxThreads = 200;
        private int minSpareThreads = 10;
        private DataSize maxHttpPostSize = DataSize.ofMegabytes(2L);
        private DataSize maxHttpHeaderSize = DataSize.ofBytes(0L);
        private DataSize maxSwallowSize = DataSize.ofMegabytes(2L);
        private Boolean redirectContextRoot = true;
        private Boolean useRelativeRedirects;
        private Charset uriEncoding;
        private int maxConnections;
        private int acceptCount;
        private List<String> additionalTldSkipPatterns;
        private final ServerProperties.Tomcat.Resource resource;

        public Tomcat() {
            this.uriEncoding = StandardCharsets.UTF_8;
            this.maxConnections = 10000;
            this.acceptCount = 100;
            this.additionalTldSkipPatterns = new ArrayList();
            this.resource = new ServerProperties.Tomcat.Resource();
        }
 }
 //tomcat的访问日志配置
 public static class Accesslog {
            private boolean enabled = false;
            private String pattern = "common";
            private String directory = "logs";
            protected String prefix = "access_log";
            private String suffix = ".log";
            private boolean rotate = true;
            private boolean renameOnRotate = false;
            private String fileDateFormat = ".yyyy-MM-dd";
            private boolean requestAttributesEnabled = false;
            private boolean buffered = true;

            public Accesslog() {
            }
   }

参考配置如下:
详细参数配置可以参考 SpringBoot项目详细配置文件修

# ===============================
# Tomcat Access Log
# ===============================
# Buffer output such that it is only flushed periodically.
server.tomcat.accesslog.buffered=true
# ${HOME}/gome/log/job/
server.tomcat.accesslog.directory=d:/log/
# Enable access log.
server.tomcat.accesslog.enabled=true
# Date format to place in log file name.
server.tomcat.accesslog.file-date-format=.yyyy-MM-dd
# Format pattern for access logs.
server.tomcat.accesslog.pattern=common
# Log file name prefix.
server.tomcat.accesslog.prefix=job_access_log
# Defer inclusion of the date stamp in the file name until rotate time.
server.tomcat.accesslog.rename-on-rotate=false
# Set request attributes for IP address, Hostname, protocol and port used for the request.
server.tomcat.accesslog.request-attributes-enabled=false
# Enable access log rotation.
server.tomcat.accesslog.rotate=true
# Log file name suffix.
server.tomcat.accesslog.suffix=.log

实现EmbeddedServletContainerCustomizer接口

在SpringBoot 1.5.10.RELEASE 版本之前,使用该方式进行Tomcat的定制,具体实现方式就是实现EmbeddedServletContainerCustomizer接口,并且把自定义实现类放入到SpringIOC容器中。

package com.feiyue.config;

import org.apache.catalina.connector.Connector;
import org.apache.catalina.valves.AccessLogValve;
import org.apache.coyote.http11.Http11NioProtocol;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.stereotype.Component;

import java.io.File;

@Component
public class MyEmbeddedServletContainerCustomizer implements EmbeddedServletContainerCustomizer {

    @Override
    public void customize(ConfigurableEmbeddedServletContainer servletContainer) {
        TomcatEmbeddedServletContainerFactory factory = (TomcatEmbeddedServletContainerFactory)servletContainer;
        //启动端口号
        factory.setPort(8001);
        //日志目录
        factory.setBaseDirectory(new File("d:/tomcat"));
        //设置日志
        factory.addContextValves(getAccessLogValve());
        //初始化
        factory.addInitializers((servletContext) -> {
            System.out.println("========= servletContext startup ============");
            //添加 监听器
//			servletContext.addListener(className);
            //添加 过滤器
//			servletContext.addFilter(filterName, filter);
            //可以设置 全局变量
            servletContext.setAttribute("startup", "true");
            System.out.println("========= servletContext startup finished ============");
        });
        //设置连接器
        factory.addConnectorCustomizers(new MyTomcatConnectorCustomizer());

    }

    /**
     * access访问日志设置
     * @return
     */
    private AccessLogValve getAccessLogValve() {
        AccessLogValve log = new AccessLogValve();
        log.setDirectory("d:/tomcat/logs");
        log.setEnabled(true);
        log.setPattern("common");
        log.setPrefix("springboot-access-log");
        log.setSuffix(".txt");
        return log;
    }

}

/**
 * tomcat connectot 连接器
 */
class MyTomcatConnectorCustomizer implements TomcatConnectorCustomizer {

    @Override
    public void customize(Connector arg0) {
        Http11NioProtocol protocol=(Http11NioProtocol)arg0.getProtocolHandler();
        //最大连接数
        protocol.setMaxConnections(2000);
        //最大线程数
        protocol.setMaxThreads(500);
        //超时时间 1s
        protocol.setConnectionTimeout(1000);
    }
}

在SpringIOC容器中装配一个EmbeddedServletContainerFactory对象

使用@Bean在SpringIOC容器中装配一个EmbeddedServletContainerFactory对象

package com.feiyue.config;

import org.apache.catalina.valves.AccessLogValve;
import org.springframework.boot.web.server.ErrorPage;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;

@Configuration
public class WebServerConfiguration {

    @Bean
    public EmbeddedServletContainerFactory createEmbeddedServletContainerFactory() {
        TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory();
        factory.setPort(8001);
        factory.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/404.html"));
        //初始化
        factory.addInitializers((servletContext) -> {
            System.out.println("========= servletContext startup ============");
            //添加 监听器
            //servletContext.addListener(className);
            //添加 过滤器
            //servletContext.addFilter(filterName, filter);
            //可以设置 全局变量
            servletContext.setAttribute("startup", "true");
            System.out.println("========= servletContext startup finished ============");
        });
        factory.addContextValves(getAccessLogValve());

        return factory;
    }

    /**
     * access访问日志设置
     * @return
     */
    private AccessLogValve getAccessLogValve() {
        AccessLogValve log = new AccessLogValve();
        log.setDirectory("d:/tomcat/logs");
        log.setEnabled(true);
        log.setPattern("common");
        log.setPrefix("springboot-access-log");
        log.setSuffix(".txt");
        return log;
    }
}

Server容器加载原理

(参照org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration
spring容器中如果 没有 EmbeddedServletContainerFactory ,则会根据使用的Web容器创建不同的EmbeddedServletContainerFactory实现类。
spring容器中如果 有 EmbeddedServletContainerFactory ,则不创建。
EmbeddedServletContainerAutoConfiguration是Servlet容器自动配置类,源码如下

package org.springframework.boot.autoconfigure.web;

import io.undertow.Undertow;
import javax.servlet.Servlet;
import org.apache.catalina.startup.Tomcat;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.util.Loader;
import org.eclipse.jetty.webapp.WebAppContext;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.SearchStrategy;
import org.springframework.boot.context.embedded.EmbeddedServletContainerCustomizerBeanPostProcessor;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.jetty.JettyEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.undertow.UndertowEmbeddedServletContainerFactory;
import org.springframework.boot.web.servlet.ErrorPageRegistrarBeanPostProcessor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
import org.springframework.util.ObjectUtils;
import org.xnio.SslClientAuthMode;

@AutoConfigureOrder(-2147483648)
@Configuration
@ConditionalOnWebApplication
@Import({EmbeddedServletContainerAutoConfiguration.BeanPostProcessorsRegistrar.class})
public class EmbeddedServletContainerAutoConfiguration {
    public EmbeddedServletContainerAutoConfiguration() {
    }

    public static class BeanPostProcessorsRegistrar implements ImportBeanDefinitionRegistrar, BeanFactoryAware {
        private ConfigurableListableBeanFactory beanFactory;

        public BeanPostProcessorsRegistrar() {
        }

        public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
            if (beanFactory instanceof ConfigurableListableBeanFactory) {
                this.beanFactory = (ConfigurableListableBeanFactory)beanFactory;
            }

        }

        public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
            if (this.beanFactory != null) {
                if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(EmbeddedServletContainerCustomizerBeanPostProcessor.class, true, false))) {
                    registry.registerBeanDefinition("embeddedServletContainerCustomizerBeanPostProcessor", new RootBeanDefinition(EmbeddedServletContainerCustomizerBeanPostProcessor.class));
                }
                if (ObjectUtils.isEmpty(this.beanFactory.getBeanNamesForType(ErrorPageRegistrarBeanPostProcessor.class, true, false))) {
                    registry.registerBeanDefinition("errorPageRegistrarBeanPostProcessor", new RootBeanDefinition(ErrorPageRegistrarBeanPostProcessor.class));
                }

            }
        }
    }
    
    //Undertow容器
    @Configuration
    @ConditionalOnClass({Servlet.class, Undertow.class, SslClientAuthMode.class})
    @ConditionalOnMissingBean(
        value = {EmbeddedServletContainerFactory.class},
        search = SearchStrategy.CURRENT
    )
    public static class EmbeddedUndertow {
        public EmbeddedUndertow() {
        }

        @Bean
        public UndertowEmbeddedServletContainerFactory undertowEmbeddedServletContainerFactory() {
            return new UndertowEmbeddedServletContainerFactory();
        }
    }

    //Jetty容器
    @Configuration
    @ConditionalOnClass({Servlet.class, Server.class, Loader.class, WebAppContext.class})
    @ConditionalOnMissingBean(
        value = {EmbeddedServletContainerFactory.class},
        search = SearchStrategy.CURRENT
    )
    public static class EmbeddedJetty {
        public EmbeddedJetty() {
        }

        @Bean
        public JettyEmbeddedServletContainerFactory jettyEmbeddedServletContainerFactory() {
            return new JettyEmbeddedServletContainerFactory();
        }
    }

    //Tomcat容器
    @Configuration
    @ConditionalOnClass({Servlet.class, Tomcat.class})
    @ConditionalOnMissingBean(
        value = {EmbeddedServletContainerFactory.class},
        search = SearchStrategy.CURRENT
    )
    public static class EmbeddedTomcat {
        public EmbeddedTomcat() {
        }

        @Bean
        public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() {
            return new TomcatEmbeddedServletContainerFactory();
        }
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值