I have a configuration YAML (application.yml) file which contains location data:
locations:
countries:
PL: Poland
DE: Germany
UK: UK
RU: Russia
I would like to load it so it will be available in the html select field.
I have created a following class:
package eu.test.springdemo.model;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Map;
#ConfigurationProperties(prefix = "locations")
public class CountryOptions {
private Map<String, String> countries;
public Map<String, String> getCountries() {
return countries;
}
public void setCountries(Map<String, String> countries) {
this.countries = countries;
}
}
Then I inject CountryOptions to Controller by #Autowire. However the list of countries is empty in controller.
Configuration of app is provided by class containing following annotations:
#Configuration
#EnableWebMvc
#EnableConfigurationProperties(CountryOptions.class)
#ComponentScan(basePackages="eu.test.springdemo")
public class DemoAppConfig implements WebMvcConfigurer {
Controller code
package eu.test.springdemo.mvc;
import eu.test.springdemo.model.CountryOptions;
import eu.test.springdemo.model.Student;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
#Controller
#RequestMapping("/")
public class HelloController {
#Autowired
CountryOptions countryOptions;
#GetMapping("/")
public String showPage() {
return "main-menu";
}
#GetMapping("/showForm")
public String showForm(Model model) {
model.addAttribute("student", new Student());
model.addAttribute("countries", countryOptions.getCountries());
return "helloworld-form";
}
}
So - any ideas why list of countries is not created from yaml file?
#ConfigurationProperties is a Spring Boot feature and will not be bound to the application.yml if you aren't using it. The best solution is usually to convert to Boot.
Related
I'm running a simple Spring boot application that retrieves details of countries from a MySQL database. The initial responses I got while running the application were in json. However, after a few edits in the application.properties file, I get my reponses in XML now. Any way to revert back to json reponses? This application is a part of a microservice application I'm trying to build with Spring cloud gateway and Eureka server.
application.properties
spring.jpa.hibernate.ddl-auto = update
spring.datasource.url= jdbc:mysql://localhost:3306/countries-microservice
spring.datasource.username= root
spring.datasource.password=
spring.datasource.driver-class-name= com.mysql.cj.jdbc.Driver
spring.application.name=countries-service
server.port=3001
eureka.client.serviceUrl.defaultZone=http://localhost:3000/eureka/
CountryRepository.java
package com.example.countriesservice.repository;
import com.example.countriesservice.model.Country;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
#Repository
public interface CountryRepository extends JpaRepository<Country, String> {
Country findByCountry(String country);
}
CountryService.java
package com.example.countriesservice.service;
import java.util.List;
import com.example.countriesservice.model.Country;
import com.example.countriesservice.repository.CountryRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
public class CountryService {
private final CountryRepository countryRepository;
#Autowired
public CountryService(CountryRepository countryRepository) {
this.countryRepository = countryRepository;
}
public List<Country> getAllCountries() {
return countryRepository.findAll();
}
public Country getCountry(String country) {
return countryRepository.findByCountry(country);
}
}
CountryController.java
package com.example.countriesservice.controller;
import com.example.countriesservice.service.CountryService;
import java.util.List;
import com.example.countriesservice.model.Country;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
#RequestMapping("/countries")
#RestController
public class CountryController {
private final CountryService countryService;
#Autowired
public CountryController(CountryService countryService) {
this.countryService = countryService;
}
#GetMapping("/getAll")
public List<Country> getAll() {
return countryService.getAllCountries();
}
#GetMapping("/{country}")
public Country getCountry(#PathVariable String country) {
return countryService.getCountry(country);
}
}
Output
Since I am still learning Spring Boot it would be great if you could explain what am I doing wrong and how to correct it in a bit detail.
Explicitly mention that a json response is required.
In CountryController.java
import org.springframework.http.MediaType;
#GetMapping(value = "/getAll", produces = { MediaType.APPLICATION_JSON_VALUE })
public List<Country> getAll() {
return countryService.getAllCountries();
}
#GetMapping(value = "/{country}", produces = { MediaType.APPLICATION_JSON_VALUE })
public Country getCountry(#PathVariable String country) {
return countryService.getCountry(country);
}
I am using custom 'YamlPropertySourceFactory' to load yaml configuration in Spring boot. When configs are loaded as Map it loads wrong key-value pair value for below scenerio.
SpringBoot 'PropertySourceFactory' assumes value of keys'CORE_3_1' and 'CORE_31' OR 'CORE-3-1' and 'CORE-31'same.
Actual Output: {CORE_31=31, CORE_32=32, CORE_3_1=31 }
Expected Output: {CORE_31=31, CORE_32=32, CORE_3_1=30 }
Below is the sample code to replicate this issue.
Yaml Config - response-mapping.yaml
mappings:
response-code:
mappings:
CORE_3_1: "30"
CORE_31: "31"
CORE_32: "32"
OR
Yaml Config - response-mapping.yaml
mappings:
response-code:
mappings:
CORE-3-1: "30"
CORE-31: "31"
CORE-32: "32"
Custom YamlPropertySourceFactory
import java.util.Properties;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
public final class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertiesPropertySource createPropertySource(#SuppressWarnings("unused") final String name, final EncodedResource encodedResource) {
final YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
final Properties properties = factory.getObject();
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
}
}
Response Config Class
import java.util.Map;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
/**
* It is used to load response codes from YAML configuration.
*/
#Configuration
#ConfigurationProperties(prefix = "mappings.response-code")
#PropertySource(value = "classpath:response-mapping.yaml", factory = YamlPropertySourceFactory.class)
public class ResponseCode {
private Map<String, String> mappings;
public Map<String, String> getMappings() {
return this.mappings;
}
public String getMapping( final String errorCode) {
return this.mappings.get(errorCode);
}
public void setMappings(final Map<String, String> value) {
this.mappings = value;
}
#Override
public String toString() {
return "ResponseCode{" + "mappings=" + this.mappings + '}';
}
}
Simple Controller to test it. - http://localhost:8080/code
package de.fiserv.controller;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class DemoController {
#Autowired
private ResponseCode responseCode;
#GetMapping(value = "/code")
public ResponseEntity<String> code() {
return new ResponseEntity<>(this.responseCode.getMappings().toString(), HttpStatus.OK);
}
}
I have found the workaround by replacing '-' OR '_' with '.' in key and it works but it would break whole design of error code mapping.
Reference: https://www.baeldung.com/spring-yaml-propertysource
Found Solution at: https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.typesafe-configuration-properties.relaxed-binding.environment-variables
Updated Yaml Config - response-mapping.yaml
mappings:
response-code:
mappings:
"[CORE_3_1]": "30"
CORE_31: "31"
Why this was happening?
Spring Boot uses some relaxed rules for binding Environment properties
to #ConfigurationProperties beans, so there does not need to be an
exact match between the Environment property name and the bean
property name. Common examples where this is useful include
dash-separated environment properties (for example, context-path binds
to contextPath), and capitalized environment properties (for example,
PORT binds to port).
So i'm trying to connect java spring to mongoDB and using a findById, but it always gives the null pointer error.
2020-08-04 13:54:01.893 ERROR 8312 --- [nio-8080-exec-2] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is java.lang.NullPointerException] with root cause
java.lang.NullPointerException: null
at pt.project.ProvaConceito_BackEnd.mongoDB.UserService.findById(UserService.java:29) ~[classes/:na]
at pt.project.ProvaConceito_BackEnd.mongoDB.mongoDBService.getUserByID(mongoDBService.java:19) ~[classes/:na]
The structure of this project is:
Java
MongoDB
mongoDBService
UserService
Pojos
User
Repositories
UserRepository
I'm gonna share the code I have right now:
mongoDBService
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pt.project.ProvaConceito_BackEnd.pojos.User;
#RestController
#CrossOrigin(origins="http://localhost:4200")
public class mongoDBService {
UserService userService = new UserService();
#RequestMapping("/concept/user")
public User getUserByID(Integer id) {
return userService.findById(1);
}
}
UserService
package pt.project.ProvaConceito_BackEnd.mongoDB;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import pt.project.ProvaConceito_BackEnd.pojos.User;
import pt.project.ProvaConceito_BackEnd.repositories.UserRepository;
import java.util.List;
#Service
public class UserService {
#Autowired(required = false)
private UserRepository userRepository;
public void save(String nome, int idade, String morada) {
userRepository.save(new User(nome, idade, morada));
}
public List<User> findAll() {
return userRepository.findAll();
}
public long count() {
return userRepository.count();
}
public User findById(Integer id) {
return userRepository.findById(id).orElse(null);
}
public void delete(Integer id) {
userRepository.deleteById(id);
}
}
Users (Pojo)
package pt.project.ProvaConceito_BackEnd.pojos;
import lombok.Getter;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;
#Document(collection = "Users")
#Getter
public class User {
#Id
private Integer id;
private String nome;
private int idade;
private String morada;
public User(String nome, int idade, String morada) {
this.nome = nome;
this.idade = idade;
this.morada = morada;
}
}
UserRepository
package pt.project.ProvaConceito_BackEnd.repositories;
import org.springframework.data.mongodb.repository.MongoRepository;
import pt.project.ProvaConceito_BackEnd.pojos.User;
public interface UserRepository extends MongoRepository<User, Integer> {
}
What am I doing wrong here? I think the problem is on mongoDBService...
in this line:
UserService userService = new UserService();
Because I think that it's not being injected, but I don't know how to solve that...
EDIT 1
I have my main class inside pt.project.ProvaConceito_BackEnd:
ProvaConceitoBackEndApplication
package pt.project.ProvaConceito_BackEnd;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class ProvaConceitoBackEndApplication {
public static void main(String[] args) {
SpringApplication.run(ProvaConceitoBackEndApplication.class, args);
}
}
Do not use new to create object that way because spring will not have any knowledge of that object and will not be able to inject it.
Since you have already annotated the UserService with #service you should use #Autowired annotation
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import pt.project.ProvaConceito_BackEnd.pojos.User;
#RestController
#CrossOrigin(origins="http://localhost:4200")
public class mongoDBService {
#Autowired
private UserService userService;
#RequestMapping("/baieuropa/user")
public User getUserByID(Integer id) {
return userService.findById(1);
}
}
Also annotate the UserRepository class with #Repository
package pt.project.ProvaConceito_BackEnd.repositories;
import org.springframework.data.mongodb.repository.MongoRepository;
import pt.project.ProvaConceito_BackEnd.pojos.User;
#Repository
public interface UserRepository extends MongoRepository<User, Integer> {
}
Remove required = false from UserService class
Make sure to have a class with #SpringBootApplication annotation in pt.project.ProvaConceito_BackEnd package.
You need to update your repository and User.class to have an ID type of string instead of Integer. This is required for mongo repositories. May not completely solve your problem but will be a step in the right direction
#Repository
public interface UserRepository extends MongoRepository<User, String> {
}
I would follow the suggestions of other contributors to remove the "new" keyword from your mongoDBService and use the #Repository annotation too.
Autowiring only works if all components that it relies on are autowired too. So in your case because the mongoDBService uses new for the UserService then it expects the Repository to use "new" too. You should instead autowire at all levels and remove the required=false from the autowiring in the UserService.
The repository requires the #Repository annotation otherwise when Spring does its component scan the repository wont be picked up without it. #Repository #RestController etc are all just stereotypes for #Component with varying degrees of additional functionality
I'm trying to create a bean of the DatastoreRepository class but I get the following error Iam using spring-boot 2.1.3
Description:
The bean 'bookRepository', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.
Action:
Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true
this is my project structure I have the Application main run class in a root package like this
com.mycompany.project
--Application.java
--controller
--domain
--repository
The class with the #SpringBootApplication is in the root package
here is my repository class
import org.springframework.cloud.gcp.data.datastore.repository.DatastoreRepository;
public interface BookRepository extends DatastoreRepository<Book, Long>{
}
Here is my domain class
import org.springframework.cloud.gcp.data.datastore.core.mapping.Entity;
import org.springframework.data.annotation.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
#Entity(name = "books")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class Book {
#Id
Long id;
String title;
}
and here is my controller class
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
#RestController
public class AppUserController {
#Autowired
BookRepository bookRepository;
#GetMapping("/booksave")
public String helloworld() {
bookRepository.save(new Book(3L, "author"));
return "book saved";
}
}
and here is my Application class
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
I think the problem is the way you are using the annotation, try changing the Injection to the Constructor, something like:
#RestController
public class AppUserController {
private BookRepository bookRepository;
#Autowired
public AppUserController (
BookRepository bookRepository){
this.bookRepository= bookRepository;
}
#GetMapping("/booksave")
public String helloworld() {
bookRepository.save(new Book(3L, "author"));
return "book saved";
}
}
Source to understand it: Spring #Autowire on Properties vs Constructor
Add this annotation, #EnableDatastoreRepositories, to your Application.java
How about using Spring data rest :
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-rest</artifactId>
</dependency>
you won't need to code controllers
import org.springframework.cloud.gcp.data.datastore.repository.DatastoreRepository;
import org.springframework.data.rest.core.annotation.RepositoryRestResource;
#RepositoryRestResource(collectionResourceRel = "xxxxxs", path = "xxxx")
public interface XXXXXRepository extends DatastoreRepository<XXXXX, String>
swagger config !!!
#Configuration
#EnableSwagger2WebMvc
#Import(SpringDataRestConfiguration.class)
public class SwaggerConfig {
I am using swagger in Java rest service.model class not populating #apimodel and #apimodelproperty. When I use of high swagger spring MVC version it populating. But, I am getting error like this version does not contain Documention config. How to reslove this problem.swagger spring MVC?
0.5.3 version pom.xml
Spring.xml
This bean contain inside of spring XML file.
<bean id="documentationConfig"
class="com.mangofactory.swagger.configuration.DocumentationConfig" />
controller HomeController.java
package com.example.anand.controller;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.wordnik.swagger.annotations.Api;
import com.wordnik.swagger.annotations.ApiOperation;
#Api(value = "main controller", description = "Endpoint for TEST Managemenent")
#Controller
#RequestMapping("/newfinalmvc")
public class HomeController {
#RequestMapping(value = "/addnew", method = RequestMethod.POST)
#ApiOperation(value = "Addnew ", notes = "Add new Controller")
public #ResponseBody Object addNew(#RequestBody hello hel) {
String getName = hel.getName();
System.out.println(getName);
hello h1 = new hello();
h1.setName(getName);
return h1;
}
}
Model class which helps to setter and getter
#Apimodel and #Apimodelproperty annotion not dispalying in model class level
public class hello {
String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Why am I facing this problem?