SpringBoot嵌入式Tomcat容器启动源码(47)

一、嵌入式Tomcat容器概述

1.1 嵌入式容器的优势

在传统的Java Web开发中,开发者通常需要将应用打包成WAR包,并部署到独立的Tomcat、Jetty等Web容器中。而SpringBoot引入嵌入式容器的概念,将Web容器与应用程序集成在一起,使得应用可以以独立的JAR包形式运行。这种方式具有显著优势:

  • 简化部署流程:无需手动安装和配置外部Web容器,只需执行java -jar命令即可启动应用,降低了部署复杂度。
  • 统一开发与生产环境:开发、测试、生产环境使用相同的容器配置,避免因环境差异导致的兼容性问题。
  • 灵活定制:开发者可以方便地对嵌入式容器进行定制,如修改端口、添加连接器、配置SSL等。

1.2 SpringBoot与Tomcat的关系

SpringBoot支持多种嵌入式Web容器,包括Tomcat、Jetty和Undertow。其中,Tomcat作为最常用的Java Web容器,凭借其稳定性和广泛的生态支持,成为SpringBoot默认的嵌入式容器。SpringBoot通过一系列的自动配置和封装,将Tomcat的启动、配置和管理集成到应用启动流程中,开发者无需编写复杂的容器启动代码,即可快速构建Web应用。

1.3 容器启动的核心流程

SpringBoot嵌入式Tomcat容器的启动流程与Spring应用的启动过程紧密结合,主要包括以下几个阶段:

  1. 容器初始化:创建Tomcat实例,加载基础配置。
  2. 配置加载:读取SpringBoot的配置属性,对Tomcat进行定制化配置。
  3. Servlet注册:将Spring管理的Servlet、Filter和Listener注册到Tomcat容器中。
  4. 容器启动:启动Tomcat,监听指定端口,等待处理客户端请求。

这些阶段相互协作,确保Tomcat容器能够正确启动并为Spring应用提供Web服务。

二、SpringBoot启动入口分析

2.1 @SpringBootApplication注解

SpringBoot应用的启动入口通常是一个带有@SpringBootApplication注解的主类,例如:

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

@SpringBootApplication是一个组合注解,它包含了以下三个核心注解:

  • @SpringBootConfiguration:继承自@Configuration,用于标识该类为配置类。
  • @EnableAutoConfiguration:开启SpringBoot的自动配置功能,根据类路径下的依赖和配置属性,自动配置应用所需的Bean。
  • @ComponentScan:启用组件扫描,自动扫描指定包及其子包下的组件(如@Component@Service@Repository等),并将其注册到Spring容器中。

这些注解为SpringBoot应用的启动和运行奠定了基础,其中@EnableAutoConfiguration在Tomcat容器的自动配置过程中起着关键作用。

2.2 SpringApplication类

SpringApplication类是SpringBoot应用启动的核心类,run方法是启动流程的入口:

public class SpringApplication {
    public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {
        return run(new Class<?>[] { primarySource }, args);
    }

    public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
        return new SpringApplication(primarySources).run(args);
    }

    public ConfigurableApplicationContext run(String... args) {
        StopWatch stopWatch = new StopWatch();
        stopWatch.start();
        ConfigurableApplicationContext context = null;
        Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
        configureHeadlessProperty();
        // 创建Spring应用上下文环境
        SpringApplicationRunListeners listeners = getRunListeners(args);
        listeners.starting();
        try {
            // 创建应用参数对象
            ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
            // 准备环境
            ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
            configureIgnoreBeanInfo(environment);
            // 创建ApplicationContext的构建器
            Banner printedBanner = printBanner(environment);
            context = createApplicationContext();
            exceptionReporters = getSpringBootExceptionReporters(environment);
            // 准备ApplicationContext
            prepareContext(context, environment, listeners, applicationArguments, printedBanner);
            // 刷新ApplicationContext
            refreshContext(context);
            // 调用后置处理方法
            afterRefresh(context, applicationArguments);
            stopWatch.stop();
            if (this.logStartupInfo) {
                new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
            }
            // 发布应用启动完成事件
            listeners.started(context);
            // 调用所有Runner的run方法
            callRunners(context, applicationArguments);
            return context;
        }
        catch (Throwable ex) {
            handleRunFailure(context, ex, exceptionReporters, listeners);
            throw new IllegalStateException(ex);
        }
    }
}

run方法主要完成以下工作:

  1. 初始化监听器:创建SpringApplicationRunListeners,用于监听应用启动过程中的各个事件(如启动开始、环境准备完成、容器启动完成等)。
  2. 准备环境:创建和配置ConfigurableEnvironment,加载配置属性(如application.propertiesapplication.yml中的配置)。
  3. 创建应用上下文:根据应用类型(Web应用或非Web应用)创建对应的ApplicationContext实例(如AnnotationConfigServletWebServerApplicationContext)。
  4. 准备上下文:将环境配置、监听器等信息注入到ApplicationContext中,并加载自动配置类。
  5. 刷新上下文:调用refreshContext方法,完成容器的初始化工作,包括Bean的定义解析、依赖注入等。
  6. 启动容器:在Web应用中,这一步会触发嵌入式Tomcat容器的启动。

三、Tomcat容器初始化

3.1 Tomcat实例创建

在SpringBoot中,Tomcat容器的初始化由TomcatServletWebServerFactory类负责。该类实现了ServletWebServerFactory接口,用于创建和配置Tomcat服务器实例。

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
        implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {

    private Tomcat tomcat;

    @Override
    protected TomcatWebServer getTomcatWebServer(Tomcat tomcat) {
        return new TomcatWebServer(tomcat, getPort() >= 0);
    }

    @Override
    protected Tomcat getTomcat() {
        if (this.tomcat == null) {
            // 创建Tomcat实例
            this.tomcat = new Tomcat();
            File baseDir = (this.baseDirectory != null) ? this.baseDirectory : createTempDir("tomcat");
            this.tomcat.setBaseDir(baseDir.getAbsolutePath());
            Connector connector = new Connector(this.protocol);
            this.tomcat.getService().addConnector(connector);
            customizeConnector(connector);
            this.tomcat.setConnector(connector);
            this.tomcat.getHost().setAutoDeploy(false);
            configureEngine(this.tomcat.getEngine());
            for (Connector additionalConnector : this.additionalTomcatConnectors) {
                this.tomcat.getService().addConnector(additionalConnector);
            }
        }
        return this.tomcat;
    }
}

getTomcat方法负责创建Tomcat实例,并进行基础配置:

  1. 设置基础目录:为Tomcat指定一个临时目录或用户自定义的目录,用于存储日志、缓存等文件。
  2. 创建连接器:默认创建一个HTTP连接器,协议为org.apache.coyote.http11.Http11NioProtocol,并可通过配置进行定制。
  3. 添加额外连接器:如果用户配置了额外的连接器(如HTTPS连接器),将其添加到Tomcat服务中。
  4. 禁用自动部署:设置Tomcat的主机不自动部署Web应用,因为SpringBoot应用的部署由自身管理。

3.2 配置加载与应用

Tomcat容器的配置信息主要来源于SpringBoot的配置属性,通过TomcatServletWebServerFactory类进行加载和应用:

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
        implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {

    @Override
    protected void customizeConnector(Connector connector) {
        super.customizeConnector(connector);
        // 配置端口
        Integer port = getPort();
        if (port != null) {
            connector.setPort(port);
        }
        // 配置最大连接数
        if (this.maxConnections != null) {
            ProtocolHandler protocolHandler = connector.getProtocolHandler();
            if (protocolHandler instanceof AbstractHttp11Protocol) {
                ((AbstractHttp11Protocol<?>) protocolHandler).setMaxConnections(this.maxConnections);
            }
        }
        // 配置URI编码
        if (this.uriEncoding != null) {
            connector.setURIEncoding(this.uriEncoding.name());
        }
        // 其他配置...
    }
}

customizeConnector方法根据配置属性对Tomcat的连接器进行定制,包括:

  • 端口配置:从配置中获取端口号,设置到连接器上,默认端口为8080。
  • 最大连接数:设置连接器支持的最大并发连接数,避免因连接过多导致性能下降。
  • URI编码:指定请求URI的编码格式,通常为UTF-8。
  • SSL配置:如果配置了HTTPS,会创建SSL连接器,并加载证书和密钥进行配置。

3.3 与Spring上下文的关联

在Tomcat容器初始化过程中,需要将其与Spring应用上下文进行关联,以便后续的Servlet注册和请求处理。

public class TomcatWebServer extends AbstractServletWebServer {

    private final Tomcat tomcat;
    private final boolean autoStart;

    public TomcatWebServer(Tomcat tomcat, boolean autoStart) {
        super(tomcat.getService(), autoStart);
        Assert.notNull(tomcat, "Tomcat Server must not be null");
        this.tomcat = tomcat;
        this.autoStart = autoStart;
    }

    @Override
    protected void postProcessContext(Context context) {
        super.postProcessContext(context);
        ServletContextInitializerBeans initializers = getServletContextInitializerBeans();
        for (ServletContextInitializer initializer : initializers.getOrderedServletContextInitializers()) {
            try {
                // 将ServletContextInitializer应用到Tomcat的上下文
                initializer.onStartup(context.getServletContext());
            }
            catch (ServletException ex) {
                throw new IllegalStateException("Failed to initialize servlet context", ex);
            }
        }
    }
}

postProcessContext方法会遍历所有的ServletContextInitializer,并调用其onStartup方法。这些ServletContextInitializer由Spring容器管理,其中包括用于注册Servlet、Filter和Listener的初始化器,它们将在后续阶段完成相关组件的注册工作。

四、Servlet、Filter和Listener注册

4.1 Servlet注册机制

SpringBoot通过ServletRegistrationBean类实现Servlet的注册。在容器启动过程中,会扫描Spring容器中所有的ServletRegistrationBean,并将其注册到Tomcat容器中。

public class ServletRegistrationBean<T extends Servlet> extends AbstractRegistrationBean<ServletRegistration.Dynamic> {

    private T servlet;

    public ServletRegistrationBean(T servlet, String... urlMappings) {
        this.servlet = servlet;
        setUrlMappings(Arrays.asList(urlMappings));
    }

    @Override
    protected ServletRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
        // 创建ServletRegistration.Dynamic对象
        ServletRegistration.Dynamic registration = servletContext.addServlet(description, this.servlet);
        if (registration == null) {
            throw new IllegalStateException("Failed to register servlet with name '" + description + "'");
        }
        // 配置Servlet映射路径
        registration.addMapping(getUrlMappings().toArray(new String[0]));
        // 配置Servlet的其他属性,如初始化参数
        for (String name : getInitParameters().keySet()) {
            registration.setInitParameter(name, getInitParameters().get(name));
        }
        // 设置Servlet的加载顺序
        if (getLoadOnStartup() != null) {
            registration.setLoadOnStartup(getLoadOnStartup());
        }
        return registration;
    }
}

addRegistration方法完成以下工作:

  1. 创建注册对象:调用ServletContextaddServlet方法,创建一个ServletRegistration.Dynamic对象,用于管理Servlet的注册信息。
  2. 配置映射路径:将ServletRegistrationBean中指定的URL映射路径添加到ServletRegistration.Dynamic中。
  3. 设置初始化参数:如果配置了初始化参数,将其设置到ServletRegistration.Dynamic中,供Servlet初始化时使用。
  4. 设置加载顺序:通过setLoadOnStartup方法设置Servlet的加载顺序,数值越小,加载优先级越高。

4.2 Filter注册机制

Filter的注册与Servlet类似,通过FilterRegistrationBean类实现:

public class FilterRegistrationBean<T extends Filter> extends AbstractFilterRegistrationBean<FilterRegistration.Dynamic> {

    private T filter;

    public FilterRegistrationBean(T filter) {
        this.filter = filter;
    }

    @Override
    protected FilterRegistration.Dynamic addRegistration(String description, ServletContext servletContext) {
        // 创建FilterRegistration.Dynamic对象
        FilterRegistration.Dynamic registration = servletContext.addFilter(description, this.filter);
        if (registration == null) {
            throw new IllegalStateException("Failed to register filter with name '" + description + "'");
        }
        // 配置Filter映射路径
        for (String urlPattern : getUrlPatterns()) {
            registration.addMappingForUrlPatterns(getDispatcherTypes(), false, urlPattern);
        }
        // 配置Filter的其他属性,如初始化参数
        for (String name : getInitParameters().keySet()) {
            registration.setInitParameter(name, getInitParameters().get(name));
        }
        return registration;
    }
}

addRegistration方法的主要步骤:

  1. 创建注册对象:调用ServletContextaddFilter方法,创建FilterRegistration.Dynamic对象。
  2. 配置映射路径:将FilterRegistrationBean中指定的URL模式添加到FilterRegistration.Dynamic中,并设置Dispatcher类型(如REQUEST、FORWARD等)。
  3. 设置初始化参数:与Servlet注册类似,将配置的初始化参数设置到FilterRegistration.Dynamic中。

4.3 Listener注册机制

Listener的注册通过ServletListenerRegistrationBean类实现:

public class ServletListenerRegistrationBean<T extends EventListener>
        extends AbstractRegistrationBean<ServletListenerRegistrationBean<T>> {

    private T listener;

    public ServletListenerRegistrationBean(T listener) {
        this.listener = listener;
    }

    @Override
    protected void registerListener(ServletContext servletContext) {
        // 将Listener添加到ServletContext中
        servletContext.addListener(this.listener);
    }
}

registerListener方法直接调用ServletContextaddListener方法,将Listener注册到Tomcat容器中。Listener会在ServletContext初始化、销毁等生命周期事件发生时被调用,用于执行相关的监听器逻辑。

五、Tomcat容器启动过程

5.1 启动触发点

在SpringBoot应用启动流程中,当ApplicationContext完成刷新后,会触发嵌入式Tomcat容器的启动。具体来说,在SpringApplication类的run方法中,refreshContext(context)方法完成容器的初始化和Bean的加载后,会调用afterRefresh(context, applicationArguments)方法,该方法最终会触发Tomcat容器的启动。

public class SpringApplication {

    protected void afterRefresh(ConfigurableApplicationContext context, ApplicationArguments args) {
        // 调用WebServer的启动方法
        WebServer webServer = context.getBean(WebServer.class);
        if (webServer != null) {
            webServer.start();
        }
        // 发布应用就绪事件
        publishEvent(new ApplicationReadyEvent(this, args, context));
    }
}

afterRefresh方法从ApplicationContext中获取WebServer实例(对于Tomcat容器,实际获取的是TomcatWebServer实例),并调用其start方法启动容器。

5.2 启动核心逻辑

TomcatWebServer类的start方法是Tomcat容器启动的核心逻辑所在:

public class TomcatWebServer extends AbstractServletWebServer {

    @Override
    public void start() throws WebServerException {
        if (this.started) {
            return;
        }
        try {
            // 启动Tomcat服务
            getTomcat().start();
            // 等待连接器绑定端口
            Connector connector = getTomcat().getConnector();
            synchronized (this.monitor) {
                this.port = getPort(connector);
            }
        }
        catch (Exception ex) {
            stopSilently();
            throw new WebServerException("Unable to start embedded Tomcat", ex);
        }
        this.started = true;
    }

    private int getPort(Connector connector) {
        if (connector.getPort() != -1) {
            return connector.getPort();
        }
        // 对于动态分配的端口,获取实际绑定的端口号
        ProtocolHandler protocolHandler = connector.getProtocolHandler();
        if (protocolHandler instanceof AbstractEndpoint)
private int getPort(Connector connector) {
    if (connector.getPort() != -1) {
        return connector.getPort();
    }
    // 对于动态分配的端口,获取实际绑定的端口号
    ProtocolHandler protocolHandler = connector.getProtocolHandler();
    if (protocolHandler instanceof AbstractEndpoint) {
        AbstractEndpoint endpoint = (AbstractEndpoint) protocolHandler;
        return endpoint.getLocalPort();
    }
    throw new IllegalStateException("Unable to determine actual port for connector [" + connector + "]");
}

start方法中,Tomcat首先调用getTomcat().start()来启动Tomcat服务。这一步会触发Tomcat内部一系列组件的初始化和启动,包括:

  1. 服务组件启动:Tomcat的Service组件会启动其关联的ConnectorEngine组件。Connector负责接收客户端请求,Engine则负责处理请求并将响应返回给Connector
  2. 协议处理器初始化Connector会初始化对应的ProtocolHandler,如Http11NioProtocol,用于处理HTTP协议相关的网络通信。该处理器会创建并启动网络端点(Endpoint),监听指定端口,等待客户端连接。
  3. 容器组件启动Engine组件会启动其内部的HostContext容器。Host代表一个虚拟主机,Context代表一个Web应用,它们会初始化相关的Servlet、Filter和Listener组件,为请求处理做好准备。

在Tomcat服务启动后,start方法会通过getPort方法获取实际绑定的端口号。如果在配置中显式指定了端口,直接返回该端口号;如果是动态分配的端口,则从ProtocolHandlerAbstractEndpoint中获取实际绑定的端口。

5.3 启动过程中的关键组件调用

Tomcat启动过程中,核心组件的调用关系如下:

  1. Bootstrap:Tomcat启动的入口类,其main方法会创建Bootstrap实例,并调用initstart方法。init方法会初始化ClassLoader Catalina实例,Catalina是Tomcat的核心类,负责管理整个容器的生命周期。
public class Bootstrap {
    private Catalina catalina = null;
    public void init() throws Exception {
        initClassLoaders();
        catalina = new Catalina();
        // 设置相关属性和监听器
        catalina.setParentClassLoader(parentClassLoader);
        loadConfiguration();
    }
    public void start() throws Exception {
        if (catalina.start() == false) {
            throw new LifecycleException("Failed to start server.");
        }
    }
}
  1. Catalina:在start方法中,Catalina会依次启动Server组件,Server又会启动其包含的Service组件。
public class Catalina {
    private Server server = null;
    public boolean start() {
        initDirs();
        // 启动Server
        if (server == null) {
            load();
        }
        try {
            server.start();
        } catch (LifecycleException e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}
  1. Service组件Service组件启动时,会先启动Connector,再启动EngineConnector启动过程中,会初始化并启动ProtocolHandler,开始监听端口;Engine启动时,会递归启动其内部的HostContext容器。
public class StandardService extends LifecycleMBeanBase implements Service {
    private Connector[] connectors = new Connector[0];
    private Engine engine = null;
    @Override
    protected void startInternal() throws LifecycleException {
        // 启动所有Connector
        synchronized (connectors) {
            for (int i = 0; i < connectors.length; i++) {
                connectors[i].start();
            }
        }
        // 启动Engine
        if (engine != null) {
            synchronized (engine) {
                engine.start();
            }
        }
    }
}
  1. ProtocolHandlerEndpoint:以Http11NioProtocol为例,其启动过程中会初始化NioEndpointNioEndpoint会创建ServerSocketChannel,绑定到指定端口,并启动Acceptor线程和Poller线程。Acceptor线程负责接收客户端连接请求,Poller线程负责处理连接的读写事件。
public class Http11NioProtocol extends AbstractHttp11Protocol<NioChannel> {
    private NioEndpoint endpoint = null;
    @Override
    protected void init() throws Exception {
        endpoint = new NioEndpoint();
        // 配置Endpoint参数
        endpoint.setServerSockProperties(serverSockProperties);
        endpoint.init();
    }
    @Override
    protected void startInternal() throws Exception {
        endpoint.start();
    }
}
  1. Container组件(EngineHostContext:这些容器启动时,会依次调用其子容器的启动方法,并初始化相关的Servlet、Filter和Listener。例如,StandardContext启动时,会调用configureContext方法,加载Servlet映射、初始化Filter和Listener等。
public class StandardContext extends ContainerBase implements Context {
    @Override
    protected void startInternal() throws LifecycleException {
        // 配置Context
        configureContext();
        // 启动子容器
        super.startInternal();
    }
    private void configureContext() {
        // 加载Servlet映射
        mapperElement();
        // 初始化Filter
        filterStart();
        // 初始化Listener
        listenerStart();
    }
}

5.4 启动失败处理

如果Tomcat容器启动过程中发生异常,TomcatWebServerstart方法会捕获异常并进行处理:

@Override
public void start() throws WebServerException {
    if (this.started) {
        return;
    }
    try {
        getTomcat().start();
        Connector connector = getTomcat().getConnector();
        synchronized (this.monitor) {
            this.port = getPort(connector);
        }
    }
    catch (Exception ex) {
        stopSilently();
        throw new WebServerException("Unable to start embedded Tomcat", ex);
    }
    this.started = true;
}

当捕获到异常时,首先会调用stopSilently方法尝试停止Tomcat容器,避免容器处于不一致的状态。然后,抛出WebServerException异常,将错误信息传递给SpringBoot应用的启动流程。SpringBoot会根据异常类型和配置,决定是否继续启动应用或终止启动,并输出相应的错误日志。

六、Tomcat与Spring的请求处理整合

6.1 请求接收与分发

当Tomcat的Connector监听到客户端请求后,会将请求传递给ProtocolHandler进行处理。以Http11NioProtocol为例,NioEndpoint的Acceptor线程接收连接请求后,会将连接封装成NioChannel,并交给Poller线程处理。Poller线程会将请求注册到事件队列,等待处理器进行处理。

public class NioEndpoint extends AbstractEndpoint<NioChannel> {
    private Acceptor acceptor = new Acceptor();
    private class Acceptor implements Runnable {
        @Override
        public void run() {
            while (running) {
                try {
                    // 接收连接
                    SocketChannel socketChannel = serverSock.accept();
                    // 封装成NioChannel
                    NioChannel channel = nioChannels.poll();
                    if (channel == null) {
                        channel = new NioChannel();
                    }
                    channel.setSocket(socketChannel);
                    // 将连接交给Poller处理
                    poller.put(channel);
                } catch (IOException ioe) {
                    // 处理异常
                }
            }
        }
    }
    private class Poller implements Runnable {
        private final LinkedBlockingQueue<NioChannel> events = new LinkedBlockingQueue<>();
        @Override
        public void run() {
            while (running) {
                try {
                    // 从队列获取事件
                    NioChannel channel = events.take();
                    // 处理请求
                    processSocket(channel, socketProperties, true);
                } catch (Exception x) {
                    // 处理异常
                }
            }
        }
    }
}

processSocket方法中,会创建SocketProcessor任务,将请求处理逻辑封装在任务中,并提交到线程池进行处理。SocketProcessor任务会调用Adapter将Tomcat的请求和响应对象转换为Servlet规范的ServletRequestServletResponse对象,然后传递给Container进行处理。

protected boolean processSocket(NioChannel socket, SocketProperties socketProperties, boolean dispatch) {
    try {
        SocketProcessor sc = new SocketProcessor(socket, socketProperties);
        // 提交任务到线程池
        getExecutor().execute(sc);
    } catch (RejectedExecutionException ree) {
        // 处理线程池拒绝任务的情况
    }
    return true;
}
private class SocketProcessor implements Runnable {
    private final NioChannel socket;
    private final SocketProperties socketProperties;
    public SocketProcessor(NioChannel socket, SocketProperties socketProperties) {
        this.socket = socket;
        this.socketProperties = socketProperties;
    }
    @Override
    public void run() {
        try {
            // 获取Adapter
            CoyoteAdapter adapter = protocol.getAdapter();
            // 处理请求
            adapter.service(socket.getSocket(), socket.getSocket().getRemoteSocketAddress());
        } catch (Exception e) {
            // 处理异常
        }
    }
}

6.2 Spring的DispatcherServlet处理请求

当Tomcat的Container接收到请求后,会根据URL映射找到对应的Servlet进行处理。在SpringBoot应用中,所有的Web请求默认会由DispatcherServlet处理。DispatcherServlet是Spring MVC的核心组件,负责请求的分发和处理。

public class DispatcherServlet extends FrameworkServlet {
    @Override
    protected void doService(HttpServletRequest request, HttpServletResponse response) throws Exception {
        logRequest(request);
        // 保存请求和响应属性
        Map<String, Object> attributesSnapshot = null;
        if (WebUtils.isIncludeRequest(request)) {
            attributesSnapshot = new HashMap<>();
            Enumeration<?> attrNames = request.getAttributeNames();
            while (attrNames.hasMoreElements()) {
                String attrName = (String) attrNames.nextElement();
                if (this.cleanupAfterInclude || attrName.startsWith(DEFAULT_STRATEGIES_PREFIX)) {
                    attributesSnapshot.put(attrName, request.getAttribute(attrName));
                }
            }
        }
        // 设置请求属性
        request.setAttribute(WEB_APPLICATION_CONTEXT_ATTRIBUTE, getWebApplicationContext());
        request.setAttribute(LOCALE_RESOLVER_ATTRIBUTE, this.localeResolver);
        request.setAttribute(THEME_RESOLVER_ATTRIBUTE, this.themeResolver);
        request.setAttribute(THEME_SOURCE_ATTRIBUTE, getThemeSource());
        try {
            // 处理请求
            doDispatch(request, response);
        } finally {
            // 恢复请求和响应属性
            if (attributesSnapshot != null) {
                restoreAttributesAfterInclude(request, attributesSnapshot);
            }
        }
    }
    protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
        HttpServletRequest processedRequest = request;
        HandlerExecutionChain mappedHandler = null;
        boolean multipartRequestParsed = false;
        WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
        try {
            ModelAndView mv = null;
            Exception dispatchException = null;
            try {
                // 检查是否为文件上传请求
                processedRequest = checkMultipart(request);
                multipartRequestParsed = (processedRequest != request);
                // 获取Handler
                mappedHandler = getHandler(processedRequest);
                if (mappedHandler == null) {
                    noHandlerFound(processedRequest, response);
                    return;
                }
                // 获取HandlerAdapter
                HandlerAdapter ha = getHandlerAdapter(mappedHandler.getHandler());
                // 处理Last-Modified请求头
                String method = request.getMethod();
                boolean isGet = "GET".equals(method);
                if (isGet || "HEAD".equals(method)) {
                    long lastModified = ha.getLastModified(request, mappedHandler.getHandler());
                    if (new ServletWebRequest(request, response).checkNotModified(lastModified) && isGet) {
                        return;
                    }
                }
                // 调用拦截器的preHandle方法
                if (!mappedHandler.applyPreHandle(processedRequest, response)) {
                    return;
                }
                // 处理请求并获取ModelAndView
                mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
                if (asyncManager.isConcurrentHandlingStarted()) {
                    return;
                }
                applyDefaultViewName(processedRequest, mv);
                // 调用拦截器的postHandle方法
                mappedHandler.applyPostHandle(processedRequest, response, mv);
            } catch (Exception ex) {
                dispatchException = ex;
            } catch (Throwable err) {
                dispatchException = new NestedServletException("Handler dispatch failed", err);
            }
            // 处理视图
            processDispatchResult(processedRequest, response, mappedHandler, mv, dispatchException);
        } catch (Exception ex) {
            // 触发异常处理
            triggerAfterCompletion(processedRequest, response, mappedHandler, ex);
        } catch (Throwable err) {
            triggerAfterCompletion(processedRequest, response, mappedHandler,
                    new NestedServletException("Handler processing failed", err));
        } finally {
            if (asyncManager.isConcurrentHandlingStarted()) {
                if (mappedHandler != null) {
                    mappedHandler.applyAfterConcurrentHandlingStarted(processedRequest, response);
                }
            } else {
                if (multipartRequestParsed) {
                    cleanupMultipart(processedRequest);
                }
            }
        }
    }
}

doDispatch方法是DispatcherServlet处理请求的核心逻辑,主要步骤包括:

  1. 检查文件上传请求:调用checkMultipart方法判断请求是否为文件上传请求,并进行相应处理。
  2. 获取Handler:通过getHandler方法根据请求URL从HandlerMapping中获取对应的Handler(通常是Controller方法)。
  3. 获取HandlerAdapter:根据Handler的类型,从HandlerAdapter列表中获取对应的HandlerAdapter,用于调用Handler。
  4. 调用拦截器:在调用Handler之前,先调用HandlerExecutionChain中配置的拦截器的preHandle方法,进行请求预处理。
  5. 处理请求:调用HandlerAdapter的handle方法处理请求,获取ModelAndView对象,包含视图信息和模型数据。
  6. 调用拦截器:在处理完请求后,调用拦截器的postHandle方法,进行请求后处理。
  7. 处理视图:根据ModelAndView对象,选择合适的视图解析器进行视图解析,并将模型数据渲染到视图中,生成响应内容。
  8. 异常处理:如果在请求处理过程中发生异常,会调用processDispatchResult方法进行异常处理,并触发拦截器的afterCompletion方法。

6.3 请求处理流程整合

Tomcat与Spring的请求处理整合流程如下:

  1. Tomcat接收请求:Tomcat的Connector监听到客户端请求,ProtocolHandler将请求封装并传递给Container
  2. Tomcat分发请求Container根据URL映射找到对应的Servlet,在SpringBoot应用中通常是DispatcherServlet
  3. Spring处理请求DispatcherServlet按照Spring MVC的流程,进行Handler查找、拦截器调用、请求处理和视图渲染。
  4. 响应返回:Spring生成响应内容后,将响应传递给Tomcat,Tomcat再将响应返回给客户端。

在这个过程中,Tomcat负责网络通信和请求的初步处理,Spring则专注于业务逻辑的处理和视图渲染,两者紧密协作,实现了完整的Web请求处理流程。

七、Tomcat容器的配置定制

7.1 通过配置文件定制

SpringBoot支持通过配置文件(如application.propertiesapplication.yml)对嵌入式Tomcat容器进行配置定制。常见的配置项包括:

  1. 端口配置:通过server.port属性设置Tomcat监听的端口号。
server.port=8081
  1. 路径配置:通过server.servlet.context-path属性设置应用的上下文路径。
server.servlet.context-path=/myapp
  1. 连接数配置:通过server.tomcat.max-connections属性设置Tomcat的最大连接数,通过server.tomcat.max-threads属性设置最大工作线程数。
server.tomcat.max-connections=1000
server.tomcat.max-threads=200
  1. SSL配置:如果需要启用HTTPS,可以配置SSL相关属性,如证书路径、密钥密码等。
server.ssl.enabled=true
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=password
server.ssl.keyStoreType=PKCS12

这些配置属性会在Tomcat

这些配置属性会在Tomcat容器初始化过程中被读取并应用。SpringBoot通过ServerProperties类来封装这些配置属性,然后在TomcatServletWebServerFactory中应用这些配置。

@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
    private Integer port;
    private String contextPath;
    private Ssl ssl;
    private Tomcat tomcat = new Tomcat();
    // getter and setter methods
    public static class Tomcat {
        private Integer maxThreads;
        private Integer maxConnections;
        private Integer acceptCount;
        private String basedir;
        // getter and setter methods
    }
    public static class Ssl {
        private Boolean enabled;
        private String keyStore;
        private String keyStorePassword;
        private String keyStoreType;
        // getter and setter methods
    }
}

TomcatServletWebServerFactorycustomizeConnector方法中,会读取这些配置属性并应用到Tomcat的连接器上:

public class TomcatServletWebServerFactory extends AbstractServletWebServerFactory
        implements ConfigurableTomcatWebServerFactory, ResourceLoaderAware {
    private final ServerProperties serverProperties;
    @Override
    protected void customizeConnector(Connector connector) {
        super.customizeConnector(connector);
        // 应用端口配置
        Integer port = getPort();
        if (port != null) {
            connector.setPort(port);
        }
        // 应用URI编码配置
        if (this.uriEncoding != null) {
            connector.setURIEncoding(this.uriEncoding.name());
        }
        // 应用SSL配置
        if (this.serverProperties.getSsl() != null && this.serverProperties.getSsl().isEnabled()) {
            configureSsl(connector, this.serverProperties.getSsl());
        }
        // 应用Tomcat特定配置
        if (this.serverProperties.getTomcat() != null) {
            configureTomcat(connector, this.serverProperties.getTomcat());
        }
    }
}

7.2 通过Java代码定制

除了配置文件,还可以通过Java代码对Tomcat容器进行更细粒度的定制。SpringBoot提供了多种方式来实现这一点:

7.2.1 实现WebServerFactoryCustomizer接口

通过实现WebServerFactoryCustomizer<TomcatServletWebServerFactory>接口,可以在容器创建之前对TomcatServletWebServerFactory进行定制。

@Configuration
public class TomcatConfig implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        // 设置基础目录
        factory.setBaseDirectory(new File("tomcat-temp"));
        // 添加连接器定制器
        factory.addConnectorCustomizers(connector -> {
            // 设置最大线程数
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractProtocol) {
                ((AbstractProtocol<?>) handler).setMaxThreads(500);
                ((AbstractProtocol<?>) handler).setAcceptCount(200);
            }
        });
        // 添加上下文定制器
        factory.addContextCustomizers(context -> {
            // 设置会话超时时间
            context.setSessionTimeout(30, TimeUnit.MINUTES);
        });
    }
}
7.2.2 注册EmbeddedServletContainerCustomizer Bean(SpringBoot 1.x)

在SpringBoot 1.x版本中,可以通过注册EmbeddedServletContainerCustomizer Bean来定制嵌入式Servlet容器:

@Bean
public EmbeddedServletContainerCustomizer tomcatCustomizer() {
    return container -> {
        if (container instanceof TomcatEmbeddedServletContainerFactory) {
            TomcatEmbeddedServletContainerFactory tomcatFactory = (TomcatEmbeddedServletContainerFactory) container;
            // 设置基础目录
            tomcatFactory.setBaseDirectory(new File("tomcat-temp"));
            // 添加连接器定制器
            tomcatFactory.addConnectorCustomizers(connector -> {
                // 设置最大线程数
                ProtocolHandler handler = connector.getProtocolHandler();
                if (handler instanceof AbstractProtocol) {
                    ((AbstractProtocol<?>) handler).setMaxThreads(500);
                    ((AbstractProtocol<?>) handler).setAcceptCount(200);
                }
            });
        }
    };
}
7.2.3 直接配置TomcatServletWebServerFactory Bean

通过直接定义TomcatServletWebServerFactory Bean,可以完全控制嵌入式Tomcat容器的创建过程:

@Bean
public TomcatServletWebServerFactory tomcatFactory() {
    TomcatServletWebServerFactory factory = new TomcatServletWebServerFactory();
    // 设置端口
    factory.setPort(8081);
    // 设置上下文路径
    factory.setContextPath("/myapp");
    // 添加连接器定制器
    factory.addConnectorCustomizers(connector -> {
        // 配置SSL
        if (useSsl) {
            connector.setScheme("https");
            connector.setSecure(true);
            Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
            protocol.setSSLEnabled(true);
            protocol.setKeystoreFile("/path/to/keystore.p12");
            protocol.setKeystorePass("password");
            protocol.setKeyAlias("tomcat");
        }
    });
    return factory;
}

7.3 高级配置选项

除了基本配置外,还可以对Tomcat进行更高级的配置:

7.3.1 配置访问日志

通过配置AccessLogValve,可以记录所有HTTP请求的详细信息:

@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
    return factory -> {
        factory.addContextValves(context -> {
            AccessLogValve accessLogValve = new AccessLogValve();
            accessLogValve.setDirectory("logs");
            accessLogValve.setPrefix("access_log");
            accessLogValve.setSuffix(".log");
            accessLogValve.setPattern("%h %l %u %t \"%r\" %s %b");
            accessLogValve.setRotatable(true);
            return accessLogValve;
        });
    };
}
7.3.2 配置WebSocket支持

要启用WebSocket支持,可以通过以下方式配置:

@Configuration
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myHandler(), "/ws").setAllowedOrigins("*");
    }
    @Bean
    public WebSocketHandler myHandler() {
        return new MyWebSocketHandler();
    }
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        container.setMaxTextMessageBufferSize(8192);
        container.setMaxBinaryMessageBufferSize(8192);
        return container;
    }
}
7.3.3 配置虚拟主机

可以配置多个虚拟主机,每个虚拟主机对应不同的域名或端口:

@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
    return factory -> {
        factory.addEngineValves(new RewriteValve());
        factory.addContextCustomizers(context -> {
            // 配置虚拟主机
            StandardHost host = (StandardHost) context.getParent();
            host.setAutoDeploy(false);
            host.setAppBase("webapps");
            // 添加额外的虚拟主机
            try {
                StandardHost anotherHost = new StandardHost();
                anotherHost.setName("anotherhost.com");
                anotherHost.setAppBase("anotherhost-webapps");
                host.getParent().addChild(anotherHost);
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    };
}

八、Tomcat容器的生命周期管理

8.1 容器生命周期接口

Tomcat的核心组件(如ServerServiceConnectorContainer等)都实现了Lifecycle接口,该接口定义了容器的生命周期方法:

public interface Lifecycle {
    // 生命周期状态常量
    String BEFORE_INIT_EVENT = "before_init";
    String AFTER_INIT_EVENT = "after_init";
    String START_EVENT = "start";
    String BEFORE_START_EVENT = "before_start";
    String AFTER_START_EVENT = "after_start";
    String STOP_EVENT = "stop";
    String BEFORE_STOP_EVENT = "before_stop";
    String AFTER_STOP_EVENT = "after_stop";
    String AFTER_DESTROY_EVENT = "after_destroy";
    String BEFORE_DESTROY_EVENT = "before_destroy";
    String PERIODIC_EVENT = "periodic";
    String CONFIGURE_START_EVENT = "configure_start";
    String CONFIGURE_STOP_EVENT = "configure_stop";
    // 生命周期方法
    void init() throws LifecycleException;
    void start() throws LifecycleException;
    void stop() throws LifecycleException;
    void destroy() throws LifecycleException;
    LifecycleState getState();
    String getStateName();
    void addLifecycleListener(LifecycleListener listener);
    LifecycleListener[] findLifecycleListeners();
    void removeLifecycleListener(LifecycleListener listener);
}

8.2 生命周期状态转换

Tomcat容器的生命周期状态通过LifecycleState枚举定义:

public enum LifecycleState {
    NEW(false, null),
    INITIALIZING(false, Lifecycle.BEFORE_INIT_EVENT),
    INITIALIZED(false, Lifecycle.AFTER_INIT_EVENT),
    STARTING_PREP(false, Lifecycle.BEFORE_START_EVENT),
    STARTING(true, Lifecycle.START_EVENT),
    STARTED(true, Lifecycle.AFTER_START_EVENT),
    STOPPING_PREP(true, Lifecycle.BEFORE_STOP_EVENT),
    STOPPING(false, Lifecycle.STOP_EVENT),
    STOPPED(false, Lifecycle.AFTER_STOP_EVENT),
    DESTROYING(false, Lifecycle.BEFORE_DESTROY_EVENT),
    DESTROYED(false, Lifecycle.AFTER_DESTROY_EVENT),
    FAILED(false, null);
    private final boolean available;
    private final String lifecycleEvent;
    LifecycleState(boolean available, String lifecycleEvent) {
        this.available = available;
        this.lifecycleEvent = lifecycleEvent;
    }
    public boolean isAvailable() {
        return available;
    }
    public String getLifecycleEvent() {
        return lifecycleEvent;
    }
}

生命周期状态转换遵循以下规则:

  1. 初始化阶段NEWINITIALIZINGINITIALIZED
  2. 启动阶段INITIALIZEDSTARTING_PREPSTARTINGSTARTED
  3. 停止阶段STARTEDSTOPPING_PREPSTOPPINGSTOPPED
  4. 销毁阶段STOPPEDDESTROYINGDESTROYED

每个状态转换都会触发相应的生命周期事件,监听器可以监听这些事件并执行相应的操作。

8.3 Spring对Tomcat生命周期的管理

SpringBoot通过TomcatWebServer类管理Tomcat容器的生命周期:

public class TomcatWebServer extends AbstractServletWebServer {
    private final Tomcat tomcat;
    private final boolean autoStart;
    private volatile boolean started = false;
    private final Object monitor = new Object();
    @Override
    public void start() throws WebServerException {
        if (this.started) {
            return;
        }
        try {
            // 启动Tomcat服务
            getTomcat().start();
            // 等待连接器绑定端口
            Connector connector = getTomcat().getConnector();
            synchronized (this.monitor) {
                this.port = getPort(connector);
            }
        }
        catch (Exception ex) {
            stopSilently();
            throw new WebServerException("Unable to start embedded Tomcat", ex);
        }
        this.started = true;
    }
    @Override
    public void stop() throws WebServerException {
        if (!this.started) {
            return;
        }
        try {
            synchronized (this.monitor) {
                // 停止Tomcat服务
                getTomcat().stop();
                getTomcat().destroy();
                this.port = -1;
            }
        }
        catch (Exception ex) {
            throw new WebServerException("Unable to stop embedded Tomcat", ex);
        }
        finally {
            this.started = false;
        }
    }
}

当Spring应用上下文刷新完成后,会调用TomcatWebServerstart方法启动Tomcat容器;当应用上下文关闭时,会调用stop方法停止并销毁Tomcat容器。

8.4 生命周期监听器

Tomcat允许注册生命周期监听器,监听容器的生命周期事件。例如,HostConfig类就是一个生命周期监听器,用于监听Host容器的生命周期事件:

public class HostConfig extends LifecycleListenerBase {
    @Override
    public void lifecycleEvent(LifecycleEvent event) {
        // 获取事件源
        Lifecycle lifecycle = event.getLifecycle();
        // 处理不同类型的事件
        if (Lifecycle.BEFORE_START_EVENT.equals(event.getType())) {
            beforeStart();
        } else if (Lifecycle.START_EVENT.equals(event.getType())) {
            start();
        } else if (Lifecycle.BEFORE_STOP_EVENT.equals(event.getType())) {
            beforeStop();
        } else if (Lifecycle.STOP_EVENT.equals(event.getType())) {
            stop();
        }
    }
    private void start() {
        // 处理Host启动事件
        Host host = (Host) lifecycle;
        // 部署应用
        deployApps();
    }
    private void stop() {
        // 处理Host停止事件
        Host host = (Host) lifecycle;
        // 取消部署应用
        undeployApps();
    }
}

SpringBoot也可以通过注册自定义的LifecycleListener来监听Tomcat容器的生命周期事件:

@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
    return factory -> {
        factory.addContextLifecycleListeners(new LifecycleListener() {
            @Override
            public void lifecycleEvent(LifecycleEvent event) {
                if (Lifecycle.START_EVENT.equals(event.getType())) {
                    System.out.println("Context started: " + event.getLifecycle());
                } else if (Lifecycle.STOP_EVENT.equals(event.getType())) {
                    System.out.println("Context stopped: " + event.getLifecycle());
                }
            }
        });
    };
}

九、Tomcat容器性能优化

9.1 线程池配置

Tomcat的性能很大程度上取决于其线程池的配置。合理配置线程池可以充分利用系统资源,提高并发处理能力。

9.1.1 线程池核心参数

Tomcat的线程池主要由以下参数控制:

  • maxThreads:最大工作线程数,默认值为200。当请求数量超过该值时,新的请求将被放入等待队列。
  • minSpareThreads:最小空闲线程数,默认值为10。Tomcat启动时会创建这些线程,以准备处理请求。
  • maxConnections:最大连接数,默认值为8192。表示Tomcat可以同时处理的最大连接数,超过该值的连接将被拒绝。
  • acceptCount:最大等待队列长度,默认值为100。当所有工作线程都在处理请求时,新的请求将被放入该队列等待处理。
9.1.2 配置示例

可以通过以下方式配置Tomcat的线程池:

@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
    return factory -> {
        factory.addConnectorCustomizers(connector -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractProtocol) {
                AbstractProtocol<?> protocol = (AbstractProtocol<?>) handler;
                // 设置最大线程数
                protocol.setMaxThreads(500);
                // 设置最小空闲线程数
                protocol.setMinSpareThreads(50);
                // 设置最大连接数
                protocol.setMaxConnections(10000);
                // 设置等待队列长度
                protocol.setAcceptCount(200);
                // 设置线程超时时间
                protocol.setConnectionTimeout(30000);
            }
        });
    };
}

9.2 连接器配置优化

除了线程池配置,还可以对Tomcat的连接器进行优化,以提高性能。

9.2.1 协议选择

Tomcat支持多种协议处理器,包括:

  • HTTP/1.1:默认协议,适用于大多数场景。
  • HTTP/2:性能更高,支持多路复用和二进制分帧,但需要HTTPS支持。
  • AJP:用于与Apache HTTP Server等前端服务器通信。

可以通过以下方式启用HTTP/2支持:

@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
    return factory -> {
        factory.addConnectorCustomizers(connector -> {
            // 启用HTTP/2
            if (connector.getProtocolHandler() instanceof Http11NioProtocol) {
                Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
                protocol.setUseHttp2(true);
            }
        });
    };
}
9.2.2 其他连接器优化参数
  • compression:启用响应内容压缩,可以减少网络传输数据量,提高响应速度。
  • connectionTimeout:设置连接超时时间,避免长时间占用线程资源。
  • keepAliveTimeout:设置持久连接的超时时间,减少连接建立和断开的开销。
  • maxKeepAliveRequests:设置每个持久连接可以处理的最大请求数,超过该值后连接将被关闭。

配置示例:

@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
    return factory -> {
        factory.addConnectorCustomizers(connector -> {
            ProtocolHandler handler = connector.getProtocolHandler();
            if (handler instanceof AbstractHttp11Protocol) {
                AbstractHttp11Protocol<?> protocol = (AbstractHttp11Protocol<?>) handler;
                // 启用压缩
                protocol.setCompression("on");
                protocol.setCompressionMinSize(2048);
                protocol.setCompressableMimeType("text/html,text/xml,text/plain,text/css,text/javascript,application/json");
                // 设置连接超时时间
                protocol.setConnectionTimeout(30000);
                // 设置持久连接超时时间
                protocol.setKeepAliveTimeout(60000);
                // 设置最大持久连接请求数
                protocol.setMaxKeepAliveRequests(100);
            }
        });
    };
}

9.3 JVM参数优化

合理配置JVM参数可以显著提高Tomcat的性能。以下是一些常用的JVM参数优化建议:

9.3.1 内存配置

根据应用的内存需求,合理配置堆内存大小:

java -Xms512m -Xmx2048m -jar myapp.jar
  • -Xms:初始堆大小,建议设置为与-Xmx相同的值,避免堆大小动态调整带来的性能开销。
  • -Xmx:最大堆大小,根据应用的内存需求进行调整。
9.3.2 垃圾回收器选择

根据应用的特性选择合适的垃圾回收器:

java -XX:+UseG1GC -jar myapp.jar
  • -XX:+UseG1GC:使用G1垃圾回收器,适用于大内存、多处理器的环境。
  • -XX:+UseParallelGC:使用并行垃圾回收器,适用于吞吐量优先的应用。
  • -XX:+UseConcMarkSweepGC:使用CMS垃圾回收器,适用于响应时间敏感的应用。
9.3.3 其他JVM参数
  • -XX:MetaspaceSize:元空间初始大小,默认值为21M。
  • -XX:MaxMetaspaceSize:元空间最大大小,默认值为无限制。
  • -XX:+HeapDumpOnOutOfMemoryError:当发生内存溢出时,生成堆转储文件,便于分析问题。

配置示例:

java -Xms1024m -Xmx1024m -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:+UseG1GC -XX:+HeapDumpOnOutOfMemoryError -jar myapp.jar

十、Tomcat容器的安全配置

10.1 SSL/TLS配置

为了保证通信安全,通常需要为Tomcat配置SSL/TLS,启用HTTPS。

10.1.1 生成证书

可以使用keytool工具生成自签名证书:

keytool -genkeypair -alias tomcat -keyalg RSA -keysize 2048 -storetype PKCS12 -keystore keystore.p12 -validity 3650
10.1.2 配置SSL

通过以下方式配置Tomcat使用HTTPS:

@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
    return factory -> {
        factory.addConnectorCustomizers(connector -> {
            // 创建SSL连接器
            connector.setPort(8443);
            connector.setSecure(true);
            connector.setScheme("https");
            Http11NioProtocol protocol = (Http11NioProtocol) connector.getProtocolHandler();
            protocol.setSSLEnabled(true);
            protocol.setKeystoreFile("/path/to/keystore.p12");
            protocol.setKeystorePass("password");
            protocol.setKeyAlias("tomcat");
            // 配置TLS版本和密码套件
            protocol.setSslProtocol("TLS");
            protocol.setCiphers("TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384");
        });
    };
}
10.1.3 HTTP自动重定向到HTTPS

可以配置Tomcat将HTTP请求自动重定向到HTTPS:

@Bean
public WebServerFactoryCustomizer<TomcatServletWebServerFactory> tomcatCustomizer() {
    return factory -> {
        // 添加HTTP连接器
        factory.addAdditionalTomcatConnectors(createHttpConnector());
    };
}
private Connector createHttpConnector() {
    Connector connector = new Connector("org.apache.coyote.http11.Http11NioProtocol");
    connector.setScheme("http");
    connector.setPort(8080);
    connector.setSecure(false);
    // 配置重定向到HTTPS
    connector.setRedirectPort(8443);
    return connector;
}

10.2 安全头设置

通过设置HTTP响应头,可以增强应用的安全性,防止常见的Web攻击。

10.2.1 使用Filter设置安全头

可以创建一个Filter来设置安全头:

@Configuration
public class SecurityHeaderConfig {
    @Bean
    public FilterRegistrationBean<SecurityHeaderFilter> securityHeaderFilter() {
        FilterRegistrationBean<SecurityHeaderFilter> registration = new FilterRegistrationBean<>();
        registration.setFilter(new SecurityHeaderFilter());
        registration.addUrlPatterns("/*");
        registration.setOrder(1);
        return registration;
    }
    public static class SecurityHeaderFilter implements Filter {
        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
                throws IOException, ServletException {
            HttpServletResponse httpResponse = (HttpServletResponse) response;
            // 设置X-Content-Type-Options头,防止MIME类型混淆攻击
            httpResponse.setHeader("X-Content-Type-Options", "nosniff");
            // 设置X-Frame-Options头,防止点击劫持攻击
            httpResponse.setHeader("X-Frame-Options", "DENY");
            // 设置X-XSS-Protection头,启用浏览器的XSS防护机制
            httpResponse.setHeader("X-XSS-Protection", "1; mode=block");
            // 设置Content-Security-Policy头,控制页面可以加载哪些资源
            httpResponse.setHeader("Content-Security-Policy", "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:");
            // 设置Strict-Transport-Security头,强制使用HTTPS
            httpResponse.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
            chain.doFilter(request, response);
        }
    }
}

10.3 防止常见Web攻击

10.3.1 CSRF保护

Spring Security提供了CSRF保护机制,可以通过配置启用:

@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
            .csrf()
                .enable();
    }
}
10.3.2 XSS防护

除了设置安全头,还可以通过输入验证和输出编码来防止XSS攻击。Spring提供了HtmlUtils.htmlEscape方法用于输出编码:

import org.springframework.web.util.HtmlUtils;
public String processUserInput(String input) {
    // 对用户输入进行HTML编码
    return HtmlUtils.htmlEscape(input);
}
10.3.3 SQL注入防护

使用参数化查询或ORM框架(如Hibernate)可以有效防止SQL注入攻击:

@Repository
public class UserRepository {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    public User findByUsername(String username) {
        // 使用参数化查询
        String sql = "SELECT * FROM users WHERE username = ?";
        return jdbcTemplate.queryForObject(sql, new Object[]{username}, new UserRowMapper());
    }
}

十一、Tomcat容器的监控与调试

11.1 内置监控功能

Tomcat提供了一些内置的监控功能,可以帮助开发者了解容器的运行状态。

11.1.1 Manager应用

Tomcat自带的Manager应用可以监控和管理容器。要启用Manager应用,需要在tomcat-users.xml中添加管理用户:

<role rolename="manager-gui"/>
<user username="admin" password="password" roles="manager-gui"/>

然后可以通过http://localhost:8080/manager/html访问Manager应用界面。

11.1.2 JMX监控

Tomcat支持通过JMX(Java Management Extensions)进行监控。可以通过以下JVM参数启用JMX:

java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=9010 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -jar myapp.jar

然后使用JConsole或VisualVM等工具连接到JMX端口,监控Tomcat的各种指标,如线程池状态、内存使用情况等。

11.2 自定义监控指标

可以通过编程方式收集和暴露自定义监控指标:

11.2.1 使用Micrometer

Micrometer是SpringBoot推荐的指标收集框架,可以与各种监控系统集成:

@Service
public class MyService {
    private final Counter requestCounter;
    private final Timer requestTimer;
    @Autowired
    public MyService(MeterRegistry registry) {
        // 创建计数器
        requestCounter = registry.counter("my.service.requests");
        // 创建计时器
        requestTimer = registry.timer("my.service.request.time");
    }
    public void processRequest() {
        // 记录请求计数
        requestCounter.increment();
        // 记录请求处理时间
        requestTimer.record(() -> {
            // 处理请求的业务逻辑
        });
    }
}
11.2.2 暴露自定义端点

可以通过SpringBoot的Actuator模块暴露自定义监控端点:

@Component
@Endpoint(id = "customMetrics")
public class CustomMetricsEndpoint {
    @ReadOperation
    public Map<String, Object> metrics() {
        Map<String, Object> result = new HashMap<>();
        // 添加自定义指标
        result.put("activeConnections", getActiveConnections());
        result.put("requestQueueSize", getRequestQueueSize());
        return result;
    }
    private int getActiveConnections() {
        // 获取活动连接数的逻辑
        return 100;
    }
    private int getRequestQueueSize() {
        // 获取请求队列大小的逻辑
        return 10;
    }
}

11.3 调试技巧

11.3.1 启用调试日志

可以通过配置logging.level属性启用Tomcat的调试日志:

logging.level.org.apache.catalina=DEBUG
logging.level.org.apache.coyote=DEBUG
11.3.2 使用调试代理

可以通过JVM参数启用远程调试:

java -Xdebug -Xrunjdwp:transport=dt_socket,address=8000,server=y,suspend=n -jar myapp.jar

然后在IDE中配置远程调试,连接到8000端口进行调试。

11.3.3 分析线程转储

当应用出现性能问题或死锁时,可以通过以下命令获取线程转储:

jstack <pid> > threaddump.txt

然后分析线程转储文件,找出问题所在。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Android 小码蜂

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值