学习笔记之Tomcat

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文件,文件结构与整体框架类似)

091248_NtTm_150089.png

  • Server :传统意义上的 Tomcat
  • Service: 是在 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.StandardContextsetter方法

该方式不支持动态部署,建议考虑在生产环境中使用。(效率)

方法三:独立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.xmlROOT.xml,则路径为 “/”

该方式可以实现热部署,因此建议在开发环境中使用。(效率)

I/O连接器

1545361332158

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;
}

Bootstrapstart()方法首先判断 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.Http11NioProtocolstart()方法实现在父类 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)

1545920509873

可以通过命令行,使用java命令执行该jar包,tomcat会自动启动,并且通过浏览器访问定义的路径可以正常访问

1545920915678

将文件 tomcat-1.0-SNAPSHOT-war-exec.jar 解压之后的项目目录为:

1545921091155

该文件中,conf目录存放的是项目的配置文件(web.xml),javax目录中存放的是相关的 jar包,META-INF目录中存放的醒目运行的相关信息,该目录的war文件和不使用tomcat插件生成的war包是相同的。

下面打开 META-INF/MANIFEST.MF 文件:

1545921394356

Main-Class:表示项目的启动类,可以通过命令行执行该类启动该项目:(一定要到解压后的目录下执行)

1545921760193

此外,只要有Main-Class的存在,其实即使将 jar 格式改为 war 格式同样可以执行(直接修改文件扩展名)。因为两种文件的实现都是实现了 ZipFile。

1545922028619

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

动态资源:应用 ServletJspServlet

如果不需要对静态资源的加载可以将 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目录下生成编译后的文件

1545968826236

首页

<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 &quot;%r&quot; %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 {
        
    ......
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值