Skip to content

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 value
  • prototype - 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 request
  • session - creates a new copy of the bean for each new HTTP session
  • application
  • 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