Spring bean constructor-arg property resolution internally - java

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.

Related

How to define a bean in ApplicationContext.xml that has no constructor?

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.

Spring Setter Injection on shadowed fields

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.

Spring autowire by constructor

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.

Spring 3 MVC: #ModelAttribute method + #Autowired not working

I am new to Spring and trying to learn #Autowired magic in SPring MVC. I was trying out a demo application using the #ModelAttribute method and #Autowired. Every time I am getting null which means #Autowired is not happening properly. Below is what I tried:
Controller
#Controller
public class ModelAttributeAutoWiredController {
#Autowired
private Employee empl;
public void setEmpl(Employee empl) {
this.empl = empl;
}
#RequestMapping(value="/home")
public ModelAndView returnhome(){
ModelAndView modelView = new ModelAndView("home");
System.out.println("Employee First Name: " + empl.getFirstName()); // NULL
return modelView;
}
#RequestMapping(value="/index")
public ModelAndView returnindex(){
ModelAndView modelView = new ModelAndView("index");
System.out.println("Employee Last Name: " + empl.getLastName()); // NULL
return modelView;
}
#ModelAttribute("empl")
public Employee populateEmployee(){
Employee empl = new Employee();
empl.setFirstName("XXX");
empl.setLastName("YYY");
return empl;
}
}
Employee
#Component
public class Employee {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
}
context 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"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.0.xsd">
<context:annotation-config />
<context:component-scan
base-package="com.pack" />
<bean id="viewResolver"
class="org.springframework.web.servlet.view.UrlBasedViewResolver">
<property name="viewClass"
value="org.springframework.web.servlet.view.JstlView" />
<property name="prefix" value="/WEB-INF/pages/" />
<property name="suffix" value=".jsp" />
</bean>
</beans>
Can someone please help me out in the above code and make me understand why #Autowired is not working?
You're confusing several Spring concepts here.
First, #Component is used for program components, generally objects that provide some service that other pieces of the program need. It is not intended for data objects such as your Employee class, and data objects that are runtime data (and not configuration objects) shouldn't be autowired, they should be passed in to the specific method calls that operate on them.
#ModelAttribute tells Spring that it should add whatever is being annotated to the MVC Model object so that it's available to the controller and view. This has nothing to do at all with #Autowired.
Here's what's happening in your code:
Your Employee class is annotated #Component, so Spring creates a singleton bean and registers it in the context. This bean never has its fields set, so they're null, but the bean itself exists, so it's wired into your controller's empl field. This is why you don't get a NullPointerException, which you would if the autowiring really weren't working.
Your #ModelAttribute is evaluated by Spring and added to the Model for each request. However, you never pass this model to any of your controllers, so they never see it.
Your controller methods create new, empty ModelAndView objects with nothing in them.
They then read the completely different, empty Employee object that was injected into empl and print out the null value on the fields (but don't throw NullPointerExceptions because the autowiring succeeded).
Please check this: http://www.mkyong.com/spring-mvc/spring-3-mvc-and-xml-example/
You'll need the <mvc:annotation-driven/> and also the <context:component-scan base-package="com.your.package" /> in your context.xml file.
Looks like this isn't possible and if you think about it, it doesn't make much sense to have a #ModelAttribute as an instance variable. Please read here to get a better understanding.
#Autowired is not intended for the domain objects like Employee.
In your case Spring creates an Employee object using the default constructor, so its lastname is null. Spring creates the Employee object because of #Component annotation on the Employee class, it has nothing to do with the #ModelAttribute.
The method annotated with #ModelAttributes instructs Spring to create another Employee object and inject it to the Model object that you don't have to create by yourself.
Just return "index" String from your "returnhome" method.
For debugging, you may add #ModelAttribute Employee e as an argument to your "returnhome" method.

Is it possible to have destination view name configurable in spring mvc 3?

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.

Categories

Resources