mn create-app example.micronaut.micronautguide --build=maven --lang=java
Micronaut Scope Types
Learn about the available scopes: Singleton, Prototype, Request, Refreshable, Context…
Authors: Sergio del Amo
Micronaut Version: 3.9.2
1. Introduction
Micronaut features an extensible bean scoping mechanism based on JSR-330.
2. What you will need
To complete this guide, you will need the following:
-
Some time on your hands
-
A decent text editor or IDE
-
JDK 1.8 or greater installed with
JAVA_HOME
configured appropriately
3. Solution
We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.
-
Download and unzip the source
4. Writing the Application
Create an application using the Micronaut Command Line Interface or with Micronaut Launch.
If you don’t specify the --build argument, Gradle is used as the build tool. If you don’t specify the --lang argument, Java is used as the language.
|
The previous command creates a Micronaut application with the default package example.micronaut
in a directory named micronautguide
.
5. Scenario
We use the following scenario to talk about the different types of scopes.
The following @Controller
injects two collaborators.
package example.micronaut.singleton;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import java.util.Arrays;
import java.util.List;
@Controller("/singleton") (1)
public class RobotController {
private final RobotFather father;
private final RobotMother mother;
public RobotController(RobotFather father, (2)
RobotMother mother) { (3)
this.father = father;
this.mother = mother;
}
@Get (4)
List<String> children() {
return Arrays.asList(
father.child().getSerialNumber(),
mother.child().getSerialNumber()
);
}
}
1 | The class is defined as a controller with the @Controller annotation mapped to the path / . |
2 | Use constructor injection to inject a bean of type RobotFather . |
3 | Use constructor injection to inject a bean of type RobotMother . |
4 | The @Get annotation maps the children method to an HTTP GET request on / . |
Each collaborator has an injection point for a bean of type Robot
.
package example.micronaut.singleton;
import io.micronaut.core.annotation.NonNull;
import jakarta.inject.Singleton;
@Singleton (1)
public class RobotFather {
private final Robot robot;
public RobotFather(Robot robot) { (2)
this.robot = robot;
}
@NonNull
public Robot child() {
return this.robot;
}
}
1 | Use jakarta.inject.Singleton to designate a class as a singleton. |
2 | Use constructor injection to inject a bean of type Robot . |
package example.micronaut.singleton;
import io.micronaut.core.annotation.NonNull;
import jakarta.inject.Singleton;
@Singleton (1)
public class RobotMother {
private final Robot robot;
public RobotMother(Robot robot) { (2)
this.robot = robot;
}
@NonNull
public Robot child() {
return this.robot;
}
}
1 | Use jakarta.inject.Singleton to designate a class as a singleton. |
2 | Use constructor injection to inject a bean of type Robot . |
Let’s discuss how the application behaves depending on the scope used for the bean of type Robot
.
6. Singleton
Singleton scope indicates only one instance of the bean will exist.
To define a singleton, annotate a class with jakarta.inject.Singleton
at the class level.
The following class creates a unique identifier in the constructor. This identifier allows us to identify how many Robot
instances are used.
package example.micronaut.singleton;
import io.micronaut.core.annotation.NonNull;
import jakarta.inject.Singleton;
import java.util.UUID;
@Singleton (1)
public class Robot {
@NonNull
private final String serialNumber;
public Robot() {
serialNumber = UUID.randomUUID().toString();
}
@NonNull
public String getSerialNumber() {
return serialNumber;
}
}
1 | Use jakarta.inject.Singleton to designate a class as a singleton. |
6.1. Singleton Test
To use the testing features described in this section, add the following dependency to your build file:
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<scope>test</scope>
</dependency>
The following test verifies @Singleton
behavior.
package example.micronaut;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.BlockingHttpClient;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
@MicronautTest (1)
class SingletonScopeTest {
@Inject
@Client("/")
HttpClient httpClient; (2)
@ParameterizedTest
@ValueSource(strings = {"/singleton"})
void onlyOneInstanceOfTheBeanExistsForSingletonBeans(String path) {
BlockingHttpClient client = httpClient.toBlocking();
Set<String> responses = new HashSet<>(executeRequest(client, path));
assertEquals(1, responses.size()); (3)
responses.addAll(executeRequest(client, path));
assertEquals(1, responses.size()); (4)
}
List<String> executeRequest(BlockingHttpClient client, String path) {
return client.retrieve(HttpRequest.GET(path),
Argument.listOf(String.class));
}
}
1 | Annotate the class with @MicronautTest so the Micronaut framework will initialize the application context and the embedded server. More info. |
2 | Inject the HttpClient bean and point it to the embedded server. |
3 | The same instance fulfills both injection points at RobotFather and RobotMother . |
4 | Same instance is used upon subsequent requests. |
7. Prototype
Prototype scope indicates that a new instance of the bean is created each time it is injected
Let’s use @Prototype
instead of @Singleton
.
package example.micronaut.prototype;
import io.micronaut.context.annotation.Prototype;
import io.micronaut.core.annotation.NonNull;
import java.util.UUID;
@Prototype (1)
public class Robot {
@NonNull
private final String serialNumber;
public Robot() {
serialNumber = UUID.randomUUID().toString();
}
@NonNull
public String getSerialNumber() {
return serialNumber;
}
}
1 | Use io.micronaut.context.annotation.Prototype to designate the scope of bean as Prototype - a non-singleton scope that creates a new bean for every injection point. |
7.1. Prototype Tests
The following test verifies the behavior of Prototype
scope.
package example.micronaut;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.BlockingHttpClient;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
@MicronautTest (1)
class PrototypeScopeTest {
@Inject
@Client("/")
HttpClient httpClient; (2)
@ParameterizedTest
@ValueSource(strings = {"/prototype"})
void prototypeScopeIndicatesThatANewInstanceOfTheBeanIsCreatedEachTimeItIsInjected(String path) {
BlockingHttpClient client = httpClient.toBlocking();
Set<String> responses = new HashSet<>(executeRequest(client, path));
assertEquals(2, responses.size()); (3)
responses.addAll(executeRequest(client, path));
assertEquals(2, responses.size()); (4)
}
private List<String> executeRequest(BlockingHttpClient client, String path) {
return client.retrieve(HttpRequest.GET(path), Argument.listOf(String.class));
}
}
1 | Annotate the class with @MicronautTest so the Micronaut framework will initialize the application context and the embedded server. More info. |
2 | Inject the HttpClient bean and point it to the embedded server. |
3 | A new instance is created to fulfill each injection point. Two instances - one for RobotFather and another for RobotMother . |
4 | Instances remain upon subsequent requests. |
8. Request
@RequestScope scope is a custom scope that indicates a new instance of the bean is created and associated with each HTTP request
package example.micronaut.request;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.http.HttpRequest;
import io.micronaut.runtime.http.scope.RequestAware;
import io.micronaut.runtime.http.scope.RequestScope;
import java.util.Objects;
@RequestScope (1)
public class Robot implements RequestAware { (2)
@NonNull
private String serialNumber;
@NonNull
public String getSerialNumber() {
return serialNumber;
}
@Override
public void setRequest(HttpRequest<?> request) {
this.serialNumber = Objects.requireNonNull(request.getHeaders().get("UUID"));
}
}
1 | Use io.micronaut.runtime.http.scope.RequestScope to designate the scope of bean as Request - a new instance of the bean is created and associated with each HTTP request. |
2 | RequestAware API allows @RequestScope beans to access to the current request. |
8.1. Request Tests
The following test verifies the behavior of @RequestScope
scope.
package example.micronaut;
import io.micronaut.core.type.Argument;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.BlockingHttpClient;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import static org.junit.jupiter.api.Assertions.assertEquals;
@MicronautTest (1)
class RequestScopeTest {
@Inject
@Client("/")
HttpClient httpClient; (2)
@Test
void requestScopeScopeIsACustomScopeThatIndicatesANewInstanceOfTheBeanIsCreatedAndAssociatedWithEachHTTPRequest() {
String path = "/request";
BlockingHttpClient client = httpClient.toBlocking();
Set<String> responses = new HashSet<>(executeRequest(client, path));
assertEquals(1, responses.size()); (3)
responses.addAll(executeRequest(client, path));
assertEquals(2, responses.size()); (4)
}
private List<String> executeRequest(BlockingHttpClient client,
String path) {
return client.retrieve(createRequest(path), Argument.listOf(String.class));
}
private HttpRequest<?> createRequest(String path) {
return HttpRequest.GET(path).header("UUID", UUID.randomUUID().toString());
}
}
1 | Annotate the class with @MicronautTest so the Micronaut framework will initialize the application context and the embedded server. More info. |
2 | Inject the HttpClient bean and point it to the embedded server. |
3 | Both injection points, RobotFather and RobotMother , are fulfilled with the same instance of Robot . An instance associated with the HTTP Request. |
4 | Both injection points are fulfilled with the new instance of Robot . |
9. Refreshable
Refreshable scope is a custom scope that allows a bean’s state to be refreshed via the /refresh endpoint.
package example.micronaut.refreshable;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.runtime.context.scope.Refreshable;
import java.util.UUID;
@Refreshable (1)
public class Robot {
@NonNull
private final String serialNumber;
public Robot() {
serialNumber = UUID.randomUUID().toString();
}
@NonNull
public String getSerialNumber() {
return serialNumber;
}
}
1 | @Refreshable scope is a custom scope that allows a bean’s state to be refreshed via the /refresh endpoint. |
Your application needs the management
dependency to enable the refresh endpoint.
<dependency>
<groupId>io.micronaut</groupId>
<artifactId>micronaut-management</artifactId>
<scope>compile</scope>
</dependency>
9.1. Refreshable Tests
The following test enables the refresh endpoint and verifies the behavior of @Refreshable
package example.micronaut;
import io.micronaut.context.annotation.Property;
import io.micronaut.core.type.Argument;
import io.micronaut.core.util.StringUtils;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.BlockingHttpClient;
import io.micronaut.http.client.HttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import static org.junit.jupiter.api.Assertions.assertEquals;
@Property(name = "endpoints.refresh.enabled", value = StringUtils.TRUE) (1)
@Property(name = "endpoints.refresh.sensitive", value = StringUtils.FALSE) (2)
@MicronautTest (3)
class RefreshableScopeTest {
@Inject
@Client("/")
HttpClient httpClient; (4)
@Test
void refreshableScopeIsACustomScopeThatAllowsABeansStateToBeRefreshedViaTheRefreshEndpoint() {
String path = "/refreshable";
BlockingHttpClient client = httpClient.toBlocking();
Set<String> responses = new HashSet<>(executeRequest(client, path));
assertEquals(1, responses.size()); (5)
responses.addAll(executeRequest(client, path));
assertEquals(1, responses.size()); (6)
refresh(client); (7)
responses.addAll(executeRequest(client, path));
assertEquals(2, responses.size()); (8)
}
private void refresh(BlockingHttpClient client) {
client.exchange(HttpRequest.POST("/refresh",
Collections.singletonMap("force", true)));
}
private List<String> executeRequest(BlockingHttpClient client, String path) {
return client.retrieve(HttpRequest.GET(path),
Argument.listOf(String.class));
}
}
1 | Annotate the class with @Property to supply configuration to the test. |
2 | The refresh endpoint is sensitive by default. To invoke it in the test, we set endpoints.refresh.sensitive to false. |
3 | Annotate the class with @MicronautTest so the Micronaut framework will initialize the application context and the embedded server. More info. |
4 | Inject the HttpClient bean and point it to the embedded server. |
5 | The same instance fulfills both injection points at RobotFather and RobotMother . |
6 | The same instance serves a new request. |
7 | Hitting the refresh endpoint, publishes a RefreshEvent, which invalidates the instance of Robot . |
8 | A new instance of Robot is created the next time the object is requested. |
10. @Context
Context scope indicates that the bean will be created at the same time as the ApplicationContext (eager initialization)
The following example uses @Context
in combination with @ConfigurationProperties
.
package example.micronaut.context;
import io.micronaut.context.annotation.ConfigurationProperties;
import io.micronaut.context.annotation.Context;
import javax.validation.constraints.Pattern;
@Context (1)
@ConfigurationProperties("micronaut") (2)
public class MicronautConfiguration {
@Pattern(regexp = "groovy|java|kotlin") (3)
private String language;
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
}
1 | The life cycle of classes annotated with io.micronaut.context.annotation.Context is bound to that of the bean context. |
2 | The @ConfigurationProperties annotation takes the configuration prefix. |
3 | Use javax.validation.constraints Constraints to ensure the data matches your expectations. |
The result is validation being performed on the Application Context start-up.
package example.micronaut;
import io.micronaut.context.ApplicationContext;
import io.micronaut.context.exceptions.BeanInstantiationException;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.function.Executable;
import java.util.Collections;
import static org.junit.jupiter.api.Assertions.*;
class ContextTest {
@Test
void lifeCycleOfClassesAnnotatedWithAtContextIsBoundToThatOfTheBeanContext() {
Executable e = () -> ApplicationContext.run(Collections.singletonMap("micronaut.language", "scala"));
BeanInstantiationException thrown = assertThrows(BeanInstantiationException.class, e);
assertTrue(thrown.getMessage().contains("language - must match \"groovy|java|kotlin\""));
}
}
11. Other scopes
Micronaut Framework ships with other built-in Scopes:
11.1. @Infrastructure
@Infrastructure scope represents a bean that cannot be overridden or replaced using @Replaces
because it is critical to the functioning of the system.
11.2. @ThreadLocal
@ThreadLocal scope is a custom scope that associates a bean per thread via a ThreadLocal
11.3. Next Steps
Read more about Scopes in the Micronaut Framework.
12. Help with the Micronaut Framework
The Micronaut Foundation sponsored the creation of this Guide. A variety of consulting and support services are available.