Tomcat
(个人学习笔记,如有错误欢迎指正!!!)
目录结构
conf目录
catalina.policy
:Tomcat 安全策略文件,控制JVM相关权限,具体参考java.security.Permission
类。
catalina.properties
:行为控制文件,比如common classloader
。
logging.properties
:日志配置文件,JDK Logging
server.xml
:Tomcat Server配置文件
GlobalNamingResources
:全局 JNDI 资源
context.xml
:全局的 context 配置文件
tomcat-users.xml
:角色配置文件,( Realm 文件实现方式)
web.xml
:Servlet 标准的 web.xml 部署文件,Tomcat 默认实现部分配置:
org.apache.catalina.servlets.DefaultServlet
org.apache.jasper.servlet.JspServlet
lib目录
Tomcat 存放公用类库
ecj-4.9.jar
:eclipse Java 编译器
jasper.jar
:JSP 编译器
logs目录
localhost.2018-12-19.log
:当 Tomcat 应用起不来的时候,多看该文件,比如:类冲突
NoClassDefFoundClassError
ClassNotFoundException
catalina.2018-12-19.log
:控制台输出,System.out
可以外置(setOut
方法)
webapps目录
简化 web 应用部署的方式
架构图
(可以参考server.xml文件,文件结构与整体框架类似)
Server
:传统意义上的 TomcatService
: 是在 Container 和 Connector 外面包了一层,讲它们组装在一起Connector:
主要任务是负责接收浏览器的发过来的 tcp 连接请求,创建一个 Request 和 Response 对象分别用于和请求端交换数据,然后会产生一个线程来处理这个请求并把产生的 Request 和 Response 对象传给处理这个请求的线程Container
:Container 是容器的父接口,所有子容器都必须实现这个接口,有四个子容器组件构成,分别是:Engine、Host、Context、Wrapper。Engine
:Engine 代表一个完整的 Servlet 引擎。Host
:一个 Host 在 Engine 中代表一个虚拟主机,这个虚拟主机的作用就是运行多个应用,它负责安装和展开这些应用,并且标识这个应用以便能够区分它们。Context
:Context 代表 Servlet 的 Context,它具备了 Servlet 运行的基本环境,理论上只要有 Context 就能运行 Servlet 了。简单的 Tomcat 可以没有 Engine 和 Host。Wrapper
: Wrapper 代表一个 Servlet,它负责管理一个 Servlet,包括的 Servlet 的装载、初始化、执行以及资源回收。
部署 web 应用
方法一:直接放置在webapps
目录
直接将war包放入到webapps
目录
方法二:修改 conf/server.xml
添加 context
元素
<Context docBase="${webAppAbsolutePath}" path="/" reloadable="true"/>
熟悉配置元素可以参考org.apache.catalina.core.StandardContext
setter方法
该方式不支持动态部署,建议考虑在生产环境中使用。(效率)
方法三:独立context.xml
配置文件
首先注意 conf/catalina/localhost
独立 context XML 配置文件路径:
${TOMCAT_HOME}/conf/${Engine.name}/${HOST.name}
+ ${ContextPath}.xml
其中 Tomcat 默认的 ${Engine.name}
为 catalina
,${HOST.name}
为 localhost
通过该方法配置的 context 的 path 属性失效。
如果 ContextPath.xml
为 ROOT.xml
,则路径为 “/”
。
该方式可以实现热部署,因此建议在开发环境中使用。(效率)
I/O连接器
NIO不表示所有读取数据的部分全部为非阻塞的。
启动过程
首先 Tomcat 服务器是通过 startup.bat
启动的,该脚本执行的便是 Bootstrap
类中的 main()
方法,在该方法中首先创建 Bootstrap
对象,然后调用 init()
方法进行初始化,之后对输入的命令的参数进行分析,调用 start()
方法。
public static void main(String args[]) {
if (daemon == null) {
// Don't set daemon until init() has completed
Bootstrap bootstrap = new Bootstrap();
try {
bootstrap.init();
} catch (Throwable t) {
......
daemon = bootstrap;
} else {
......
}
try {
String command = "start";
if (args.length > 0) {
command = args[args.length - 1];
}
if (command.equals("startd")) {
args[args.length - 1] = "start";
daemon.load(args);
daemon.start();
} else if (command.equals("stopd")) {
......
}
}
Bootstrap
中的初始化方法主要的工作是通过java的反射机制加载 org.apache.catalina.startup.Catalina
类,并且通过构造函数生成对象实例,
public void init()
throws Exception
{
setCatalinaHome();
setCatalinaBase();
initClassLoaders();
Thread.currentThread().setContextClassLoader(catalinaLoader);
SecurityClassLoad.securityClassLoad(catalinaLoader);
if (log.isDebugEnabled())
log.debug("Loading startup class");
Class<?> startupClass =
catalinaLoader.loadClass
("org.apache.catalina.startup.Catalina");
Object startupInstance = startupClass.newInstance();
if (log.isDebugEnabled())
log.debug("Setting startup class properties");
String methodName = "setParentClassLoader";
Class<?> paramTypes[] = new Class[1];
paramTypes[0] = Class.forName("java.lang.ClassLoader");
Object paramValues[] = new Object[1];
paramValues[0] = sharedLoader;
Method method =
startupInstance.getClass().getMethod(methodName, paramTypes);
method.invoke(startupInstance, paramValues);
catalinaDaemon = startupInstance;
}
Bootstrap
的 start()
方法首先判断 Catalina
的实例是否为 null ,如果为 null,进行初始化,不为 null,通过反射机制调用实例的 start()
方法。
public void start()
throws Exception {
if( catalinaDaemon==null ) init();
Method method = catalinaDaemon.getClass().getMethod("start", (Class [] )null);
method.invoke(catalinaDaemon, (Object [])null);
}
Catalina
类的 start()
方法主要工作是先判断 server是否为空,如果为空,便通过load()
方法进行加载,在该方法中,会创建一个 Digester
对象,该对象是一个 xml 文件的解析器,该解析器会分析 tomcat 的 server.xml文件,并且根据该文件中的内容实例化 server,engine,host,connector等内容。之后调用 server的 start()
方法。
public void start() {
if (getServer() == null) {
load();
}
......
try {
getServer().start();
}
......
if (await) {
await();
stop();
}
}
Server
接口的默认实现类为 StandardServer
,并且根据生命周期管理的接口,最终会调用 startInternal()
方法,在该方法中会循环调用 service 的 start()
方法。
protected void startInternal() throws LifecycleException {
......
synchronized (services) {
for (int i = 0; i < services.length; i++) {
services[i].start();
}
}
}
Service
接口的默认实现类为 StandardService
,最终会调用 startInternal()
方法,启动 container,启动所有的 Executor,同时启动 所有的 Connector。
protected void startInternal() throws LifecycleException {
......
if (container != null) {
synchronized (container) {
container.start();
}
}
synchronized (executors) {
for (Executor executor: executors) {
executor.start();
}
}
synchronized (connectors) {
for (Connector connector: connectors) {
try {
if (connector.getState() != LifecycleState.FAILED) {
connector.start();
}
}
......
}
}
}
Connector
中的startInternal会启动protocolHandler
,该接口有非常多的实现类,下面着重讲 org.apache.coyote.http11.Http11NioProtocol
protected void startInternal() throws LifecycleException {
......
try {
protocolHandler.start();
} catch (Exception e) {
......
}
mapperListener.start();
}
org.apache.coyote.http11.Http11NioProtocol
的 start()
方法实现在父类 org.apache.coyote.AbstractProtocol
中,该方法的主要工作是 启动 AbstractEndpoint
,该接口也有许多的实现,下面主要讲 NioEndpoint
。
public void start() throws Exception {
if (getLog().isInfoEnabled())
getLog().info(sm.getString("abstractProtocolHandler.start",
getName()));
try {
endpoint.start();
} catch (Exception ex) {
getLog().error(sm.getString("abstractProtocolHandler.startError",
getName()), ex);
throw ex;
}
}
start()
方法实现在 AbstractEndpoint
抽象类中,先调用了 bind()
方法,然后调用 startInternal()
方法。
public final void start() throws Exception {
if (bindState == BindState.UNBOUND) {
bind();
bindState = BindState.BOUND_ON_START;
}
startInternal();
}
在 bind()
方法中,绑定ip地址和端口,并且将serverSock设置为阻塞
public void bind() throws Exception {
......
serverSock.socket().bind(addr,getBacklog());
serverSock.configureBlocking(true); //mimic APR behavior
......
selectorPool.open();
}
主要创建 poller 线程,之后通过 startAcceptorThreads()
方法启动 接收线程。
public void startInternal() throws Exception {
......
pollers = new Poller[getPollerThreadCount()];
for (int i=0; i<pollers.length; i++) {
pollers[i] = new Poller();
Thread pollerThread = new Thread(pollers[i], getName() + "-ClientPoller-"+i);
pollerThread.setPriority(threadPriority);
pollerThread.setDaemon(true);
pollerThread.start();
}
startAcceptorThreads();
}
}
该方法主要是创建接收线程并且启动线程。
protected final void startAcceptorThreads() {
int count = getAcceptorThreadCount();
acceptors = new Acceptor[count];
for (int i = 0; i < count; i++) {
acceptors[i] = createAcceptor();
Thread t = new Thread(acceptors[i], getName() + "-Acceptor-" + i);
t.setPriority(getAcceptorThreadPriority());
t.setDaemon(getDaemon());
t.start();
}
}
第一个 Acceptor
类是 org.apache.tomcat.util.net.AbstractEndpoint
中的内部类,实现了Runnable
接口,第二个 Acceptor
类是 org.apache.tomcat.util.net.NioEndpoint
的内部类,继承了前面的 Acceptor
类,并且该类的 run()
方法主要是接受请求。
public abstract static class Acceptor implements Runnable {
......
}
protected class Acceptor extends AbstractEndpoint.Acceptor {
public void run() {
while (running) {
......
SocketChannel socket = null;
try {
// Accept the next incoming connection from the server
// socket
socket = serverSock.accept();
} catch (IOException ioe) {
......
if (running && !paused) {
if (!setSocketOptions(socket)) {
......
}
......
}
}
}
该方法构造 NioChannel
对象,并且将该队行注册到轮询线程中。
protected boolean setSocketOptions(SocketChannel socket) {
// Process the connection
try {
//disable blocking, APR style, we are gonna be polling it
socket.configureBlocking(false);
Socket sock = socket.socket();
socketProperties.setProperties(sock);
NioChannel channel = nioChannels.poll();
......
getPoller0().register(channel);
} catch (Throwable t) {
......
}
return true;
}
Poller
类是 org.apache.tomcat.util.net.NioEndpoint
的内部类,该类实现了 Runnable
接口,其中 run()
方法中通过 selector.select()
选择处理的请求。
public class Poller implements Runnable {
public void run() {
// Loop until destroy() is called
while (true) {
......
try {
if ( !close ) {
if (wakeupCounter.getAndSet(-1) > 0) {
//if we are here, means we have other stuff to do
//do a non blocking select
keyCount = selector.selectNow();
} else {
keyCount = selector.select(selectorTimeout);
}
......
Iterator<SelectionKey> iterator =
keyCount > 0 ? selector.selectedKeys().iterator() : null;
// Walk through the collection of ready keys and dispatch
// any active event.
while (iterator != null && iterator.hasNext()) {
SelectionKey sk = iterator.next();
KeyAttachment attachment = (KeyAttachment)sk.attachment();
// Attachment may be null if another thread has called
// cancelledKey()
if (attachment == null) {
iterator.remove();
} else {
attachment.access();
iterator.remove();
processKey(sk, attachment);
}
}//while
......
}
protected boolean processKey(SelectionKey sk, KeyAttachment attachment) {
boolean result = true;
try {
......
if (!processSocket(channel, SocketStatus.OPEN, true))
processSocket(channel, SocketStatus.DISCONNECT, true);
} else {
//future placement of a WRITE notif
if (!processSocket(channel, SocketStatus.OPEN, true))
processSocket(channel, SocketStatus.DISCONNECT, true);
}
} else {
result = false;
}
} else {
......
}
return result;
}
}
根据选择的SelectionKey,通过 processKey()
方法调用 processSocket()
将请求交给处理线程执行。
public boolean processSocket(NioChannel socket, SocketStatus status, boolean dispatch) {
try {
KeyAttachment attachment = (KeyAttachment)socket.getAttachment(false);
if (attachment == null) {
return false;
}
attachment.setCometNotify(false); //will get reset upon next reg
SocketProcessor sc = processorCache.poll();
if ( sc == null ) sc = new SocketProcessor(socket,status);
else sc.reset(socket,status);
if ( dispatch && getExecutor()!=null ) getExecutor().execute(sc);
else sc.run();
} catch (RejectedExecutionException rx) {
log.warn("Socket processing request was rejected for:"+socket,rx);
return false;
} catch (Throwable t) {
ExceptionUtils.handleThrowable(t);
// This means we got an OOM or similar creating a thread, or that
// the pool and its queue are full
log.error(sm.getString("endpoint.process.fail"), t);
return false;
}
return true;
}
Tomcat Maven 插件
Tomcat 7 是 Servlet 3.0 的实现,ServletContainerInitializer
首先在 <plugin>
中添加tomcat maven插件的相关依赖,以及相关的配置
<plugin>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
<executions>
<execution>
<id>tomcat-run</id>
<goals>
<goal>exec-war-only</goal>
</goals>
<phase>package</phase>
<configuration>
<path>/</path>
</configuration>
</execution>
</executions>
</plugin>
然后运行mvn 命令 mvn -Dmaven.test.skip -U clean package
对项目进行打包,传统如果不加tomcat插件指挥在target目录中创建 tomcat.war 文件,添加插件之后,会生成一个 tomcat-1.0-SNAPSHOT-war-exec.jar(注意此时项目中的 pom.xml 文件中 <package>
中配置的为 war)
可以通过命令行,使用java命令执行该jar包,tomcat会自动启动,并且通过浏览器访问定义的路径可以正常访问
将文件 tomcat-1.0-SNAPSHOT-war-exec.jar 解压之后的项目目录为:
该文件中,conf目录存放的是项目的配置文件(web.xml),javax目录中存放的是相关的 jar包,META-INF目录中存放的醒目运行的相关信息,该目录的war文件和不使用tomcat插件生成的war包是相同的。
下面打开 META-INF/MANIFEST.MF 文件:
Main-Class:表示项目的启动类,可以通过命令行执行该类启动该项目:(一定要到解压后的目录下执行)
此外,只要有Main-Class的存在,其实即使将 jar 格式改为 war 格式同样可以执行(直接修改文件扩展名)。因为两种文件的实现都是实现了 ZipFile。
Tomcat API
首先需要导入tomcat的相关依赖,里面的各项数据其实与插件的各项数据是相同的。
<dependency>
<groupId>org.apache.tomcat.maven</groupId>
<artifactId>tomcat7-maven-plugin</artifactId>
<version>2.1</version>
</dependency>
对于tomcat API的使用,我们可以先分析一下源码,根据前面 tomcat maven插件的分析,可以知道tomcat 的引导类为 org.apache.tomcat.maven.runner.Tomcat7RunnerCli
(注意该类不是常规情况使用 startup.bat
启动所执行的类,而是 org.apache.catalina.startup.Bootstrap
)。该类中存在 main
方法:
public static void main( String[] args )
throws Exception
{
CommandLineParser parser = new GnuParser();
CommandLine line = null;
try
{
line = parser.parse( Tomcat7RunnerCli.options, args );
}
......
Tomcat7Runner tomcat7Runner = new Tomcat7Runner();
tomcat7Runner.runtimeProperties = buildStandaloneProperties();
......
// here we go
tomcat7Runner.run();
}
该 main
方法主要的工作是通过命令行解析参数,配置一些信息,创建一个 Tomcat7Runner
对象,之后继续配置一些信息,然后调用该对象的 run
方法。run
方法如下。主要的工作是创建需要的目录,创建 tomcat 实例,创建service,host,以及添加相应的connector,同时加载指定目录下的项目。
public void run()
throws Exception
{
......
// create tomcat various paths
new File( extractDirectory, "conf" ).mkdirs();
new File( extractDirectory, "logs" ).mkdirs();
new File( extractDirectory, "webapps" ).mkdirs();
new File( extractDirectory, "work" ).mkdirs();
File tmpDir = new File( extractDirectory, "temp" );
tmpDir.mkdirs();
System.setProperty( "java.io.tmpdir", tmpDir.getAbsolutePath() );
System.setProperty( "catalina.base", extractDirectoryFile.getAbsolutePath() );
System.setProperty( "catalina.home", extractDirectoryFile.getAbsolutePath() );
// start with a server.xml
if ( serverXmlPath != null || useServerXml() )
{
......
}
else
{
tomcat = new Tomcat()
......
tomcat.getHost().setAppBase( new File( extractDirectory, "webapps" ).getAbsolutePath() );
......
if ( httpPort > 0 )
{
Connector connector = new Connector( connectorHttpProtocol );
connector.setPort( httpPort );
......
tomcat.getService().addConnector( connector );
tomcat.setConnector( connector );
}
......
// add webapps
for ( Map.Entry<String, String> entry : this.webappWarPerContext.entrySet() )
{
......
baseDir = new File( extractDirectory, "webapps/ROOT.war" ).getAbsolutePath();
context = tomcat.addWebapp( "", baseDir );
}
......
tomcat.start();
Runtime.getRuntime().addShutdownHook( new TomcatShutdownHook() );
}
waitIndefinitely();
}
可以根据上面源代码的过程来使用 API 自定义tomcat:
public class EmbeddedTomcatServer {
public static void main(String[] args) throws LifecycleException, ServletException {
String classesPath = System.getProperty("user.dir")+ File.separator+"tomcat"
+ File.separator+"target" + File.separator + "classes";
System.out.println(classesPath);
//创建Tomcat
Tomcat tomcat = new Tomcat();
tomcat.setPort(12345);//设置端口号
//添加 host,get方法会判断是否host是否为空,如果为空自动创建一个
Host host = tomcat.getHost();
host.setName("localhost");
host.setAppBase("webapp");
String webapp = System.getProperty("user.dir")+ File.separator+"tomcat"
+ File.separator+"src" + File.separator + "main" + File.separator + "webapp";
String contextPath = "/";
Context context = tomcat.addWebapp(contextPath,webapp);//设置查找class文件的路径
if(context instanceof StandardContext){
StandardContext standardContext = (StandardContext)context;
standardContext.setDefaultWebXml(webapp + File.separator + "conf/web.xml");//设置查找配置文件的路径
Wrapper wrapper = tomcat.addServlet(contextPath,"demoServlet",new IndexServlet());//将wrapper添加到指定路径的context容器中,并且指定servlet实例和名称
wrapper.addMapping("/demo");//添加映射
}
//获取service并且添加connector,并且对connector进行相应的设置,使用这样访问9090接口和12345接口均可以进行通信,因为service可以包括多个connector
Service service = tomcat.getService();
Connector connector = new Connector();
connector.setPort(9090);
connector.setURIEncoding("UTF-8");
connector.setProtocol("HTTP/1.1");
service.addConnector(connector);
tomcat.start();//开始
tomcat.getServer().await();//阻塞进程,防止主线程执行完
}
}
Spring Boot 嵌入式 Tomcat
注解 @SpringBootApplication
,该注解包括多个注解,包括springboot的配置,自动装配,自动扫描等
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {
SpringApplication.run(SpringboottomcatApplication.class, args);
该条语句本身就是运行配置类,还可以使用如下方法定义运行对各配置类,其中 TomcatConfiguration
也是配置类
new SpringApplicationBuilder()
.sources(SpringboottomcatApplication.class,TomcatConfiguration.class)
.run(args);
对于扩展嵌入式Tomcat,可以定义一个配置类,实现WebServerFactoryCustomizer
接口(spring boot 2.0才有这个接口),然后重写该方法,可以根据factory的类型调用不同的方法,来自定义嵌入式容器。
@Configuration
public class TomcatConfiguration implements WebServerFactoryCustomizer {
@Override
public void customize(WebServerFactory factory) {
System.err.println(factory.getClass());
}
}
配置与调优
web.xml
tomcat需要对静态资源的处理和动态资源的处理
静态资源:DefaultServlet
动态资源:应用 Servlet
, JspServlet
如果不需要对静态资源的加载可以将 web.xml 文件中的关于 DefaultServlet
的配置移除掉(.html)
<servlet>
<servlet-name>default</servlet-name>
<servlet-class>org.apache.catalina.servlets.DefaultServlet</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>0</param-value>
</init-param>
<init-param>
<param-name>listings</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
如果不需要加载 jsp 文件,可以将 web.xml 文件中关于 JspServlet
的配置移除掉(.jsp)
<servlet>
<servlet-name>jsp</servlet-name>
<servlet-class>org.apache.jasper.servlet.JspServlet</servlet-class>
<init-param>
<param-name>fork</param-name>
<param-value>false</param-value>
</init-param>
<init-param>
<param-name>xpoweredBy</param-name>
<param-value>false</param-value>
</init-param>
<load-on-startup>3</load-on-startup>
</servlet>
如果需要 JspServlet
对jsp文件进行处理,可以对 JspServlet
的参数进行相应的配置,具体可以参考web.xml文件中注释中的描述,例如,因为jsp文件在执行的过程中需要进行编译,影响性能,可以采用预编译的方式,这时可以设置 JspServlet
的参数 development=false
,这样在执行的过程中就不会对jsp文件进行编译,但是需要预编译,需要添加插件:
<plugin>
<groupId>org.apache.sling</groupId>
<artifactId>jspc-maven-plugin</artifactId>
<version>2.1.0</version>
<executions>
<execution>
<goals>
<goal>jspc</goal>
</goals>
</execution>
</executions>
</plugin>
这样在对文件打包的时候会自动对jsp文件进行编译,但是使用该插件时,必须将需要编译jsp文件放置到 src/main/scripts
目录中。
执行 mvn clean package
命令后,会在 target/org/apache/jsp
目录下生成编译后的文件
首页
<welcome-file-list>
<welcome-file>index.html</welcome-file>
<welcome-file>index.htm</welcome-file>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
如果配置中存在该部分配置,当访问根路径时,会寻找这些文件,如果不需要这些内容可以将这些配置移除
扩展名映射
<mime-mapping>
<extension>html</extension>
<mime-type>text/html</mime-type>
</mime-mapping>
表示如果接收的为.html文件,会自动返回 text/html
的内容类型
Servlet应用默认的 web.xml文件时 conf/web.xml 文件,如果应用自己定义了web.xml文件,最终会将两者合并
server.xml
<Valve className="org.apache.catalina.valves.AccessLogValve" directory="logs"
prefix="localhost_access_log" suffix=".txt"
pattern="%h %l %u %t "%r" %s %b" />
valve 的执行过程会消耗执行时间,所以如果不需要valve可以将valve的配置移除
关闭自动重载,将 reloadable
设置为 false
<Context docBase="${webAppAbsolutePath}" path="/" reloadable="false"/>
改变线程池的线程数量:
<Executor name="tomcatThreadPool" namePrefix="catalina-exec-"
maxThreads="150" minSpareThreads="4"/>
<Connector executor="tomcatThreadPool"
port="8080" protocol="HTTP/1.1"
connectionTimeout="20000"
redirectPort="8443" />
org.apache.catalina.Executor
实际上继承的是 java.util.concurrent.Executor
org.apache.catalina.core.StandardThreadExecutor
实现了该接口,并且设置最大最小线程数量的方法实际上是调用 org.apache.tomcat.util.threads.ThreadPoolExecutor
的方法,并且该类继承自 java.util.concurrent.ThreadPoolExecutor
protected ThreadPoolExecutor executor = null;
public void setMaxThreads(int maxThreads) {
this.maxThreads = maxThreads;
if (executor != null) {
executor.setMaximumPoolSize(maxThreads);
}
}
public void setMinSpareThreads(int minSpareThreads) {
this.minSpareThreads = minSpareThreads;
if (executor != null) {
executor.setCorePoolSize(minSpareThreads);
}
}
对Spring Boot 嵌入式 tomcat 的配置,可以通过在 application.properties
文件中设置相应的参数来对tomcat进行相应的配置,通过 org.springframework.boot.autoconfigure.web.ServerProperties
进行加载。
server.tomcat.maxthreads = 99
server.tomcat.minSpareThreads = 9
@ConfigurationProperties(prefix = "server", ignoreUnknownFields = true)
public class ServerProperties {
......
public static class Tomcat {
......
}