一、嵌入式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应用的启动过程紧密结合,主要包括以下几个阶段:
- 容器初始化:创建Tomcat实例,加载基础配置。
- 配置加载:读取SpringBoot的配置属性,对Tomcat进行定制化配置。
- Servlet注册:将Spring管理的Servlet、Filter和Listener注册到Tomcat容器中。
- 容器启动:启动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方法主要完成以下工作:
- 初始化监听器:创建
SpringApplicationRunListeners,用于监听应用启动过程中的各个事件(如启动开始、环境准备完成、容器启动完成等)。 - 准备环境:创建和配置
ConfigurableEnvironment,加载配置属性(如application.properties或application.yml中的配置)。 - 创建应用上下文:根据应用类型(Web应用或非Web应用)创建对应的
ApplicationContext实例(如AnnotationConfigServletWebServerApplicationContext)。 - 准备上下文:将环境配置、监听器等信息注入到
ApplicationContext中,并加载自动配置类。 - 刷新上下文:调用
refreshContext方法,完成容器的初始化工作,包括Bean的定义解析、依赖注入等。 - 启动容器:在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实例,并进行基础配置:
- 设置基础目录:为Tomcat指定一个临时目录或用户自定义的目录,用于存储日志、缓存等文件。
- 创建连接器:默认创建一个HTTP连接器,协议为
org.apache.coyote.http11.Http11NioProtocol,并可通过配置进行定制。 - 添加额外连接器:如果用户配置了额外的连接器(如HTTPS连接器),将其添加到Tomcat服务中。
- 禁用自动部署:设置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方法完成以下工作:
- 创建注册对象:调用
ServletContext的addServlet方法,创建一个ServletRegistration.Dynamic对象,用于管理Servlet的注册信息。 - 配置映射路径:将
ServletRegistrationBean中指定的URL映射路径添加到ServletRegistration.Dynamic中。 - 设置初始化参数:如果配置了初始化参数,将其设置到
ServletRegistration.Dynamic中,供Servlet初始化时使用。 - 设置加载顺序:通过
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方法的主要步骤:
- 创建注册对象:调用
ServletContext的addFilter方法,创建FilterRegistration.Dynamic对象。 - 配置映射路径:将
FilterRegistrationBean中指定的URL模式添加到FilterRegistration.Dynamic中,并设置Dispatcher类型(如REQUEST、FORWARD等)。 - 设置初始化参数:与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方法直接调用ServletContext的addListener方法,将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内部一系列组件的初始化和启动,包括:
- 服务组件启动:Tomcat的
Service组件会启动其关联的Connector和Engine组件。Connector负责接收客户端请求,Engine则负责处理请求并将响应返回给Connector。 - 协议处理器初始化:
Connector会初始化对应的ProtocolHandler,如Http11NioProtocol,用于处理HTTP协议相关的网络通信。该处理器会创建并启动网络端点(Endpoint),监听指定端口,等待客户端连接。 - 容器组件启动:
Engine组件会启动其内部的Host和Context容器。Host代表一个虚拟主机,Context代表一个Web应用,它们会初始化相关的Servlet、Filter和Listener组件,为请求处理做好准备。
在Tomcat服务启动后,start方法会通过getPort方法获取实际绑定的端口号。如果在配置中显式指定了端口,直接返回该端口号;如果是动态分配的端口,则从ProtocolHandler的AbstractEndpoint中获取实际绑定的端口。
5.3 启动过程中的关键组件调用
Tomcat启动过程中,核心组件的调用关系如下:
Bootstrap类:Tomcat启动的入口类,其main方法会创建Bootstrap实例,并调用init和start方法。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.");
}
}
}
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;
}
}
Service组件:Service组件启动时,会先启动Connector,再启动Engine。Connector启动过程中,会初始化并启动ProtocolHandler,开始监听端口;Engine启动时,会递归启动其内部的Host和Context容器。
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();
}
}
}
}
ProtocolHandler与Endpoint:以Http11NioProtocol为例,其启动过程中会初始化NioEndpoint。NioEndpoint会创建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();
}
}
Container组件(Engine、Host、Context):这些容器启动时,会依次调用其子容器的启动方法,并初始化相关的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容器启动过程中发生异常,TomcatWebServer的start方法会捕获异常并进行处理:
@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规范的ServletRequest和ServletResponse对象,然后传递给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处理请求的核心逻辑,主要步骤包括:
- 检查文件上传请求:调用
checkMultipart方法判断请求是否为文件上传请求,并进行相应处理。 - 获取Handler:通过
getHandler方法根据请求URL从HandlerMapping中获取对应的Handler(通常是Controller方法)。 - 获取HandlerAdapter:根据Handler的类型,从HandlerAdapter列表中获取对应的HandlerAdapter,用于调用Handler。
- 调用拦截器:在调用Handler之前,先调用HandlerExecutionChain中配置的拦截器的
preHandle方法,进行请求预处理。 - 处理请求:调用HandlerAdapter的
handle方法处理请求,获取ModelAndView对象,包含视图信息和模型数据。 - 调用拦截器:在处理完请求后,调用拦截器的
postHandle方法,进行请求后处理。 - 处理视图:根据
ModelAndView对象,选择合适的视图解析器进行视图解析,并将模型数据渲染到视图中,生成响应内容。 - 异常处理:如果在请求处理过程中发生异常,会调用
processDispatchResult方法进行异常处理,并触发拦截器的afterCompletion方法。
6.3 请求处理流程整合
Tomcat与Spring的请求处理整合流程如下:
- Tomcat接收请求:Tomcat的
Connector监听到客户端请求,ProtocolHandler将请求封装并传递给Container。 - Tomcat分发请求:
Container根据URL映射找到对应的Servlet,在SpringBoot应用中通常是DispatcherServlet。 - Spring处理请求:
DispatcherServlet按照Spring MVC的流程,进行Handler查找、拦截器调用、请求处理和视图渲染。 - 响应返回:Spring生成响应内容后,将响应传递给Tomcat,Tomcat再将响应返回给客户端。
在这个过程中,Tomcat负责网络通信和请求的初步处理,Spring则专注于业务逻辑的处理和视图渲染,两者紧密协作,实现了完整的Web请求处理流程。
七、Tomcat容器的配置定制
7.1 通过配置文件定制
SpringBoot支持通过配置文件(如application.properties或application.yml)对嵌入式Tomcat容器进行配置定制。常见的配置项包括:
- 端口配置:通过
server.port属性设置Tomcat监听的端口号。
server.port=8081
- 路径配置:通过
server.servlet.context-path属性设置应用的上下文路径。
server.servlet.context-path=/myapp
- 连接数配置:通过
server.tomcat.max-connections属性设置Tomcat的最大连接数,通过server.tomcat.max-threads属性设置最大工作线程数。
server.tomcat.max-connections=1000
server.tomcat.max-threads=200
- 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
}
}
在TomcatServletWebServerFactory的customizeConnector方法中,会读取这些配置属性并应用到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的核心组件(如Server、Service、Connector、Container等)都实现了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;
}
}
生命周期状态转换遵循以下规则:
- 初始化阶段:
NEW→INITIALIZING→INITIALIZED - 启动阶段:
INITIALIZED→STARTING_PREP→STARTING→STARTED - 停止阶段:
STARTED→STOPPING_PREP→STOPPING→STOPPED - 销毁阶段:
STOPPED→DESTROYING→DESTROYED
每个状态转换都会触发相应的生命周期事件,监听器可以监听这些事件并执行相应的操作。
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应用上下文刷新完成后,会调用TomcatWebServer的start方法启动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
然后分析线程转储文件,找出问题所在。
2105

被折叠的 条评论
为什么被折叠?



