I have a class that contain injections and mandatory(final) fields. For common I can use MicronautBeanFactory.getBean(type) OR BeanContext.getBean(type) to get bean from context, but in this situation I must pass type and args.
I've created simple test for this
#MicronautTest
public class ETLExecutorTest {
#Inject
private MicronautBeanFactory micronautBeanFactory;
#Test
void testGetBean() {
Object[] args = new Object[] {"name", "spec", 1L};
ObjectInstance instance = micronautBeanFactory.getBean(ObjectInstance.class, args);
}
}
Object(bean) code
#Prototype
public class ObjectInstance {
#Inject
private ObjectStorage objectStorage;
private final String name;
private final String spec;
private final Long id;
public ObjectInstance(String name, String spec, Long id) {
this.name = name;
this.spec = spec;
this.id = id;
}
}
When I run it I receive exception
io.micronaut.context.exceptions.DependencyInjectionException: Failed to inject value for parameter [name] of class: com.ObjectInstance
Message: Multiple possible bean candidates found: [java.lang.String, java.lang.String, java.lang.String]
Path Taken: new ObjectInstance([String name],String specName,Long accountId)
at io.micronaut.context.AbstractBeanDefinition.getBeanForConstructorArgument(AbstractBeanDefinition.java:1016)
at com.$TableInstanceDefinition.build(Unknown Source)
at io.micronaut.context.DefaultBeanContext.doCreateBean(DefaultBeanContext.java:1598)
at io.micronaut.context.DefaultBeanContext.getScopedBeanForDefinition(DefaultBeanContext.java:2076)
at io.micronaut.context.DefaultBeanContext.getBeanForDefinition(DefaultBeanContext.java:1991)
at io.micronaut.context.DefaultBeanContext.getBeanInternal(DefaultBeanContext.java:1963)
at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:610)
at io.micronaut.spring.context.factory.MicronautBeanFactory.getBean(MicronautBeanFactory.java:264)
Caused by: io.micronaut.context.exceptions.NonUniqueBeanException: Multiple possible bean candidates found: [java.lang.String, java.lang.String, java.lang.String]
at io.micronaut.context.DefaultBeanContext.findConcreteCandidate(DefaultBeanContext.java:1701)
at io.micronaut.context.DefaultApplicationContext.findConcreteCandidate(DefaultApplicationContext.java:395)
at io.micronaut.context.DefaultBeanContext.lastChanceResolve(DefaultBeanContext.java:2289)
at io.micronaut.context.DefaultBeanContext.findConcreteCandidateNoCache(DefaultBeanContext.java:2212)
at io.micronaut.context.DefaultBeanContext.lambda$findConcreteCandidate$57(DefaultBeanContext.java:2155)
at io.micronaut.core.util.clhm.ConcurrentLinkedHashMap.lambda$compute$0(ConcurrentLinkedHashMap.java:721)
at java.util.concurrent.ConcurrentHashMap.computeIfAbsent(ConcurrentHashMap.java:1660)
at io.micronaut.core.util.clhm.ConcurrentLinkedHashMap.compute(ConcurrentLinkedHashMap.java:733)
at io.micronaut.core.util.clhm.ConcurrentLinkedHashMap.computeIfAbsent(ConcurrentLinkedHashMap.java:710)
at io.micronaut.context.DefaultBeanContext.findConcreteCandidate(DefaultBeanContext.java:2154)
at io.micronaut.context.DefaultBeanContext.getBeanInternal(DefaultBeanContext.java:1943)
at io.micronaut.context.DefaultBeanContext.getBean(DefaultBeanContext.java:1082)
at io.micronaut.context.AbstractBeanDefinition.getBeanForConstructorArgument(AbstractBeanDefinition.java:1007)
Also I tried to do another test, but in this case I receive object without injected fields
#MicronautTest
public class ETLExecutorTest {
#Inject
private BeanContext beanContext;
#Test
void testGetBean() {
Object[] args = new Object[] {"name", "spec", 1L};
BeanDefinition<ObjectInstance> definition = beanContext.getBeanDefinition(ObjectInstance.class);
ObjectInstance instance = definition.getConstructor().invoke(args); // there are no injections here: ObjectStorage of instance = null.
}
}
Could you tell me, please, what I do wrong ???
micronaut trying to create bean ObjectInstance through the constructor but can't find String name to inject, looks like it’s just a simple field for the ObjectInstance and in this case, it works as expected:
io.micronaut.context.exceptions.DependencyInjectionException: Failed to inject value for parameter [name]
if you add a default constructor, then the ObjectInstance will be created and you can get bean via beanContext.getBean(ObjectInstance.class):
#Prototype
public class ObjectInstance {
#Inject
private ObjectStorage objectStorage;
private String name;
private String spec;
private Long id;
public ObjectInstance() {}
public ObjectInstance(String name, String spec, Long id) {
this.name = name;
this.spec = spec;
this.id = id;
}
}
Also pay attention to MicronautBeanFactory implements ListableBeanFactory, this is for integration with Spring
P.S. I would recommend you change your code structure, POJO should not contain beans
Related
I have a configuration class with this constructor:
public CxfConfigurerImpl(#Value("${cxfclient.timeout.connection}") long connectionTimeout,
#Value("${cxfclient.timeout.connection-request}") long connectionRequestTimeout,
#Value("${cxfclient.timeout.receive}") long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
this.connectionTimeout = connectionTimeout;
this.connectionRequestTimeout = connectionRequestTimeout;
}
But I have different values to set according with my endpoints requests.
Ex:
#Value("${cxfclient.timeout-three.connection}")
private long connectionTimeout3;
#Value("${cxfclient.timeout-three.connection-request}")
private long connectionRequestTimeout3;
#Value("${cxfclient.timeout-three.receive}")
private long receiveTimeout3;
#Autowired
private CxfConfigurerImpl cxfConfigurer3Seg = new CxfConfigurerImpl(connectionTimeout3, connectionRequestTimeout3, receiveTimeout3);
My environment variables are:
CXFCLIENT_TIMEOUT_CONNECTION=1000
CXFCLIENT_TIMEOUT_CONNECTIONREQUEST=1000
CXFCLIENT_TIMEOUT_RECEIVE=1000
CXFCLIENT_TIMEOUTTHREE_CONNECTION=3000
CXFCLIENT_TIMEOUTTHREE_CONNECTIONREQUEST=3000
CXFCLIENT_TIMEOUTTHREE_RECEIVE=3000
The problem is that for the object "cxfConfigurer3Seg" I'm getting the default constructor values (1000). Is there any way to override the values?
OBS: I can't change the "CxfConfigurerImpl" constructor implementation.
Please notes that
#Value : by definition is an Annotation used at the field or method/constructor parameter levelthat indicates a default value expression for the annotated element.
And Note that actual processing of the #Value annotation is performedby a BeanPostProcessor
So values are setted after bean built, otherwise you will already find the value 1000 in your variables
So you should change your implementation here :
You should init configuration in the application level and use #Qualifier for two different bean instead the #Value constructor injection:
#Component
#Getter
#Setter
#ToString
public class B {
private long receiveTimeout;
private long connectionTimeout;
private long connectionRequestTimeout;
public B () {
}
public B ( long connectionTimeout,
long connectionRequestTimeout,
long receiveTimeout) {
this.receiveTimeout = receiveTimeout;
this.connectionTimeout = connectionTimeout;
this.connectionRequestTimeout =
connectionRequestTimeout;
}
}
In the spring boot application startup class : build two beans with two configurations : (b1, b2)
#Value("${cxfclient.timeout-three.connection}")
private long connectionTimeout3;
#Value("${cxfclient.timeout-three.connection-request}")
private long connectionRequestTimeout3;
#Value("${cxfclient.timeout-three.receive}")
private long receiveTimeout3;
#Value("${cxfclient.timeout.connection}")
private long receiveTimeout;
#Value("${cxfclient.timeout.connection-request}")
private long connectionTimeout;
#Value("${cxfclient.timeout.receive}")
private long connectionRequestTimeout;
#Bean ("b1")
public B createAsB1() {
return new B (connectionTimeout3, connectionRequestTimeout3, receiveTimeout3);
}
#Bean ("b2")
public B createAsB2() {
return new B(connectionTimeout, connectionRequestTimeout, receiveTimeout);
}
use them by :
#Component
#Getter
#Setter
#ToString
public class UseB {
#Autowired
#Qualifier ("b1")
private B b1;
#Autowired
#Qualifier ("b2")
private B b2;
}
String d = ab.getB1().toString();
System.out.println(d);
d = ab.getB2().toString();
System.out.println(d);
result of run :
B(receiveTimeout=3000, connectionTimeout=3000,
connectionRequestTimeout=3000)
B(receiveTimeout=1000, connectionTimeout=1000,
connectionRequestTimeout=1000)
There are two options -
1- if you want both the beans available in your application context then you can create two separate beans with two different values.
2- If you want to have both values in your application but only one active bean at a time then you can use #ConditionalOnBean, attaching [here][1] a document for your help. Below is sample rough conditionalonbean code snippet and can enable only one of bean from your configurations file.
#ConditionalOnBean(
"prefix" = "..",
"havingValue" = "true",
"name" = "abc")
public A methodA() {}
#ConditionalOnBean(
"prefix" = "..",
"havingValue" = "false",
"name" = "abc")
public A methodB() {}
I am creating two constructor for my jersey resource, however only one is the one being able to call,
here is the sample code,
public class jerseyresoure {
private String name;
private int age;
#Inject
public jerseyresoure (String name){
this.name = name;
}
#Inject
public jerseyresoure (int age){
this.age= age;
}
}
the get cosntructor with the parameter int is the one being called successfully,
can you help me with this scenario?
According to the CDI specification it is illegal to annotate more than one constructor with #Inject (see section 3.9 of the CDI specification):
If a bean class does not explicitly declare a constructor using #Inject, the constructor that accepts no parameters is the bean
constructor.
If a bean class has more than one constructor annotated #Inject, the container automatically detects the problem and treats it
as a definition error.
A bean constructor may have any number of parameters. All parameters of a bean constructor are injection points.
So what you can do is as follows:
public class jerseyresoure {
private String name;
private int age;
#Inject
public jerseyresoure (String name, int age){
this.name = name;
this.age = age;
}
}
I assume you are using a producer method so that values (name and age) get injected.
Lombok misses field's annotation while auto generating constructor. Is there a way to retain field's annotation in constructor input params?
Class to generate constructor,
#RequiredArgsConstructor(onConstructor = #__(#Inject))
public class Test {
#Named("MyField")
private final String field;
#Named("MyHandler")
private final SomeHandler handler;
}
Generated class :
public class Test {
#Named("MyField")
private final String field;
#Named("MyField")
private final SomeHandler handler;
#Inject
public Test(final String field, final SomeHandler handler) {
this.field = field;
this.handler = handler;
}
}
Desired class :
public class Test {
#Named("MyField")
private final String field;
#Named("MyHandler")
private final SomeHandler handler;
#Inject
public Test(#Named("MyField")final String field,
#Named("MyHandler")final SomeHandler handler) {
this.field = field;
this.handler = handler;
}
}
In version v1.18.4 Lombok added support for copying specific annotations. Meaning, that if you put following setting to lombok.config:
lombok.copyableAnnotations += com.google.inject.name.Named
and apply following Lombok annotations to your class:
#RequiredArgsConstructor(onConstructor = #__(#Inject))
public class Hello {
#NonNull #Named("my-name") String name;
}
the #Named annotation should be copied to your generated constructor argument.
Limitations: this does not work when annotation can't be put on a field or annotation on a field overrides constructor initialization
There's no such feature and it looks like nobody cares. I proposed it once and started to implement it, but gave up (no demand and too much work).
It could look like
#RequiredArgsConstructor(onConstructor=#__(#Inject))
public class Something {
#OnConstructor(#Named("userName"))
private final String userName;
#OnConstructor(#Named("userPassword"))
private final String userPassword;
private final int anotherField;
private final int yetAnotherField;
}
or maybe just
#RequiredArgsConstructor(
onConstructor=#__(#Inject),
moveToConstructorArg=#__(#Named))
public class Something {
#Named("userName")
private final String userName;
#Named("userPassword")
private final String userPassword;
private final int anotherField;
private final int yetAnotherField;
}
or it could be controlled using lombok.config as you probably want all #Named annotations to be moved to the constructor.
I'm afraid, if you want it, then you have to do it yourself (my incomplete implementation might help you a bit).
FTR: There's a feature request now.
I want to store a property into the database as a Long, but use the object with helper methods in the code.
However the object type is a custom type I have that has an internal value (a long) that I want to store to the database.
public final class MyBean extends Number implements Serializable, Comparable<MyBean>
{
private long myValue;
public MyBean(Long value) { this.myValue = value; }
// Other helper methods and overrides
public MyBean valueOf(Long data)
{
return new MyBean(data);
}
#Override
public String toString()
{
return String.valueOf(myValue);
}
}
This is how I am using it:
#Entity
#Table(name = "mybeans")
public class MyBean implements Serializable
{
private static final long serialVersionUID = 1L;
MyBean myBean;
#Id
#Column(name = "mybean", nullable = false)
public MyBean getMyBean() { return myBean; }
public void setMyBean(MyBean value) { this.myBean = value; }
}
Deserializing this object calls toString and works fine (jax-rs/jersey). But when I try to pull it out of the database using my EJB, the error I get is:
The object [1,427,148,028,955], of class [class java.lang.Long], could
not be converted to [class com.MyBean]
Saving it produced the error
Can't infer the SQL type to use for an instance of com.MyBean. Use
setObject() with an explicit Types value to specify the type to use.
Which makes sense.
But what methods can I add in to male the EJB get the long as the value and use the long to set up a new object?
ANSWER:
Making the class #Embeddable and adding the following attributes worked.
#Embedded
#AttributeOverrides({
#AttributeOverride(name="value", column=#Column(name="mybean"))
})
(I didn't add EmbeddedId because I added a serial primary key id and just made this a column)
The one caveat is that it won't work with dynamic weaving. I had to add
<property name="eclipselink.weaving" value="static"/>
to my persistence.xml
You can try making MyBean an Embeddable to use that as an EmbeddedId, something like this:
#Embeddable
public final class MyBean extends Number implements Serializable, Comparable<MyBean> {
private Long myValue;
public MyBean(Long myValue) {
this.myValue = myValue;
}
// Other helper methods and overrides
public MyBean valueOf(Long data) {
return new MyBean(data);
}
#Override
public String toString() {
return String.valueOf(myValue);
}
}
In your entity, MyBean will be an EmbeddedId and will look like this:
#Entity
#Table(name = "mybeans")
public class MyEntity implements Serializable {
private static final long serialVersionUID = 1L;
private MyBean myBean;
#EmbeddedId
#AttributeOverride(name="myValue", #Column(name="mybean_id"))
public MyBean getMyBean() {
return myBean;
}
public void setMyBean(MyBean myBean) {
this.myBean = myBean;
}
}
Adjust MyBean as you need, such as making Transient some attributes.
First of all, i have this code:
#Service
public class ClientService {
public void addClient(#Valid Client client) { // Client class has some javax.validation.constraints on his attributes
Set<ConstraintViolation<Long>> violations = this.validator.validate(id);
ValidationHandler.handleViolations(violations);
...
}
public void deleteClient(#Digits(fraction = 0, integer = 5) Long id) throws ValidationException {
Set<ConstraintViolation<Long>> violations = this.validator.validate(id);
ValidationHandler.handleViolations(violations);
...
}
}
public class Client {
#Digits(fraction=0, integer=5, message="Invalid ID")
private Long id;
#Size(min = 2, max = 10, message = "Client's name length should be between {min} and {max}")
private String name;
private ClientType type;
private Gender gender;
// Getters and setters
}
So, my question is this. When i call deleteClient() and then the validator validates the object parameter (id), i am getting always non violations, notwithstanding i am passing invalid values (e.g 343435454). I was reading about this and this happens because i haven't implemented any Spring AOP validator interceptor for this methods, is this right? If yes, then i do not understand why when i call addClient() with invalid data the validator is validating correctly. Can anybody explain me why is this happening ?