I'm getting the below during the server startup. I'm trying to apply constructor injection here but it doesn't see to work? I do not want to assign any default value to gameId. The purpose of the class is to act as a model How can I fix this?
Error
Parameter 0 of constructor in Score required a bean of type 'java.lang.String' that could not be found.
no qualifying bean of type 'java.lang.String'
Code
#Repository
public class Score {
private final Map<String, List<BatsmanScore>> scoreRepository = new HashMap<>();
private final List<BatsmanScore> batsmanScores;
private final String gameId;
public Score(String gameId, List<BatsmanScore> batsmanScores) {
this.gameId = gameId;
this.batsmanScores = batsmanScores;
}
public List<BatsmanScore> getBatsmanScores() {
return batsmanScores;
}
public List<BatsmanScore> getBatsmanScoresByGameId(String gameId) {
return scoreRepository.get(gameId);
}
public String getGameId() {
return gameId;
}
public void storeScores(String gameId, List<BatsmanScore> batsmanScores) {
if (!scoreRepository.containsKey(gameId)) {
scoreRepository.put(gameId, new ArrayList<>());
}
scoreRepository.get(gameId).addAll(batsmanScores);
}
}
Related
I was reading about rich vs anemic domain models and wanted to convert an existing anemic domain model into a rich domain model. However, springboot dependecy injection is in the way. How can I fix this? What is the correct approach to refactor an anemic domain model to a rich domain model?
I though about adding a public default constructor but wouldn't that violate the rules for rich-domain models and also it would mean that I can't mark gameId/the list of scores as final?
Error
Parameter 0 of constructor in Score required a bean of type 'java.lang.String' that could not be found.
no qualifying bean of type 'java.lang.String'
Original
#Service
public class ScoreService {
private final Map<String, List<BatsmanScore>> scores;
public ScoreService(Map<String, List<BatsmanScore>> scores) {
this.scores = scores;
}
public List<BatsmanScore> getBatsmanScores(String gameId) {
return scores.get(gameId);
}
public void storeScores(String gameId, List<BatsmanScore> batsmanScores) {
if (!scores.containsKey(gameId)) {
scores.put(gameId, new ArrayList<>());
}
scores.get(gameId).addAll(batsmanScores);
}
}
public class Score {
private List<BatsmanScore> batsmanScores;
private String gameId;
public Score() { }
public Score(String gameId, List<BatsmanScore> batsmanScores) {
this.gameId = gameId;
this.batsmanScores = batsmanScores;
}
public List<BatsmanScore> getBatsmanScores() {
return batsmanScores;
}
public String getGameId() {
return gameId;
}
}
Modified
#Service
public class ScoreService {
private final Score score;
public ScoreService(Score score) {
this.score = score;
}
public List<BatsmanScore> getBatsmanScores(String gameId) {
return score.getBatsmanScoreByGameId(gameId);
}
public void storeScores(String gameId, List<BatsmanScore> batsmanScores) {
score.storeGameScore(gameId, batsmanScores);
}
}
#Repository
public class Score {
private final Map<String, List<BatsmanScore>> scoreRepository = new HashMap<>();
private final List<BatsmanScore> batsmanScores;
private final String gameId;
public Score(String gameId, List<BatsmanScore> batsmanScores) {
this.gameId = gameId;
this.batsmanScores = batsmanScores;
}
public List<BatsmanScore> getBatsmanScores() {
return batsmanScores;
}
public List<BatsmanScore> getBatsmanScoresByGameId(String gameId) {
return scoreRepository.get(gameId);
}
public String getGameId() {
return gameId;
}
public void storeScores(String gameId, List<BatsmanScore> batsmanScores) {
if (!scoreRepository.containsKey(gameId)) {
scoreRepository.put(gameId, new ArrayList<>());
}
scoreRepository.get(gameId).addAll(batsmanScores);
}
}
Anemic domain model is when the business logic is in the services, but here it already is in the domain model, in the form of the Score.addAll() method:
public class Score {
private List<BatsmanScore> batsmanScores;
private String gameId;
public Score() { }
public Score(String gameId, List<BatsmanScore> batsmanScores) {
this.gameId = gameId;
this.batsmanScores = batsmanScores;
}
public List<BatsmanScore> getBatsmanScores() {
return batsmanScores;
}
public String getGameId() {
return gameId;
}
public void addAll(List<BatsmanScore> batsmanScores)
{
if (this.batsmanScores == null) {
this.batsmanScores = new ArrayList<BatsmanScore>();
}
foreach(BatsmanScore score in batsmanScores) {
// you can implement some more business logic here
this.batsmanScores.Add(score);
}
}
}
However, your are messing your repository and your service. The service usually handles the use case and orchestrate operations around the repository and the domain model:
#Service
public class AddScoreUseCase {
private final IScoreRepository repository;
public AddScoreUseCase(IScoreRepository repository) {
this.repository = repository;
}
public void storeScores(String gameId, List<BatsmanScore> batsmanScores) {
Score score = repository.getByGameId(gameId);
score.addAll(batsmanScores);
repository.saveChanges(score);
}
}
The repository is responsible to translate domain operations into the persistence. Here is an example of an in-memory persistence but you could also write a database repository, that would retrieve and store data from the database:
#Repository
public class InMemoryScoreRepository implements IScoreRepository {
private final Map<String, List<BatsmanScore>> scoreRepository = new HashMap<>();
public InMemoryScoreRepository() {
}
public Score getByGameId(String gameId) {
if (!data.containsKey(gameId)) {
return new Score(gameId, new ArrayList<>());
}
return new Score(gameId, this.data[gameId]);
}
public void saveChanges(Score score) {
if (!this.data.containsKey(score.getGameId())) {
scoreRepository.put(score.getGameId(), score.getBatsmanScores());
} else {
this.data[score.getGameId()] = score.getBatsmanScores();
}
}
}
I wanted to create a class based on the builder pattern. Using the static method build. Which would return a properly built object based on initial validation checking whether a given object exists in the database.
#Component
#Data
#Builder
public class GetBookedSeatsRequest {
#Autowired
private MovieRepository movieRepository;
#Autowired
public CinemaRepository cinemaRepository;
#Autowired
public PropertiesMovieRepository propertiesMovieRepository;
private String cinemaName;
private String movieName;
private String movieRoom;
#JsonFormat(pattern="yyyy-MM-dd; HH:mm:ss",shape = JsonFormat.Shape.STRING)
private LocalDateTime localDateTime;
private List<Integer> wantedSeats;
public GetBookedSeatsRequest build(ReservationModel reservationModel) throws CinemaNotFoundException, MovieNotFoundException, PropertyMovieNotFoundException {
boolean cinemaExist = cinemaRepository.existsByCinemaName(reservationModel.getCinemaName());
if (!cinemaExist) {
throw new CinemaNotFoundException("Cinema doesn't exist");
}
boolean movieExist = movieRepository.existsByMovieName(reservationModel.getMovieName());
if (!movieExist) {
throw new MovieNotFoundException("Movie doesn't exist");
}
boolean roomExist = movieRepository.existsByMovieRoom(reservationModel.getMovieRoom());
if (!roomExist) {
throw new MovieNotFoundException("Movie Romm doesn't exist");
}
boolean existData = propertiesMovieRepository.existsByStartTimeOfTheMovie(reservationModel.getDateAndTime());
if (!existData) {
throw new PropertyMovieNotFoundException("This data doesn't exist");
}
// boolean existSeats = movieRepository.existsBySeating(reservationModel.getSeatsToBooked());
// if (!existSeats) {
// throw new MovieNotFoundException("This seats doesn't exist");
// }
GetBookedSeatsRequest correct = GetBookedSeatsRequest.builder()
.cinemaName(reservationModel.getCinemaName())
.movieName(reservationModel.getMovieName())
.movieRoom(reservationModel.getMovieRoom())
.localDateTime(reservationModel.getDateAndTime())
.wantedSeats(reservationModel.getSeatsToBooked())
.build();
return correct;
}
}
#Data
#AllArgsConstructor
public class ReservationModel {
private String cinemaName;
private String movieName;
private String movieRoom;
#JsonFormat(pattern="yyyy-MM-dd; HH:mm:ss",shape = JsonFormat.Shape.STRING)
private LocalDateTime dateAndTime;
private List<Integer> seatsToBooked;
}
But I still got some erros. What am I doing wrong, I am learing Spring Boot. Thanks for help
Description:
Parameter 3 of constructor in com.cinema.booking.aop.GetBookedSeatsRequest required a bean of type 'java.lang.String' that could not be found.
Action:
Consider defining a bean of type 'java.lang.String' in your configuration.
I have the following converter:
#Component
public class CountryEnumConverter implements Converter<String, CountryEnum> {
#Override
public CountryEnum convert(String country) {
CountryEnum countryEnum = CountryEnum.getBySign(country);
if (countryEnum == null) {
throw new IllegalArgumentException(country + " - Country is not supported!");
}
return countryEnum;
}
}
Registered it is invoked when used for RequestParam
#GetMapping(value = RestApiEndpoints.RESULTS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResultDto> getResults(
Principal principal,
#RequestParam CountryEnum country) {
....
}
But this converter is never invoked when used for field in the RequstBody:
#GetMapping(value = RestApiEndpoints.RESULTS, produces = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<ResultDto> getResults(
Principal principal,
#RequestBody MyBody myBody) {
....
}
public class MyBody {
#NotNull
private CountryEnum country;
public MyBody() {
}
public CountryEnum getCountry() {
return country;
}
public void setCountry(CountryEnum country) {
this.country = country;
}
}
Your existing org.springframework.core.convert.converter.Converter instance will only work with data submitted as form encoded data. With #RequestBody you are sending JSON data which will be deserialized using using the Jackson library.
You can then create an instance of com.fasterxml.jackson.databind.util.StdConverter<IN, OUT>
public class StringToCountryTypeConverter extends StdConverter<String, CountryType> {
#Override
public CountryType convert(String value) {
//convert and return
}
}
and then apply this on the target property:
public class MyBody {
#NotNull
#JsonDeserialize(converter = StringToCountryTypeConverter.class)
private CountryEnum country;
}
Given the similarity of the 2 interfaces I would expect that you could create one class to handle both scenarios:
public class StringToCountryTypeConverter extends StdConverter<String, CountryType>
implements org.springframework.core.convert.converter.Converter<String, CountryType> {
#Override
public CountryType convert(String value) {
//convert and return
}
}
I found out that if I add the following code to my CountryEnum will do the trick.
#JsonCreator
public static CountryEnum fromString(String value) {
CountryEnumConverter converter = new CountryEnumConverter();
return converter.convert(value);
}
My Model Class is
#Data
public class Beer {
public Beer(int i, String string, float d) {
this.beerId = i;
this.beerName = string;
this.price = d;
}
public Beer() {
}
private int beerId;
private String beerName;
private float price;
}
And the Service Interface is
public interface IBeerService {
#Secured("ROLE_ADMIN")
#PreAuthorize("#beer.price > 0.0f")
Beer add(Beer beer);
#Secured("ROLE_USER")
#RolesAllowed("ROLE_USER")
List<Beer> getAll();
}
The interface is implemented as below.
#Service
public class BeerService implements IBeerService {
private List<Beer> beerRepository = new ArrayList<>();
#Override
public Beer add(Beer beerToAdd) {
if(!beerRepository.contains(beerToAdd)) {
beerToAdd.setBeerId(this.beerRepository.size()+1);
this.beerRepository.add(beerToAdd);
}
return beerToAdd;
}
#Override
public List<Beer> getAll() {
return beerRepository;
}
}
In the controller, I can able to see the object which is not null, in the debugger.
I am getting an error as
org.springframework.expression.spel.SpelEvaluationException: EL1007E: Property or field 'price' cannot be found on null
at org.springframework.expression.spel.ast.PropertyOrFieldReference.readProperty(PropertyOrFieldReference.java:213) ~[spring-expression-5.1.9.RELEASE.jar:5.1.9.RELEASE]
at org.springframework.expression.spel.ast.PropertyOrFieldReference.getValueInternal(PropertyOrFieldReference.java:104) ~[spring-expression-5.1.9.RELEASE.jar:5.1.9.RELEASE]
I've a BeanDefinitionRegistryPostProcessor class that registers beans dynamically. Sometimes, the beans being registered have the Spring Cloud annotation #RefreshScope.
However, when the cloud configuration Environment is changed, such beans are not being refreshed. Upon debugging, the appropriate application events are triggered, however, the dynamic beans don't get reinstantiated. Need some help around this. Below is my code:
TestDynaProps:
public class TestDynaProps {
private String prop;
private String value;
public String getProp() {
return prop;
}
public void setProp(String prop) {
this.prop = prop;
}
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
#Override
public String toString() {
StringBuilder builder = new StringBuilder();
builder.append("TestDynaProps [prop=").append(prop).append(", value=").append(value).append("]");
return builder.toString();
}
}
TestDynaPropConsumer:
#RefreshScope
public class TestDynaPropConsumer {
private TestDynaProps props;
public void setProps(TestDynaProps props) {
this.props = props;
}
#PostConstruct
public void init() {
System.out.println("Init props : " + props);
}
public String getVal() {
return props.getValue();
}
}
BeanDefinitionRegistryPostProcessor:
public class PropertyBasedDynamicBeanDefinitionRegistrar implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {
private ConfigurableEnvironment environment;
private final Class<?> propertyConfigurationClass;
private final String propertyBeanNamePrefix;
private final String propertyKeysPropertyName;
private Class<?> propertyConsumerBean;
private String consumerBeanNamePrefix;
private List<String> dynaBeans;
public PropertyBasedDynamicBeanDefinitionRegistrar(Class<?> propertyConfigurationClass,
String propertyBeanNamePrefix, String propertyKeysPropertyName) {
this.propertyConfigurationClass = propertyConfigurationClass;
this.propertyBeanNamePrefix = propertyBeanNamePrefix;
this.propertyKeysPropertyName = propertyKeysPropertyName;
dynaBeans = new ArrayList<>();
}
public void setPropertyConsumerBean(Class<?> propertyConsumerBean, String consumerBeanNamePrefix) {
this.propertyConsumerBean = propertyConsumerBean;
this.consumerBeanNamePrefix = consumerBeanNamePrefix;
}
#Override
public void setEnvironment(Environment environment) {
this.environment = (ConfigurableEnvironment) environment;
}
#Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory arg0) throws BeansException {
}
#Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefRegistry) throws BeansException {
if (environment == null) {
throw new BeanCreationException("Environment must be set to initialize dyna bean");
}
String[] keys = getPropertyKeys();
Map<String, String> propertyKeyBeanNameMapping = new HashMap<>();
for (String k : keys) {
String trimmedKey = k.trim();
String propBeanName = getPropertyBeanName(trimmedKey);
registerPropertyBean(beanDefRegistry, trimmedKey, propBeanName);
propertyKeyBeanNameMapping.put(trimmedKey, propBeanName);
}
if (propertyConsumerBean != null) {
String beanPropertyFieldName = getConsumerBeanPropertyVariable();
for (Map.Entry<String, String> prop : propertyKeyBeanNameMapping.entrySet()) {
registerConsumerBean(beanDefRegistry, prop.getKey(), prop.getValue(), beanPropertyFieldName);
}
}
}
private void registerConsumerBean(BeanDefinitionRegistry beanDefRegistry, String trimmedKey, String propBeanName, String beanPropertyFieldName) {
String consumerBeanName = getConsumerBeanName(trimmedKey);
AbstractBeanDefinition consumerDefinition = preparePropertyConsumerBeanDefinition(propBeanName, beanPropertyFieldName);
beanDefRegistry.registerBeanDefinition(consumerBeanName, consumerDefinition);
dynaBeans.add(consumerBeanName);
}
private void registerPropertyBean(BeanDefinitionRegistry beanDefRegistry, String trimmedKey, String propBeanName) {
AbstractBeanDefinition propertyBeanDefinition = preparePropertyBeanDefinition(trimmedKey);
beanDefRegistry.registerBeanDefinition(propBeanName, propertyBeanDefinition);
dynaBeans.add(propBeanName);
}
private String getConsumerBeanPropertyVariable() throws IllegalArgumentException {
Field[] beanFields = propertyConsumerBean.getDeclaredFields();
for (Field bField : beanFields) {
if (bField.getType().equals(propertyConfigurationClass)) {
return bField.getName();
}
}
throw new BeanCreationException(String.format("Could not find property of type %s in bean class %s",
propertyConfigurationClass.getName(), propertyConsumerBean.getName()));
}
private AbstractBeanDefinition preparePropertyBeanDefinition(String trimmedKey) {
BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(PropertiesConfigurationFactory.class);
bdb.addConstructorArgValue(propertyConfigurationClass);
bdb.addPropertyValue("propertySources", environment.getPropertySources());
bdb.addPropertyValue("conversionService", environment.getConversionService());
bdb.addPropertyValue("targetName", trimmedKey);
return bdb.getBeanDefinition();
}
private AbstractBeanDefinition preparePropertyConsumerBeanDefinition(String propBeanName, String beanPropertyFieldName) {
BeanDefinitionBuilder bdb = BeanDefinitionBuilder.genericBeanDefinition(propertyConsumerBean);
bdb.addPropertyReference(beanPropertyFieldName, propBeanName);
return bdb.getBeanDefinition();
}
private String getPropertyBeanName(String trimmedKey) {
return propertyBeanNamePrefix + trimmedKey.substring(0, 1).toUpperCase() + trimmedKey.substring(1);
}
private String getConsumerBeanName(String trimmedKey) {
return consumerBeanNamePrefix + trimmedKey.substring(0, 1).toUpperCase() + trimmedKey.substring(1);
}
private String[] getPropertyKeys() {
String keysProp = environment.getProperty(propertyKeysPropertyName);
return keysProp.split(",");
}
The Config class:
#Configuration
public class DynaPropsConfig {
#Bean
public PropertyBasedDynamicBeanDefinitionRegistrar dynaRegistrar() {
PropertyBasedDynamicBeanDefinitionRegistrar registrar = new PropertyBasedDynamicBeanDefinitionRegistrar(TestDynaProps.class, "testDynaProp", "dyna.props");
registrar.setPropertyConsumerBean(TestDynaPropConsumer.class, "testDynaPropsConsumer");
return registrar;
}
}
Application.java
#SpringBootApplication
#EnableDiscoveryClient
#EnableScheduling
public class Application extends SpringBootServletInitializer {
private static Class<Application> applicationClass = Application.class;
public static void main(String[] args) {
SpringApplication sa = new SpringApplication(applicationClass);
sa.run(args);
}
}
And, my bootstrap.properties:
spring.cloud.consul.enabled=true
spring.cloud.consul.config.enabled=true
spring.cloud.consul.config.format=PROPERTIES
spring.cloud.consul.config.watch.delay=15000
spring.cloud.discovery.client.health-indicator.enabled=false
spring.cloud.discovery.client.composite-indicator.enabled=false
application.properties
dyna.props=d1,d2
d1.prop=d1prop
d1.value=d1value
d2.prop=d2prop
d2.value=d2value
Here are some guesses:
1) Perhaps the #RefreshScope metadata is not being passed to your metadata for the bean definition. Call setScope()?
2) The RefreshScope is actually implemented by https://github.com/spring-cloud/spring-cloud-commons/blob/master/spring-cloud-context/src/main/java/org/springframework/cloud/context/scope/refresh/RefreshScope.java, which itself implements BeanDefinitionRegistryPostProcessor. Perhaps the ordering of these two post processors is issue.
Just guesses.
We finally resolved this by appending the #RefreshScope annotation on the proposed dynamic bean classes using ByteBuddy and then, adding them to Spring Context using Bean Definition Post Processor.
The Post Processor is added to spring.factories so that it loads before any other dynamic bean dependent beans.