Stage 19: inspect the product application with BeanFactory and ApplicationContext

Reused: the running product application. New focus: two interfaces to its container.

The practical requirement: add an Application information page for the developer maintaining our catalog. It shows the loaded product count, checks that the product data resource is available, and demonstrates that two interface references obtain the same managed ProductService object.

ApplicationInfoController is a new Java class we write to prepare that page. BeanFactory and ApplicationContext are Spring interfaces, not new classes we write. Spring Boot has already started the application context; we do not create another container for this page.

We continue the Stage 18 application. Its real delivery page and report remain available; its demo controller remains excluded. Constructor injection, Model and Thymeleaf are familiar mechanisms reused here.

Add an information page that demonstrates one existing container, the same service bean through two interfaces, and resource access.

Open this stage in the online learning path

Before you start

Use JDK 21 or newer and Maven 3.9 or newer. Verify with java -version and mvn -version. Extract this project into its own folder; do not merge its files with another stage.

Continue the Stage 18 product application. Begin with the information page and diagram, then inspect the four changed files and the interface-reference explanation.

The practical requirement

A developer maintaining the catalog wants a small information page to confirm that the application has loaded 30 products and can locate its bundled product-data resource. We add two explanatory checks to that page: are these container references the same, and do their service lookups return the same object?

The count and resource check are practical diagnostic results. The reference comparisons are deliberately visible learning aids for understanding the container APIs, rather than a customer feature.

Application information results in a red box, two references to one context, and resource access through ApplicationContext
The page we add and the existing objects behind it. Open the full-size diagram.
GET /application-info → Existing ApplicationContext → BeanFactory reference → Service and resource checks → Model → application-info.html

Where the information page is added: four files

The existing application startup, scan filter, product service, repository, data, report and delivery functionality are reused. application.properties declares this stage's port, 8119.

File — jump to complete sourceWhat changes
ApplicationInfoController.javaNew controller: inspect the existing container, service references and product resource.
application-info.htmlNew information page displaying the results from Model.
products.htmlAdd an Application information link to the existing product list.
style.cssHighlight the diagnostic results with a red-bordered panel.
Blue = familiar constructor and ModelYellow = container inspection and information page

ApplicationInfoController.java

ApplicationInfoController.java · focus lines
    public ApplicationInfoController(ApplicationContext context) {
        this.context = context;
    }

ApplicationInfoController.java

ApplicationInfoController.java · focus lines
        BeanFactory factory = context;
        ProductService fromContext = context.getBean(ProductService.class);
        ProductService fromFactory = factory.getBean(ProductService.class);

ApplicationInfoController.java

ApplicationInfoController.java · focus lines
        model.addAttribute("sameContainer", factory == context);
        model.addAttribute("sameService", fromContext == fromFactory);

ApplicationInfoController.java

ApplicationInfoController.java · focus lines
        model.addAttribute("resourceAvailable",
                context.getResource("classpath:data/products.txt").exists());

Four complete files below have yellow APPLICATION INFO headings. The repository still reads the same product file; this controller only checks resource existence through the context.

Understand the two names before running the experiment

What is BeanFactory?

BeanFactory is a Spring interface describing basic access to managed beans. For example, getBean(ProductService.class) asks the container for the bean matching that type. This is not a call to new ProductService(). In our application ProductService has the default singleton scope, so these lookups return the existing shared bean.

What is ApplicationContext?

ApplicationContext is the broader Spring container interface we have been using indirectly since Boot started our application. It includes the BeanFactory contract through its parent interfaces, plus application facilities such as resource access, messages and events. This stage demonstrates only resource access; the other facilities can wait.

Spring supports injecting the active ApplicationContext into our controller. We do not add @Component to the Spring interface, write an implementation, or declare a new context with @Bean. Boot has already prepared the context.

Does this line create another container?

Same object, another interface reference
BeanFactory factory = context;

No. It declares a variable with the interface type BeanFactory and copies the existing context reference into it. Both variables point to the same object. The declared interface controls which methods Java lets us call through that variable. The object itself has not changed.

ExpressionMeaning here
factory == contextDo these references point to the exact same container object? Yes.
fromContext == fromFactoryDid both lookups return the exact same ProductService object? Yes, with the singleton bean in this project.
fromContext.count()Use that existing service to obtain 30; the service delegates to its existing repository.
context.getResource(...).exists()Ask the context to locate the named resource and check whether it exists.

The == operator compares object references here, not whether two objects have similar field values. The UI turns these boolean results into Yes or No.

What does classpath mean here?

The file src/main/resources/data/products.txt is packaged as an application resource. The resource name classpath:data/products.txt identifies it without depending on the terminal's working directory. exists() checks that the resource can be located; it does not parse JSON or validate its contents. ProductRepository continues to perform the existing data-reading work.

When do we use these APIs?

Container inspection is useful in diagnostic code, tests and framework integration. Our ordinary ProductController still receives ProductService through its constructor. We use getBean explicitly on this learning page to examine the container, not as a recommendation to replace constructor injection throughout the application.

For normal applications, keep using the application context Boot provides. BeanFactory and ApplicationContext are not simply “lazy container” versus “eager container”; creation timing also depends on scope, lazy settings and configuration. The next stages examine scopes.

Similar words, different jobs: BeanFactory is an interface for container operations. A factory method annotated @Bean is a method we write to supply a bean, as in ReportConfig. They are related to object management but are not interchangeable terms.

Guided experiment: inspect, break a lookup, restore

  1. Run the unchanged project and open http://localhost:8119/products. Select Application information.
  2. Expect Total products: 30 and Yes for the same-container, same-service and resource checks.
  3. Refresh the page: the results stay the same. The controller uses the already prepared container and service references.

A. A missing resource is different from a missing bean

Stop the application. In ApplicationInfoController.java change only the resource string classpath:data/products.txt to classpath:data/missing-products.txt. Leave the actual data file and repository unchanged. Restart and open /application-info: the resource result becomes No while the product count stays 30. Restore the original resource string afterward.

B. Ask for an unregistered bean

Stop the application. Temporarily replace the fromFactory lookup line with the line below. Restart, then open /application-info.

ApplicationInfoController.java · intentional failure
ProductService fromFactory = (ProductService) factory.getBean("unknownProductService");

The application can start, but that page request fails with HTTP 500 because the lookup runs inside the request method and the bean name does not exist. The terminal shows NoSuchBeanDefinitionException. This differs from a required constructor dependency missing during startup.

Stop, restore factory.getBean(ProductService.class), run mvn test and restart. The page returns HTTP 200 and the normal results. Do not leave the deliberate error in the project.

Spring documentation: additional ApplicationContext capabilities

What we change and why

  1. Meet the new controller

    ApplicationInfoController is our new class for preparing the information page. Spring injects its active ApplicationContext through the familiar constructor mechanism. This is the context Boot already created.

  2. Read one context through two interfaces

    BeanFactory factory = context stores the existing reference in a variable typed as BeanFactory. It constructs no new object. ApplicationContext includes the BeanFactory contract through its parent interfaces.

  3. Obtain the existing service

    Both getBean(ProductService.class) calls resolve the existing singleton bean in this application. The == comparison checks identity. The service still obtains its product information from the existing repository.

  4. Use an additional context facility

    ApplicationContext also offers resource access. getResource with classpath:data/products.txt and exists() checks resource availability. It does not read or validate product contents.

  5. Render familiar Model values

    The controller puts the count and boolean results into Model. Thymeleaf displays the count and translates each boolean to Yes or No. These values are calculated for the request using the existing container.

Run this example

Open a terminal in the extracted folder containing pom.xml. Run one command at a time.

Terminal · Windows, macOS and Linux
mvn test
mvn spring-boot:run

Open http://localhost:8119/products. Keep the terminal running. Stop with Ctrl+C. If the port is occupied, edit server.port in application.properties before restarting.

Expected result

Expected output · ignore framework startup messages
Open http://localhost:8119/products
Click Application information
Total products: 30
Same container reference: Yes
Same ProductService object: Yes
Product data resource available: Yes
Real product delivery and report remain available; /demo/delivery remains HTTP 404.

Understand each complete file

Create the following files at exactly these paths, or use the ZIP. Source files include their imports. The test checks the behaviour described in the lesson.

pom.xml
pom.xml · complete file
<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-stage-19-container-interfaces</artifactId><version>1.0.0</version>
  <properties><java.version>21</java.version></properties>
  <dependencies>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-webmvc</artifactId></dependency>
    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</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/delivery/DeliveryInfoService.java
src/main/java/com/edupoly/delivery/DeliveryInfoService.java · complete file
package com.edupoly.delivery;

import org.springframework.stereotype.Service;

@Service
public class DeliveryInfoService {
    public String deliveryTime() { return "3-5 working days"; }
    public int deliveryCharge() { return 49; }
}
src/main/java/com/edupoly/delivery/DemoDeliveryController.java
src/main/java/com/edupoly/delivery/DemoDeliveryController.java · complete file
package com.edupoly.delivery;

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

@Controller
public class DemoDeliveryController {
    @GetMapping("/demo/delivery")
    public String preview(Model model) {
        model.addAttribute("deliveryTime", "Instant delivery (demo)");
        model.addAttribute("deliveryCharge", 0);
        return "demo-delivery";
    }
}
src/main/java/com/edupoly/lesson/Application.java
src/main/java/com/edupoly/lesson/Application.java · complete file
package com.edupoly.lesson;

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

@SpringBootApplication
public class Application {
    public static void main(String[] args) { SpringApplication.run(Application.class, args); }
}
src/main/java/com/edupoly/lesson/DeliveryScanConfig.java
src/main/java/com/edupoly/lesson/DeliveryScanConfig.java · complete file
package com.edupoly.lesson;

import com.edupoly.delivery.DemoDeliveryController;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;

@Configuration
@ComponentScan(
    basePackages = "com.edupoly.delivery",
    excludeFilters = @ComponentScan.Filter(
        type = FilterType.ASSIGNABLE_TYPE,
        classes = DemoDeliveryController.class
    )
)
public class DeliveryScanConfig { }
src/main/java/com/edupoly/lesson/Notifier.java
src/main/java/com/edupoly/lesson/Notifier.java · complete file
package com.edupoly.lesson;

public class Notifier {
    public String message(int productCount) {
        return "Catalog report prepared for " + productCount + " products.";
    }
}
src/main/java/com/edupoly/lesson/Product.java
src/main/java/com/edupoly/lesson/Product.java · complete file
package com.edupoly.lesson;

import java.math.BigDecimal;

public class Product {
    private int id;
    private String name;
    private String category;
    private BigDecimal price;
    private String brand;
    private int stock;
    private String description;
    private int warrantyMonths;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public int getStock() {
        return stock;
    }

    public void setStock(int stock) {
        this.stock = stock;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public int getWarrantyMonths() {
        return warrantyMonths;
    }

    public void setWarrantyMonths(int warrantyMonths) {
        this.warrantyMonths = warrantyMonths;
    }
}
src/main/java/com/edupoly/lesson/ProductController.java
src/main/java/com/edupoly/lesson/ProductController.java · complete file
package com.edupoly.lesson;

import com.edupoly.delivery.DeliveryInfoService;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.server.ResponseStatusException;

@Controller
public class ProductController {
    private final ProductService service;

    private final DeliveryInfoService delivery;

    public ProductController(ProductService service, DeliveryInfoService delivery) {
        this.service = service;
        this.delivery = delivery;
    }

    @GetMapping({"/", "/products"})
    public String products(Model model) {
        model.addAttribute("products", service.findAll());
        model.addAttribute("totalProducts", service.count());
        return "products";
    }

    @GetMapping("/products/{id}")
    public String details(@PathVariable("id") int id, Model model) {
        for (Product product : service.findAll()) {
            if (product.getId() == id) {
                model.addAttribute("product", product);
                model.addAttribute("deliveryTime", delivery.deliveryTime());
                model.addAttribute("deliveryCharge", delivery.deliveryCharge());
                return "product-details";
            }
        }
        throw new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found");
    }
}
src/main/java/com/edupoly/lesson/ProductRepository.java
src/main/java/com/edupoly/lesson/ProductRepository.java · complete file
package com.edupoly.lesson;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Repository;
import tools.jackson.databind.json.JsonMapper;

@Repository
public class ProductRepository {
    private final Product[] products;

    public ProductRepository() throws IOException {
        var resource = new ClassPathResource("data/products.txt");
        try (var input = resource.getInputStream()) {
            JsonMapper mapper = JsonMapper.builder().build();
            products = mapper.readValue(input, Product[].class);
        }
    }

    public Product[] findAll() { return products; }

    public List<String> titles() {
        List<String> titles = new ArrayList<>();
        for (Product product : products) {
            titles.add(product.getName());
        }
        return titles;
    }
}
src/main/java/com/edupoly/lesson/ProductService.java
src/main/java/com/edupoly/lesson/ProductService.java · complete file
package com.edupoly.lesson;

import org.springframework.stereotype.Service;

@Service
public class ProductService {
    private final ProductRepository repository;

    public ProductService(ProductRepository repository) {
        this.repository = repository;
    }

    public Product[] findAll() { return repository.findAll(); }

    public int count() {
        return repository.titles().size();
    }
}
src/main/java/com/edupoly/lesson/ReportConfig.java
src/main/java/com/edupoly/lesson/ReportConfig.java · complete file
package com.edupoly.lesson;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class ReportConfig {
    @Bean
    public Notifier notifier() {
        return new Notifier();
    }
}
src/main/java/com/edupoly/lesson/ReportController.java
src/main/java/com/edupoly/lesson/ReportController.java · complete file
package com.edupoly.lesson;

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

@Controller
public class ReportController {
    private final ReportService reports;

    public ReportController(ReportService reports) {
        this.reports = reports;
    }

    @GetMapping("/report")
    public String report(Model model) {
        model.addAttribute("totalProducts", reports.productCount());
        model.addAttribute("reportMessage", reports.message());
        return "report";
    }
}
src/main/java/com/edupoly/lesson/ReportService.java
src/main/java/com/edupoly/lesson/ReportService.java · complete file
package com.edupoly.lesson;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class ReportService {
    private final ProductService products;

    public ReportService(ProductService products) {
        this.products = products;
    }

    private Notifier notifier;

    @Autowired(required = false)
    public void setNotifier(Notifier notifier) {
        this.notifier = notifier;
    }

    public int productCount() { return products.count(); }

    public String message() {
        if (notifier == null) return "";
        return notifier.message(productCount());
    }
}
APPLICATION INFOsrc/main/java/com/edupoly/lesson/ApplicationInfoController.java

New controller: inspect the existing container, service references and product resource.

src/main/java/com/edupoly/lesson/ApplicationInfoController.java · complete file
package com.edupoly.lesson;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class ApplicationInfoController {
    private final ApplicationContext context;

    public ApplicationInfoController(ApplicationContext context) {
        this.context = context;
    }

    @GetMapping("/application-info")
    public String information(Model model) {
        BeanFactory factory = context;
        ProductService fromContext = context.getBean(ProductService.class);
        ProductService fromFactory = factory.getBean(ProductService.class);

        model.addAttribute("totalProducts", fromContext.count());
        model.addAttribute("sameContainer", factory == context);
        model.addAttribute("sameService", fromContext == fromFactory);
        model.addAttribute("resourceAvailable",
                context.getResource("classpath:data/products.txt").exists());
        return "application-info";
    }
}
src/main/resources/data/products.txt
src/main/resources/data/products.txt · complete file
[
  {
    "id": 1,
    "name": "Wireless Mouse",
    "category": "Electronics",
    "price": 699,
    "brand": "EduTech",
    "stock": 12,
    "description": "Wireless Mouse for everyday learning, study and work. Product 1 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 2,
    "name": "Mechanical Keyboard",
    "category": "Electronics",
    "price": 3499,
    "brand": "EduTech",
    "stock": 19,
    "description": "Mechanical Keyboard for everyday learning, study and work. Product 2 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 3,
    "name": "USB-C Hub",
    "category": "Electronics",
    "price": 1599,
    "brand": "EduTech",
    "stock": 26,
    "description": "USB-C Hub for everyday learning, study and work. Product 3 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 4,
    "name": "Laptop Stand",
    "category": "Electronics",
    "price": 1299,
    "brand": "EduTech",
    "stock": 33,
    "description": "Laptop Stand for everyday learning, study and work. Product 4 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 5,
    "name": "Webcam",
    "category": "Electronics",
    "price": 2199,
    "brand": "EduTech",
    "stock": 40,
    "description": "Webcam for everyday learning, study and work. Product 5 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 6,
    "name": "Bluetooth Speaker",
    "category": "Electronics",
    "price": 2499,
    "brand": "EduTech",
    "stock": 47,
    "description": "Bluetooth Speaker for everyday learning, study and work. Product 6 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 7,
    "name": "Noise-Cancelling Headphones",
    "category": "Electronics",
    "price": 4999,
    "brand": "EduTech",
    "stock": 54,
    "description": "Noise-Cancelling Headphones for everyday learning, study and work. Product 7 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 8,
    "name": "Portable SSD",
    "category": "Electronics",
    "price": 6499,
    "brand": "EduTech",
    "stock": 61,
    "description": "Portable SSD for everyday learning, study and work. Product 8 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 9,
    "name": "USB Flash Drive",
    "category": "Electronics",
    "price": 599,
    "brand": "EduTech",
    "stock": 68,
    "description": "USB Flash Drive for everyday learning, study and work. Product 9 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 10,
    "name": "Monitor",
    "category": "Electronics",
    "price": 12999,
    "brand": "EduTech",
    "stock": 15,
    "description": "Monitor for everyday learning, study and work. Product 10 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 11,
    "name": "Desk Lamp",
    "category": "Electronics",
    "price": 899,
    "brand": "EduTech",
    "stock": 22,
    "description": "Desk Lamp for everyday learning, study and work. Product 11 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 12,
    "name": "Power Bank",
    "category": "Electronics",
    "price": 1799,
    "brand": "EduTech",
    "stock": 29,
    "description": "Power Bank for everyday learning, study and work. Product 12 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 13,
    "name": "Smartphone Stand",
    "category": "Electronics",
    "price": 399,
    "brand": "EduTech",
    "stock": 36,
    "description": "Smartphone Stand for everyday learning, study and work. Product 13 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 14,
    "name": "HDMI Cable",
    "category": "Electronics",
    "price": 499,
    "brand": "EduTech",
    "stock": 43,
    "description": "HDMI Cable for everyday learning, study and work. Product 14 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 15,
    "name": "Ethernet Adapter",
    "category": "Electronics",
    "price": 999,
    "brand": "EduTech",
    "stock": 50,
    "description": "Ethernet Adapter for everyday learning, study and work. Product 15 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 16,
    "name": "Graphics Tablet",
    "category": "Electronics",
    "price": 3999,
    "brand": "EduTech",
    "stock": 57,
    "description": "Graphics Tablet for everyday learning, study and work. Product 16 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 17,
    "name": "Microphone",
    "category": "Electronics",
    "price": 2999,
    "brand": "EduTech",
    "stock": 64,
    "description": "Microphone for everyday learning, study and work. Product 17 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 18,
    "name": "Laptop Backpack",
    "category": "Electronics",
    "price": 1499,
    "brand": "EduTech",
    "stock": 71,
    "description": "Laptop Backpack for everyday learning, study and work. Product 18 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 19,
    "name": "Screen Cleaner",
    "category": "Electronics",
    "price": 249,
    "brand": "EduTech",
    "stock": 18,
    "description": "Screen Cleaner for everyday learning, study and work. Product 19 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 20,
    "name": "Mouse Pad",
    "category": "Electronics",
    "price": 299,
    "brand": "EduTech",
    "stock": 25,
    "description": "Mouse Pad for everyday learning, study and work. Product 20 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 21,
    "name": "Notebook",
    "category": "Study Essentials",
    "price": 149,
    "brand": "StudyWorks",
    "stock": 32,
    "description": "Notebook for everyday learning, study and work. Product 21 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 22,
    "name": "Ballpoint Pen Set",
    "category": "Study Essentials",
    "price": 199,
    "brand": "StudyWorks",
    "stock": 39,
    "description": "Ballpoint Pen Set for everyday learning, study and work. Product 22 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 23,
    "name": "Water Bottle",
    "category": "Study Essentials",
    "price": 599,
    "brand": "StudyWorks",
    "stock": 46,
    "description": "Water Bottle for everyday learning, study and work. Product 23 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 24,
    "name": "Desk Organizer",
    "category": "Study Essentials",
    "price": 449,
    "brand": "StudyWorks",
    "stock": 53,
    "description": "Desk Organizer for everyday learning, study and work. Product 24 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 25,
    "name": "Reading Light",
    "category": "Study Essentials",
    "price": 699,
    "brand": "StudyWorks",
    "stock": 60,
    "description": "Reading Light for everyday learning, study and work. Product 25 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 26,
    "name": "Calculator",
    "category": "Study Essentials",
    "price": 499,
    "brand": "StudyWorks",
    "stock": 67,
    "description": "Calculator for everyday learning, study and work. Product 26 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 27,
    "name": "Whiteboard",
    "category": "Study Essentials",
    "price": 1299,
    "brand": "StudyWorks",
    "stock": 14,
    "description": "Whiteboard for everyday learning, study and work. Product 27 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 28,
    "name": "Marker Set",
    "category": "Study Essentials",
    "price": 299,
    "brand": "StudyWorks",
    "stock": 21,
    "description": "Marker Set for everyday learning, study and work. Product 28 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 29,
    "name": "Book Stand",
    "category": "Study Essentials",
    "price": 799,
    "brand": "StudyWorks",
    "stock": 28,
    "description": "Book Stand for everyday learning, study and work. Product 29 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 30,
    "name": "Travel Mug",
    "category": "Study Essentials",
    "price": 649,
    "brand": "StudyWorks",
    "stock": 35,
    "description": "Travel Mug for everyday learning, study and work. Product 30 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  }
]
APPLICATION INFOsrc/main/resources/static/style.css

Highlight the diagnostic results with a red-bordered panel.

src/main/resources/static/style.css · complete file
body{margin:0;padding:28px 16px;background:#f5f7fb;color:#17253d;font:16px/1.6 system-ui,sans-serif}main{max-width:1100px;margin:20px auto;padding:28px;background:#fff;border:1px solid #dbe2ec;border-radius:12px}h1{margin-top:0}a{color:#2457a6;text-underline-offset:3px}.table-scroll{overflow-x:auto}table{width:100%;border-collapse:collapse}th,td{text-align:left;padding:12px;border-bottom:1px solid #dbe2ec}th{background:#edf3fc}tbody tr:hover{background:#f8faff}.detail{max-width:760px}dl{display:grid;grid-template-columns:160px minmax(0,1fr);gap:12px 20px;margin:24px 0}dt{font-weight:650}dd{margin:0;overflow-wrap:anywhere}@media(max-width:500px){main{padding:16px}dl{grid-template-columns:1fr;gap:4px}dd{margin-bottom:12px}}

.brand{color:#164aa5;font-weight:700}.total-products{display:inline-block;border:3px solid #ef111f;border-radius:4px;padding:12px 18px;background:#fff7f7;color:#14213a;font-size:24px;font-weight:700;margin:4px 0 18px}

.report-action{display:inline-block;background:#164aa5;color:white;padding:10px 16px;border-radius:6px;text-decoration:none}.report-count{font-size:24px;font-weight:700;color:#164aa5}.report-message{background:#fff7f7;border:3px solid #ef111f;border-radius:4px;padding:16px 20px;color:#14213a}

.delivery-info{border:3px solid #ef111f;background:#fff7f7;padding:18px 22px;border-radius:6px;margin:24px 0}.delivery-info h2{margin-top:0}

.application-info{border:3px solid #ef111f;background:#fff7f7;padding:18px 22px;border-radius:6px;margin:24px 0}.application-info h2{margin-top:0}
src/main/resources/templates/demo-delivery.html
src/main/resources/templates/demo-delivery.html · complete file
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>DEMO delivery preview</title><link rel="stylesheet" th:href="@{/style.css}"></head>
<body><main class="detail">
    <h1>DEMO delivery preview</h1>
    <p>Sample values for demonstrations.</p>
    <section class="delivery-info">
        <h2>Sample delivery terms</h2>
        <p>Standard delivery: <span th:text="${deliveryTime}">Instant delivery (demo)</span></p>
        <p>Delivery charges: INR <span th:text="${deliveryCharge}">0</span></p>
    </section>
    <p><a th:href="@{/products/1}">View real product delivery</a></p>
</main></body></html>
src/main/resources/templates/product-details.html
src/main/resources/templates/product-details.html · complete file
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title th:text="${product.name}">Product details</title>
    <link rel="stylesheet" th:href="@{/style.css}">
</head>
<body>
<main class="detail">
    <h1>Product details</h1>
    <h2 th:text="${product.name}">Wireless Mouse</h2>
    <dl>
        <dt>ID</dt><dd th:text="${product.id}">1</dd>
        <dt>Name</dt><dd th:text="${product.name}">Wireless Mouse</dd>
        <dt>Category</dt><dd th:text="${product.category}">Electronics</dd>
        <dt>Price (INR)</dt><dd th:text="${product.price}">699</dd>
        <dt>Brand</dt><dd th:text="${product.brand}">EduTech</dd>
        <dt>Stock</dt><dd th:text="${product.stock}">12</dd>
        <dt>Description</dt><dd th:text="${product.description}">Product description</dd>
        <dt>Warranty (months)</dt><dd th:text="${product.warrantyMonths}">12</dd>
    </dl>
    <section class="delivery-info" id="delivery-info">
        <h2>Delivery information</h2>
        <p>Standard delivery: <span th:text="${deliveryTime}">3-5 working days</span></p>
        <p>Delivery charges: INR <span th:text="${deliveryCharge}">49</span></p>
    </section>
    <a th:href="@{/products}">Back to products</a>
</main>
</body>
</html>
src/main/resources/templates/report.html
src/main/resources/templates/report.html · complete file
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1"><title>Product report</title><link rel="stylesheet" th:href="@{/style.css}"></head>
<body><main>
    <p class="brand">Edupoly · Product catalog</p>
    <h1>Product report</h1>
    <p class="report-count">Total products: <strong th:text="${totalProducts}">30</strong></p>
    <p class="report-message" id="report-message" th:if="${!reportMessage.isEmpty()}" th:text="${reportMessage}">Catalog report prepared.</p>
    <p><a th:href="@{/products}">Back to products</a></p>
</main></body></html>
APPLICATION INFOsrc/main/resources/templates/application-info.html

New information page displaying the results from Model.

src/main/resources/templates/application-info.html · complete file
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Application information</title><link rel="stylesheet" th:href="@{/style.css}"></head>
<body><main>
    <h1>Application information</h1>
    <p>Developer check of the product application.</p>
    <section class="application-info" id="application-info-results">
        <h2>Total products: <strong id="product-count" th:text="${totalProducts}">30</strong></h2>
        <p>Same container reference: <strong id="same-container" th:text="${sameContainer ? 'Yes' : 'No'}">Yes</strong></p>
        <p>Same ProductService object: <strong id="same-service" th:text="${sameService ? 'Yes' : 'No'}">Yes</strong></p>
        <p>Product data resource available: <strong id="resource-available" th:text="${resourceAvailable ? 'Yes' : 'No'}">Yes</strong></p>
    </section>
    <p>The two interface references use the existing application context.</p>
    <p><a th:href="@{/products}">Back to products</a></p>
</main></body></html>
src/main/resources/application.properties
src/main/resources/application.properties · complete file
server.port=8119
spring.application.name=spring-stage-19-container-interfaces
src/test/java/com/edupoly/lesson/LessonTest.java
src/test/java/com/edupoly/lesson/LessonTest.java · complete file
package com.edupoly.lesson;

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

class LessonTest {
    @Test void twoInterfacesReferToOneContextAndSingleton() {
        try (var context = new AnnotationConfigApplicationContext(ProductRepository.class, ProductService.class)) {
            org.springframework.beans.factory.BeanFactory factory = context;
            assertSame(context, factory);
            assertSame(context.getBean(ProductService.class), factory.getBean(ProductService.class));
            assertEquals(30, factory.getBean(ProductService.class).count());
        }
    }

    @Test void missingResourceAndMissingBeanHaveDifferentResults() {
        try (var context = new AnnotationConfigApplicationContext(ProductRepository.class, ProductService.class)) {
            assertTrue(context.getResource("classpath:data/products.txt").exists());
            assertFalse(context.getResource("classpath:data/missing-products.txt").exists());
            assertThrows(org.springframework.beans.factory.NoSuchBeanDefinitionException.class,
                    () -> context.getBean("unknownProductService"));
        }
    }

    @Test void browserCanOpenInformationWhileExistingFeaturesStillWork() throws Exception {
        try (var context = org.springframework.boot.SpringApplication.run(Application.class, "--server.port=0")) {
            int port = context.getEnvironment().getProperty("local.server.port", Integer.class);
            var client = java.net.http.HttpClient.newHttpClient();
            String base = "http://127.0.0.1:" + port;
            var list = client.send(java.net.http.HttpRequest.newBuilder(java.net.URI.create(base + "/products")).build(), java.net.http.HttpResponse.BodyHandlers.ofString());
            assertEquals(200, list.statusCode()); assertTrue(list.body().contains("Application information"));
            var info = client.send(java.net.http.HttpRequest.newBuilder(java.net.URI.create(base + "/application-info")).build(), java.net.http.HttpResponse.BodyHandlers.ofString());
            assertEquals(200, info.statusCode());
            assertTrue(info.body().contains("id=\"product-count\">30</strong>"));
            for (String id : java.util.List.of("same-container", "same-service", "resource-available")) {
                assertTrue(info.body().contains("id=\"" + id + "\">Yes</strong>"));
            }
            assertFalse(info.body().contains("th:text"));
            var product = client.send(java.net.http.HttpRequest.newBuilder(java.net.URI.create(base + "/products/1")).build(), java.net.http.HttpResponse.BodyHandlers.ofString());
            assertEquals(200, product.statusCode()); assertTrue(product.body().contains("<span>49</span>"));
            var demo = client.send(java.net.http.HttpRequest.newBuilder(java.net.URI.create(base + "/demo/delivery")).build(), java.net.http.HttpResponse.BodyHandlers.ofString());
            assertEquals(404, demo.statusCode());
            var report = client.send(java.net.http.HttpRequest.newBuilder(java.net.URI.create(base + "/report")).build(), java.net.http.HttpResponse.BodyHandlers.ofString());
            assertEquals(200, report.statusCode()); assertTrue(report.body().contains("Catalog report prepared for 30 products."));
        }
    }
}

Try a failure, then correct it

Follow the missing-resource and missing-bean experiments. A missing resource produces No without changing the product count. A lookup of unknownProductService inside the controller request produces HTTP 500 and NoSuchBeanDefinitionException. Restore the original lookups.

Words you can now explain

WordMeaning in this example
BeanFactory — new focusThe basic container interface for bean lookup and dependency management.
ApplicationContext — existing container, now examinedIncludes the BeanFactory contract and broader application facilities.
getBeanObtain a managed bean from the container; these lookups return the existing singleton service.
Object reference and == — Java concept reusedCompare whether variables point to exactly the same object.
Classpath resourceAn application resource packaged from src/main/resources.
Constructor injection, Model and Thymeleaf — reusedSupply the context and render the diagnostic values.

Practice assignment

Add a second ProductService lookup through factory and compare it with fromFactory. Display the result on the information page. Explain why assigning context to another interface variable creates neither a new container nor another service object.

Read the official documentation →