Follow one managed object from its constructor to initialization, use and cleanup.
Open this stage in the online learning path
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.
This lesson reuses the product catalog and Todo responsibilities from Stages 8–14. Only the feature described below is new. Read the ordered explanation before opening the complete files.
The constructor prints its message first. @PostConstruct runs after construction and dependency injection. Do not expect field-injected dependencies to be ready inside a constructor.
The lesson calls read only after the context is refreshed, so initialization has already completed.
Application uses try-with-resources. Closing the context invokes @PreDestroy on the singleton. A forced process kill cannot guarantee cleanup callbacks.
Resolving Draft creates and initializes a prototype. Spring does not track its full destruction lifecycle, so closing this context does not call Draft.destroy. A prototype holding resources needs explicit cleanup by its owner.
Open a terminal in the extracted folder containing pom.xml. Run one command at a time.
mvn test
mvn spring-boot:runThis is a console lesson. It does not open a web port. Application.main starts a Spring context, calls the demonstration and closes the context. Read the terminal, not a browser. Spring Boot’s Maven plugin is used as a convenient launcher; these examples show Spring Framework directly.
Catalog constructor
Catalog initialized
Catalog used
Draft initialized
Catalog destroyedObject identifiers and elapsed times can vary. Compare the relationship between values rather than copying an identifier.
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.
<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-23-lifecycle</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>
</dependencies>
<build><plugins><plugin><groupId>org.springframework.boot</groupId><artifactId>spring-boot-maven-plugin</artifactId></plugin></plugins></build>
</project>
package com.edupoly.lesson;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.stereotype.Component;
@Component public class Catalog {
private boolean ready;
public Catalog() { System.out.println("Catalog constructor"); }
@PostConstruct public void initialize() { ready = true; System.out.println("Catalog initialized"); }
public boolean ready() { return ready; }
public void read() { System.out.println("Catalog used"); }
@PreDestroy public void destroy() { ready = false; System.out.println("Catalog destroyed"); }
}
package com.edupoly.lesson;
import jakarta.annotation.PostConstruct;
import jakarta.annotation.PreDestroy;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
@Component @Scope("prototype") public class Draft {
public boolean destroyed;
@PostConstruct public void initialize() { System.out.println("Draft initialized"); }
@PreDestroy public void destroy() { destroyed = true; System.out.println("Draft destroyed"); }
}
package com.edupoly.lesson;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
@Component public class Lesson {
private final Catalog catalog; private final ApplicationContext context;
public Lesson(Catalog catalog, ApplicationContext context) { this.catalog = catalog; this.context = context; }
public void run() { catalog.read(); context.getBean(Draft.class); }
}
package com.edupoly.lesson;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Application {
public static void main(String[] args) throws Exception {
try (var context = new AnnotationConfigApplicationContext(AppConfig.class)) {
context.getBean(Lesson.class).run();
}
}
}
package com.edupoly.lesson;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
@Configuration
@ComponentScan(basePackages = "com.edupoly.lesson")
public class AppConfig { }
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 lifecycleInitializesAndDestroysSingletonButNotPrototype() {
var context = new AnnotationConfigApplicationContext(AppConfig.class);
Catalog catalog = context.getBean(Catalog.class); Draft draft = context.getBean(Draft.class);
assertTrue(catalog.ready()); context.close();
assertFalse(catalog.ready()); assertFalse(draft.destroyed);
}
}
Replace the managed catalog lookup with new Catalog().read(). You see construction and use but no container-driven initialization for that unmanaged object. Restore the managed lookup.
| Word | Meaning in this example |
|---|---|
| @PostConstruct | Initialization callback after dependencies are supplied. |
| @PreDestroy | Cleanup callback when this managed singleton’s context closes. |
| Lifecycle | The sequence of construction, initialization, use and destruction. |
Add a second prototype lookup. Predict how many initialization messages appear and whether any Draft destroyed message appears.