Skip to content
JackSparrow414
Go back

How Does Spring Perform Property Injection?

Table of contents

Open Table of contents

Article body

The previous article established when Spring registers, creates, and instantiates beans. If Class A has a property of type Class B, how does Spring inject B into A?

public class UserServiceImpl {
    private SchoolServiceImpl schoolService;

    public String getSchoolServiceMethod(){
        return schoolService.getTimeStr();
    }

    public void setSchoolService(SchoolServiceImpl schoolService) {
        this.schoolService = schoolService;
    }

}

First Approach:

Here, a setter injects SchoolServiceImpl into UserServiceImpl. Configure userService.xml as follows:

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--Setter injection-->
   <bean id="userService" class="com.learn.spring.spring01.service.UserServiceImpl">
     <property name="schoolService" ref="schoolService"/>
   </bean>
    <bean id="schoolService" class="com.learn.spring.spring01.service.SchoolServiceImpl">
    </bean>
</beans>

As discussed previously, Spring parses the XML at startup and converts its bean elements into BeanDefinition objects. Debugging shows that this registration step is the same as before.

Next comes bean creation. Steps two and three in the previous article involved a list of bean names, beanDefinitionNames. Spring iterates over this list and calls getBean(beanName) for each element to create the bean, populate its properties, and instantiate it.

Debugging shows:

After userServiceImpl is created, execution enters populateBean, which populates its properties. The property in userServiceImpl is schoolServiceImpl.

The call chain is populateBean -> applyPropertyValues -> valueResolver.resolveValueIfNecessary (a key method that chooses a strategy based on the argument type) -> resolveReference -> this.beanFactory.getBean(resolvedName). It ultimately calls getBean(beanName) again. The rest matches the previous article: if schoolServiceImpl exists, return it; otherwise, create it, populate its properties, instantiate it, and return to resolveValueIfNecessary to set the property value. Instantiation then continues, and the userServiceImpl bean is ready for use.

The steps amount to:

Create A -> finish creation -> populate A’s property B -> find no corresponding B instance in the cache -> create B, populate its properties, and instantiate B -> assign the instantiated B to A’s property B -> instantiate A -> done.

A Question:

Could schoolServiceImpl already be ready when userServiceImpl needs it injected?

To answer this, we need a few key points:

  1. During getBean(beanName), if getSingleton(beanName) can retrieve schoolServiceImpl, it will not be created again. Entries are put into the map used by getSingleton during getBean execution.

  2. In other words, getBean(beanName) must be called for schoolServiceImpl before userServiceImpl. The element order in beanDefinitionNames determines the call order.

  3. When are elements added to beanDefinitionNames? During XML parsing, which iterates from top to bottom. Therefore, placing the schoolServiceImpl bean element before userServiceImp is enough.

Second Approach: Constructor Injection

public class UserServiceImpl {
    private SchoolServiceImpl schoolService;

    public UserServiceImpl(SchoolServiceImpl schoolService) {
       this.schoolService = schoolService;
    }

    public String getSchoolServiceMethod(){
        return schoolService.getTimeStr();
    }
}
<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--Instantiate schoolService before userService, so userService can retrieve it directly from the cache when populating properties-->
    <bean id="schoolService" class="com.learn.spring.spring01.service.SchoolServiceImpl">
    </bean>

   <bean id="userService" class="com.learn.spring.spring01.service.UserServiceImpl">
       <!--Constructor injection-->
       <constructor-arg ref="schoolService"/>
   </bean>

</beans>

The constructor workflow differs slightly from setter injection. When getBean(beanName) eventually reaches createBeanInstance, it calls autowireConstructor -> resolveConstructorArguments -> resolveValueIfNecessary, returning to the same resolution method. During instance creation, the constructor executes this.SchoolServiceImpl = schoolServiceImpl, effectively populating the property early. When populateBean is later called, there is essentially no code left to execute; the method does nothing in this case. Unlike setter injection, which ultimately invokes a setter, it does not invoke such a method.

The steps amount to:

Create A -> find A’s constructor and use it to create A -> the constructor argument is B, which the code assigns to A’s property B -> finish creating A -> populate properties -> instantiate -> done.

For more detailed usage and principles, see the dependency injection section of Spring’s official documentation.


Share this post:

Previous Post
When Does Spring Instantiate Beans?
Next Post
Understanding Spring IoC

Comments

Questions, corrections, and experiences are welcome. Sign in with GitHub to comment; both language versions share this discussion.

Comments are available on the live site only.