I have this doubt....
I was trying to have a default value for a field in my object, default value I wanted to bring it from a properties.
So, I thought about use it this way
#Value("${app.message.text:default}")
final String text;
final Object object
MyClass(Object object){
this.object = object;
}
//getters for both fields
Well, text is always null....
Then I tried this way
final String text;
final Object object
MyClass(Object object, #Value("${app.message.text:default}") final String text){
this.object = object;
this.text = text
}
It does work, but then at the constructors I have to specify the value of text.... then I will never use the default from the property... Did I misunderstand this? is there anyway to archive what I am trying using this #Value?
Thanks.
The reason why it won't work at property level:
When you annotate a class member, Spring uses reflection to set the value (after object is created). Now, in this case, the field is marked as final so it has to be set while creating an object (and not after the object is created) so the only way to #Value final fields would be to use constructor injection.
The reason why it doesn't work in constructor:
Spring only sets the value using #Value if the bean is created via Spring (i.e. by #Component, #Configuration or #Bean annotation so make sure your class is annotated with either of these.
Related
I'm trying to make a CDI extension which will validate a Java object which is bound to configuration values.
public class ExampleConfig {
#Range(min = 1000, max = 9999)
private int value;
#Inject
public ExampleConfig(#ConfigProperty(name = "value") int value) {
this.value = value;
}
public int getValue() {
return value;
}
}
When I make the above class with the #Singleton annotation, it works correctly. On startup the CDI extension validates the class which reads an environment variable called "value".
Class: com.example.ExampleConfig
Property: value
Value: 22222
Reason: must be between 1000 and 9999
When I replace #Singleton with #ApplicationScoped instead, when injecting and using this class myself, it works as intended, but in the CDI extension, javax.validation.Validator appears to always treat the value as 0.
Class: com.example.ExampleConfig$Proxy$_$$_WeldClientProxy
Property: value
Value: 0
Reason: must be between 1000 and 9999
I'm struggling to see why this is the case, is anyone able to provide guidance on how to read the value correctly?
Two things I've been trying to achieve to no avail is:
Have the extension enforce initialization on startup for respective classes.
Make the CDI extension wait until the bean has initialized.
The following is how I'm calling #validate:
public void afterDeploymentValidation(#Observes AfterDeploymentValidation adv, BeanManager bm) {
Set<ConstraintViolation<?>> allViolations = new HashSet<>();
for (Class<?> type : types)
{
final Object typeImpl = BeanProvider.getContextualReference(bm, type, false);
Set<ConstraintViolation<?>> violations = (Set<ConstraintViolation<?>>)(Object)validator.validate(typeImpl);
allViolations.addAll(violations);
}
// Omitted for brevity.
}
Several things:
First of all, if all you're trying to do is get Bean Validation working, just put the Hibernate Validator CDI project on your runtime classpath. Nothing else needed; the end.
If you're doing something else, you're probably running into the fact that a contextual reference to a bean in a normal scope is a client proxy. In less stuffy terms, that means it's a proxy, a shell, a holder—and its "innards" (its referent, the thing it is proxying) is not "inflated" until some method is called on the proxy, like toString() or a business method. I'm guessing that what's happening in your case is the validator is looking for validatable fields directly on the proxy.
One way to "inflate" a contextual reference is to just call toString() on it right away before doing something else. So just call typeImpl.toString() before you do anything else to "inflate" the reference.
I don't think there's any guarantee that the proxy will somehow magically make the proxied object's fields available to you. For that, you'll need to get the inner proxied object. Each CDI implementation does this a little differently, but in Weld you can get this programmatically with some casting.
I found this post for connecting a Java Bean as property binding with an existing JavaFX property. The binding should target a boolean property:
class MyClass {
private boolean loaded;
public boolean isLoaded() {
return loaded;
}
// Value changed internally
}
For real beans, meaning beans with setters the following works like a charm. But I've the problem that there's no setter for the loaded property, just because it's set internally and shouldn't be modifyable for external classes.
BooleanProperty loadedProeprty = new JavaBeanBooleanPropertyBuilder()
.bean(bean)
.name("loaded")
.getter("isLoaded")
.build();
Is there any way to create still a property for such "beans" without a setter? For now I just get a NoSuchMethodException for the expected setter MyClass.setLoaded(boolean).
Use ReadOnlyJavaBeanBooleanPropertyBuilder instead.
Normal properties in JavaFX are always read/write and thus require a setter. The read only variant creates a read only property and thus does not require a setter.
I would like to use #Value on a property but I always get 0(on int).
But on a constructor parameter it works.
Example:
#Component
public class FtpServer {
#Value("${ftp.port}")
private int port;
public FtpServer(#Value("${ftp.port}") int port) {
System.out.println(port); // 21, loaded from the application.properties.
System.out.println(this.port); // 0???
}
}
The object is spring managed, else the constructor parameter wouldn't work.
Does anyone know what causes this weird behaviour?
Field injection is done after objects are constructed since obviously the container cannot set a property of something which doesn't exist. The field will be always unset in the constructor.
If you want to print the injected value (or do some real initialization :)), you can use a method annotated with #PostConstruct, which will be executed after the injection process.
#Component
public class FtpServer {
#Value("${ftp.port}")
private int port;
#PostConstruct
public void init() {
System.out.println(this.port);
}
}
I think the problem is caused because Spring's order of execution:
Firstly, Spring calls the constructor to create an instance, something like:
FtpServer ftpServer=new FtpServer(<value>);
after that, by reflection, the attribute is filled:
code equivalent to ftpServer.setPort(<value>)
So during the constructor execution the attribute is still 0 because that's the default value of an int.
This is a member injection:
#Value("${ftp.port}")
private int port;
Which spring does after instantiating the bean from its constructor. So at the time spring is instantiating the bean from the class, spring has not injected the value, thats why you are getting the default int value 0.
Make sure to call the variable after the constructor have been called by spring, in case you want to stick with member injection.
class A {
private String field1="123";
private String field2="prefix"+field1;
getter&setter;
}
filed1 is injected by one value("abc") defined in property file.
In some cases, value of field2 is always "prefix123" rather than "prefixabc".
Does property injection and initialization have order?
It could be safer to initialize field two inside a postconstruct method:
#PostConstruct
void initFieldTwo(){
field2="prefix"+field1;
}
That way you know it will happen after field 1 has been initialized.
I'm using IReport (JasperStudio plugin for Eclipse) and I'm trying to create a report with a JavaBean as source.
Suppose I have these two classes:
public class MyClass {
private String myClassAttribute;
// getter and setter for myClassAttribute
}
public class AnotherMyClass {
private String anotherMyClassAttribute;
private MyClass myClass;
// getter and setter for anotherMyClassAttribute
// getter and setter for myClass
}
If I choose AnotherMyClass as JavaBeanSource I can set only fields from that class (anotherMyClassAttribute), I didn't find a way to set a text to getMyClass().getmyClassAttribute().
Do JavaBeans stop at level one or is there a way to use attribute from other classes between references?
Thanks.
In report define field $F{myClass} with type MyClass
In text field use expression $F{myClass}.getMyClassAttribute()
No, it doesn't stop at level one, you may go as deep as you want. You may use the attribute like myClass.myClassAttribute. And for setting a value to it, myClass.myClassAttribute = "some value"