I am trying to understand better Spring Framework and I would like to understand in what way, ServletDispatcher interacts with a Servlet Container, in this case Tomcat, but using programatic approach.
Given the following example:
public class Main {
public static void main(String[] args) throws Exception {
int port = 8081; // specify the desired port
Tomcat tomcat = new Tomcat();
tomcat.setBaseDir("."); // Set the base directory (optional)
// Set up the connector
Connector connector = new Connector();
connector.setPort(port);
tomcat.getService().addConnector(connector);
// Add a servlet context and configure the Spring Boot application
Context context = tomcat.addContext("", null);
// Create and configure the DispatcherServlet
DispatcherServlet dispatcherServlet = new DispatcherServlet();
dispatcherServlet.setContextClass(AnnotationConfigWebApplicationContext.class); // Use AnnotationConfigWebApplicationContext
// Manually create an AnnotationConfigApplicationContext and register the configuration class
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfig.class);
// Set the DispatcherServlet's application context
dispatcherServlet.setApplicationContext(applicationContext);
// Register the DispatcherServlet as a servlet
Tomcat.addServlet(context, "dispatcherServlet", dispatcherServlet);
context.addServletMappingDecoded("/", "dispatcherServlet");
tomcat.start();
tomcat.getServer().await();
}
@RestController
public static class MyRestController {
@GetMapping("/hello")
public String hello() {
return "Hello, Spring Boot!";
}
@PostConstruct
private void postConstruct() {
System.out.println("Running RestController");
}
}
@Configuration
@ComponentScan(basePackages = "com.mycompany.app")
public static class SpringConfig {
@PostConstruct
private void postConstruct() {
System.out.println("Running");
}
}
}
when I make a http request:
curl http://localhost:8081/hello
then I notice that Spring Container is loaded but when I execute a http request, DispatcherServlet is not able to pass the request to the RestController. What is missing in my configuration? Because RestController was loaded in the Spring Container.
Logs:
Running
Running RestController
Aug 14, 2023 5:21:03 PM org.apache.coyote.AbstractProtocol init
INFO: Initializing ProtocolHandler ["http-nio-8081"]
Aug 14, 2023 5:21:03 PM org.apache.catalina.core.StandardService startInternal
INFO: Starting service [Tomcat]
Aug 14, 2023 5:21:03 PM org.apache.catalina.core.StandardEngine startInternal
INFO: Starting Servlet engine: [Apache Tomcat/10.1.11]
Aug 14, 2023 5:21:03 PM org.apache.coyote.AbstractProtocol start
INFO: Starting ProtocolHandler ["http-nio-8081"]
Aug 14, 2023 5:21:09 PM org.apache.catalina.core.ApplicationContext log
INFO: Initializing Spring DispatcherServlet 'dispatcherServlet'
17:21:09.549 [http-nio-8081-exec-1] INFO org.springframework.web.servlet.DispatcherServlet -- Initializing Servlet 'dispatcherServlet'
17:21:09.999 [http-nio-8081-exec-1] INFO org.springframework.web.servlet.DispatcherServlet -- Completed initialization in 447 ms
17:21:10.021 [http-nio-8081-exec-1] WARN org.springframework.web.servlet.PageNotFound -- No mapping for GET /hello
Note: I know that I could use Spring Boot directly, but I trying to learn how Tomcat connect with Spring Framework :)
Many thanks for your comment, I was able to fix it:
public static void main(String[] args) throws Exception {
Connector connector = new Connector();
connector.setPort(8080);
Tomcat tomcat = new Tomcat();
tomcat.getService().addConnector(connector);
File base = new File(System.getProperty("java.io.tmpdir"));
Context context = tomcat.addContext("", base.getAbsolutePath());
AnnotationConfigWebApplicationContext appContext = new AnnotationConfigWebApplicationContext();
appContext.register(SpringConfig.class);
appContext.refresh();
DispatcherServlet dispatcherServlet = new DispatcherServlet(appContext);
Wrapper wrapper = context.createWrapper();
wrapper.setName("dispatcherServlet");
wrapper.setServlet(dispatcherServlet);
context.addChild(wrapper);
wrapper.setLoadOnStartup(1);
wrapper.addMapping("/");
try {
tomcat.start();
tomcat.getServer().await();
} catch (LifecycleException e) {
e.printStackTrace();
}
}
@RestController
public static class MyRestController {
@GetMapping("/hello")
public String hello() {
return "Hello world";
}
@PostConstruct
private void postConstruct() {
System.out.println("Running RestController");
}
}
@Configuration
@ComponentScan(basePackages = "com.mycompany.app")
public static class SpringConfig {
@PostConstruct
private void postConstruct() {
System.out.println("Running");
}
}
}
It is pretty interesting how Spring Boot help developers minimizing the effort to run a web application with few annotations.
You can see the POC here: https://github.com/jabrena/servlet-archaeology