Stage 2: let a controller handle a request

Keep the same HTML files and add one Java class. Visiting /aboutus now runs our Java method before serving the page.

@Controller lets Spring discover this class as a web controller. @GetMapping connects GET /aboutus to aboutUs(). The println message appears in the server terminal, not in the browser. forward:/aboutus.html is an internal handoff to the static file; the browser address stays /aboutus. No template engine is needed. /aboutus.html remains directly accessible and bypasses this method.

Run this project

Requires JDK 21+ and Maven 3.9+. Extract the ZIP and open a terminal in spring-controller, where pom.xml is located. First check java -version and mvn -version. Internet is needed for the first dependency download.

Set an available port in src/main/resources/application.properties. The default is 8081. Stop the previous stage with Ctrl+C before starting this one, or choose another port.

mvn spring-boot:run

Open http://localhost:8081/ in your browser. Replace the port if you changed it.

Try it

Open /aboutus and watch the server terminal. Then open /aboutus.html directly and observe that our message is not printed for that direct file request.

GET /aboutus -> aboutUs() -> forward:/aboutus.html -> same HTML page

Complete files

pom.xml

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.1.1</version>
    <relativePath/>
  </parent>
  <groupId>com.edupoly</groupId>
  <artifactId>spring-controller</artifactId>
  <version>1.0.0</version>
  <properties>
    <java.version>21</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webmvc</artifactId>
    </dependency>
</dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>

src/main/java/com/edupoly/hello/HelloApplication.java

package com.edupoly.hello;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class HelloApplication {
    public static void main(String[] args) {
        SpringApplication.run(HelloApplication.class, args);
    }
}

src/main/resources/application.properties

# Change this number if the port is already in use.
server.port=8081

src/main/resources/static/aboutus.html

<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><title>About Us</title></head>
<body>
  <h1>About Us</h1>
  <p>This is our first Spring website.</p>
  <a href="/">Home</a>
</body>
</html>

src/main/resources/static/index.html

<!doctype html>
<html lang="en">
<head><meta charset="UTF-8"><title>Home</title></head>
<body>
  <h1>Hello World</h1>
  <a href="/aboutus.html">About Us</a>
</body>
</html>

src/main/java/com/edupoly/hello/PageController.java

package com.edupoly.hello;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class PageController {
    @GetMapping("/aboutus")
    public String aboutUs() {
        System.out.println("Someone requested the About Us page");
        return "forward:/aboutus.html";
    }
}

After Stage 2: the Spring words to recognize

Focus: Connect the controller class we wrote to the object Spring manages.

TermOur concrete exampleIts place in Spring
ControllerPageController.aboutUs() handles GET /aboutus through @GetMapping.A Spring MVC controller handles a mapped web request.
Component@Controller marks PageController as a specialized component.Spring can discover eligible annotated classes in scanned packages.
BeanSpring manages an instance of PageController.A bean is the actual object Spring manages; PageController is the class definition.
ContainerSpring creates and manages the PageController instance.The Spring container is the system that manages these bean objects.

The PageController instance is the specific object being managed. The About Us HTML file is a resource, not a bean. We will demonstrate dependency injection and shared bean instances in Stage 8.

You should now be able to say: I wrote the PageController class. Spring manages a PageController object as a bean and calls its mapped method.

One small check: In this example, point to the class, explain what its object is, and say why that object is called a bean.

When ready: useful next stages after Stage 7

A useful continuation: planned next stages

These are the next learning steps, not additional projects already implemented in this bundle. Each proposed change gives the new terminology a concrete example.

Next stageOne useful changeWords to learn when we make that change
8. A separate product-file readerMove the existing file-reading code into ProductFileReader and have Spring supply that object to ProductController through its constructor. Print object creation and request messages to observe reuse.@Component, dependency, constructor injection, DI, IoC and default singleton scope.
9. Product operationsAdd ProductService for rules such as choosing products currently in stock.@Service, business rule and separation of responsibilities.
10. Product data accessPlace file-based reading and lookup behind a product repository.Repository pattern, @Repository and data access.
11. MongoDB storageReplace the text-file storage with MongoDB while keeping the page flow familiar.@Document, ID, MongoRepository, persistence and CRUD.
12. Explicit bean configurationSupply a helper through a configured factory method and inspect how it is registered.@Configuration, @Bean, factory method, bean definition and registry.

Until Stage 8 is implemented, our examples do not demonstrate Spring injecting a separate ProductFileReader into ProductController. The current product controller still reads the file itself. Keep the original getInputStream() and try-with-resources code for that step.