Using Spring Boot with JavaFX - Using Spring Profiles

Posted at — Sep 20, 2017
Riekpil logo
Learn how to test real-world applications with the Testing Spring Boot Applications Masterclass. Comprehensive online course with 8 modules and 130+ video lessons to master well-known Java testing libraries: JUnit 5, Mockito, Testcontainers, WireMock, Awaitility, Selenium, LocalStack, Selenide, and Spring's Outstanding Test Support.

In the previous post, I showed ./blog/2017/09/18/using-spring-boot-with-javafx/[how to get started with Spring Boot and JavaFX].

Continuing with the same example, we are going to create a new implementation of the CountryService interface that talks to the Open AQ Platform API so the list of countries will be a lot bigger than our HardcodedListCountryService we have now.

Using Retrofit

First, we add 2 dependencies in our pom.xml to be able to use Retrofit:

<dependency>
    <groupId>com.squareup.retrofit2</groupId>
    <artifactId>retrofit</artifactId>
    <version>2.3.0</version>
</dependency>
<dependency>
    <groupId>com.squareup.retrofit2</groupId>
    <artifactId>converter-jackson</artifactId>
    <version>2.3.0</version>
</dependency>

Using Retrofit, we can easily get information from the API and transform it using Jackson into Java Objects.

First, we create an interface with a method for each call to the API we want to do:

public interface OpenAqApi {

    @GET("countries")
    Call<OpenAqCountriesResponse> listCountries();
}

Next, we create objects that correspond to the returned JSON structures:

@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
public class OpenAqCountriesResponse {

    private List<OpenAqCountry> results;

}

and

@Getter
@Setter
@JsonIgnoreProperties(ignoreUnknown = true)
public class OpenAqCountry {

    private String code;
    private String name;

}

Note: We use @JsonIgnoreProperties(ignoreUnknown = true) to avoid having to add properties in Java for JSON fields we are not interested in anyway.

Finally, we create an OpenAqServiceImpl class that sets up Retrofit for our API:

@Component
public class OpenAqServiceImpl implements OpenAqService {

    private final OpenAqApi api;

    public OpenAqServiceImpl() {

        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(interceptor).build();

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("https://api.openaq.org/v1/")
                .addConverterFactory(JacksonConverterFactory.create())
                .client(client)
                .build();

        api = retrofit.create(OpenAqApi.class);
    }

    @Override
    public Set<OpenAqCountry> listCountries() {
        try {
            OpenAqCountriesResponse response = api.listCountries().execute().body();

            if (response != null) {
                return new HashSet<>(response.getResults());
            } else {
                return Collections.emptySet();
            }

        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
}

Note: The current code also logs the HTTP requests and responses. For this to work, you need an additional dependency:

<dependency>
    <groupId>com.squareup.okhttp3</groupId>
    <artifactId>logging-interceptor</artifactId>
    <version>3.8.0</version>
</dependency>

If you don’t want/need this logging, you can remove the dependency.

With all this plumbing in place, we can create our actual CountryServiceImpl that is quite simple:

@Component
public class CountryServiceImpl implements CountryService {

    private final OpenAqService service;

    public CountryServiceImpl(OpenAqService service) {
        this.service = service;
    }

    @Override
    public Set<Country> getAllCountries() {
        return service.listCountries().stream()
                      .map(openAqCountry -> new Country(openAqCountry.getCode(), openAqCountry.getName()))
                      .collect(Collectors.toSet());
    }
}

Spring profiles

However, if we now start our JavaFX application, Spring will complain that there are 2 classes that implement CountryService and it has no clue which one it has to inject into our controller. To solve this, we will add 2 extra annotations to the HardcodedListCountryService:

@Component
@Profile("offline")
@Primary
public class HardcodedListCountryService implements CountryService {

    @Override
    public Set<Country> getAllCountries() {

        Set<Country> result = new HashSet<>();
        result.add(new Country("AU", "Australia"));
        result.add(new Country("BR", "Brazil"));
        result.add(new Country("BE", "Belgium"));

        return result;
    }
}
  • @Profile("offline") instructs Spring to only create an instance of this class when the "offline" profile is active.

  • @Primary instructs Spring to always give preference to this instance when autowiring

As a result, if we start the application without any argument, it will use the "online" version of the CountryService that uses the Open AQ API. When starting with

--spring.profiles.active=offline

as program arguments, the hardcoded list will be used without contacting the API online.

Summary

Using Spring profiles makes it really easy to switch your dependencies depending on how you want to run the application. There are many cases where this can be useful, ranging from the API does not exist yet, the API is currently down or maybe you want to have some "nicer" data for screenshots.

This know-how originated during the development of a PegusApps project.

If you want to be notified in the future about new articles, as well as other interesting things I'm working on, join my mailing list!
I send emails quite infrequently, and will never share your email address with anyone else.