Force SpingBoot to use Gson over Jackson - spring-boot

I am trying to force SpringBoot to use Gson instead of Jackson. I've read most of the articles I've found online and I am still seeing Jackson being used. Here's what I've done
Added
spring:
http: { converters: { preferred-json-mapper: gson } }
mvc: { converters: {preferred-json-mapper: gson } }
in application.yaml
Updated POM
Added gson dependency
Added jackson-databind to exclusion list in spring-boot-starter-web depedency.
Added #EnableAutoConfiguration(exclude = JacksonAutoConfiguration.class) to main class.
Written below #Configuration class:
#Configuration
#Slf4j
public class MyConfig implements WebMvcConfigurer {
#Override
public void extendMessageConverters (List<HttpMessageConverters<?>> converters) {
log.debug("Setting gson converter");
converters.add(new GsonHttpMessageConverter(myCustomGsonInstance()));
}
public Gson myCustomGsonInstance() {
return new Gson();
}
}
When running tests in debug, I can see that Jackson is still listed in the HttpMessageConverters list and Gson is not.
Update:
This behavior is seen while running live and in the below test class.
#AutoConfigureMockMvc
#SpringBootTest(webEnvironment = MOCK)
#ExtendWith(MockitoExtension.class)
public class MyTestClass {
#Autowired
private MyController controller;
private MockMvc mockMvc;
#BeforeEach
public void setUp(){
mockMvc = MockMvcBuilders.standaloneSetup(controller)
// .setMessageConverters(new GsonHttpMessageConverter(myCustomGsonInstance())) // if I add this, the test passes.
.build();
}
#Test
public void happyFlow(){
// given
URI uri = "/test/uri";
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
// when
String responseBody = mockMvc.perform(get(uri).headers(headers)).andReturn().getResponse().getContentAsString();
// then
assertThat(responseBody, wasSerializedByGson());
}
}

It looks like you're using the wrong property for configuring the preferred JSON mapper. You are using spring.http.converters.preferred-json-mapper but the correct property is spring.mvc.converters.preferred-json-mapper. In application.yaml, that would be the following:
spring:
mvc:
converters:
preferred-json-mapper: gson

Spring Boot comes with Gson Auto Configuration support: Source Code
So you have to Autowire the Gson singleton instance to be used by your WebMvcConfigurer in addition to enabling the yaml property:
#Configuration
#Slf4j
public class MyConfig implements WebMvcConfigurer {
#Autowired
private Gson gson;
#Override
public void extendMessageConverters (List<HttpMessageConverters<?>> converters) {
log.debug("Setting gson converter");
converters.add(new GsonHttpMessageConverter(gson));
}
}
And the yaml properties borrowed from Andy Wilkinson:
spring:
mvc:
converters:
preferred-json-mapper: gson
With this setup Spring MVC is using the same Gson instance as the one Autowired in your configuration.
And in your test, it should look like this:
#WebMvcTest(MyController.class)
public class MyTestClass {
#Autowired
private MockMvc mockMvc;
#Autowired
private MyController controller;
#Test
public void happyFlow(){
// given
URI uri = "/test/uri";
HttpHeaders headers = new HttpHeaders();
headers.set(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE);
// when
String responseBody = mockMvc.perform(get(uri).headers(headers)).andReturn().getResponse().getContentAsString();
// then
assertThat(responseBody, wasSerializedByGson());
}
}

Related

required a bean of type 'org.springframework.cloud.netflix.ribbon.SpringClientFactory' that could not be found

I have this test project which I would like to migrate to more recent version:
#Configuration
public class LoadbalancerConfig extends RibbonLoadBalancerClient {
public LoadbalancerConfig(SpringClientFactory clientFactory) {
super(clientFactory);
}
}
Full code example: https://github.com/rcbandit111/Generic_SO_POC/blob/master/src/main/java/org/merchant/database/service/sql/LoadbalancerConfig.java
Do you know how I can migrate this code to latest load balancer version?
I think examining the RibbonAutoConfiguration class gives you a good hint of how you should configure things.
First remove #Configuration from LoadbalancerConfig, I also renamed LoadbalancerConfig to CustomLoadbalancer to prevent confusion.
public class CustomLoadbalancer extends RibbonLoadBalancerClient {
public CustomLoadbalancer(SpringClientFactory clientFactory) {
super(clientFactory);
}
}
add the following dependency to your gradle
com.netflix.ribbon:ribbon:2.7.18
then add a configuration class like:
#Configuration
#ConditionalOnClass({Ribbon.class})
#AutoConfigureAfter(name = "org.springframework.cloud.netflix.eureka.EurekaClientAutoConfiguration")
#ConditionalOnProperty(value = "spring.cloud.loadbalancer.ribbon.enabled",
havingValue = "true", matchIfMissing = true)
#AutoConfigureBefore(LoadBalancerAutoConfiguration.class)
public class LoadBalancerClientConfig {
#Autowired(required = false)
private List<RibbonClientSpecification> configurations = new ArrayList<>();
#Bean
public CustomLoadbalancer customLoadbalancer() {
return new CustomLoadbalancer(springClientFactory());
}
#Bean
public SpringClientFactory springClientFactory() {
SpringClientFactory factory = new SpringClientFactory();
factory.setConfigurations(this.configurations);
return factory;
}
}
If you want to use Spring cloud load balancer instead of above configuration add spring-cloud-starter-loadbalancer dependency to your gradle.build and for configuration you only need this bean:
#LoadBalanced
#Bean
RestTemplate getRestTemplate() {
return new RestTemplate();
}
This RestTemplate pretty works identical to standard RestTemplate class, except instead of using physical location of the service, you need to build the URL using Eureka service ID.
Here is an example of how could you possibly use it in your code
#Component
public class LoadBalancedTemplateClient {
#Autowired
RestTemplate restTemplate;
#Autowired
User user;
public ResponseEntity<Result> getResult() {
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder
.fromHttpUrl("http://application-id/v1/") // application id registered in eureka
.queryParam("id", user.getUserId());
return restTemplate.getForEntity(uriComponentsBuilder.toUriString(),
Result.class);
}
}
Also if you wish to use reactive client the process is the same first define the bean:
#LoadBalanced
#Bean
WebClient.Builder webClientBuilder() {
return WebClient.builder();
}
and then inject and use it when you need:
#Autowired
private WebClient.Builder webClient;
public Mono<String> doSomething() {
return webClient
.build()
.get()
.uri("http://application-id/v1/")
.retrieve()
.bodyToMono(String.class);
}
Also you can check documentation for additional information: documentation

Springboot Mockito Test and Autowired messageSource in controller org.springframework.context.NoSuchMessageException

I have a Springboot app with REST controller and Mockito unit test cases written for it. The problem is I am getting NoSuchMessageException by reading messageSource in the RestController when running the test cases.
But not happening when calling it in actually using Postman or other Rest clients.
(I use Lombok to avoid boilerplate codes).
The Rest Controller code
#RestController
#RequestMapping(value = VERSION + "/product")
#Slf4j
#RequiredArgsConstructor
public class ProductController implements CommonController {
private final ProductService productService;
private final MessageSource messageSource;
#PostMapping(path = "")
public ResponseEntity<CommonResponseDTO> saveProduct(#Valid #RequestBody ProductSaveRequest request) {
return addNewProduct(request);
}
private String getMessage(String key) {
return messageSource.getMessage(key, new Object[0], Locale.getDefault());
}
}
The messageSource Config
#Configuration
public class AppConfig {
#Bean
public MessageSource messageSource() {
ReloadableResourceBundleMessageSource messageSource = new ReloadableResourceBundleMessageSource();
messageSource.setBasenames("classpath:messages/exception-message", "classpath:messages/success-message");
messageSource.setCacheSeconds(60); //reload messages every 60 seconds
return messageSource;
}
}
Test class
#Slf4j
#ExtendWith(SpringExtension.class)
#WebMvcTest(value = ProductController.class)
class ProductControllerTest {
#Autowired
private MockMvc mockMvc;
#MockBean
private ProductService productService;
private static final String PRODUCT_PATH = "/v1/product";
#Test
void saveProduct() throws Exception {
// productService.add to respond back with mockProduct
Mockito.doNothing().when(productService).add(Mockito.any(Product.class));
// Send course as body to /students/Student1/courses
RequestBuilder requestBuilder = MockMvcRequestBuilders
.post(PRODUCT_PATH)
.accept(MediaType.APPLICATION_JSON)
.content("{\n" +
" \"name\": \"Apple\"\n" +
"}")
.contentType(MediaType.APPLICATION_JSON);
MvcResult result = mockMvc.perform(requestBuilder).andReturn();
MockHttpServletResponse response = result.getResponse();
assertEquals(HttpStatus.CREATED.value(), response.getStatus());
}
}
Exception
org.springframework.context.NoSuchMessageException: No message found under code 'success.confirmation.common.added.code' for locale 'en_US'.
But above message exists and successfully appears in actual REST clients.
Project structure
Thanks in advance.
The #WebMvcTest annotation only loads relevant beans (e.g. Filter, #ControllerAdvice, WebMvcConfigurer) for testing your controller. By default, this TestContext doesn't include any custom #Configuration beans.
You have explicitly import your config:
#Slf4j
// #ExtendWith(SpringExtension.class) not needed as part of #WebMvcTest with recent Spring Boot versions
#Import(AppConfig.class)
#WebMvcTest(value = ProductController.class)
class ProductControllerTest {
// your test
}
In case you're relying on the auto-configuration of the MessageSource you can enable it for your test with #ImportAutoConfiguration(MessageSourceAutoConfiguration.class).

Spring-Boot 2.3.0.RELEASE Unable to autowire RestTemplate for JUnit 5 test

I have configured the necessary Beans in #Configuration class but have not been able to get the RestTemplate injected into my test class for testing.
#Configuration
public class AppConfig {
#Bean
public ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
#Bean
public RestTemplate restTemplate(ProtobufHttpMessageConverter converter) {
RestTemplate http2Template = new RestTemplate(new OkHttp3ClientHttpRequestFactory());
List<HttpMessageConverter<?>> converters = http2Template.getMessageConverters();
converters.add(converter);
http2Template.setMessageConverters(converters);
return http2Template;
}
}
Test class:
#ExtendWith(SpringExtension.class)
#AutoConfigureWebClient(registerRestTemplate = true)
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = {RestTemplate.class, ProtobufHttpMessageConverter.class})
#ActiveProfiles("dev")
public class GRPCRestApiTest {
#Autowired
private RestTemplate restTemplate;
#Test
public void GetOneCourseUsingRestTemplate() throws IOException {
assertNotNull(restTemplate, "autowired restTemplate is NULL!");
ResponseEntity<Course> course = restTemplate.getForEntity(COURSE_URL, Course.class);
assertResponse(course.toString());
HttpHeaders headers = course.getHeaders();
}
}
Any advice and insight is appreciated
The classes attribute of the annotation #SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT, classes = {RestTemplate.class, ProtobufHttpMessageConverter.class}) takes component classes to load the application context. You should not put in here anything except your main Spring Boot class or leave it empty.
Furthermore #AutoConfigureWebClient(registerRestTemplate = true) as you want to use the bean you configure inside your application (at least that's what I understood from your question).
So your test setup should look like the following:
// #ExtendWith(SpringExtension.class) can be omitted as it is already part of #SpringBootTest
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.DEFINED_PORT)
#ActiveProfiles("dev")
public class GRPCRestApiTest {
#Autowired
private RestTemplate restTemplate;
#Test
public void GetOneCourseUsingRestTemplate() throws IOException {
assertNotNull(restTemplate, "autowired restTemplate is NULL!");
ResponseEntity<Course> course = restTemplate.getForEntity(COURSE_URL, Course.class);
assertResponse(course.toString());
HttpHeaders headers = course.getHeaders();
}
}
This should now start your whole Spring Boot context in dev profile and you should have access to all your beans you define inside your production code like AppConfig.

Spring component depends on the configuration which load props from application.properties but props are not loaded

I have some difficulties and I can't realize how to let it work because of lack of knowledge in Spring framework.
What I'm trying to do is pretty simple. I want to use injected AppProperties in RestTemplateComponent constuctor or getRestTemplate method but all props are null.
I think I understand why.. it's because RestTemplateComponent by the order was loaded first and that's why AppProperties props are null.
Is it possible some how to tell to Spring to load AppProperties first in order to use it in RestTemplateComponent.
By the way problem occurs only on start there is no any problem when I inject it in controller and during request use these objects.
application.properties
integration.url=http://...
#Configuration
#ConstructorBinding
#ConfigurationProperties("integration")
public class AppProperties {
private String url;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}
#Component
public class RestTemplateComponent {
private final AppProperties appProperties;
public RestTemplateComponent(AppProperties appProperties) {
this.appProperties = appProperties;
}
#Bean
public RestTemplate getRestTemplate() {
RestTemplate restTemplate = new RestTemplate();
//Custom authorization
return restTemplate;
}
}
Ok, finally I did it work by adding annotation #PropertySource({"classpath:application.properties"}) within my AppProperties class and then when I try to autowire AppProperties in SpringBootApplication I see in debug that props are initialized.

Spring - should I use #Bean or #Component?

Here is the current code at my work.
Method 1
#Configuration
public class AppConfig {
#Bean
#Autowired(required = false)
public HttpClient createHttpClient() {
// do some connections configuration
return new HttpClient();
}
#Bean
#Autowired
public NameClient nameClient(HttpClient httpClient,
#Value("${ServiceUrl:NotConfigured}")
String serviceUrl) {
return new NameClient(httpClient, serviceUrl);
}
}
And the NameClient is a simple POJO looks like following
public class NameClient {
private HttpClient client;
private String url;
public NameClient(HttpClient client, String url) {
this.client = client;
this.url = url;
}
// other methods
}
Instead of using #Bean to configure, I wanted to follow this pattern:
Method 2
#Configuration
public class AppConfig {
#Bean
#Autowired(required = false)
public HttpClient createHttpClient() {
// do some connections configuration
return new HttpClient();
}
}
And use auto-scanning feature to get the bean
#Service //#Component will work too
public class NameClient {
#Autowired
private HttpClient client;
#Value("${ServiceUrl:NotConfigured}")
private String url;
public NameClient() {}
// other methods
}
Why the first method above is used/preferred? What is the advantage of one over the other? I read about the difference between using #Component and #Bean annotations.
They're equivalent.
You would typically use the second one when you own the NameClient class and can thus add Spring annotations in its source code.
You would use the first one when you don't own the NameClient class, and thus can't annotate it with the appropriate Spring annotations.

Resources