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;
Related
First off, please excuse my question due to my being new to spring boot ecosystem. In my application, I've a vaadin page, where I want to submit user details to DB, using repository. In my view class, I've added them as #autowired fields, however, during the runtime, I see that their values are run so the operation fails. I know that to benefit from #autowired, the instances should not be created newly during constructing but I couldn't figure out how I should do it on my own. Here are my classes:
#SuppressWarnings("serial")
public class LoginAwareComposite extends Composite<Div> {
#Autowired
private ApplicationEventPublisher publisher;
public LoginAwareComposite() {
}
#Override
protected void onAttach(AttachEvent event) {
super.onAttach(event);
UserCredentials userPrincipal = UI.getCurrent().getSession().getAttribute(UserCredentials.class);
if (userPrincipal != null) {
// --- NOT LOGGED IN
UI.getCurrent().navigate(AddressBookManagementView.class);
}
}
}
#SuppressWarnings("serial")
#Route(value = "account")
#Theme(value = Lumo.class, variant = Lumo.LIGHT)
public class AddressBookManagementView extends LoginAwareComposite {
private VerticalLayout pageLayout = new VerticalLayout();
public AddressBookManagementView() {
getContent().setSizeFull();
getContent().add(initPage());
}
private Component initPage() {
pageLayout.getStyle().set("padding-left", "0px");
pageLayout.getStyle().set("padding-bottom", "0px");
pageLayout.getStyle().set("padding-right", "0px");
pageLayout.getStyle().set("overflow", "auto");
pageLayout.setSizeFull();
pageLayout.add(new HeaderLayout(), new BodyLayout(), new FooterLayout());
return pageLayout;
}
}
#SuppressWarnings("serial")
#SpringComponent
public class BodyLayout extends VerticalLayout {
// some fields
#Autowired
EmailRepository emailRepository;
#Autowired
FaxRepository faxRepository;
public BodyLayout() {
init(); //this function inits the view, and eventually inits the on click event for submit button , which then calls my function
}
private void myFunction() {
//here i use the repository entities but they do return null although they are autowired
}
So what happens is, in BodyLayout's constructor we call init() function which is used to init the layout and give functionality buttons etc, one of subfunctions inside the init method gives functionality to submit button using myFunction. MyFuction uses the repository entity but it returns null.
Since you are using springboot with vaadin ensure the following :
Make sure that the #Repository annotation is used on your repository interfaces like on EmailRepository.
Try using constructor injection for your repository classes like :
Try like below :
#SuppressWarnings("serial")
#SpringComponent
#UIScope
public class BodyLayout extends VerticalLayout {
// some fields
private final EmailRepository emailRepository;
private final FaxRepository faxRepository;
#Autowired
public BodyLayout(EmailRepository emailRepository, FaxRepository faxRepository) {
this.emailRepository = emailRepository;
this.faxRepository = faxRepository;
init(); //this function inits the view, and eventually inits the on click event for submit button , which then calls my function
}
private void myFunction() {
//here i use the repository entities but they do return null although they are autowired
}
I was able to get it working as follows:
#SuppressWarnings("serial")
#Route(value = "account")
#Theme(value = Lumo.class, variant = Lumo.LIGHT)
#UIScope
#SpringComponent
public class AddressBookManagementView extends LoginAwareComposite {
private VerticalLayout pageLayout = new VerticalLayout();
#Autowired
BodyLayout bodyLayout;
public AddressBookManagementView(BodyLayout bodyLayout) {
this.bodyLayout = bodyLayout;
getContent().setSizeFull();
getContent().add(initPage());
}
private Component initPage() {
pageLayout.getStyle().set("padding-left", "0px");
pageLayout.getStyle().set("padding-bottom", "0px");
pageLayout.getStyle().set("padding-right", "0px");
pageLayout.getStyle().set("overflow", "auto");
pageLayout.setSizeFull();
pageLayout.add(new HeaderLayout(), bodyLayout, new FooterLayout());
return pageLayout;
}
Then BodyLayout is
#SuppressWarnings("serial")
#UIScope
#SpringComponent
public class BodyLayout extends VerticalLayout {
private final EmailRepository emailRepository;
private final FaxRepository faxRepository;
#Autowired
public BodyLayout(EmailRepository emailRepository, FaxRepository faxRepository) {
this.emailRepository = emailRepository;
this.faxRepository = faxRepository;
init();
}
Roughly only #Route, layouts, and the vaadin init listener takes part in automatic dependency injection (that is: the vaadin spring integration asks the spring context to build them). If you do new MyClass() it never takes part in DI. Using field based injection with #Autowired hides this problem - so using constructor based injection is the "industry standard". The other way around is to not build your own instances, if you want to take part in DI but ask the spring context to build an instance for you.
I have a Spring mvc #RestController class where the method parameter is annotated with #RequestBody. Something like this:
#RestController
#RequestMapping("/features")
public class FeatureController {
#PostMapping
public Feature createFeature(#RequestBody Feature feature) {
......
}
}
My Feature class has a private constructor built using a Builder pattern. So I created HttpMessageConverter called FeatureConverter and registered it properly using extendMessageConverters. The converter uses Jackson to parse the JSON and then uses Feature.Builder to create an instance of Feature.
My problem is that Spring registers an instance of MappingJackson2HttpMessageConverter before my custom FeatureConverter. As a result the parsing is attempted by MappingJackson2HttpMessageConverter even before it can reach FeatureConverter. Since the constructor is private so MappingJackson2HttpMessageConverter fails.
My question is how do I change the order so that FeatureConverter is asked before MappingJackson2HttpMessageConverter. Is there a proper way of doing it?
Spring MVC WebMvcConfigurerAdapter initialize HttpMessageConverters like that:
protected final List<HttpMessageConverter<?>> getMessageConverters() {
if (this.messageConverters == null) {
this.messageConverters = new ArrayList<HttpMessageConverter<?>>();
configureMessageConverters(this.messageConverters);
if (this.messageConverters.isEmpty()) {
addDefaultHttpMessageConverters(this.messageConverters);
}
extendMessageConverters(this.messageConverters);
}
return this.messageConverters;
}
See: Github - WebMvcConfigurationSupport
As you can see there is 3 method:
configureMessageConverters, empty by default it's where you are suppose to add converters
addDefaultHttpMessageConverters, where as the name said will create all default converters, the default MappingJackson2HttpMessageConverter is created there.
extendMessageConverters which as the documentation:
Override this method to extend or modify the list of converters after
it has been configured. This may be useful for example to allow
default converters to be registered and then insert a custom
converter through this method.
Is to use when you want to modify the list after having all the defaults converter added.
To answer your question, if you don't need defaults converters you just have to Override configureMessageConverters and add it:
#Override
public void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(new FeatureConverter());
}
If you need them you can either copy-paste the addDefaultHttpMessageConverters content and add it into configureMessageConverters and play with the order of initialisation there (ugly solution).
Otherwise you can use extendMessageConverters to add it after the default initialization and play with the order:
#Override
public void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
converters.add(the_index_you_want, new FeatureConverter());
}
If I understand you right you want to use your Builder for deserialization of Feature object.
Maybe you can just use Jackson annotations (without FeatureConverter):
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
#JsonDeserialize(builder = Feature.FeatureBuilder.class)
public class Feature {
private final String name;
public Feature(String name) {
this.name = name;
}
public String getName() {
return name;
}
/**
* Builder
*/
#JsonPOJOBuilder(withPrefix = "")
public static final class FeatureBuilder {
private String name = "";
private FeatureBuilder() {
}
public static FeatureBuilder create() {
return new FeatureBuilder();
}
public FeatureBuilder name(String name) {
this.name = name;
return this;
}
/**
* Build Feature object
*/
public Feature build() {
Feature feature = new Feature(this.name);
return feature;
}
}
}
If you use Lombok:
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder;
import lombok.Builder;
import lombok.Getter;
#Getter
#Builder
#JsonDeserialize(builder = Feature.FeatureBuilder.class)
public class Feature {
private final String name;
/**
* Builder
*/
#JsonPOJOBuilder(withPrefix = "")
public static final class FeatureBuilder {
}
}
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?
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";
}
}
I have a HsqldbReconciler (for "work" with a HSQLDB database) which I autowired, like:
#Autowired
HsqldbReconciler hsqldbReconciler;
In Future there will be a OracleReconciler, MssqlReconciler, etc. I will need to use them accordingly to the type of connection a user has chosen.
How should I implement this? Usually I would have a kind of factory, which returns only the needed Reconciler. The only way in spring, I can currently imagine, is to Autowire an instance of each Reconciler, then use one of them in the code. Is there a better way?
make a Factory Class that will contain all your beans, e.g
#Component
class Factory{
#Autowired HsqldbReconciler hsqldb;
#Autowired OracleReconciler oracle;
#Autowired MssqlReconciler mssql;
public Object getInstance(String type){
switch(type){
case "mssql" : return mssql;
case "oracle" : return oracle;
// and so on
default : return null;
}
}
}
Now use this Factory as follows
class SomeClass{
#Autowired private Factory factory;
public Object someMethod(){
Object reconciler = factory.getInstance("mssql");
((MssqlReconciler)reconciler).someMethod();
}
}
Define them in your Config with the same name, but different conditions:
#Bean(name = "dbReconciler")
#Conditional(HsqldbReconcilerEnabled.class)
public ReconcilerBase getHsqldbReconciler() {
return new HsqldbReconciler();
}
#Bean(name = "dbReconciler")
#Conditional(OracleReconcilerEnabled.class)
public ReconcilerBase getOracleReconciler() {
return new OracleReconciler();
}
#Bean(name = "dbReconciler")
#Conditional(MssqlReconcilerEnabled.class)
public ReconcilerBase getMssqlReconciler() {
return new MssqlReconciler();
}
create conditions reading from app.properties:
HsqldbReconciler.enabled=true
OracleReconciler.enabled=false
MssqlReconciler.enabled=false
like this:
public class HsqldbReconcilerEnabled implements Condition {
private static final String PROP_ENABLED = "HsqldbReconciler.enabled";
#Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
String property = context.getEnvironment().getProperty(PROP_ENABLED);
return Boolean.parseBoolean(property);
}
}
// etc...
use like:
#Autowired
#Qualifier("dbReconciler")
ReconcilerBase dbReconsiler;
ensure you're not enabling multiple beans at the same time.