Yonatan Alon
Yonatan Alon

Reputation: 140

How can I customize spring boot embedded tomcat thread pool?

Tomcat architecture is comprised of the following elements: Server => Service => Engine => Host => Context tomcat architecture

When configuring a standard Tomcat server, we can configure a custom thread pool by specifying the following in our server.xml file: (below is pseudo-code)

<Server>
  <Service name="Catalina">
    <Connector port="8080"/>
    <Executor name="custom-pool" className="my.package.poolImplementation" />
    <Engine name="Catalina" defaultHost="localhost">  
      <Here be more elements />
    </Engine>
  </Service>
</Server>

(specifically, the Executor name="custom-pool" className="my.package.poolImplementation")

How do I configure Spring Boot to allow the same behaviour programmatically ?
(WITHOUT using Spring configuration files)

No matter where i searched, or how hard I tried, I couldn't find any answer or example.
Thanks in advance

Upvotes: 1

Views: 5075

Answers (3)

Yonatan Alon
Yonatan Alon

Reputation: 140

Considering two and a half years have passed since I originally asked this question, I think it is time that I shared our solution for the benefit of anyone that might read this in the future.

We ended up writing a custom component that implements WebServerFactoryCustomizer. Spring Boot will scan for all beans before starting its embedded Tomcat server. If it detects a bean that implements this interface, it will invoke the customize() method and pass the server factory as an argument to the function.

From there, it was straightforward:

package your.pack.age.name;

import org.apache.catalina.core.StandardThreadExecutor;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
import org.springframework.boot.web.embedded.tomcat.TomcatWebServer;
import org.springframework.boot.web.server.WebServerFactoryCustomizer;
import org.springframework.stereotype.Component;

@Component
public class TomcatServerConfig implements WebServerFactoryCustomizer<TomcatServletWebServerFactory> {
    private final StandardThreadExecutor customExecutor;

    public TomcatServerConfig() {
        this.customExecutor = YourExecutorImplementation();
    }

    @Override
    public void customize(TomcatServletWebServerFactory factory) {
        /*This web server is the Tomcat server embedded in Spring Boot*/
        TomcatWebServer webServer = (TomcatWebServer)factory.getWebServer()
        webServer.getTomcat().getService().addExecutor(this.customExecutor);
    }
}

(The actual code we used was simplified here, for the sake of a clear answer)

It is also worth noting that similar code needs to be written for the Tomcat Connectors, using TomcatConnectorCustomizer:

package your.pack.age.name;

import org.apache.catalina.connector.Connector;
import org.springframework.boot.web.embedded.tomcat.TomcatConnectorCustomizer;
import org.springframework.stereotype.Component;

@Component
public class TomcatConnectorConfig implements TomcatConnectorCustomizer {
    private final StandardThreadExecutor customExecutor;

    public TomcatConnectorConfig() {
        this.customExecutor = YourExecutorImplementation();
    }

    @Override
    public void customize(Connector connector) {
         connector.getProtocolHandler().setExecutor(this.customExecutor);
    }
}

For convenience, I am adding a skeletal custom implementation of a thread executor:

import org.apache.catalina.LifecycleException;
import org.apache.catalina.core.StandardThreadExecutor;

public class HTTPThreadExecutor extends StandardThreadExecutor {
    public HTTPThreadExecutor() {
        super();

        /* Custom constructor code here */
    }

    @Override
    protected void startInternal() throws LifecycleException {
        super.startInternal();

        /* Any work you need done upon startup */
    }
}

Upvotes: 2

danidacila
danidacila

Reputation: 157

I needed to customize Tomcat also. I ended up with a code like this:

@Component
public class TomcatCustomizer extends TomcatServletWebServerFactory {
  @Override
  protected void postProcessContext(Context context) {
    Engine engine = (Engine) context.getParent().getParent();
    Service service = engine.getService();
    Server server = service.getServer();
    Connector connector = service.findConnectors()[0];
  }
}

You can then set different properties of the server, service, engine, connector. From the object service you can also access the executor and change it. This part I never tried. Whatever you change it will override and complete the spring-boot configuration, you will not loose the spring-boot config.

As Yonatan mentioned one can add an executor with service.addExecutor(...) there is also a method from removing an executor.

I needed this kind of detailed access to the configuration because I needed to configure the server.getGlobalNamingResources(). Also to add a JAASRealm and a Valve. I do not see how one could achieve complete config access with just the customize method.

Upvotes: 0

Joseph
Joseph

Reputation: 160

I looked up some source code (see TomcatServletWebServerFactory.java/ServletWebServerFactoryConfiguration.java) and found a way to do that.

@Bean
public TomcatProtocolHandlerCustomizer<?> tomcatProtocolHandlerCustomizer() {
    return protocolHandler -> {
        protocolHandler.setExecutor(...);
    };
}

Upvotes: 4

Related Questions