Is it possible to use Jackson as the serializer/marshaller for JSON data instead of JAXB when using Jersey Client API?
If so how to configure it?
OK, I found it out, it turns out to be quite simple after all:
ClientConfig cc = new DefaultClientConfig();
cc.getClasses().add(JacksonJsonProvider.class);
Client clientWithJacksonSerializer = Client.create(cc);
The JacksonJsonProvider comes from the jackson-jaxrs package.
You may skip the creation of external config and register the provider directly:
Client client = ClientBuilder.newClient().register(JacksonJsonProvider.class)
Solution with JacksonJaxbJsonProvider
Common way how to use Jackson with custom configuration in Jersey client was to use JacksonJaxbJsonProvider for example like this
JacksonJaxbJsonProvider provider = new JacksonJaxbJsonProvider();
provider.setMapper(yourObjectMapper());
Client client = ClientBuilder.newClient(new ClientConfig(provider));
Unfortunately in Jersey 2.26 they copied JacksonJaxbJsonProvider class
from com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider artifact (Jackson)
to org.glassfish.jersey.media:jersey-media-json-jackson artifact (Jersey)
and changed package
from com.fasterxml.jackson.jaxrs.json
to org.glassfish.jersey.jackson.internal.jackson.jaxrs.json.
It is still possible to use this approach it's just needed to change JacksonJaxbJsonProvider import.
Apart from JacksonJaxbJsonProvider being now in internal package drawback is also
that you must know on which Jersey version your code runs which might be a problem when different dependencies require different Jersey versions.
Better solution with ContextResolver<ObjectMapper>
Better possibility how to configure Jackson in Jersey client is to use the same way how it is configured in Jersey server which is to create ObjectMapper provider like this:
#Provider
#Produces(MediaType.APPLICATION_JSON)
public class ObjectMapperProvider implements ContextResolver<ObjectMapper> {
private ObjectMapper objectMapper = yourObjectMapper();
#Override
public ObjectMapper getContext(Class<?> objectType) {
return objectMapper;
}
}
and use it for example like this:
ClientConfig clientConfig = new ClientConfig();
clientConfig.register(JacksonFeature.class); // usually auto-discovered
clientConfig.register(new ObjectMapperProvider());
Client client = ClientBuilder.newClient(clientConfig);
If you have both the server and the client you can reuse ObjectMapperProvider class.
It seems that this approach works from Jersey version 2.9.
You might also want to try org.codehaus.jackson.jaxrs.JacksonJaxbJsonProvider (jackson-jaxrs 1.6.1).
I ran into similar issue, but for me none of the suggestions given here worked.
What worked for me was below piece of code:
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Client;
...
ClientBuilder clientBuilder = ClientBuilder.newBuilder()
clientBuilder.register(JacksonFeature.class);
...
Client client = clientBuilder.build();
The key change was usage of JacksonFeature.class - it comes from jersey-media-json-jackson-x.yy.jar
I got clue to use this solution from this article - http://www.baeldung.com/jersey-jax-rs-client
For jersey 2.22.2 and Jackson 2.7.2 gradle dependencies are:
dependencies {
compile("com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:2.7.2")
compile("org.glassfish.jersey.core:jersey-client:2.22.2")
}
Sample client code is:
final String name = "world";
final Client client = ClientBuilder.newClient().register(JacksonJaxbJsonProvider.class);
final WebTarget target = client.target("http://localhost:8080").path("hello").path(name);
final Message message = target.request().get(Message.class);
System.out.println(message.getWelcomeMessage()); // hello world
Related
When I upgrade implementation 'org.springframework.cloud:spring-cloud-openfeign-core:2.2.2.RELEASE' to latest version implementation 'org.springframework.cloud:spring-cloud-openfeign-core:3.1.1'
I get error for this imported class:
import org.springframework.cloud.openfeign.ribbon.CachingSpringLoadBalancerFactory
import org.springframework.cloud.openfeign.ribbon.LoadBalancerFeignClient;
#Bean
public Client client(
HttpClientConnectionManager httpClientConnectionManager,
CachingSpringLoadBalancerFactory lbClientFactory,
SpringClientFactory clientFactory) {
CloseableHttpClient closeableHttpClient = HttpClients.custom()
.setConnectionManager(httpClientConnectionManager)
.build();
ApacheHttpClient delegate = new ApacheHttpClient(closeableHttpClient);
return new LoadBalancerFeignClient(delegate, lbClientFactory, clientFactory);
}
Do you know how I have to replace them in order to use the latest version?
ribbon was excluded from that spring boot version, you should now use loadbalancer instead. Check this class org.springframework.cloud.openfeign.loadbalancer.FeignLoadBalancerAutoConfiguration, from my POV, you just need to remove all your ribbon dependencies from the code and that's it.
I have a class which builds multiple RestTemplates using RestTemplateBuilder:
private RestTemplate build(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder
.rootUri("http://localhost:8080/rest")
.build();
}
For my test setup I use #AutoConfigureMockRestServiceServer and mock responses using MockServerRestTemplateCustomizer:
mockServerRestTemplateCustomizer.getServer()
.expect(ExpectedCount.times(2),
requestToUriTemplate("/some/path/{withParameters}", "withParameters"))
.andRespond(withSuccess());
My test passes when I uncomment the spring-boot-actuator dependency in my pom and fails in the other scenario with the following message.
Expected: /some/path/parameter
Actual: http://localhost:8080/rest/pos/some/path/withParameters
I noticed by debugging through MockServerRestTemplateCustomizer that spring-boot-actuator applies a "DelegateHttpClientInterceptor" for supporting their built in metrics for rest templates. However this creates a problem with the following code which I found in RootUriRequestExpectationManager:
public static RequestExpectationManager forRestTemplate(RestTemplate restTemplate,
RequestExpectationManager expectationManager) {
Assert.notNull(restTemplate, "RestTemplate must not be null");
UriTemplateHandler templateHandler = restTemplate.getUriTemplateHandler();
if (templateHandler instanceof RootUriTemplateHandler) {
return new RootUriRequestExpectationManager(((RootUriTemplateHandler) templateHandler).getRootUri(),
expectationManager);
}
return expectationManager;
}
Because as mentioned above spring-boot-actuator registers a "DelegateHttpClientInterceptor" which leads to the above code not recognizing the RootUriTemplateHandler and therefore not matching the request using requestToUriTemplate.
What am I missing here to get this working?
As Andy Wilkinson pointed out, this seems to be a bug in Spring boot. I created an issue with a sample project.
I am struggling to find what could be wring here; need help.
I am using spring-data-redis 2.4.1
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration()
redisStandaloneConfiguration.setHostname(hostname)
redisStandaloneConfiguration.setPort(6379)
redisStandaloneConfiguration.setPassword("password")
I then create lettuceClientConfigurationBuilder and specify clientName
I then use lettuceClientConfiguration and redisStandaloneConfiguration to create ClientConnectionFactory.
However, when we call getConnection() on the connection Factory, we get
WrongPass Invalid username-password pair
The same set of username-password works with Redis-CLI on cmd prompt.
Is there is something wrong in the way I am using in my java application?
Any pointer/hint towards solving this would be greatly appreciated.
Spring Boot configures LettuceConnectionFactory for you, you can specify the connection params on the application.properties file.
spring.redis.database=0
spring.redis.host=localhost
spring.redis.port=6379
spring.redis.password=yourPassword
spring.redis.timeout=60000
If you wanna do it programmatically, set the spring.redis.password in application.properties and try this:
#Configuration
class AppConfig {
#Bean
public LettuceConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration("server", 6379));
}
}
I had mistaken username for clientname set on LettuceClientConfigurationBuilder but username had to be specified on the redisstandaloneconfiguratuon.
This works for me; also please note acl was introduced only after lettuce 2.4.1 so any prior version will not work.
redisStandaloneConfiguration.setUsername(connectionFactoryConfigs.getUserName());
I've a filter (OncePerRequestFilter) which basically intercepts incoming request and logs traceId, spanId etc. which works well,
this filter lies in a common module which is included in other projects to avoid including spring sleuth dependency in all of my micro-services, the reason why I've created it as a library because any changes to library will be common to all modules.
Now I've to add a new propagation key which need to be propagated to all services via http headers like trace and spanId for that I've extracted current span from HttpTracing and added a baggage key to it (as shown below)
Span span = httpTracing.tracing().tracer().currentSpan();
String corelationId =
StringUtils.isEmpty(request.getHeader(CORELATION_ID))
? "n/a"
: request.getHeader(CORELATION_ID);
ExtraFieldPropagation.set(CUSTOM_TRACE_ID_MDC_KEY_NAME, corelationId);
span.annotate("baggage_set");
span.tag(CUSTOM_TRACE_ID_MDC_KEY_NAME, corelationId);
I've added propagation-keys and whitelisted-mdc-keys to my application.yml (with my library) file like below
spring:
sleuth:
propagation-keys:
- x-corelationId
log:
slf4j:
whitelisted-mdc-keys:
- x-corelationId
After making this change in filter the corelationId is not available when I make a http call to another service with same app, basically keys are not getting propagated.
In your library you can implement ApplicationEnvironmentPreparedEvent listener and add the configuration you need there
Ex:
#Component
public class CustomApplicationListener implements ApplicationListener<ApplicationEvent> {
private static final Logger log = LoggerFactory.getLogger(LagortaApplicationListener.class);
public void onApplicationEvent(ApplicationEvent event) {
if (event instanceof ApplicationEnvironmentPreparedEvent) {
log.debug("Custom ApplicationEnvironmentPreparedEvent Listener");
ApplicationEnvironmentPreparedEvent envEvent = (ApplicationEnvironmentPreparedEvent) event;
ConfigurableEnvironment env = envEvent.getEnvironment();
Properties props = new Properties();
props.put("spring.sleuth.propagation-keys", "x-corelationId");
props.put("log.slf4j.whitelisted-mdc-keys:", "x-corelationId");
env.getPropertySources().addFirst(new PropertiesPropertySource("custom", props));
}
}
}
Then in your microservice you will register this custom listener
public static void main(String[] args) {
ConfigurableApplicationContext context = new SpringApplicationBuilder(MyApplication.class)
.listeners(new CustomApplicationListener()).run();
}
I've gone through documentation and seems like I need to add spring.sleuth.propagation-keys and whitelist them by using spring.sleuth.log.slf4j.whitelisted-mdc-keys
Yes you need to do this
is there another way to add these properties in common module so that I do not need to include them in each and every micro services.
Yes, you can use Spring Cloud Config server and a properties file called application.yml / application.properties that would set those properties for all microservices
The answer from Mahmoud works great when you want register the whitelisted-mdc-keys programatically.
An extra tip when you need these properties also in a test, then you can find the anwser in this post: How to register a ApplicationEnvironmentPreparedEvent in Spring Test
I am using groovy, spring5, springboot2 and i was trying to replace Resttemplate with WebClient inorder to makes some synchronous HTTP(REST) calls. Retrieve() in webClient.get().uri("").retrieve() is not getting recognized(compilation error) in .groovy while the same code works in .java
WebClientJava.java
import org.springframework.web.reactive.function.client.WebClient;
public class WebClientJava {
String retriever(){
WebClient webClient = WebClient.create();
String responsess = webClient.get().uri("").retrieve().bodyToMono(String.class).block();
return responsess;
}
}
WebClientGroovy.groovy
import org.springframework.web.reactive.function.client.WebClient
class WebClientGroovy {
String retriever(){
WebClient webClient = WebClient.create()
WebClient.RequestHeadersSpec responsess = webClient.get().uri("").retrieve().bodyToMono(String.class).block()
return responsess
}
}
i expect it work with groovy as it is working with java. Does any one encounter or has any thoughts?
Your problem appears to be caused by a bug in IntelliJ IDEA as the sample project builds successfully on the command line with Maven.
The uri method returns S with S being defined as S extends RequestHeadersSpec<?>. The IDE appears to be unable to cope with this and believes it's dealing with a ? which it treats as java.lang.Object. I would recommend reporting the problem to JetBrains.
In the meantime, you can work around the problem by casting the return from uri:
String responses = ((WebClient.RequestHeadersSpec)webClient.get().uri(""))
.retrieve().bodyToMono(String.class).block()