9

I am trying to follow the best practise of autowiring Webclient using WebClient Builder but little confused.

Here is my Main Application in which i am producing a Webclient Builder and autowiring it in one of my service class

 @SpringBootApplication
    public class MyApplication {
        @Bean
        public WebClient.Builder getWebClientBuilder() {
            return WebClient.builder();
        }
    
        public static void main(String[] args) {
            SpringApplication.run(MyApplication.class, args);
        }}


ServiceImpl Class

    public class MyServiceImpl implements MyService {
        private static final String API_MIME_TYPE = "application/json";
        private static final String API_BASE_URL = "http://localhost:8080";
        private static final String USER_AGENT = "Spring 5 WebClient";
        private static final Logger logger = LoggerFactory.getLogger(MyServiceImpl.class);
    
        @Autowired
        private WebClient.Builder webClientBuilder;
    
        @Override
        public Mono<Issue> createIssue(Fields field) {
            return webClientBuilder.build()
                    .post()
                    .uri("/rest/api/")
                    .body(Mono.just(field), Fields.class)
                    .retrieve()
                    .bodyToMono(Issue.class);
        }}

I am trying to build the webClientBuilder with BaseURl, DefaultHeader etc. I tried to initialize it inside MyServiceImpl Constructer but not sure if its correct or not.

public MyServiceImpl() {
            this.webClientBuilder
                    .baseUrl(API_BASE_URL).defaultHeader(HttpHeaders.CONTENT_TYPE, API_MIME_TYPE)
                    .defaultHeader(HttpHeaders.USER_AGENT, USER_AGENT)
                    .build();
        } 

Am i doing it correct or is there a better way to do it.

Currently I have 2 ServiceImpls to call Different Apis and thats the reason i tried to set the 'baseurl' and other defaults in service itself.

Please Help. TIA

Kamal Garg
  • 107
  • 1
  • 2
  • 9

1 Answers1

17

Usually, your approach would be something like this:

@SpringBootApplication
public class MyApplication {

    public static void main(String[] args) {
        SpringApplication.run(MyApplication.class, args);
    }
}

@Configuration
public class MyApplicationConfiguration {
    
    @Bean
    public WebClient myWebClient(WebClient.Builder webClientBuilder) {
        return webClientBuilder
                .baseUrl(API_BASE_URL)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, API_MIME_TYPE)
                .defaultHeader(HttpHeaders.USER_AGENT, USER_AGENT)
                .build();
    }
}

@Service
public class MySericeImpl implements MyService {

    @Autowired
    private WebClient myWebClient;

    @Override
    public Mono<Issue> createIssue(Fields field) {
        return myWebClient
                .post()
                .uri("/rest/api/")
                .body(Mono.just(field), Fields.class)
                .retrieve()
                .bodyToMono(Issue.class);
    }
 }

The key thing to remember is that WebClient.Builder is already pre-configured for you and Bean is already created. So you just need to autowire it, adjust the configuration and build final WebClient.

It is also possible to use another approach to configure it. There are 3 main approaches to customize WebClient. See official docs for more details https://docs.spring.io/spring-boot/docs/current/reference/html/spring-boot-features.html#boot-features-webclient.


Edit for consuming more APIs - configure multiple WebClients and autowire them in an appropriate service class.

@Configuration
public class MyApplicationConfiguration {
    
    @Bean
    public WebClient myWebClientForApi1(WebClient.Builder webClientBuilder) {
        return webClientBuilder
                .clone()
                .baseUrl(API_1_BASE_URL)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, API_MIME_TYPE)
                .defaultHeader(HttpHeaders.USER_AGENT, USER_AGENT)
                .build();
    }

    @Bean
    public WebClient myWebClientForApi2(WebClient.Builder webClientBuilder) {
        return webClientBuilder
                .clone()
                .baseUrl(API_2_BASE_URL)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, API_MIME_TYPE)
                .build();
    }
}
rasto
  • 601
  • 4
  • 7
  • Thanks @raestio(Updated my question) - Actually i have 2 ServiceImpls to call 2 different RestApis so i tried initialising baseurl in serviceimpl itself. Currently i am trying Monolithic way with both the services in a same app. The next is i want to try it as microservice thn this approach will fine. – Kamal Garg Oct 30 '20 at 07:45
  • 3
    No problem, I've updated the answer as well. You can use 'clone()' method and configure multiple WebClients without changing the other one. Then you can just autowire the one you want in a service class with '@Qualifier'. There is no need to create WebClient in the service classes. It's the configuration, so it's good to have it in @Configuration classes. It's more clear and you are closer to Single Responsibility Principle. – rasto Nov 02 '20 at 11:28
  • 2
    Yes, the approach would be to set them in the place where you are creating WebClients, which is Config class - MyApplicationConfiguration. So you can pick the properties with @Value annotation. On the side note, usually you don't want to have a password directly included. Even if it's encoded, it's not encrypted so you can still see the password value. Which you don't want unless it's just some personal project and not for some company where everyone would be able to see it. You can either encrypt it or if develop the app for Cloud and use e.g. Kubernetes, you can use Secrets. – rasto Nov 03 '20 at 12:02
  • Thanks man, Yes indeed i am going to use Kubernetes in near future and i currently preparing for CKAD also :) . I have worked with HTTPClient Library and Rest Template but I am very new to this Webclient part and struggling a lot. Actually i am working on Servicenow APIs like get,create,update. i have few questions which i am planning will post in Stackover flow this week. will paste it here in our conversation – Kamal Garg Nov 03 '20 at 13:07
  • 1
    Happy to help :) – rasto Nov 03 '20 at 14:21