Create a mixed initialized component for existing fields using lombok and spring - spring

I want to create a spring component that can be partially initialized automatically by spring (all those final fields) and partially initialized manually (the remaining non final fields). So I used lombok and tried a lots of variants but couldn't make it work:
(ex: adding #AllArgsConstructor(access = PACKAGE) will not be instantiated by spring because of
Caused by: java.lang.NoSuchMethodException: Task.<init>())
#RequiredArgsConstructor
#Builder (toBuilder=true)
#Component
public class Task {
private final Repo1Repository repo1; //<- should be automatically initialized
private final Repo2Repository repo2; //<- should be automatically initialized
private final Repo3Repository repo3; //<- should be automatically initialized
private String tableName; //<- will be manually initialized with the builder
private boolean special; //<- will be manually initialized with the builder
...
}
Intended usage will be something like:
#RequiredArgsConstructor
#Configuration
public class ConfigurationClass {
private final Task task; //<- automatically injected
#Bean
public void createBean (){
return task.toBuilder ().tableName("TABLE").special(true).build();
}
#Bean
public void createBean2 (){
return task.toBuilder().tableName("TABLE2").special(true).build();
}
...
}
Using lombok 1.18.24 & spring 5.3.20 (from spring boot 2.6.8)

Although this may be possible somehow, I suggest a different approach.
Right now you are trying to mix two concerns: the modeling of the actual Task class, and creating a templating mechanism for populating instances of Task with default values. You should separate these concerns by explicitly modeling them:
#Builder
public class Task {
private final Repo1Repository repo1;
private final Repo2Repository repo2;
private final Repo3Repository repo3;
private final String tableName;
private final boolean special;
}
#AllArgsConstructor
#Component
public class TaskTemplate {
private final Repo1Repository repo1;
private final Repo2Repository repo2;
private final Repo3Repository repo3;
public Task.TaskBuilder builder() {
return Task.builder().repo1(repo1).repo2(repo2).repo3(repo3);
}
}

Related

Best Approach to load application.yml in spring boot application

I am having Spring Boot application and having application.yml with different properties and loading as below.
#Configuration
#ConfigurationProperties(prefix="applicationprops")
public class ApplicationPropHolder {
private Map<String,String> mapProperty;
private List<String> myListProperty;
//Getters & Setters
}
My Service or Controller Class in which I get this properties like below.
#Service
public ApplicationServiceImpl {
#Autowired
private ApplicationPropHolder applicationPropHolder;
public String getExtServiceInfo(){
Map<String,String> mapProperty = applicationPropHolder.getMapProperty();
String userName = mapProperty.get("user.name");
List<String> listProp = applicationPropHolder.getMyListProperty();
}
}
My application.yml
spring:
profile: dev
applicationprops:
mapProperty:
user.name: devUser
myListProperty:
- DevTestData
---
spring:
profile: stagging
applicationprops:
mapProperty:
user.name: stageUser
myListProperty:
- StageTestData
My questions are
In my Service class i am defining a variable and assigning Propertymap for every method invocation.Is it right appoach?
Is there any other better way I can get these maps without assigning local variable.
There are three easy ways you can assign the values to instance variables in your bean class.
Use the #Value annotation as follows
#Value("${applicationprops.mapProperty.user\.name}")
private String userName;
Use the #PostConstruct annotation as follows
#PostConstruct
public void fetchPropertiesAndAssignToInstanceVariables() {
Map<String, String> mapProperties = applicationPropHolder.getMapProperty();
this.userName = mapProperties.get( "user.name" );
}
Use #Autowired on a setter as follows
#Autowired
public void setApplicationPropHolder(ApplicationPropHolder propHolder) {
this.userName = propHolder.getMapProperty().get( "user.name" );
}
There may be others, but I'd say these are the most common ways.
Hope, you are code is fine.
Just use the below
#Configuration
#ConfigurationProperties(prefix="applicationprops")
public class ApplicationPropHolder {
private Map<String,String> mapProperty;
private List<String> myListProperty;
public String getUserName(){
return mapProperty.get("user.name");
}
public String getUserName(final String key){
return mapProperty.get(key);
}
}
#Service
public ApplicationServiceImpl {
#Autowired
private ApplicationPropHolder applicationPropHolder;
public String getExtServiceInfo(){
final String userName = applicationPropHolder.getUserName();
final List<String> listProp = applicationPropHolder.getMyListProperty();
}
}

Make #ConfigurationProperties properties partially mandatory

Given the following simple (not nested) configuration properties class:
#ConfigurationProperties("env")
public class MyServiceProperties {
private String anyProperty;
private Boolean anyOther;
...
}
How can I make sure that anyProperty is mandatory, i.e. env.any-property must be set to startup the application? Is there any difference for nested configuration property classes?
You can perform all kind of validations.
#Validated
#ConfigurationProperties("env")
public class MyServiceProperties {
#NotNull
#Min(5)
private String anyProperty;
// this is for nested objects
#Valid
#NotNull
private FooNested fooNested;
public static class FooNested{
#NotNull
private String someVal;
}
}
You could also perform manual validation in setter
#Validated
#ConfigurationProperties("env")
public class MyServiceProperties {
private String anyProperty;
public void setAnyProperty(String anyProp){
// just an example
if(anyProp.lenght < 6){
throw new RuntimeException();
}
this.anyProperty = anyProp;
}
}

Spring boot cache not working in #PostConstruct

I'm building a "class cache", with classes I want to call later.
The main goal is that I don't want scan the context every time that a class instance is needed.
# Model / Repository classes
#Getter
#RequiredArgsConstructor
public class Block implements Serializable {
private final String className;
private final Set<String> classCandidates = new HashSet<>();
public boolean addCandidate(final String classCandidate) {
return this.classCandidates.add(classCandidate);
}
}
#Slf4j
#Component
#CacheConfig(cacheNames = ConstantsCache.CACHE_BLOCK)
public class BlockRepository {
#Cacheable(key = "#className")
public Block findByInputClass(final String className) {
log.info("---> Loading classes for class '{}'", className);
val block = new Block(className);
findCandidates(block);
return block;
}
}
First to evaluate the cache, I've put the cache method #Autowired in a #RestController, wich works fine. The cache is populated when I call the rest method.
#RestController
public class Controller {
#Autowired
BlockRepository blockRepository;
#RequestMapping("/findByInputClass")
public Block findByInputClass(#RequestParam("className") final String className) {
return blockRepository.findByInputClass(className);
}
}
After doing that, I've moved the #Autowired object to a #Service, creating a method to self-populate the cache. But this does not work. The cache is not populated when the #PostConstructor method is called.
#Slf4j
#Component
public class BlockCacheService {
#Autowired
BlockRepository blockRepository;
#PostConstruct
private void postConstruct() {
log.info("*** {} PostConstruct called.", this.getClass().getTypeName());
val block = blockRepository.findByInputClass(ConstantsGenerics.BLOCK_PARENT_CLASS);
final Set<String> inputClasses = getInputFromCandidates(block.getClassCandidates());
appendClassesToCache(inputClasses);
}
private void appendClassesToCache(final Set<String> inputClasses) {
for (val inputClass : inputClasses) {
blockRepository.findByInputClass(inputClass);
}
}
}
How can I properly populate the cache using a service or component, that must start with the application.
Thanks in advance.
EDIT:
I've found a possible solution here: https://stackoverflow.com/a/28311225/1703546
Than I've changed the #Service code to put the cache manually instead of use the #Cacheable magic abstraction.
The class now is like this.
#Slf4j
#Component
public class BlockCacheService {
#Autowired
CacheManager cacheManager;
#Autowired
BlockRepository blockRepository;
#PostConstruct
private void postConstruct() {
log.info("*** {} PostConstruct called.", this.getClass().getTypeName());
val block = blockRepository.findByInputClass(ConstantsGenerics.BLOCK_PARENT_CLASS);
final Set<String> inputClasses = getInputFromCandidates(block.getClassCandidates());
appendClassesToCache(inputClasses);
}
private void appendClassesToCache(final Set<String> inputClasses) {
for (val inputClass : inputClasses) {
val block = blockRepository.findByInputClass(inputClass);
cacheManager.getCache(ConstantsCache.CACHE_BLOCK).put(block.getClassName(), block);
}
}
}
Now the cache is populated correctly, but the question is, this is the best solution?
Thanks.
You can't use an aspect in #PostConstruct as it may not have been created yet (and that is documented by the way).
One possible way to make that work is to implement SmartInitializingBean instead as it gives a callback when all singletons have been fully initialized (including their aspect. Changing that on your original service should work.
Having said that, this code of yours has an impact on the startup time. Why don't you let your cache to be filled lazily instead?

Spring Data Rest Repository with abstract class / inheritance

I can't get Spring Data Rest with class inheritance working.
I'd like to have a single JSON Endpoint which handles all my concrete classes.
Repo:
public interface AbstractFooRepo extends KeyValueRepository<AbstractFoo, String> {}
Abstract class:
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = MyFoo.class, name = "MY_FOO")
})
public abstract class AbstractFoo {
#Id public String id;
public String type;
}
Concrete class:
public class MyFoo extends AbstractFoo { }
Now when calling POST /abstractFoos with {"type":"MY_FOO"}, it tells me: java.lang.IllegalArgumentException: PersistentEntity must not be null!.
This seems to happen, because Spring doesn't know about MyFoo.
Is there some way to tell Spring Data REST about MyFoo without creating a Repository and a REST Endpoint for it?
(I'm using Spring Boot 1.5.1 and Spring Data REST 2.6.0)
EDIT:
Application.java:
#SpringBootApplication
#EnableMapRepositories
public class Application {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
I'm using Spring Boot 1.5.1 and Spring Data Release Ingalls.
KeyValueRepository doesn't work with inheritance. It uses the class name of every saved object to find the corresponding key-value-store. E.g. save(new Foo()) will place the saved object within the Foo collection. And abstractFoosRepo.findAll() will look within the AbstractFoo collection and won't find any Foo object.
Here's the working code using MongoRepository:
Application.java
Default Spring Boot Application Starter.
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
AbstractFoo.java
I've tested include = JsonTypeInfo.As.EXISTING_PROPERTY and include = JsonTypeInfo.As.PROPERTY. Both seem to work fine!
It's even possible to register the Jackson SubTypes with a custom JacksonModule.
IMPORTANT: #RestResource(path="abstractFoos") is highly recommended. Else the _links.self links will point to /foos and /bars instead of /abstractFoos.
#JsonTypeInfo(use = JsonTypeInfo.Id.NAME, include = JsonTypeInfo.As.EXISTING_PROPERTY, property = "type")
#JsonSubTypes({
#JsonSubTypes.Type(value = Foo.class, name = "MY_FOO"),
#JsonSubTypes.Type(value = Bar.class, name = "MY_Bar")
})
#Document(collection="foo_collection")
#RestResource(path="abstractFoos")
public abstract class AbstractFoo {
#Id public String id;
public abstract String getType();
}
AbstractFooRepo.java
Nothing special here
public interface AbstractFooRepo extends MongoRepository<AbstractFoo, String> { }
Foo.java & Bar.java
#Persistent
public class Foo extends AbstractFoo {
#Override
public String getType() {
return "MY_FOO";
}
}
#Persistent
public class Bar extends AbstractFoo {
#Override
public String getType() {
return "MY_BAR";
}
}
FooRelProvider.java
Without this part, the output of the objects would be separated in two arrays under _embedded.foos and _embedded.bars.
The supports method ensures that for all classes which extend AbstractFoo, the objects will be placed within _embedded.abstractFoos.
#Component
#Order(Ordered.HIGHEST_PRECEDENCE)
public class FooRelProvider extends EvoInflectorRelProvider {
#Override
public String getCollectionResourceRelFor(final Class<?> type) {
return super.getCollectionResourceRelFor(AbstractFoo.class);
}
#Override
public String getItemResourceRelFor(final Class<?> type) {
return super.getItemResourceRelFor(AbstractFoo.class);
}
#Override
public boolean supports(final Class<?> delimiter) {
return AbstractFoo.class.isAssignableFrom(delimiter);
}
}
EDIT
Added #Persistent to Foo.java and Bar.java. (Adding it to AbstractFoo.java doesn't work). Without this annotation I got NullPointerExceptions when trying to use JSR 303 Validation Annotations within inherited classes.
Example code to reproduce the error:
public class A {
#Id public String id;
#Valid public B b;
// #JsonTypeInfo + #JsonSubTypes
public static abstract class B {
#NotNull public String s;
}
// #Persistent <- Needed!
public static class B1 extends B { }
}
Please see the discussion in this resolved jira task for details of what is currently supported in spring-data-rest regarding JsonTypeInfo. And this jira task on what is still missing.
To summarize - only #JsonTypeInfo with include=JsonTypeInfo.As.EXISTING_PROPERTY is working for serialization and deserialization currently.
Also, you need spring-data-rest 2.5.3 (Hopper SR3) or later to get this limited support.
Please see my sample application - https://github.com/mduesterhoeft/spring-data-rest-entity-inheritance/tree/fixed-hopper-sr3-snapshot
With include=JsonTypeInfo.As.EXISTING_PROPERTY the type information is extracted from a regular property. An example helps getting the point of this way of adding type information:
The abstract class:
#Entity #Inheritance(strategy= SINGLE_TABLE)
#JsonTypeInfo(use=JsonTypeInfo.Id.NAME,
include=JsonTypeInfo.As.EXISTING_PROPERTY,
property="type")
#JsonSubTypes({
#Type(name="DECIMAL", value=DecimalValue.class),
#Type(name="STRING", value=StringValue.class)})
public abstract class Value {
#Id #GeneratedValue(strategy = IDENTITY)
#Getter
private Long id;
public abstract String getType();
}
And the subclass:
#Entity #DiscriminatorValue("D")
#Getter #Setter
public class DecimalValue extends Value {
#Column(name = "DECIMAL_VALUE")
private BigDecimal value;
public String getType() {
return "DECIMAL";
}
}

Spring #Value property for custom class

Is it possible to use Spring's #Value annotation to read and write property values of a custom class type?
For example:
#Component
#PropertySource("classpath:/data.properties")
public class CustomerService {
#Value("${data.isWaiting:#{false}}")
private Boolean isWaiting;
// is this possible for a custom class like Customer???
// Something behind the scenes that converts Custom object to/from property file's string value via an ObjectFactory or something like that?
#Value("${data.customer:#{null}}")
private Customer customer;
...
}
EDITED SOLUTION
Here is how I did it using Spring 4.x APIs...
Created new PropertyEditorSupport class for Customer class:
public class CustomerPropertiesEditor extends PropertyEditorSupport {
// simple mapping class to convert Customer to String and vice-versa.
private CustomerMap map;
#Override
public String getAsText()
{
Customer customer = (Customer) this.getValue();
return map.transform(customer);
}
#Override
public void setAsText(String text) throws IllegalArgumentException
{
Customer customer = map.transform(text);
super.setValue(customer);
}
}
Then in application's ApplicationConfig class:
#Bean
public CustomEditorConfigurer customEditorConfigurer() {
Map<Class<?>, Class<? extends PropertyEditor>> customEditors =
new HashMap<Class<?>, Class<? extends PropertyEditor>>(1);
customEditors.put(Customer.class, CustomerPropertiesEditor.class);
CustomEditorConfigurer configurer = new CustomEditorConfigurer();
configurer.setCustomEditors(customEditors);
return configurer;
}
Cheers,
PM
You have to create a class extending PropertyEditorSupport.
public class CustomerEditor extends PropertyEditorSupport {
#Override
public void setAsText(String text) {
Customer c = new Customer();
// Parse text and set customer fields...
setValue(c);
}
}
It's possible but reading Spring documentation. You could see this example:
Example usage
#Configuration
#PropertySource("classpath:/com/myco/app.properties")
public class AppConfig {
#Autowired
Environment env;
#Bean
public TestBean testBean() {
TestBean testBean = new TestBean();
testBean.setName(env.getProperty("testbean.name"));
return testBean;
}
}
See details here
Spring can read properties and load them directly into a class.
Moreover, you can add #ConfigurationProperties(prefix = "data") on top of the class, instead of wiring each nested property one by one, by making the code cleaner.
Given all that, here is the final example with explanations:
// File: CustomerConfig.java
#Configuration
// Set property source file path (optional)
#PropertySource("classpath:/data.properties")
// Put prefix = "data" here so that Spring read properties under "data.*"
#ConfigurationProperties(prefix = "data")
public class CustomerConfig {
// Note: Property name here is the same as in the file (data.customer)
// Spring will automatically read and put "data.customer.*" properties into this object
private Customer customer;
// Other configs can be added here too... without wiring one-by-one
public setCustomer(Customer customer){
this.customer = customer;
}
public getCustomer(){
return this.customer;
}
}
That's it, now you have "data.customer.*" properties, loaded and accessible via CustomerConfig.getCustomer().
To integrate it into your service (based on your example code):
// File: CustomerService.java
#Component
#PropertySource("classpath:/data.properties")
public class CustomerService {
#Value("${data.isWaiting:#{false}}")
private Boolean isWaiting;
#Autowired // Inject configs, either with #Autowired or using constructor injection
private CustomerConfig customerConfig;
public void myMethod() {
// Now its available for use
System.out.println(customerConfig.getCustomer().toString());
}
}
This way no "magical hack" is required to read configs into a class.
Take a look at the #ConfigurationProperties documentation/examples, and this post for more useful info.
Note: I'd suggest against using PropertyEditorSupport, since
a) it was built for different purpose, may change in future by breaking the code
b) it requires manual "handling" code inside => possible bugs
Instead, use what was built right for that purpose (Spring already has it), in order to both make the code easier to understand, and to gain possible inner improvements/optimizations which might be done in the future (or present).
Further improvements: Your CustomerService seems to be cluttered with configs (#PropertyService) too. I'd suggest reading those properties via another class too (similarly) then wiring that class here, instead of doing all in the CustomerService.
If you want to use it with lists, there is a workaround using array instead.
Define your property as Customer[] instead of List then:
in ApplicationConfig class:
#Bean
public CustomEditorConfigurer customEditorConfigurer() {
Map<Class<?>, Class<? extends PropertyEditor>> customEditors =
new HashMap<Class<?>, Class<? extends PropertyEditor>>(1);
customEditors.put(Customer.class, CustomerPropertiesEditor.class);
customEditors.put(Customer[].class, CustomerPropertiesEditor.class);
CustomEditorConfigurer configurer = new CustomEditorConfigurer();
configurer.setCustomEditors(customEditors);
return configurer;
}
In CustomerEditor:
public class CustomerEditor extends PropertyEditorSupport {
public static final String DEFAULT_SEPARATOR = ",";
#Override
public void setAsText(String text) {
String[] array = StringUtils.delimitedListToStringArray(text, this.separator);
if (this.emptyArrayAsNull && array.length == 0) {
super.setValue((Object) null);
} else {
if (this.trimValues) {
array = StringUtils.trimArrayElements(array);
}
// Convert String[] to Customer[]
super.setValue(...);
}
}
}
If you want to use an existing converter/constructor, you can just call it within your expression.
For example:
#Value("#{T(org.test.CutomerMap).transform('${serialized.customer}')}")
private Customer customer;

Resources