I faced with a problem of beans configuration in spring boot. In other words:
I have 3 microservices (utils, orders, admin). Utils for utility classes, orders for book orders, admin for administrate something. Orders and Admin have as a dependency Utils module. In Utils I have class that used in Orders, but not in the Admin module, but the Admin module has dependency on Utils because it used other utility classes. Back to orders :) Orders used class from Utils for example Payment that has Autowired RestTemplate and RestTemplate need Configuration. Some code below for understanding ...
#Import({
RestTemplateConfig.class})
#SpringBootApplication(scanBasePackages = "com.test")
public class OrdersApp {
public static void main(String[] args) {
SpringApplication.run(OrdersApp.class, args);
}
}
#Import({
RestTemplateConfig.class})
#SpringBootApplication(scanBasePackages = "com.test")
public class AdminApp {
public static void main(String[] args) {
SpringApplication.run(OrdersApp.class, args);
}
}
public class Payment{
private final RestTemplate restTemplate;
}
public Payment(
RestTemplate restTemplate
) {
this.restTemplate = restTemplate;
}
#EnableConfigurationProperties({RestTemplateConfigProperties.class})
public class RestTemplateConfig {
#Bean("restTemplate")
public RestTemplate restTemplate(RestTemplateBuilder builder, RestTemplateConfigProperties configProperties) {
return builder
.setConnectTimeout(Duration.ofSeconds(configProperties.getConnectTimeoutSec()))
.setReadTimeout(Duration.ofSeconds(configProperties.getReadTimeoutSec()))
.build();
}
}
BUT
In AdminApp I don't use Payment class that has autowired rest template that need configuration and I don't need to Import RestTemplateConfig.class for AdminApp, but AdminApp has dependency in pom.xml on Utils that has Payment that has restTemplate that need configuratuion (LOL)
How I cant improve confirations and bean init for this situation
Related
How to run code from class with #SpringBootApplication annotation. I want to run my code without calling to controller and get info from terminal not web browser. I tried to call weatherService in #SpringBootApplication but I've got a application failed start with description
The dependencies of some of the beans in the application context form a cycle:
┌─────┐
| weatherClientApplication
↑ ↓
| weatherService defined in file [C:\Users\xxx\IdeaProjects\weatherclient\target\classes\com\xxx\restapiclient\service\WeatherService.class]
└─────┘
#SpringBootApplication
public class WeatherClientApplication {
private WeatherService weatherService;
public WeatherClientApplication(WeatherService weatherService) {
this.weatherService = weatherService;
}
private static final Logger log = LoggerFactory.getLogger(WeatherClientApplication.class);
public static void main(String[] args) {
SpringApplication.run(WeatherClientApplication.class, args);
}
#Bean
public RestTemplate restTemplate(RestTemplateBuilder builder){
return builder.build();
}
#Bean
public CommandLineRunner run(RestTemplate restTemplate) throws Exception {
return args -> {
log.info(weatherService.getTemperatureByCityName("Krakow"));
};
}
}
#Service
public class WeatherService {
private RestTemplate restTemplate;
public WeatherService(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
public String getTemperatureByCityName(String cityName) {
String url = "http://api.openweathermap.org/data/2.5/weather?q=" + cityName + "&APPID=" + API_KEY + "&units=metric";
Quote quote = restTemplate.getForObject(url, Quote.class);
return String.valueOf(quote.getMain().getTemp());
}
}
You can do this by using main method and by using ApplicationContext, In this approach you don't need any CommandLineRunner
public static void main(String[] args) {
ApplicationContext context = SpringApplication.run(WeatherClientApplication.class, args);
WeatherService service = (WeatherService)context.getBean("weatherService");
service. getTemperatureByCityName("cityname");
}
1) What you want is implementing CommandLineRunner and define the entry point of your application in the public void run(String... args) method defined in this interface.
2) As said by Spring you have a cycle : break it with a injection outside the constructor.
Such as :
#SpringBootApplication
public class WeatherClientApplication implements CommandLineRunner{
#Autowired
private WeatherService weatherService;
//...
#Override
public void run(String... args) {
log.info(weatherService.getTemperatureByCityName("Krakow"));
}
//...
}
Generally constructor injection should be favored over field or setter injection but in your case, that is acceptable.
You are creating a cycle as you are injecting a service in the #SpringBootApplication itself. Constructor injection means that nothing can really happen until the class is built but that service is going to be created later on.
Don't use field injection on your #SpringBootApplication as it represents the root context. Your CommandLineRunner injects a RestTemplate but you are not using it. If you replace that by the WeatherService and remove the constructor injection, things should work just fine.
I am glad you find the weather application useful by the way :)
I have a microservice application which has a REST controller and uses an ObjectMapper - trip-definition-service.
#SpringBootApplication
public class TripDefintionService extends TripCommonApplication {
public static void main(String[] args) {
SpringApplication.run(args, TripDefinitionService.class);
}
Now I want another microservice application to create a bean for that same ObjectMapper - trip-common-service.
#SpringBootApplication
public abstract class TripCommonApplication { }
As of now I have build trip-common-service and have kept a jar as a dependency on trip-definition-service.
Now I have a configuration created on trip-common for ObjectMapper as follows -
#Configuration
#Qualifier("standardObjectMapper")
public ObjectMapper getMapper() {
return new ObjectMapper();
}
And I am using a the ObjectMapper which was created in trip-common in my trip-definition-service.
#Service
public class Service {
#Autowired
#Qualifier("standardObjectMapper")
private ObjectMapper objectMapper;
public void doSomething() {
objectMapper.readValue(......);
}
Now when I start my trip-definition-service, it says its not able to find an ObjectMapper bean of type standardObjectMapper.
Need some help/suggestions on this and how to get this done.
I want to implement custom repo with Spring data mongodb.
Application.java:
#SpringBootApplication
public class Application implements CommandLineRunner{
#Autowired
private CustomerRepositoryCustom repo;
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
#Override
public void run(String... args) throws Exception {
System.out.println(this.repo.customMethod());
}
}
My custom repository CustomerRepositoryCustom.java
public interface CustomerRepositoryCustom {
List<Customer> customMethod();
}
Custom implementation CustomCustomerRepositoryImpl.java
public class CustomCustomerRepositoryImpl implements CustomerRepositoryCustom {
#Autowired
private MongoTemplate mongoTemplate;
#Override
public List<Customer> customMethod() {
return this.mongoTemplate.findAll(Customer.class);
}
}
Code Structure
-Application.java
dal
model...
repository
-CustomCustomerRepositoryImpl.java
-CustomerRepositoryCustom.java
When I try to build it, i get an error:
**Description**:
Field repo in socketApp.Application required a bean of type 'socketApp.dal.repository.CustomerRepositoryCustom' that could not be found.
**Action**:
Consider defining a bean of type 'socketApp.dal.repository.CustomerRepositoryCustom' in your configuration.
You have to make Spring aware of your repository. For a Spring Boot application this is typically done by adding this annotation to your application ...
#EnableMongoRepositories("com.package.path.to.repository")
.... thereby telling Spring Boot where to look for Mongo repositories and then let your interface extend org.springframework.data.mongodb.repository.MongoRepository.
For example:
public interface CustomerRepositoryCustom extends MongoRepository {
List<Customer> customMethod();
}
Alternatively, you could annotate your CustomCustomerRepositoryImpl with #Repository and ensure that it is in a package which is scanned by Spring Boot.
EDIT: This question is specifically pertaining to the #RestClientTest annotation introduced in spring-boot 1.4.0 which is intended to replace the factory method.
Problem:
According to the documentation the #RestClientTest should correctly configure a MockRestServiceServer to use when testing a REST client. However when running a test I am getting an IllegalStateException saying the MockServerRestTemplateCustomizer has not been bound to a RestTemplate.
Its worth noting that I'm using Gson for deserialization and not Jackson, hence the exclude.
Does anyone know how to correctly use this new annotation? I haven't found any examples that require more configuration then when I have already.
Configuration:
#SpringBootConfiguration
#ComponentScan
#EnableAutoConfiguration(exclude = {JacksonAutoConfiguration.class})
public class ClientConfiguration {
...
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.basicAuthorization(username, password);
}
}
Client:
#Service
public class ComponentsClientImpl implements ComponentsClient {
private RestTemplate restTemplate;
#Autowired
public ComponentsClientImpl(RestTemplateBuilder builder) {
this.restTemplate = builder.build();
}
public ResponseDTO getComponentDetails(RequestDTO requestDTO) {
HttpEntity<RequestDTO> entity = new HttpEntity<>(requestDTO);
ResponseEntity<ResponseDTO> response =
restTemplate.postForEntity("/api", entity, ResponseDTO.class);
return response.getBody();
}
}
Test
#RunWith(SpringRunner.class)
#RestClientTest(ComponentsClientImpl.class)
public class ComponentsClientTest {
#Autowired
private ComponentsClient client;
#Autowired
private MockRestServiceServer server;
#Test
public void getComponentDetailsWhenResultIsSuccessShouldReturnComponentDetails() throws Exception {
server.expect(requestTo("/api"))
.andRespond(withSuccess(getResponseJson(), APPLICATION_JSON));
ResponseDTO response = client.getComponentDetails(requestDto);
ResponseDTO expected = responseFromJson(getResponseJson());
assertThat(response, is(expectedResponse));
}
}
And the Exception:
java.lang.IllegalStateException: Unable to use auto-configured MockRestServiceServer since MockServerRestTemplateCustomizer has not been bound to a RestTemplate
Answer:
As per the answer below there is no need to declare a RestTemplateBuilder bean into the context as it is already provided by the spring-boot auto-configuration.
If the project is a spring-boot application (it has #SpringBootApplication annotation) this will work as intended. In the above case however the project was a client-library and thus had no main application.
In order to ensure the RestTemplateBuilder was injected correctly in the main application context (the bean having been removed) the component scan needs a CUSTOM filter (the one used by #SpringBootApplication)
#ComponentScan(excludeFilters = {
#ComponentScan.Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class)
})
The MockRestServiceServer instance should be constructed from the static factory, using a RestTemplate. See this article for a detailed description of the testing process.
In your example, you can do:
#RunWith(SpringRunner.class)
#RestClientTest(ComponentsClientImpl.class)
public class ComponentsClientTest {
#Autowired
private ComponentsClient client;
#Autowired
private RestTemplate template;
private MockRestServiceServer server;
#Before
public void setUp() {
server= MockRestServiceServer.createServer(restTemplate);
}
/*Do your test*/
}
You have RestTemplateBuilder at two places. At ClientConfiguration class and at ComponentsClientImpl class. Spring boot 1.4.0 auto-configure a RestTemplateBuilder which can be used to create RestTemplate instances when needed. Remove below code from ClientConfiguration class and run your test.
#Bean
public RestTemplateBuilder restTemplateBuilder() {
return new RestTemplateBuilder()
.rootUri(rootUri)
.basicAuthorization(username, password);
}
I am new to Spring & WebService and trying a few guides on Spring.io.
I planned to create a basic RESTful WebService which consumes Google Direction API and returns just the status.
Here are the classes:
Resource
#JsonIgnoreProperties(ignoreUnknown=true)
public class Direction {
// getters & setters
public Direction() {
super();
}
private String status;
public String toString() {
return status;
}
}
Controller
#Controller
public class Consumer {
public Consumer() {
super();
}
#Resource
private String url;
#Resource
private RestTemplate client;
#Resource
private String apiKey;
#RequestMapping(value = "/directions", method=RequestMethod.GET)
public #ResponseBody Direction consume(#RequestParam(value="source") String source, #RequestParam(value="destination") String destination) {
return consumeDirections(buildURI(source, destination));
}
// Builds URI
private String buildURI(...) {
...
}
private Direction consumeDirections(final String requestURI) {
return client.getForObject(requestURI, Direction.class);
}
}
Configuration v1
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Consumer.class, args);
}
}
Springconfig
http://pastebin.com/dsNVBWQq
Spring returns that No qualifying bean of type [java.lang.String] found for dependency.
This happens for all the beans in Consumer.
However, this works Configuration v2
#Configuration
#ComponentScan
#EnableAutoConfiguration
public class Application {
#Resource
private Consumer consumer;
public void execute() {
System.out.println(consumer.consume("x", "z"));
}
public static void main(String[] args) {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext("application-config.xml");
context.getBean(Application.class).execute();
}
}
Some observations
#Resouce(Explicitly define bean) doesnt work for v1
SpringApplication is not aware of the Springconfig and fails during bean instantiation
I would like to understand why this issue crops up and how to resolve it?
The reason is very easy, the xml config is not loaded. have a look at Spring-Boot: XML Config
if you don't wanna touch existing xml, you need another #configuration annotated class and #ImportResource to load the xml configuration, just like the document says.
IMO, you don't need apiKey and url in the config, you should annotate them with #value, and define them in a .properties file. There are also default settings of spring boot, you get take advantage of it. like, name the properities application.properities and put it on classpath, spring boot will load it automatically.