javaspringspring-mvctomcatspring-framework-beans

Why DispatcherServlet is not passing the request to RestController?


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

Source code: https://github.com/jabrena/servlet-archaeology/blob/feature/servlet3/myServlet3/src/main/java/com/mycompany/app/Main.java

Note: I know that I could use Spring Boot directly, but I trying to learn how Tomcat connect with Spring Framework :)


Solution

  • 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