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.
Related
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
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.
I've just created a FactoryBean implementation in order to request RestTemplate:
#Component
public class RestTemplateFactory
implements FactoryBean<RestTemplate>, InitializingBean {
//init resttemplate headers
}
So, now I'm able to inject a RestTemplate at whichever class:
#Service
public class DocumentServiceBackOffice {
private RestTemplate restTemplate;
public DocumentServiceBackOffice(RestTemplate restTemplate) {//...}
}
However, I'd like to create another FactoryBean<RestTemplate> in order to initialize other parameters.
How could I create in order to inject one or other according to a qualifier?
Any ideas?
EDIT
#Component
public class RestTemplateFactory
implements FactoryBean<RestTemplate>, InitializingBean {
private RestTemplate restTemplate;
private JWTService jwtService;
public RestTemplateFactory(JWTService jwtService) {
this.jwtService = jwtService;
}
public RestTemplate getObject() {
return this.restTemplate;
}
public Class<RestTemplate> getObjectType() {
return RestTemplate.class;
}
public boolean isSingleton() {
return true;
}
public void afterPropertiesSet() {
this.restTemplate = new RestTemplate();
JWTHeaderRequestInterceptor jwtInterceptor = new JWTHeaderRequestInterceptor(this.jwtService);
this.restTemplate.setInterceptors(Arrays.asList(jwtInterceptor));
}
}
Instead of using a FactoryBean just use an #Bean annotated method which accepts a RestTemplateBuilder and use that to configure instances.
#Bean
#Primary
public RestTemplate fooRestTemplate(RestTemplateBuilder builder, JWTService jwtService) {
return builder.additionalInterceptors(Collections.singletonList(new JwtHeaderInterceptor(jwtService)).build();
}
#Bean
public RestTemplate barRestTemplate(RestTemplateBuilder builder {
return builder.build();
}
This will result in 2 available RestTemplate instances. The fooRestTemplate (marked as default due to #Primary) and barRestTemplate. To specify the specific one to use add an #Qualifier("barRestTemplate") to use the not default one.
public DocumentServiceBackOffice(#Qualifier("barRestTemplate") RestTemplate restTemplate) { ... }
Another way to do it would be defining a configuration with two RestTemplate beans with qualifiers.
#Configuration
public class Configuration {
#Bean
#Qualifier("firstRestTemplate")
public RestTemplate firstRestTemplate(){
// any building logic here
return new Resttemplate();
}
#Bean
#Qualifier("secondRestTemplate")
public RestTemplate secondRestTemplate(){
// any building logic here
return new Resttemplate();
}
}
Then, in your code, use the right #Qualifier when autowiring.
Setter injection example:
#Service
public class Service {
#Autowired
#Qualifier("firstRestTemplate")
private RestTemplate template;
// ...
}
Constructor injection example:
#Service
public class Service {
private RestTemplate template;
public Service(#Autowired #Qualifier("firstRestTemplate") RestTemplate template) {
this.template = template;
}
// ...
}
I am having a wrapper that autowire ribbon aware RestTemplate and then I add a interceptor-
#Component
public class MyRestTemplate {
#Autowired
private RestTemplate restTemplate;
#PostConstruct
public void init(){
restTemplate.setInterceptors(Collections.singletonList(new
MyRestTemplateRequestInterceptor(applicationName)));
}
}
If I #AutoWire RestTemplate in some other component, will I get same instance of RestTemplate with this incterceptor? My observation is the autowired resttemplate in other component also adds this interceptor. Please help to validate the understanding.
Yyou will get the same instance of RestTemplate. When the bean MyRestTemplate is constructor (rather after it's construction), you are adding the MyRestTemplateRequestInterceptor to the RestTemplate interceptor.
So this interceptor will be applied to RestTemplate.
If you are looking to apply an interceptor for a specific feign client you can create your own configuration:
#Configuration
#IgnoreDuringScan
public class FeignClientConfiguration {
#Autowired
private Environment environment;
#Bean
public Encoder feignEncoder() {
return new GsonEncoder();
}
#Bean
public ErrorDecoder feignDecoder() {
return new GsonDecoder();
}
#Bean
public RequestInterceptor requestHeaderInterceptor() {
return new RequestInterceptor() {
#Override
public void apply(RequestTemplate requestTemplate) {
//impl
}
};
}
#Bean
public Feign.Builder feingBuilder() {
return Feign.builder().encoder(feignEncoder()).decoder(feignDecoder()).errorDecoder(new YourErrorDecoder())
.requestInterceptor(requestHeaderInterceptor);
}
}
Then just apply the configuration to your feign client:
#FeignClient(name = "myFeign", url = "url/...", configuration = {
FeignClientConfiguration.class
})
public interface YourFeignClient {
//....
}
I'm trying to pass a protobuf parameter to a REST endpoint but I get
org.springframework.web.client.HttpServerErrorException: 500 null
each time I try. What I have now is something like this:
#RestController
public class TestTaskEndpoint {
#PostMapping(value = "/testTask", consumes = "application/x-protobuf", produces = "application/x-protobuf")
TestTaskComplete processTestTask(TestTask testTask) {
// TestTask is a generated protobuf class
return generateResult(testTask);
}
}
#Configuration
public class AppConfiguration {
#Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
}
#SpringBootApplication
public class JavaConnectorApplication {
public static void main(String[] args) {
SpringApplication.run(JavaConnectorApplication.class, args);
}
}
and my test looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#SpringBootTest
#WebAppConfiguration
public class JavaConnectorApplicationTest {
#Configuration
public static class RestClientConfiguration {
#Bean
RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) {
return new RestTemplate(Arrays.asList(hmc));
}
#Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
}
#Autowired
private RestTemplate restTemplate;
private int port = 8081;
#Test
public void contextLoaded() {
TestTask testTask = generateTestTask();
final String url = "http://127.0.0.1:" + port + "/testTask/";
ResponseEntity<TestTaskComplete> customer = restTemplate.postForEntity(url, testTask, TestTaskComplete.class);
// ...
}
}
I'm sure that it is something with the parameters because if I create a variant which does not take a protobuf parameter but returns one it just works fine. I tried debugging the controller code but the execution does not reach the method so the problem is probably somewhere else. How do I correctly parametrize this REST method?
This is my first stack overflow answer but I was a lot to frustred from searching for working examples with protobuf over http and spring.
the answer https://stackoverflow.com/a/44592469/15705964 from Jorge is nearly correct.
Like the comments mention: "This won't work in itself. You need to add a converter somewhere at least."
Do it like this:
#Configuration
public class WebConfig implements WebMvcConfigurer {
#Autowired
ProtobufHttpMessageConverter protobufHttpMessageConverter;
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(protobufHttpMessageConverter);
}
}
The ProtobufHttpMessageConverter will do his job automatically and add the object to your controller methode
#RestController
public class ProtobufController {
#PostMapping(consumes = "application/x-protobuf", produces = "application/x-protobuf")
public ResponseEntity<TestMessage.Response> handlePost(#RequestBody TestMessage.Request protobuf) {
TestMessage.Response response = TestMessage.Response.newBuilder().setQuery("This is a protobuf server Response")
.build();
return ResponseEntity.ok(response);
}
Working example with send and reseive with rest take a look: https://github.com/Chriz42/spring-boot_protobuf_example
Here it's the complete answer
#SpringBootApplication
public class JavaConnectorApplication {
public static void main(String[] args) {
SpringApplication.run(JavaConnectorApplication.class, args);
}
}
Then you need to provide the right configuration.
#Configuration
public class AppConfiguration {
//You need to add in this list all the messageConverters you will use
#Bean
RestTemplate restTemplate(ProtobufHttpMessageConverter hmc) {
return new RestTemplate(Arrays.asList(hmc,smc));
}
#Bean
ProtobufHttpMessageConverter protobufHttpMessageConverter() {
return new ProtobufHttpMessageConverter();
}
}
And finally your RestController.
#RestController
public class TestTaskEndpoint {
#PostMapping(value = "/testTask")
TestTaskComplete processTestTask(#RequestBody TestTask testTask) {
// TestTask is a generated protobuf class
return generateResult(testTask);
}
}
The #RequestBody annotation: The body of the request is passed through an HttpMessageConverter (That you already defined) to resolve the method argument depending on the content type of the request
And your test class:
#RunWith(SpringRunner.class)
#SpringBootTest
#WebAppConfiguration
public class JavaConnectorApplicationTest {
#Autowired
private RestTemplate restTemplate;
private int port = 8081;
#Test
public void contextLoaded() {
TestTask testTask = generateTestTask();
final String url = "http://127.0.0.1:" + port + "/testTask/";
ResponseEntity<TestTaskComplete> customer = restTemplate.postForEntity(url, testTask, TestTaskComplete.class);
// Assert.assertEquals("dummyData", customer.getBody().getDummyData());
}
}