Content of this article
- Dependency injection based on constructor
- Dependency injection based on setter
Dependency injection based on constructor
case
Define two simple bean classes, BeanOne and BeanTwo. The former depends on the latter.
package com.crab.spring.ioc.demo02; public class BeanTwo { }
package com.crab.spring.ioc.demo02; /** * @author zfd * @version v1.0 * @date 2022/1/12 16:59 */ public class BeanOne { private int age; private String name; private BeanTwo beanTwo; /** * Constructor for dependency injection, defining 3 dependencies * @param age * @param name * @param beanTwo */ public BeanOne(int age, String name, BeanTwo beanTwo) { this.age = age; this.name = name; this.beanTwo = beanTwo; } @Override public String toString() { return "BeanOne{" + "age=" + age + ", name='" + name + '\'' + ", beanTwo=" + beanTwo + '}'; } }
Implement bean definition and dependency injection through xml configuration file
<?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="bean2" class="com.crab.spring.ioc.demo02.BeanTwo"/> <bean id="bean1" class="com.crab.spring.ioc.demo02.BeanOne"> <constructor-arg name="age" index="0" type="int" value="20"/> <constructor-arg name="name" index="1" type="java.lang.String" value="xxx"/> <constructor-arg name="beanTwo" index="2" type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/> </bean> </beans>
A test class to verify the injection
package com.crab.spring.ioc.demo02; /** * @author zfd * @version v1.0 * @date 2022/1/12 17:09 */ public class demo02Test { @Test public void test_construct() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo02/spring1.xml"); BeanOne bean1 = context.getBean("bean1", BeanOne.class); System.out.println(bean1); context.close(); }
The output is as follows
BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@5204062d}
Compared with the configuration file, the three dependencies of BeanOne are injected through the constructor, which meets the expectation and is very simple.
Detailed explanation of constructor ARG
The list of elements supported by the tag constructor arg is as follows.
Yuansu name | effect |
---|---|
name | Parameter name |
index | Parameter index, starting at 0 |
type | Parameter type |
value | value |
ref | bean reference |
For example, the configuration in the case
<bean id="bean1" class="com.crab.spring.ioc.demo02.BeanOne"> <constructor-arg name="age" index="0" type="int" value="20"/> <constructor-arg name="name" index="1" type="java.lang.String" value="xxx"/> <constructor-arg name="beanTwo" index="2" type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/> </bean>
Note: some of the above elements do not have to be configured without ambiguity. For example, when index is specified, the parameter location can be located, and the name can not be configured. For example, if the dependent bean is referenced through ref, the type can be omitted
<bean id="bean1" class="com.crab.spring.ioc.demo02.BeanOne"> <constructor-arg index="0" type="int" value="20"/> <constructor-arg index="1" type="java.lang.String" value="xxx"/> <constructor-arg index="2" ref="bean2"/> </bean>
matters needing attention
When the name element is used in < constructor / > to specify the parameter name in the constructor, especially whether the method parameter name is retained after compilation. for instance
// Method parameters before Compilation public BeanOne(int age, String name, BeanTwo beanTwo) // Compiled method parameters public BeanOne(int var1, String var2, BeanTwo var3)
After compilation, the method parameters are compiled into var1, which will invalidate the construction injection of only specifying name.
How to solve it? Two methods are provided
Add compilation parameters to Maven compilation plug-in, keep the parameter name, and add the following plug-in configuration under pom file
<build> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <encoding>UTF8</encoding> <compilerArgs> <arg>-parameters</arg> </compilerArgs> </configuration> </plugin> </plugins> </build>
Use the @ ConstructorProperties JDK annotation to explicitly name constructor parameters
/** * Constructor for dependency injection, defining 3 dependencies * @param age * @param name * @param beanTwo */ @ConstructorProperties({"age", "name", "beanTwo"}) // Explicitly declare construction parameter names public BeanOne(int age, String name, BeanTwo beanTwo) { this.age = age; this.name = name; this.beanTwo = beanTwo; }
Dependency injection based on setter
DI based on Setter is accomplished by calling the setter with the parameterless constructor or the parameterless static factory method to instantiate bean and then invoke the setter method on bean.
A simple class, BeanThree, relies on BeanOne and BeanTwo and provides a Setter method to set.
package com.crab.spring.ioc.demo02; /** * @author zfd * @version v1.0 * @date 2022/1/13 8:18 */ public class BeanThree { private BeanTwo beanTwo; private BeanOne beanOne; public void setBeanTwo(BeanTwo beanTwo) { this.beanTwo = beanTwo; } public void setBeanOne(BeanOne beanOne) { this.beanOne = beanOne; } }
The corresponding configuration file can set the property reference or property value through ref or name in the tag property
<?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="bean2" class="com.crab.spring.ioc.demo02.BeanTwo"/> <!--Constructor Inject --> <bean id="bean1" class="com.crab.spring.ioc.demo02.BeanOne"> <constructor-arg name="age" index="0" type="int" value="20"/> <constructor-arg name="name" index="1" type="java.lang.String" value="xxx"/> <constructor-arg name="beanTwo" index="2" type="com.crab.spring.ioc.demo02.BeanTwo" ref="bean2"/> </bean> <!--setter injection--> <bean id="bean3" class="com.crab.spring.ioc.demo02.BeanThree"> <!-- 1 ref element--> <property name="beanOne" ref="bean1"></property> <!-- 2 ref label--> <property name="beanTwo"> <ref bean="bean2"></ref> </property> <property name="name" value="xxxx"/> </bean> </beans>
Run the test class and results. It can be seen that dependency injection is successful
public class demo02Test { @Test public void test_construct() { ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("demo02/spring1.xml"); BeanOne bean1 = context.getBean("bean1", BeanOne.class); System.out.println(bean1); System.out.println("demonstration Setter injection"); BeanThree beanThree = context.getBean(BeanThree.class); System.out.println(beanThree); context.close(); } }
BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24} demonstration Setter injection BeanThree{name='xxxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24, beanOne=BeanOne{age=20, name='xxx', beanTwo=com.crab.spring.ioc.demo02.BeanTwo@68ceda24}}
Dependency resolution process
- ApplicationContext is created and initialized with configuration metadata that describes all bean s. Configuration metadata can be specified through XML, Java code, or annotations.
- For each bean, its dependencies are expressed in the form of properties, constructor parameters, or parameters of static factory methods (if static factory methods are used instead of ordinary constructors). When the bean is actually created, these dependencies are provided to the bean.
- Each property or constructor parameter is the actual definition of the value to be set, or a reference to another bean in the container.
- Each property or constructor parameter with a value is converted from its specified format to the actual type of the property or constructor parameter. By default, Spring can convert the values provided in string format to all built-in types, such as int, long, string, boolean, etc.
What is circular dependency and how to deal with it?
If constructor injection is mainly used, an unresolved cyclic dependency scenario may be created.
For example, class a requires an instance of class B to be injected through the constructor, and class B requires an instance of class A to be injected through the constructor. If the beans of class A and class B are configured to inject into each other, the Spring IoC container will detect this circular reference at runtime and throw a beancurrentyincreationexception.
One possible solution is to edit the source code of some classes configured by setters rather than constructors. Alternatively, avoid constructor injection and use only setter injection. In other words, cyclic dependencies can be configured using setter injection.
Unlike the typical case (no circular dependency), the circular dependency between bean A and bean B forces one bean to inject another bean before full initialization (a typical chicken or egg first scenario).
one man's meat is another man's poison.
Choose constructor injection or Setter injection?
According to the official recommendation of Spring, constructors are used to enforce dependencies, and setter methods or configuration methods are used for optional dependencies. Note that using the @ Required annotation on the setter method can be used to make a property a Required dependency; However, constructor injection for programmatic validation with parameters is preferable.
The Spring team usually advocates constructor injection because it allows you to implement application components as immutable objects and ensure that the required dependencies are not empty. In addition, constructor injected components are always returned to the client (calling) code in a fully initialized state. As a side note, a large number of constructor parameters are a bad code smell, which means that this class may have too many responsibilities and should be refactored to better solve the appropriate separation of concerns. Setter injection should be mainly used for optional dependencies that can be assigned reasonable default values in the class. Otherwise, you must perform a non null check anywhere your code uses dependencies. One advantage of setter injection is that the setter method allows the objects of this class to be reconfigured or re injected later.
Sometimes, when dealing with third-party classes without source code, constructor injection may be the only available form of DI if the third-party class does not expose any setter methods.
summary
This article demonstrates two methods of dependency injection: constructor injection and Setter method injection, and compares how to choose these two methods. The next article continues to delve into dependency injection.
Source code address: https://github.com/kongxubihai/pdf-spring-series/tree/main/spring-series-ioc/src/main/java/com/crab/spring/ioc/demo02
Knowledge sharing, please indicate the source for reprint. There is no order in learning, the one who reaches is the first!