Can't figure out why I am getting a NullPointerException on my platform in my Configuration class when I try to print out the object.
Here's the yaml file:
platform:
platformType: CVN
hwVariant:
canesVariant: HW
hullNumber: XXX
tri: INST
Here is my parent object class Configuration.java
public class Configuration {
private Platform platform;
public Configuration() {
}
public Platform getPlatform() {
return platform;
}
public void setPlatform(Platform platform) {
this.platform = platform;
}
#Override
public String toString() {
return platform.toString();
}
}
Here's my Platform.java:
import java.util.Map;
public class Platform {
private String platformType;
private Map<String, String> hwVariant;
private String hullNumber;
private String tri;
public Platform() {
}
public String getPlatformType() {
return platformType;
}
public Map<String, String> getHWVariant() {
return hwVariant;
}
public String getHull() {
return hullNumber;
}
public String getTri() {
return tri;
}
public void setPlatformType(String platformType) {
this.platformType = platformType;
}
public void setHWVariant(Map<String, String> hwVariant) {
this.hwVariant = hwVariant;
}
public void setHullNumber(String hullNumber) {
this.hullNumber = hullNumber;
}
public void setTri(String tri) {
this.tri = tri;
}
#Override
public String toString() {
return "platform: \n" +
" platformType: " + platformType + "\n" +
" hwVariant: " + hwVariant + "\n" +
" hullNumber: " + hullNumber + "\n" +
" tri: " + tri;
}
}
Here's the NullPointerException I'm getting:
File is: E:\workspace_neon\PlatformInterviewer\resources\uswdss-platform.yml
Exception in thread "main" Can't construct a java object for tag:yaml.org,2002:net.progeny.setup.model.Configuration; exception=null
in 'reader', line 1, column 1:
platform:
^
at org.yaml.snakeyaml.constructor.Constructor$ConstructYamlObject.construct(Constructor.java:314)
at org.yaml.snakeyaml.constructor.BaseConstructor.constructObjectNoCheck(BaseConstructor.java:204)
at org.yaml.snakeyaml.constructor.BaseConstructor.constructObject(BaseConstructor.java:193)
at org.yaml.snakeyaml.constructor.BaseConstructor.constructDocument(BaseConstructor.java:159)
at org.yaml.snakeyaml.constructor.BaseConstructor.getSingleData(BaseConstructor.java:146)
at org.yaml.snakeyaml.Yaml.loadFromReader(Yaml.java:524)
at org.yaml.snakeyaml.Yaml.loadAs(Yaml.java:518)
at net.progeny.setup.YamlConfiguration.main(YamlConfiguration.java:28)
Caused by: java.lang.NullPointerException
at net.progeny.setup.model.Configuration.toString(Configuration.java:31)
at java.lang.String.valueOf(String.java:2994)
at java.lang.StringBuilder.append(StringBuilder.java:131)
at org.yaml.snakeyaml.constructor.Constructor$ConstructMapping.constructJavaBean2ndStep(Constructor.java:268)
at org.yaml.snakeyaml.constructor.Constructor$ConstructMapping.construct(Constructor.java:149)
at org.yaml.snakeyaml.constructor.Constructor$ConstructYamlObject.construct(Constructor.java:309)
... 7 more
I just can't seem to get it working when I added the inner nested hwVariant option. If I remove the hwVariant and canesVariant from my yaml file it works fine, albeit without those key value pairs. What am I missing?
Change Getter and Setter method of field 'hwVariant' of class Platform to following.
public Map<String, String> getHwVariant() {
return hwVariant;
}
public void setHwVariant(Map<String, String> hwVariant) {
this.hwVariant = hwVariant;
}
SnakeYaml internally uses jackson to map yaml document into a java object. Jackson internally calls getter and setter method of the fields, so it expects getter and setter to be in standard form.
Related
I'm trying to pass a data from angular 8 to spring boot in a restAPI .
this is my entity in spring :
public class Prometheus {
public String scrape_interval; // static
public External_labels external_labels; //static
public String job_name; // added by user
public String metrics_path; // static
public ArrayList<?> static_configs;
public ArrayList<String> targets; // added by user
public String scrapeIntervalValue ; //added by user
public Prometheus(String scrape_interval, External_labels external_labels) {
super();
this.scrape_interval = scrape_interval;
this.external_labels = external_labels;
}
#SuppressWarnings("rawtypes")
public Prometheus(ArrayList targets) {
this.targets = targets;
}
public Prometheus(String scrape_interval,
com.pfe.masterMonitor.entities.prometheusEntities.Prometheus.External_labels external_labels,
String job_name, String metrics_path, ArrayList<?> static_configs, ArrayList<String> targets,
String scrapeIntervalValue) {
super();
this.scrape_interval = scrape_interval;
this.external_labels = external_labels;
this.job_name = job_name;
this.metrics_path = metrics_path;
this.static_configs = static_configs;
this.targets = targets;
// this.targetValue = targetValue;
this.scrapeIntervalValue = scrapeIntervalValue;
}
public Prometheus(String job_name, String metrics_path, ArrayList<?> static_configs) {
this.job_name = job_name;
this.metrics_path = metrics_path;
this.static_configs = static_configs;
}
public Prometheus() {
}
public String getScrape_interval() {
return scrape_interval;
}
public void setScrape_interval(String scrape_interval) {
this.scrape_interval = scrape_interval;
}
public External_labels getExternal_labels() {
return external_labels;
}
public void setExternal_labels(External_labels external_labels) {
this.external_labels = external_labels;
}
public String getJob_name() {
return job_name;
}
public ArrayList<?> getTargetValue() {
return targets;
}
public void setTargetValue(ArrayList<String> targets) {
this.targets = targets;
}
public void setJob_name(String job_name) {
this.job_name = job_name;
}
public String getMetrics_path() {
return metrics_path;
}
public void setMetrics_path(String metrics_path) {
this.metrics_path = metrics_path;
}
public ArrayList<?> getStatic_configs() {
return static_configs;
}
public void setStatic_configs(ArrayList<?> static_configs) {
this.static_configs = static_configs;
}
public ArrayList<?> getTargets() {
return targets;
}
public void setTargets(ArrayList<String> targets) {
this.targets = targets;
}
public String getScrapeIntervalValue() {
return scrapeIntervalValue;
}
public void setScrapeIntervalValue(String scrapeIntervalValue) {
this.scrapeIntervalValue = scrapeIntervalValue;
}
public External_labels External_labels(String env) {
// TODO Auto-generated method stub
return new External_labels(env);
}
public class External_labels {
public String env;
public External_labels(String env) {
this.env = env;
}
public String getEnv() {
return env;
}
public void setEnv(String env) {
this.env = env;
}
}
}
and this my model in angular :
export class Prometheus {
job_name: String;
scrapeIntervalValue: String;
targets: String[][] = [];
scrape_interval: String
external_labels: External_labels
metrics_path : String ;
static_configs: Array<String>;
}
the problem is the data contain a list which is targets ( refer to angular and spring entities ) and when making a consol.log to check the json it look like targets is not a list but it's a String object :
this is the json from the console of chrome :
and this the error in spring boot console log :
Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of java.util.ArrayList (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('localhpst9500'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of java.util.ArrayList (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('localhpst9500') at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 12]2022-04-06 03:28:03.278 WARN 40412 --- [nio-9200-exec-8] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot construct instance of java.util.ArrayList (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('localhost:9500'); nested exception is com.fasterxml.jackson.databind.exc.MismatchedInputException: Cannot construct instance of java.util.ArrayList (although at least one Creator exists): no String-argument constructor/factory method to deserialize from String value ('localhost:9500') at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 12] (through reference chain: com.pfe.masterMonitor.entities.prometheusEntities.Prometheus["targets"])]
Is there any way to make a super-class document (e.g. index name = user) and create two child classes (Admin, Guest) to save all this to user index but with different fields? E.g. Add to super-class field type and based on this field fetch right entity? ELK 7.19, Spring Data 4.3.1.
You can do that. Make the base class abstract. I have this in a test setup with the following classes:
#Document(indexName = "type-hints")
public abstract class BaseClass {
#Id
private String id;
#Field(type = FieldType.Text)
private String baseText;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getBaseText() {
return baseText;
}
public void setBaseText(String baseText) {
this.baseText = baseText;
}
#Override
public String toString() {
return "BaseClass{" +
"id='" + id + '\'' +
", baseText='" + baseText + '\'' +
'}';
}
}
public class DerivedOne extends BaseClass {
#Field(type = FieldType.Text)
private String derivedOne;
public String getDerivedOne() {
return derivedOne;
}
public void setDerivedOne(String derivedOne) {
this.derivedOne = derivedOne;
}
#Override
public String toString() {
return "DerivedOne{" +
"derivedOne='" + derivedOne + '\'' +
"} " + super.toString();
}
}
public class DerivedTwo extends BaseClass {
#Field(type = FieldType.Text)
private String derivedTwo;
public String getDerivedTwo() {
return derivedTwo;
}
public void setDerivedTwo(String derivedTwo) {
this.derivedTwo = derivedTwo;
}
#Override
public String toString() {
return "DerivedTwo{" +
"derivedTwo='" + derivedTwo + '\'' +
"} " + super.toString();
}
}
interface TypeHintRepository extends ElasticsearchRepository<BaseClass, String> {
SearchHits<? extends BaseClass> searchAllBy();
}
#RestController
#RequestMapping("/typehints")
public class TypeHintController {
private static final Logger LOGGER = LoggerFactory.getLogger(TypeHintController.class);
private final TypeHintRepository repository;
public TypeHintController(TypeHintRepository repository) {
this.repository = repository;
}
#GetMapping
public void test() {
List<BaseClass> docs = new ArrayList<>();
DerivedOne docOne = new DerivedOne();
docOne.setId("one");
docOne.setBaseText("baseOne");
docOne.setDerivedOne("derivedOne");
docs.add(docOne);
DerivedTwo docTwo = new DerivedTwo();
docTwo.setId("two");
docTwo.setBaseText("baseTwo");
docTwo.setDerivedTwo("derivedTwo");
docs.add(docTwo);
repository.saveAll(docs);
SearchHits<? extends BaseClass> searchHits = repository.searchAllBy();
for (SearchHit<? extends BaseClass> searchHit : searchHits) {
LOGGER.info(searchHit.toString());
}
}
}
Hi I'm using Spring Web Client to access Taste Dive REST API.
Here's the method I use to access the endpoint
public TasteDiveItemDto getReccs(Collection<Book> bookCollection) {
String parameters = "";
for(Book b : bookCollection)
parameters = parameters + "book:" + b.getTitle() + "%2C";
// remove the %2C at end of string
parameters = parameters.substring(0, parameters.length() - 3);
logger.info("Parameters = " + parameters);
// We want 2 reccs for every 1 book in the collection
int limit = bookCollection.size() * 2;
String req = "/similar" + "?q=" + parameters + "&" + "k=" + API_KEY + "&" + "limit=" + Integer.toString(limit);
logger.info("req = " + req);
logger.info("Accessing TasteDive API...");
return CLIENT.get().uri(req).retrieve().bodyToMono(TasteDiveItemDto.class).block();
}
And here are my POJOs
public class TasteDiveItemDto {
#JsonProperty("Similar")
private Similar similar;
public Similar getSimilar() {
return similar;
}
public void setSimilar(Similar similar) {
this.similar = similar;
}
#Override
public String toString() {
return "TasteDiveItemDto [similar=" + similar + "]";
}
}
public class Similar {
#JsonProperty("Info")
private Info[] info;
#JsonProperty("Results")
private Info[] results;
public Info[] getInfo() {
return info;
}
public void setInfo(Info[] info) {
this.info = info;
}
public Info[] getResults() {
return results;
}
public void setResults(Info[] results) {
this.results = results;
}
#Override
public String toString() {
return "Similar [info=" + Arrays.toString(info) + ", results=" + Arrays.toString(results) + "]";
}
}
public class Info {
#JsonProperty("Name")
private String name;
#JsonProperty("Type")
private String type;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
#Override
public String toString() {
return "Info [name=" + name + ", type=" + type + "]";
}
}
Here's what I'm getting back from the endpoint when I use my toString method
TasteDiveItemDto
[
similar=
Similar
[
info=
[
Info
[
name=book:chemistry%2cbook:norwegian wood%2cbook:infinite jest%2cbook:flowers for algernon,
type=unknown
],
Info
[
name=book:chemistry%2cbook:norwegian wood%2cbook:infinite jest%2cbook:flowers for algernon,
type=unknown
]
],
results=[]
]
]
So what's essentially getting mapped are the parameters I put in the endpoint and not the correct JSON response. I've tried to use #JsonProperty on the methods and fields but to no luck.
I'm still a beginner at Spring and only read a bit of the Web Client documentation so I can't figure out entirely what I'm doing incorrectly. The strange thing is when I used Web Client to access Google Books API using the same methodology it worked out perfectly.
Please let me know if you need anymore information from my program. Thanks!
I am creating REST API using spring framework. My entity is based on one table and REST API is supposed to be invoked using POST operation with below JSON structure. Can someone explain me how to map the entity class so that it can consume below-shown json.
Since my entity is based on only one table, I am not able to understand how can it create nested json objects for same table properties.
{
"process_ar_receipt": {
"message_header": {
"source_system_guid": "DDED-DBCD-REV-E1F4343DB3434",
"source_system": "MeSo_TravelAds"
},
"receipt_header": {
"customer_number": "123",
"source_receipt_number": "TESTRCPT_1523",
}
}
}
you could use Gson to convert the json to a DTO
https://jarroba.com/gson-json-java-ejemplos/
pseudo code
assuming your Entity class as
#Entity(name="foo")
class Data{
#Id
private String source_system_guid;
#Column
private String source_system;
#Column
private String customer_number;
#Column
private String source_receipt_number;
public Data() {}
public Data(String ssId, String sourceSystm, String custNum, String srcRcptNum) {
this.source_system_guid = ssId;
this.source_system = sourceSystm;
this.customer_number = custNum;
this.source_receipt_number = srcRcptNum;
}
public String getSource_system_guid() {
return source_system_guid;
}
public void setSource_system_guid(String source_system_guid) {
this.source_system_guid = source_system_guid;
}
public String getSource_system() {
return source_system;
}
public void setSource_system(String source_system) {
this.source_system = source_system;
}
public String getCustomer_number() {
return customer_number;
}
public void setCustomer_number(String customer_number) {
this.customer_number = customer_number;
}
public String getSource_receipt_number() {
return source_receipt_number;
}
public void setSource_receipt_number(String source_receipt_number) {
this.source_receipt_number = source_receipt_number;
}
}
Now since your DTO/BO i.e. Data Transfer Object or Business Object is different from the actual entity we will create the required BO object as below
class DataTO{
#JsonProperty("process_ar_receipt")
private ReceiptTO receiptTO=new ReceiptTO();
public ReceiptTO getReceiptTO() {
return receiptTO;
}
public void setReceiptTO(ReceiptTO receiptTO) {
this.receiptTO = receiptTO;
}
}
class ReceiptTO{
#JsonProperty("message_header")
private MessageHeader messageHeder = new MessageHeader();
#JsonProperty("receipt_header")
private ReceiptHeader receiptHeder = new ReceiptHeader();
public MessageHeader getMessageHeder() {
return messageHeder;
}
public void setMessageHeder(MessageHeader messageHeder) {
this.messageHeder = messageHeder;
}
public ReceiptHeader getReceiptHeder() {
return receiptHeder;
}
public void setReceiptHeder(ReceiptHeader receiptHeder) {
this.receiptHeder = receiptHeder;
}
}
class MessageHeader{
#JsonProperty("source_System_Guid")
private String sourceSystemId;
#JsonProperty("system_Id")
private String systemId;
public String getSourceSystemId() {
return sourceSystemId;
}
public void setSourceSystemId(String sourceSystemId) {
this.sourceSystemId = sourceSystemId;
}
public String getSystemId() {
return systemId;
}
public void setSystemId(String systemId) {
this.systemId = systemId;
}
}
class ReceiptHeader{
#JsonProperty("customer_number")
private String customerNumber;
#JsonProperty("source_rcpt_number")
private String sourceReceiptNumber;
public String getCustomerNumber() {
return customerNumber;
}
public void setCustomerNumber(String customerNumber) {
this.customerNumber = customerNumber;
}
public String getSourceReceiptNumber() {
return sourceReceiptNumber;
}
public void setSourceReceiptNumber(String sourceReceiptNumber) {
this.sourceReceiptNumber = sourceReceiptNumber;
}
}
The #JsonProperty annotation is imported from org.codehaus.jackson.annotate.JsonProperty; i.e from jackson jar
Now a Simple Test class to demo DTO/BO back and forth Entity conversion
public class Test{
public static void main(String[] args) throws JsonGenerationException, JsonMappingException, IOException {
List<Data> datas = new ArrayList<Data>();
datas.add(new Data("DDED-DBCD-REV-E1F4343DB3434","MeSo_TravelAds","123","TESTRCPT_1523"));
datas.add(new Data("ADED-EWQD-REV-E1F4343YG3434","FooSo_MusicAds","132","TESTRCPT_1523"));
datas.add(new Data("YDED-YUTR-REV-E1F43UIDB3434","BarSo_HealthAds","143","TESTRCPT_1523"));
List<DataTO> dataTOs = new ArrayList<DataTO>();
for (Data data : datas) {
DataTO dataTO = new DataTO();
dataTO.getReceiptTO().getMessageHeder().setSourceSystemId(data.getSource_system_guid());
dataTO.getReceiptTO().getMessageHeder().setSystemId(data.getSource_system());
dataTO.getReceiptTO().getReceiptHeder().setCustomerNumber(data.getCustomer_number());
dataTO.getReceiptTO().getReceiptHeder().setSourceReceiptNumber(data.getSource_receipt_number());
dataTOs.add(dataTO);
}
ObjectMapper mapper = new ObjectMapper();
String str = mapper.writeValueAsString(dataTOs);
System.out.println(str);
}
}
This will give you below result
[
{
"process_ar_receipt":{
"message_header":{
"source_System_Guid":"DDED-DBCD-REV-E1F4343DB3434",
"system_Id":"MeSo_TravelAds"
},
"receipt_header":{
"customer_number":"123",
"source_rcpt_number":"TESTRCPT_1523"
}
}
},
{
"process_ar_receipt":{
"message_header":{
"source_System_Guid":"ADED-EWQD-REV-E1F4343YG3434",
"system_Id":"FooSo_MusicAds"
},
"receipt_header":{
"customer_number":"132",
"source_rcpt_number":"TESTRCPT_1523"
}
}
},
{
"process_ar_receipt":{
"message_header":{
"source_System_Guid":"YDED-YUTR-REV-E1F43UIDB3434",
"system_Id":"BarSo_HealthAds"
},
"receipt_header":{
"customer_number":"143",
"source_rcpt_number":"TESTRCPT_1523"
}
}
}
]
similarly the other conversion
String input = "{ \r\n" +
" \"process_ar_receipt\":{ \r\n" +
" \"message_header\":{ \r\n" +
" \"source_System_Guid\":\"ADED-EWQD-REV-E1F4343YG3434\",\r\n" +
" \"system_Id\":\"FooSo_MusicAds\"\r\n" +
" },\r\n" +
" \"receipt_header\":{ \r\n" +
" \"customer_number\":\"132\",\r\n" +
" \"source_rcpt_number\":\"TESTRCPT_1523\"\r\n" +
" }\r\n" +
" }\r\n" +
" }";
DataTO dataTO = mapper.readValue(input, DataTO.class);
System.out.println(dataTO.getReceiptTO().getMessageHeder().getSourceSystemId());
System.out.println(dataTO.getReceiptTO().getMessageHeder().getSystemId());
System.out.println(dataTO.getReceiptTO().getReceiptHeder().getCustomerNumber());
System.out.println(dataTO.getReceiptTO().getReceiptHeder().getSourceReceiptNumber());
this will print
ADED-EWQD-REV-E1F4343YG3434
FooSo_MusicAds
132
TESTRCPT_1523
You dont have to use the mapper code you can directly add the jackson converter as HttpMessageConverted which will convert the JSON to java object automatically
#Configuration
#EnableWebMvc
public class WebConfiguration extends WebMvcConfigurerAdapter {
... other configurations
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Jackson2ObjectMapperBuilder builder = new Jackson2ObjectMapperBuilder();
builder.serializationInclusion(JsonInclude.Include.NON_NULL);
builder.propertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
builder.serializationInclusion(Include.NON_EMPTY);
builder.indentOutput(true).dateFormat(new SimpleDateFormat("yyyy-MM-dd"));
converters.add(new MappingJackson2HttpMessageConverter(builder.build()));
converters.add(new MappingJackson2XmlHttpMessageConverter(builder.createXmlMapper(true).build()));
}
}
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.