Profiles and scopes¶
Bean scopes¶
At this stage, we already know that it is not the developer who is responsible for creating the components, but the IoC container. By default, Spring
creates one copy of each component that we define. This means that the class instances marked with the annotations @Component
,@Repository
, @Service
or@Controller
are singletons.
However, sometimes there is a need to create several copies of a component. This means that we want to change the so-called scope. Spring allows you to change the scope using the @Scope
annotation, and the possible values that we provide inside the annotation are:
singleton
- the default and most frequently used valueprototype
- creates a new class instance in case a container is asked to provide a component
The next values are possible to use when creating a web application, i.e. we use e.g. the spring-boot-starter-web
starter. Constant values are inside the WebApplicationContext
class:
request
- creates a new bean for each HTTP requestsession
- creates a new copy of the bean for each new HTTP sessionapplication
websocket
In addition to being able to supply a constant directly to the @Scope
annotation, we can also use the annotations for which the value results from its name, i.e .:
@RequestScope
@SessionScope
@ApplicationScope
Example - prototype¶
The example below shows the use of the prototype
scop:
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import java.util.Random;
@Component
@Scope("prototype")
public class RandomNumberProvider {
private final int value = new Random().nextInt();
public int getValue() {
return value;
}
}
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class FirstController {
private final RandomNumberProvider randomNumberProvider;
@GetMapping("/api/number/val-a")
public Integer showFirstNumber() {
return randomNumberProvider.getValue();
}
}
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class SecondController {
private final RandomNumberProvider randomNumberProvider;
@GetMapping("/api/number/val-b")
public Integer fetchSecondNumber() {
return randomNumberProvider.getValue();
}
}
Invoking the following HTTP requests will return different results to us because the bean that is injected into both controllers defined above is a different instance:
curl --request GET 'http://localhost:8080/api/number/val-b'
curl --request GET 'http://localhost:8080/api/number/val-a'
Example - request¶
import org.springframework.stereotype.Component;
import org.springframework.web.context.annotation.RequestScope;
import java.util.Random;
@Component
@RequestScope
public class RandomNumberProvider {
private final int value = new Random().nextInt();
public int getValue() {
return value;
}
}
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequiredArgsConstructor
public class FirstController {
private final RandomNumberProvider randomNumberProvider;
@GetMapping("/api/number/val-a")
public Integer showFirstNumber() {
return randomNumberProvider.getValue();
}
}
Performing several HTTP requests as below should each return a different value, because for each request a new instance of the RandomNumberProvider
class is created:
curl --request GET 'http://localhost:8080/api/number/val-a'
Profiles¶
Another mechanism, very often used in practice, due to its flexibility, is the ability to define profiles. Profiles in the application allow you to quickly switch between ready-made configurations. A profile is an ordinary string of characters and gives you the ability to:
- create a profile-specific file [
application.properties
] (application_configuration.md # applicationproperties-file) - creating profile-specific [beans] (components_ioc.md # beans-i-registration-in-ioc-container)
The application.properties file vs profiles¶
Each profile enables the creation of a configuration file specific to that profile. This file should be in the project resources and have a name
application-NAZWAPROFILU.properties
Importantly, this file overwrites the values found in the default file application.properties
, but when a certain key has a value inapplication.properties
but not in application-PROFILE NAME.properties
, then the value will be taken from theapplication.properties
, i.e. having defined files:
#plik application.properties
pl.sdacademy.group.prop-a=prop_A_value
pl.sdacademy.group.prop-b=16
#plik application-dev.properties
pl.sdacademy.group.prop-b=17
and by executingthe application in the dev
profile properties will have the following values:
pl.sdacademy.group.prop-a=prop_A_value
pl.sdacademy.group.prop-b=17
Beans vs profiles¶
It is also possible to define a bean that will be created only when the application is launched in a specific profile. The annotation @Profile
is used for this, which we combine with the annotations@Component
or @Bean
and inside which we put:
- name of the profile in which the bean should be created
- profile name preceded by the
!
character. Thanks to this, the bean will be created for each profile other than the one provided.
The example below shows the use of this mechanism:
import java.text.DateFormat;
public interface DateFormatProvider {
DateFormat get();
}
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@Profile("dev")
@Component
public class DevDateFormatProvider implements DateFormatProvider {
@Override
public DateFormat get() {
return new SimpleDateFormat();
}
}
import com.fasterxml.jackson.databind.util.StdDateFormat;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Component;
import java.text.DateFormat;
@Profile("!dev") // for all profiles except for "dev"
@Component
public class ProdDateFormatProvider implements DateFormatProvider {
@Override
public DateFormat get() {
return new StdDateFormat();
}
}
NOTE: In practice, a developer profile is often created to simplify application launch (e.g. it uses embedded H2 instead of MySQL).
Executing application with profiles¶
In order to run a given application with a specific profile (s), we should pass this information to the application in some way. If we want to run the application using many profiles, we should separate their names with commas. We can do this in many ways, for example:
-
we can set the profile in the
SPRING_PROFILES_ACTIVE
environment variable, i.e. in the command line execute:export SPRING_PROFILES_ACTIVE=profile_name java -jar sb-0.0.1-SNAPSHOT.jar # or another jar representing the spring boot application built
-
we can pass its value to the virtual machine in an argument named
spring.profiles.active
. For example, using the IntelliJ IDE, we can enter in the VM options field in the startup configuration:-Dspring.profiles.active = profile_name
-
when we have IntelliJ Ultimate, we can open the run configuration window (so-called Run Configurations) and enter the names of the profiles in the Active Profiles field
If the application uses a profile, this information should be logged in at the start of the application, e.g.:
2020-09-25 21:17:27.076 INFO 12628 --- [main] ... : The following profiles are active: dev