I want to shadow a variable and change its type from boolean to string in its child class. Yet, the setter injection fails. I know it is a bad practice to use shadowing, however I would like to know if it is possible and how.
Example what I would like to do:
public class A {
public boolean field;
#SuppressWarnings("javadoc")
public boolean isField() {
return field;
}
#SuppressWarnings("javadoc")
public void setField(boolean field) {
this.field = field;
}
}
I want to shadow the attribute 'field' to be a String in the subclass.
public class B extends A {
private String field;
#SuppressWarnings("javadoc")
public String getField() {
return field;
}
#SuppressWarnings("javadoc")
public void setField(String field) {
this.field = field;
}
}
I get an exception that the setter Injection does not work:
org.springframework.beans.TypeMismatchException: Failed to convert property value of type [java.lang.String] to required type [boolean] for property 'field'
<bean id="exampleBean" class="B" >
<property name="field" value="value"/>
</bean>
Though I haven't tested it. But you can try the following way.
<bean id="beanA" class="A">
<property name="field" value="false"></property>
</bean>
<bean id="beanB" parent="beanA" class="B">
<property name="field" value="anyValue"></property>
</bean>
Let me know If it works for you.
Edit-
Please go through the Bean definition inheritance doc.
I have tested the example for shadowed field that you are using. You must have to configure a bean for parent class as well, because if you only configure bean for child class, you would get exception below -
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: No bean named 'helloWorld' is defined
I don't know what you are trying to accomplish using so-called shadowed fields. But here I have created a working example which might help you. Check it out.
MainApp.java
package com.example;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MainApp {
public static void main(String args[]){
ApplicationContext context = new ClassPathXmlApplicationContext("Beans.xml");
HelloWorld hw = (HelloWorld) context.getBean("helloWorld");
System.out.println("Is message:" + hw.isMsg());
HelloCountry hc = (HelloCountry) context.getBean("helloCountry");
System.out.println("Message:" + hc.getMessage());
}
}
class HelloWorld {
private boolean message;
public void setMessage(boolean message){
this.message = message;
}
public boolean isMsg(){
return message;
}
}
class HelloCountry extends HelloWorld {
private String message;
public void setMessage(String message){
this.message = message;
}
public String getMessage(){
return message;
}
}
Beans.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- A parent class bean must be defined to create its instance -->
<bean id = "helloWorld" class = "com.example.HelloWorld">
<property name = "message" value = "false"/>
</bean>
<bean id = "helloCountry" class = "com.example.HelloCountry">
<property name = "message" value = "Hello Country!!!"/>
</bean>
</beans>
Output
Note-
You would get a below exception only when you pass any value other than boolean in your parent bean. In your case, your data type of child class field is matching with the injected bean value, but you must have to have a configured bean for parent class as well. In short this is how It works Dependency Injection in case of Inheritance.-
Caused by: org.springframework.beans.TypeMismatchException: Failed to convert property value of type 'java.lang.String' to required type 'boolean' for property 'message';
Let me know If it was helpful.
Related
I have a class
public class DataStore {
public String name;
public String username;
public String password;
public String token;
public String connectionString;
public String type;
public String scheme;
public boolean usesBasicAuth;
public boolean usesBearerAuth;
}
I need to create an bean for it in another project. But i need to fill the fields somehow. The problem is I can not use <constructor-arg ... /> because there is no constructor.
The code below results in BeanCreationException: "Could not resolve matching constructor"
<bean id="dataStore"
class="com.fressnapf.sdk.dataaccess.services.DataStore">
<constructor-arg index="0" value="${spring.datastore.name}"/>
...
</bean>
Assuming, You have public (getters and setters for your) properties, and only the "default (no args) constructor", then You can change your configuration to:
<bean id="dataStore" class="com.fressnapf.sdk.dataaccess.services.DataStore">
<property name="connectionString" value="..."/>
<!-- ... -->
</bean>
Using property instead of constructor-arg.
Docs (Spring 4.2): https://docs.spring.io/spring/docs/4.2.x/spring-framework-reference/html/xsd-configuration.html
Yes, there is a constructor.
If you don't explicitly put one, Java will automatically add a default (non argument) constructor.
Use that one.
It will set all the instance variables to the default value of their type.
In your case: null for every String variable, false for every boolean.
I am using these classes:
Coach.java
public interface Coach {
void getDailyWorkout();
String getName();
String getExperience();
}
TrackCoach.java
public class TrackCoach implements Coach {
private String name;
private String experience;
public TrackCoach(String name, int num, String experience) {
this.name = name;
this.experience = experience;
}
public void getDailyWorkout() {
System.out.println("Run 5k on track");
}
public String getName() {
return name;
}
public String getExperience() {
return experience;
}
#Override
public String toString() {
return "TrackCoach{" +
"name='" + name + '\'' +
", experience='" + experience + '\'' +
'}';
}
}
appContext.xml
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="coach4" class="com.prakhar.TrackCoach">
<constructor-arg value="7 years"/>
<constructor-arg name="name" value="Mr. Track #4"/>
<constructor-arg name="num" value="10"/>
</bean>
</beans>
When I am trying to get the coach4 bean, I am getting an error of UnsatisfiedDependencyException type. I am not able to understand how Spring resolves constructor arguments, the actual steps / flow. Its like a magic to me, sometimes it works, sometimes it doesn't. For example, if we remove second constructor-arg's name property, it will be working fine.
Could anyone please tell me how Spring resolves constructor arguments internally? I am using Spring 5.2.3.
In your case order matters:
<bean id="coach4" class="com.prakhar.TrackCoach">
<constructor-arg value="7 years"/>
<constructor-arg name="name" value="Mr. Track #4"/>
<constructor-arg name="num" value="10"/>
</bean>
public TrackCoach(String name, int num, String experience) {
So, the first constructor-arg is interpreted as name, because it's the first argument of constructor in TrackerCoach class.
Then you have second constructor-arg, which have name attribute, which has the value of name, because of this conflict happens. You have 2 args which match to name constructor arg and 0 which match to experience.
When you remove name attribute in XML, 7 years goas as name Mr. Track #4 goas as experience and 10 goas as num.
Hope this helps.
The constructor-arg element within the bean element is used to set the property value thru constructor injection.
In your case, your Object (TrackCoach) only has two properties and three constructor arguments. The reason it works when you remove the second name property is because the defaults become two which match the object properties and it then gets matched the two propeties of the TrackCoach object. There is no property called num, therefore the spring container will blow up with an Exception.
In a nutshell, your object properties have to match your constructor arguments.
I use idref tag to reference another bean, but spring framework thrown a type convertion error, please see my spring.xml as below:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id = "triangle" class="org.dxc.javatechnology.Triangle">
<property name="type" value="Test Value Injection"></property>
<property name = "c">
<idref bean= "cycle"/>
</property>
<constructor-arg index="0" value="Test Name"></constructor-arg>
<constructor-arg index = "1" value="20"></constructor-arg>
</bean>
<bean id="cycle" class = "org.dxc.javatechnology.Cycle">
<property name = "radir" value = "5" />
</bean>
</beans>
Triangle.java:
package org.dxc.javatechnology;
public class Triangle {
private String type;
private Cycle c;
private String name;
private int height;
public int getHeight() {
return height;
}
public Triangle(){}
public Triangle(String name)
{
this.name = name;
}
public Triangle(String name,int height)
{
this.name = name;
this.height = height;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Cycle getC() {
return c;
}
public void setC(Cycle c) {
this.c = c;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public void draw(){
System.out.println(getType() + " Triangle drawn");
}
}
Cycle.java
package org.dxc.javatechnology;
public class Cycle {
private int radir;
public int getRadir() {
return radir;
}
public void setRadir(int radir) {
this.radir = radir;
}
}
Main method:
public static void main(String[] args) {
#SuppressWarnings("resource")
ApplicationContext context = new ClassPathXmlApplicationContext("ConfigurationFiles/spring.xml");
Triangle triangle = (Triangle) context.getBean("triangle");
triangle.draw();
System.out.println(triangle.getC().getRadir());
System.out.println(triangle.getName());
System.out.println(triangle.getHeight());
}
the error is as below:
Apr 08, 2017 11:16:03 AM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext#497470ed: startup date [Sat Apr 08 11:16:03 CST 2017]; root of context hierarchy
Apr 08, 2017 11:16:03 AM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [ConfigurationFiles/spring.xml]
Apr 08, 2017 11:16:03 AM org.springframework.context.support.ClassPathXmlApplicationContext refresh
WARNING: Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'triangle' defined in class path resource [ConfigurationFiles/spring.xml]: Initialization of bean failed; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'java.lang.String' to required type 'org.dxc.javatechnology.Cycle' for property 'c'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'org.dxc.javatechnology.Cycle' for property 'c': no matching editors or conversion strategy found
Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'triangle' defined in class path resource [ConfigurationFiles/spring.xml]: Initialization of bean failed; nested exception is org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'java.lang.String' to required type 'org.dxc.javatechnology.Cycle' for property 'c'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'org.dxc.javatechnology.Cycle' for property 'c': no matching editors or conversion strategy found
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:564)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:139)
at org.springframework.context.support.ClassPathXmlApplicationContext.<init>(ClassPathXmlApplicationContext.java:83)
at org.dxc.javatechnology.DrwaingApp.test3(DrwaingApp.java:32)
at org.dxc.javatechnology.DrwaingApp.main(DrwaingApp.java:10)
Caused by: org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'java.lang.String' to required type 'org.dxc.javatechnology.Cycle' for property 'c'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'org.dxc.javatechnology.Cycle' for property 'c': no matching editors or conversion strategy found
at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:605)
at org.springframework.beans.AbstractNestablePropertyAccessor.convertForProperty(AbstractNestablePropertyAccessor.java:617)
at org.springframework.beans.BeanWrapperImpl.convertForProperty(BeanWrapperImpl.java:216)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.convertForProperty(AbstractAutowireCapableBeanFactory.java:1577)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.applyPropertyValues(AbstractAutowireCapableBeanFactory.java:1536)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1276)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
... 12 more
Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'org.dxc.javatechnology.Cycle' for property 'c': no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:306)
at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:590)
... 18 more
I don't know what's the problem, can anyone help me?
It's not idref it's just ref:
<property name = "c" ref = "cycle"/>
"idref" is completely different from "ref" . It is easy to get confused . I thought this is best explained with an example rather than lines and lines of text .
Consider any two classes with ONLY String as properties { NOT REFERENCES}
package com.imran.beanwrapper;
public class IdrefOne {
String first;
public String getFirst() {
return first;
}
public void setFirst(String first) {
this.first = first;
}
}
package com.imran.beanwrapper;
public class IdrefTwo {
String second;
public String getSecond() {
return second;
}
public void setSecond(String second) {
this.second = second;
}
}
xml:
<bean id="one"
class="com.imran.beanwrapper.IdrefOne">
<property name="first" value="hai first"/>
</bean>
<bean id="two"
class="com.imran.beanwrapper.IdrefTwo">
<property name="second">
<idref bean="one"/>
</property>
</bean>
</beans>
Main class :
ApplicationContext context=new
ClassPathXmlApplicationContext
("wrapperTest.xml");
IdrefOne
one=context.getBean("one",IdrefOne.class);
IdrefTwo
two=context.getBean("two",IdrefTwo.class);
System.out.println(one.getFirst());
System.out.println(two.getSecond());
ouput:
hai first
one
Did u notice the output of System.out.println(two.getSecond()) ?
one
which is equal to "id value" of the first bean namely com.imran.beanwrapper.IdrefOne ( Not equal to bean itself or string value of dependency of bean. Avoid confusion)
Note that the both classes (IdrefOne , IdrefTwo )must have string as dependencies not Object subclasses as dependencies i.e, reference classes
If in the above code if our pojo classes have Object subclasses as dependencies the above code will end up with an exception
Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.imran.beanwrapper.IdrefOne' for property '': no matching editors or conversion strategy found
So, we can conclude for the above code setup with strings as dependencies for the pojo classes IdrefOne , IdrefTwo. The code
<property name="second">
<idref bean="one"/>
</property>
produces same results as the following code
<property name="second" value="one">
</property>
With first XML snippet using 'idref' , if a bean named "one" is not found in the container an exception is thrown. With second xml snippet , u can give Any String value as u want to 'value' attribute .
So the question is why not use the regular "value" which is generally used and avoid all the this confusion ?
Answer is in the spring documentation:
https://docs.spring.io/spring-framework/docs/3.2.x/spring-framework-reference/html/beans.html#beans-idref-element
The first form is preferable to the second, because using the 'idref' tag allows the container to validate at deployment time that the referenced, named bean actually exists. In the second variation, no validation is performed on the value that is passed to the targetName property of the client bean. Typos are only discovered (with most likely fatal results) when the client bean is actually instantiated. If the client bean is a prototype bean, this typo and the resulting exception may only be discovered long after the container is deployed.
A common place (at least in versions earlier than Spring 2.0) where the 'idref' element brings value is in the configuration of AOP interceptors in a ProxyFactoryBean bean definition. Using 'idref' elements when you specify the interceptor names prevents you from misspelling an interceptor id.
I have two classes Test and SampleClassWithMultipleProperty for which I have bean definitions as follows:
<bean name="test" class="Test" autowire="constructor">
</bean>
<bean id="instance1" class="SampleClassWithMultipleProperty">
<constructor-arg type="java.lang.String" value="constructor-arg-1"/>
<constructor-arg type="java.lang.String" value="constructor-arg-2"/>
<constructor-arg type="java.lang.String" value="constructor-arg-3"/>
<constructor-arg type="int" value="4"/>
<constructor-arg type="java.lang.Integer" value="5"/>
</bean>
When I specify any name id like for example "instance1" ,"instance2","instance3" it works.
But when I have another bean of same type SampleClassWithMultipleProperty as follows :
<bean id="instance2" class="SampleClassWithMultipleProperty">
<constructor-arg type="java.lang.String" value="constructor-arg-1-ind"/>
<constructor-arg type="java.lang.String" value="constructor-arg-2-ind"/>
<constructor-arg type="java.lang.String" value="constructor-arg-3-ind"/>
<constructor-arg type="int" value="4"/>
<constructor-arg type="java.lang.Integer" value="5"/>
</bean>
The container is not able to initialize the bean instance of SampleClassWithMultipleProperty (with name sampleClassWithMultipleProperty) inside the Test class (Has a relation).
It successfully does that when I change any of the ids of the bean from "instance1" to "sampleClassWithMultipleProperty" OR "instance2" to "sampleClassWithMultipleProperty" .
But why is it behaving like this. I read somewhere that autowiring by constructor is similar to autowiring byType. So ideally it should match the type of the bean i.e. class name.
Please find my classes below:
public class Test {
SampleClassWithMultipleProperty sampleClassWithMultipleProperty;
public Test() {
super();
// TODO Auto-generated constructor stub
}
public Test(SampleClassWithMultipleProperty sampleClassWithMultipleProperty) {
super();
System.out.println("in Test constructor");
this.sampleClassWithMultipleProperty = sampleClassWithMultipleProperty;
}
public SampleClassWithMultipleProperty getSampleClassWithMultipleProperty() {
return sampleClassWithMultipleProperty;
}
public void setSampleClassWithMultipleProperty(
SampleClassWithMultipleProperty sampleClassWithMultipleProperty) {
this.sampleClassWithMultipleProperty = sampleClassWithMultipleProperty;
}
}
public class SampleClassWithMultipleProperty {
private String property1;
private String property2;
private String property3;
private int property4;
private Integer property5;
public SampleClassWithMultipleProperty() {
super();
// TODO Auto-generated constructor stub
}
public SampleClassWithMultipleProperty(String property1, String property2, String property3,
int property4, Integer property5) {
super();
this.property1 = property1;
this.property2 = property2;
this.property3 = property3;
this.property4 = property4;
this.property5 = property5;
}
public String getProperty1() {
return property1;
}
public void setProperty1(String property1) {
this.property1 = property1;
}
public String getProperty2() {
return property2;
}
public void setProperty2(String property2) {
this.property2 = property2;
}
public String getProperty3() {
return property3;
}
public void setProperty3(String property3) {
this.property3 = property3;
}
public int getProperty4() {
return property4;
}
public void setProperty4(int property4) {
this.property4 = property4;
}
public Integer getProperty5() {
return property5;
}
public void setProperty5(Integer property5) {
this.property5 = property5;
}
}
Hi I am not a spring expert but I know some bits and did some searching around and I would like to answer on basis of that.
I assume you have default-autowire by id. You have two bean definitions of class SampleClassWithMultipleProperty. This both are eligible for creation and to be used as constructor arg for Test.
By default, autowiring scan and matches all bean definitions in scope. If you want to exclude some bean definitions so that they can not be injected through autowiring mode, you can do this using autowire-candidate set to false.
So what you can do is:
<bean id="instance2" class="SampleClassWithMultipleProperty" autowire-candidate="false">
Also you have option to use default-autowire-candidates property which should help you out here. You can just pass a pattern which will be used to scan the probable candidates for bean creation.
Reference Link 1
Reference Link 2
Reference Link 3
Your Test bean has 2 constructors : a no arg constructor and a constructor that needs an instance of a SampleClassWithMultipleProperty.
When you only have one SampleClassWithMultipleProperty instance in your classpath spring will just choose the constructor that takes a SampleClassWithMultipleProperty.
When you have two SampleClassWithMultipleProperty spring won't be able to choose between them so the no arg constructor is going to be chosen.
When you are changing the id of one of your SampleClassWithMultipleProperty bean that match the name of the parameter of the constructor spring will be able to choose between them and will be able to create your bean with the constructor that expect an instance of SampleClassWithMultipleProperty.
If in the second case (2 SampleClassWithMultipleProperty beans and none matching the parameter's name) you would have had a NoUniqueBeanDefinitionException because spring would not have been able to use a proper constructor to build your bean.
Spring will automatically choose the constructor satisfying the most parameters possible.
Code snippet is like this:
#Controller
#RequestMapping(value="/test")
public class TestController {
........
#RequestMapping(method=RequestMethod.GET)
public String getCreateForm(Model model) {
model.addAttribute(new AccountBean());
return "newtest";
}
.........
"newtest" is the hard-coded view name. Is it possible to have it configured in an XML-style Spring config file? Thank you!
I guess the real question is how to configure properties of autodiscovered bean via XML.
You can do it by defining a <bean> with the same name as the autodiscovered one have (when the name of autodiscovered bean is not specified, it's assumed to be a classname with the first letter decapitalized):
#Controller
#RequestMapping(value="/test")
public class TestController {
private String viewName = "newtest";
public void setViewName(String viewName) {
this.viewName = viewName;
}
#RequestMapping(method=RequestMethod.GET)
public String getCreateForm(Model model) {
model.addAttribute(new AccountBean());
return viewName;
}
}
.
<bean id = "testController" class = "TestController">
<property name = "viewName" value = "oldtest" />
</bean>
Another option is to use #Value with SpEL expressions
#Value("#{testViewName}") private String viewName;
.
<bean id = "testViewName" class = "java.lang.String">
<constructor-arg value = "oldtest" />
</bean>
or property placeholders
#Value("${testViewName}") private String viewName;
.
<context:property-placeholder location = "viewnames" />
viewnames.properties:
testViewName=oldtest
Well, it is possible to return any string there. So yes - it can be configured.
Update: there are many ways to configure it, one of which (and my preference) being a combination of PropertyPlaceholderConfigurer and the #Value annotation, but that was already covered by axtavt.