preprocess configuration values in Spring - spring

In a Spring bean, I need to process a configuration property before using is, e.g.:
#Component
class UsersController {
#Value("${roles}")
private String rolesAsString;
private List<String> roles;
#PostConstruct
public void initRoles() {
// just an example, not necessarily string splitting
roles = rolesAsString.split(",");
}
This works, but I am left with an unneeded member variable 'rolesString'. What would be a clean concise way to only keep the processed value?

Properties is
roles=role1,role2,role3
Code is :
#Value("#{'${roles}'.split(',')}")
private List<String> roles;

Related

Spring property binding with multiple separation

I have an application.property like this:
somevalue.api.test=something
somevalue.anotherproperty=stuff
I have made a configuration bean like this:
#Configuration
#ConfigurationProperties("somevalue")
public class SomeProperties {
#NotNull
private String apiTest;
#NotNull
private String anotherproperty;
}
Is it possible to refer to api.test like apiTest?
Mainly my issue is that I want to use the somevalue starting point for both property. I know if I don't separate with a dot the apiTest and I use it in this way somevalue.api-test I can refer to that with apiTest in my bean, but in my case it's not possible the renaming. So with dot separation can I achieve the same result or I should create two separate config bean, one refering to somevalue.api and the another only to somevalue?
If you can't rename the property then no, you can't reference it using String apiTest. You need an additional class as follows:
#Configuration
#ConfigurationProperties("somevalue")
public class GcssProperties {
#NotNull
private GcssApiProperties api;
#NotNull
private String anotherproperty;
}
public class GcssApiProperties {
#NotNull
private String test;
}
This should work.

Spring RefreshScope with SpEL deferred Evaluation

I'm having trouble knowing how this is possible or a potential work around. I am looking at configuring variables that are in the form of "foo,bar,baz" into a List as separate elements. Currently the code looks like
#RefreshScope
#Configuration
#Getter
public class fakeConfiguration {
#Value("#{PropertySplitter.toSet('${properties:}')}")
private final Set<String> field = new HashSet<>();
}
#Component("PropertySplitter")
#SuppressWarnings("unused")
public class PropertySplitter {
public Set<String> toSet(String property) {
Set<String> set = new HashSet<>();
if(!property.trim().isEmpty()){
Collections.addAll(set, property.split("\\s*,\\s*"));
}
return set;
}
}
This properly evaluates the String list into a Set however the refreshScope never gets triggered properly. If I use
#Value("${properties:}")
private final String fieldAsString;
I can see the field properly refresh, but I'd like to actively convert the value to a list as it is changed. Is this possible?
In newer version of spring-boot below works for application.properties and Application.yml
#Value("#{${props.list}}")
private List<String> fieldList;
If you use Application.yml you can arrange the objects
props:
list:
- val1
- val2
and then use in code
#Value("${props.list}")
private List<String> ymlList
Last, You can try the below as well
#Value("#{'${props.list}'.split(',')}")
private List<String> myList;

How to make Set using spring-data-aerospike

Environment:
spring-boot v2.0.4 RELEASE
spring-data-aerospike v2.0.1.RELEASE
java - 8
Here are my application code and properties.
// application.properties
aerospike.hosts=xxx:3000
aerospike.namespace=test
// aerospike configuration class
#Configuration
#RequiredArgsConstructor
#EnableConfigurationProperties(AerospikeConfiguration.AerospikeConfigurationProperties.class)
#EnableAerospikeRepositories(basePackageClassses = TestAeroRepository.class)
public class AerospikeConfiguration extends AbstractAerospikeDataConfiguration {
private final AerospikeConfigurationProperties aerospikeConfigurationProperties;
#Override
protected Collection<Host> getHosts() {
return Host.parseServiceHosts(aerospikeConfigurationProperties.getHosts());
}
#Override
protected String nameSpace() {
return aerospikeConfigurationProperties.getNamespace();
}
#Data
#Validate
#ConfigurationProperties("aerospike")
public static class AerospikeConfigurationProperties {
#NotEmpty
String hsots;
#NotEmpty
String namespace;
}
}
# Entity class
#Value
#Document
#Builder(toBuilder = true)
#AllArgsConstructor
public class testEntity() {
#Id
int id;
#Field
String name;
#Field
String timestamp;
}
#Repository
public interface TestAeroRepository extends AerospikeRepository<TestEntity, Integer> {
}
public interface TestAeroService {
void save();
}
#Service
#RequiredArgsConstructor
public class TestAeroServiceImpl implements TestAeroService {
private final TestAeroRepository testAeroRepository;
#Override
public void save(TestEntity entity) {
testAeroRepository.save(entity);
}
}
I checked Aerospike client connection has no problem.
But error is occurred when save() method is executed.
org.springframework.cglib.core.ReflectUtils.defineClass(Ljava/lang/String;[BLjava/lang/ClassLoader;Ljava/security/ProtectionDomain;Ljava/lang/Class;)Ljava/lang/Class;
Have to make sets before execute the application? I didn't make sets.
Any problem with my code?
You’re using an old version of spring-data-aerospike (2.0.1.RELEASE was released on April 2019) is there any chance you can upgrade to the latest version? 2.4.2.RELEASE
You can see how to setup a simple spring data aerospike application here: https://medium.com/aerospike-developer-blog/simple-web-application-using-java-spring-boot-aerospike-database-and-docker-ad13795e0089
Please share the entire project’s code and the entire exception.
I would look into:
The configuration class (The Aerospike Beans creation).
The content of the testEntity class - are you using #Id annotation on the primary key field?
Extending the repository class with specifying the testEntity object (… extends AerospikeRepository<testEntity, Object> {) you can see an example in the link I added.
The set is automatically created and takes the name of your object class, which is testEntity in your case. For example, based on your code, if you do not specify a collection in the #Document annotation a set named "testEntity" will automatically be created. I added the #Document(collection = "testEntitys") annotation and all I did was create two set. Once you insert your first record, run the "SHOW SETS" aql command and it will be there. So that's one way to do it.

Why I receive 404 error use Spring MVC framework?

When I send request http://localhost:8080/pets My server response 404!
The code on github: https://github.com/Teemitze/petstore
I build war file. Version spring 2.2.6.RELEASE
#Controller
#RequestMapping("/pets")
public class PetsController {
#Autowired
PetRepository petRepository;
#PostMapping("/addPet")
public void addPet(Pet pet) {
petRepository.save(pet);
}
#GetMapping
#ModelAttribute
public String pets(Model model) {
List<Pet> petList = new ArrayList<>();
petList.add(getPet());
petList.add(getPet());
petList.add(getPet());
model.addAttribute("pets", petList);
return "allPets";
}
public Pet getPet() {
Pet pet = new Pet();
pet.setId(1L);
pet.setName("Мурзик");
pet.setPrice(100);
pet.setBirthday(Date.valueOf("2019-12-12"));
pet.setSex("М");
return pet;
}
}
I checked out your code and found a few issues.
1) Package structure
Move controller, dto, repo packages to the main package (com.petstore)
Since the main application is inside the (com.petstore) package and the controller is outside the package, so it fails to scan the class.
2) Use annotation #Entity for the Pet entity class with #Id for the id property
3) Remove #ModelAttribute from pets() method since you are not binding any method parameter.
After this, I see the /pets
SpringBoot project requires define some configuration conventions that need to be follow in order to start a minimum application.
Some points you have to consider when you want to start a spring boot application.
For example:
Your SpringBootApplication(PetstoreApplication) class should be in the directory level above your other packages so that it can scan all classes.
If you want to use SpringData JPA you have to manage your model class
#Data
#Entity
public class Pet {
#Id
private long id;
private String name;
private String sex;
private Date birthday;
private byte[] photo;
private int price;
}
because it is handled by respository
public interface PetRepository extends CrudRepository<Pet, Long>
Need minimum configuration for Thymeleaf https://www.thymeleaf.org/doc/tutorials/3.0/thymeleafspring.html
You are making a GET request for a resource "/pets" so no need #ModelAttribute in get mapping method
#GetMapping()
public String allPets(Model model) {
Make sure your html files is under resources/templates directory.
Check out the reference docs
spring mvc
spring data jpa

Spring Boot Controller endpoint and ModelAttribute deep access

I would like to know how to access a deep collection class attribute in a GET request. My endpoint maps my query strings through #ModelAttribute annotation:
Given that:
public class MyEntity
{
Set<Item> items;
Integer status;
// getters setters
}
public class Item
{
String name;
// getters setters
}
And my GET request: localhost/entities/?status=0&items[0].name=Garry
Produces bellow behavior?
#RequestMapping(path = "/entities", method = RequestMethod.GET)
public List<MyEntity> findBy(#ModelAttribute MyEntity entity) {
// entity.getItems() is empty and an error is thrown: "Property referenced in indexed property path 'items[0]' is neither an array nor a List nor a Map."
}
Should my "items" be an array, List or Map? If so, there´s alternatives to keep using as Set?
Looks like there is some problem with the Set<Item>.
If you want to use Set for the items collection you have to initialize it and add some items:
e.g. like this:
public class MyEntity {
private Integer status;
private Set<Item> items;
public MyEntity() {
this.status = 0;
this.items = new HashSet<>();
this.items.add(new Item());
this.items.add(new Item());
}
//getters setters
}
but then you will be able to set only the values of this 2 items:
This will work: http://localhost:8081/map?status=1&items[0].name=asd&items[1].name=aaa
This will not work: http://localhost:8081/map?status=1&items[0].name=asd&items[1].name=aaa&items[2].name=aaa
it will say: Invalid property 'items[2]' of bean class MyEntity.
However if you switch to List:
public class MyEntity {
private Integer status;
private List<Item> items;
}
both urls map without the need to initialize anything and for various number of items.
note that I didn't use #ModelAttribute, just set the class as paramter
#GetMapping("map")//GetMapping is just a shortcut for RequestMapping
public MyEntity map(MyEntity myEntity) {
return myEntity;
}
Offtopic
Mapping a complex object in Get request sounds like a code smell to me.
Usually Get methods are used to get/read data and the url parameters are used to specify the values that should be used to filter the data that has to be read.
if you want to insert or update some data use POST, PATCH or PUT and put the complex object that you want to insert/update in the request body as JSON(you can map that in the Spring Controller with #RequestBody).

Resources