Can't bind properties with #Bean and #ConfigurationProperties - java

I am reading configuration from properties file. Now I am having an error that I think is related to sequence of initialization of spring bean.
If I do private Map name = new HashMap<>(); It can be successfully load from properties file.
But now I am having Could not bind properties to ServiceNameConfig
I don't know why this happen and how to deal with it.
#ConfigurationProperties(prefix = "amazon.service")
#Configuration
#EnableConfigurationProperties(ServiceNameConfig.class)
public class ServiceNameConfig {
//If I do private Map<String, String> name = new HashMap<>(); It can be successfully load from properties file.
private Map<String, String> name;
#Bean(value = "serviceName")
public Map<String, String> getName() {
return name;
}
public void setName(Map<String, String> name) {
this.name = name;
}
}
its usage;
#Autowired
#Qualifier("serviceName")
Map<String, String> serviceNameMap;

You can replace your config class to be like this (simpler);
#Configuration
public class Config {
#Bean
#ConfigurationProperties(prefix = "amazon.service")
public Map<String, String> serviceName() {
return new HashMap<>();
}
}
For #ConfigurationProperties injection, you'd need to supply an empty instance of the bean object. Check more about it on baeldung
Or an alternative way, you can use a pojo class to handle the configuration. For example;
You have properties like;
amazon:
service:
valueA: 1
valueB: 2
details:
valueC: 3
valueD: 10
and you can use a pojo like the following;
class Pojo {
private Integer valueA;
private Integer valueB;
private Pojo2 details;
// getter,setters
public static class Pojo2 {
private Integer valueC;
private Integer valueD;
// getter,setters
}
}
and use it in the config class like;
#Configuration
public class Config {
#Bean
#ConfigurationProperties(prefix = "amazon.service")
public Pojo serviceName() {
return new Pojo();
}
}

Related

spring boot redis boolean value is not working

I am trying to get data from redis using crudRepository but it is not filtering by the boolean field. When I write repository.findAll() i am getting a list of objects and this object list's elements has a false field as I saved. But if I write a query for filtering just 'false' ones, it is returning only an empty array. Here is my code:
#Data
#Builder
#NoArgsConstructor
#AllArgsConstructor
#RedisHash("StateData")
public class StateDataCache implements Serializable {
#Id String id;
private String deviceId;
private String topic;
private String message;
private String lat;
private String lon;
private boolean isActive;
}
#Repository
public interface StateDataCacheRepository extends CrudRepository<StateDataCache, String> {
List<StateDataCache> findAllByActiveFalseAndDeviceId(String deviceId);
List<StateDataCache> findAllByActiveIs(boolean sent);
List<StateDataCache> findAllByActiveFalse();
}
#Configuration
#EnableRedisRepositories
public class RedisConfig {
#Value("${spring.data.redis.host}")
private String redisHost;
#Value("${spring.data.redis.port}")
private int redisPort;
#Bean
public LettuceConnectionFactory redisStandAloneConnectionFactory() {
return new LettuceConnectionFactory(new RedisStandaloneConfiguration(redisHost, redisPort));
}
#Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisStandAloneConnectionFactory());
return template;
}
}

BeanDefinitionRegistryPostProcessor ignores Encrypted properties

I've registered a Spring Post Processor to register some beans based on application properties.
Also I would like to add support for encrypted properties.
I use spring and jasypt spring boot
Here are my configurations:
application.yml:
rest:
jwt-client:
location:
base-url: loc_URL
invoke-timeout:
connection: 5000
read: 500
jwt-config:
token: ENC(gRbmO8Gv2Yb3vUf27nykDPd5/wjVdj+r5REr5HcIh4k=)
Post Processor class:
#RequiredArgsConstructor
public class DynamicClientBeanConfiguration implements BeanDefinitionRegistryPostProcessor {
private static final String BEAN_POSTFIX = "-api-client-config";
private final Environment environment;
#Override
public void postProcessBeanDefinitionRegistry(#NonNull BeanDefinitionRegistry registry) {
Binder.get(environment)
.bind("rest.jwt-client", Bindable.mapOf(String.class, JwtClientConfig.class))
.orElse(new LinkedHashMap<>())
.forEach((key, value) -> {
GenericBeanDefinition bd = new GenericBeanDefinition();
bd.setBeanClass(JwtClientConfig.class);
bd.setInstanceSupplier(() -> value);
bd.addQualifier(new AutowireCandidateQualifier(key + BEAN_POSTFIX));
registry.registerBeanDefinition(key + BEAN_POSTFIX, bd);
log.debug("Registered JwtClientConfig bean with #Qualifier(\"{}\")", key + BEAN_POSTFIX);
});
}
#Override
public void postProcessBeanFactory(#NonNull ConfigurableListableBeanFactory beanFactory) {
// Nothing to override
}
}
Custom Configuration Properties class:
#Getter
#Setter
#ConfigurationProperties
public class JwtClientConfig {
private String baseUrl;
private InvokeTimeout invokeTimeout;
private JwtConfig jwtConfig;
#Getter
#Setter
public static class InvokeTimeout {
private int connection;
private int read;
}
#Getter
#Setter
public static class JwtConfig {
private String token;
}
}
However when the following part of code in the post processor executed it binds raw data from properties like ENC(...) but not decrypted as I expect:
Binder.get(environment)
.bind("rest.jwt-client", Bindable.mapOf(String.class, JwtClientConfig.class))
.orElse(new LinkedHashMap<>())
It it possible to bind already decrypted properties in Post Processor?

RedisTemplate use GenericFastJsonRedisSerializer can't deserialize parent class attributes

First of all, I create a parent class implements Serializable, then create a child class to extends it. And I use RedisTemplate with GenericFastJsonRedisSerializer config to put data into redis, but when I get it back from Reids, the data is changed.Please tell me why and how to fix it?
#Data
public class People implements Serializable {
public Long id;
public String name;
}
#EqualsAndHashCode(callSuper = true)
#Data
public class Student extends People{
private String sName;
}
#Bean(name = "redisTemplate")
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory factory) {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(factory);
RedisSerializer keySerializer = new StringRedisSerializer();
RedisSerializer valueSerializer = new GenericFastJsonRedisSerializer();
redisTemplate.setDefaultSerializer(valueSerializer);
redisTemplate.setKeySerializer(keySerializer);
redisTemplate.setValueSerializer(valueSerializer);
redisTemplate.setHashKeySerializer(keySerializer);
redisTemplate.setHashValueSerializer(valueSerializer);
RedisUtil.init(redisTemplate);
return redisTemplate;
}
#Autowired
private RedisTemplate<String, Object> redisTemplate;
#Test
public void cacheDeserialize(){
Student student = new Student();
student.setId(11111111111111111L);
student.setName("parent name");
student.setSName("child name");
redisTemplate.opsForValue().set("parent-child-test", student);
Object value = redisTemplate.opsForValue().get("parent-child-test");
System.out.println(value); // print:Student(sName=child name), but I wish it print Student(id=111111111,name=parent name,sName=child name)
}
But:
Test result image
Solution one: use Lombok.annotation #ToString(callSuper = true) in chile class;
Solution two: take parent class attrs to child class;
You need to implement both classes with Serializable and define serialVersionUID like this
public class People implements Serializable {
private static final long serialVersionUID = 7156526077883281623L;
}

org.springframework.core.convert.converter.Converter convert some classes to one(or class+parameter to one class)

I have org.springframework.core.convert.converter.Converter
#Component
public class CatalogConverter implements Converter<ServiceCatalogType, Catalog> {
#Override
public Catalog convert(ServiceCatalogType source) {
Catalog catalog = new Catalog();
//convert
return catalog;
}
}
And I register this converter:
#Configuration
public class ConvertersConfig {
private final CatalogConverter catalogConverter;
#Autowired
public ConvertersConfig(CatalogConverter catalogConverter) {
this.catalogConverter = catalogConverter;
}
#Bean(name="conversionService")
ConversionService conversionService() {
ConversionServiceFactoryBean factoryBean = new ConversionServiceFactoryBean();
HashSet<Converter> converters = new HashSet<>();
converters.add(catalogConverter);
factoryBean.setConverters(converters);
factoryBean.afterPropertiesSet();
return factoryBean.getObject();
}
}
But I need pass some parameter to my custom converter. I have some ways:
Pass it in constructor - but how can register this converter?
Use wrapper
class Wrapper{
private ServiceCatalogType catalog;
private String uuid;
}
and change converter like this:
implements Converter<ServiceCatalogType, Wrapper>
Or maybe Spring has another way?
EDIT
I need next.
in service:
pulic void send() {
ServiceCatalogType cs = getServiceCatalogType();//get from net
User user = getUser();//get from db
//convert cs+user to Catalog(all cs fields+ some user fields to catalog)
Catalog catalog = conversionService.convert(cs, user, Catalog.class);
}
EDIT2
Wrapper implementation:
#Data
#AllArgsConstructor
#NoArgsConstructor
public class CatalogWrapper {
private ServiceCatalogType serviceCatalogType;
private User user;
}
CatalogWrapper wrapper = new CatalogWrapper(getServiceCatalog(), getUser);
catalog = conversionService.convert(wrapper, Catalog.class);

Spring #ConfigurationProperties for multiple properties returning empty

application.properties file contains properties that have sub properties:
status.available=00, STATUS.ALLOWED
status.forbidden=01, STATUS.FORBIDDEN
status.authdenied=05, STATUS.AUTH_DENIED
The idea was to get those properties into the application like this:
#Configuration
#ConfigurationProperties(prefix = "status")
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE)
public class StatusProperties {
private Map <String, List <String>> statusMapping;
public Map <String, List <String>> getStatusMapping () {
return statusMapping;
}
public void setStatusMapping (Map <String, List <String>> statusMapping) {
this.statusMapping = statusMapping;
}
}
The problem is that this Map is returned empty. I must be doing something wrong. Maybe this is not even possible in Spring to do like this?
I'm not sure about your choice regarding the data type and its assignment. I'd suggest you to rethink this design.
To your main question:
Spring can't know, that status.* should be mapped to private Map <String, List <String>> statusMapping;. Also as your class is named *properties, It seems that you don't want it to be a #Configuration class. Consider the following pattern:
First, create a properties class to hold the properties:
#ConfigurationProperties(prefix = "status")
public class StatusProperties {
private Map.Entry<Integer, String> available;
private Map.Entry<Integer, String> forbidden;
private Map.Entry<Integer, String> authdenied;
public Map.Entry<Integer, String> getAvailable() {
return available;
}
public void setAvailable(Map.Entry<Integer, String> available) {
this.available = available;
}
public Map.Entry<Integer, String> getForbidden() {
return forbidden;
}
public void setForbidden(Map.Entry<Integer, String> forbidden) {
this.forbidden = forbidden;
}
public Map.Entry<Integer, String> getAuthdenied() {
return authdenied;
}
public void setAuthdenied(Map.Entry<Integer, String> authdenied) {
this.authdenied = authdenied;
}
}
Now, your IDE should be able to read the docs from the setters while editing application.properties and check the validity. Spring can autowire the fields and automatically create the correct data types for you.
Consider mapping the Entries to a Map (Or, as I already told, change the design)
Now, you can use this properties class in your configuration:
#Configuration
#EnableConfigurationProperties(StatusProperties.class)
public class StatusConfiguration {
#Bean
public MyBean myBean(StatusProperties properties) {
return new MyBean(properties);
}
}
I found the solution:
application.properties:
app.statuses[0].id=00
app.statuses[0].title=STATUS.ALLOWED
app.statuses[1].id=01
app.statuses[1].title=STATUS.FORBIDDEN
app.statuses[2].id=02
app.statuses[2].title=STATUS.CONTRACT_ENDED
Properties.java
#Component
#ConfigurationProperties(prefix = "app")
#JsonAutoDetect(fieldVisibility = JsonAutoDetect.Visibility.NONE)
public class StatusProperties {
private List<Status> statuses = new ArrayList<>();
public List <Status> getStatuses () {
return statuses;
}
public void setStatuses (List <Status> statuses) {
this.statuses = statuses;
}
public static class Status {
private String id;
private String title;
public String getId () {
return id;
}
public void setId (String id) {
this.id = id;
}
public String getTitle () {
return title;
}
public void setTitle (String title) {
this.title = title;
}
}
}

Categories