Lombok is a great tool to avoid boilerplate code, this time we will show you how to do constructor injection using Lombok in a Spring Webflux application. If you want to know more about how to create Spring Webflux please go to my previous post getting started with Spring Webflux here. Then, let’s create a new Spring Boot project with Webflux as dependencies:
spring init --dependencies=webflux,lombok --build=gradle --language=java spring-webflux-required-args-constructor
Here is the complete build.gradle
file generated:
plugins {
id 'org.springframework.boot' version '2.2.2.RELEASE'
id 'io.spring.dependency-management' version '1.0.8.RELEASE'
id 'java'
}
group = 'com.jos.dem.spring.webflux.lombok'
version = '1.0.0-SNAPSHOT'
sourceCompatibility = '12'
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-webflux'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
testImplementation 'io.projectreactor:reactor-test'
}
test {
useJUnitPlatform()
}
Now please consider this PersonController
package com.jos.dem.spring.webflux.lombok.controller;
import com.jos.dem.spring.webflux.lombok.model.Person;
import com.jos.dem.spring.webflux.lombok.service.PersonService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Slf4j
@RestController
@RequestMapping("/persons")
@RequiredArgsConstructor
public class PersonController {
private final PersonService personService;
@GetMapping("/")
public Flux<Person> findAll(){
log.info("Calling find persons");
return personService.getAll();
}
@GetMapping("/{nickname}")
public Mono<Person> findById(@PathVariable String nickname){
log.info("Calling find person by nickname: {}", nickname);
return personService.getByNickname(nickname);
}
}
That’s it, with @RequiredArgsConstructor
we are avoiding to use @Autowired private PersonService personSerivice;
and with that action we have a code more clean and easy to test. We are using Lombok as well for logging with @Slf4
annotation. This is the controller test case:
package com.jos.dem.spring.webflux.lombok;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import com.jos.dem.spring.webflux.lombok.model.Person;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
import org.springframework.test.web.reactive.server.WebTestClient;
@SpringBootTest(webEnvironment = WebEnvironment.RANDOM_PORT)
class PersonControllerTest {
@Autowired
private WebTestClient webTestClient;
@Test
@DisplayName("Should get all persons")
void shouldGetAllPersons() {
webTestClient.get()
.uri("/persons/")
.exchange()
.expectStatus().isOk()
.expectBodyList(Person.class)
.value(persons -> {
assertTrue(persons.contains(new Person("josdem", "joseluis.delacruz@gmail.com")), "should contain josdem");
assertTrue(persons.contains(new Person("tgrip", "tgrip@email.com")), "should contain tgrip");
assertTrue(persons.contains(new Person("edzero", "edzero@email.com")), "should contain edzero");
assertTrue(persons.contains(new Person("skuarch", "skuarch@email.com")), "should contain skuarch");
assertTrue(persons.contains(new Person("jeduan", "jeduan@email.com")), "should contain jeduan");
});
}
@Test
@DisplayName("Should get josdem")
void shouldGetPerson(){
webTestClient.get()
.uri("/persons/josdem")
.exchange()
.expectStatus().isOk()
.expectBody(Person.class)
.value(person -> {
assertEquals(new Person("josdem", "joseluis.delacruz@gmail.com"), person, "should get josdem");
});
}
}
This is our PersonService
package com.jos.dem.spring.webflux.lombok.service;
import com.jos.dem.spring.webflux.lombok.model.Person;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
public interface PersonService {
Flux<Person> getAll();
Mono<Person> getByNickname(String nickname);
}
And this the implementation:
package com.jos.dem.spring.webflux.lombok.service.impl;
import com.jos.dem.spring.webflux.lombok.model.Person;
import com.jos.dem.spring.webflux.lombok.service.PersonService;
import java.util.HashMap;
import java.util.Map;
import java.util.stream.Stream;
import javax.annotation.PostConstruct;
import org.springframework.stereotype.Service;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
@Service
public class PersonServiceImpl implements PersonService {
private Map<String, Person> persons = new HashMap<>();
@PostConstruct
public void setup(){
Stream.of(new Person("josdem", "joseluis.delacruz@gmail.com"),
new Person("tgrip", "tgrip@email.com"),
new Person("edzero", "edzero@email.com"),
new Person("skuarch", "skuarch@email.com"),
new Person("jeduan", "jeduan@email.com"))
.forEach(person -> persons.put(person.getNickname(), person));
}
public Flux<Person> getAll(){
return Flux.fromIterable(persons.values());
}
public Mono<Person> getByNickname(String nickname){
return Mono.just(persons.get(nickname));
}
}
To run the project:
gradle bootRun
To test the project:
gradle test
To browse the project go here, to download the project:
git clone git@github.com:josdem/spring-webflux-required-args-constructor.git