1.4. dependencies
Typical enterprise applications don’t let you include a single object or a single bean. Even with the simplest application, there are objects that work together to render the application as consistent as the end user sees it. The next section explains how to go from defining multiple independent Bean definitions to an application that implements object collaboration to achieve a full realization of the goal.
1.4.1. Dependency injection
Dependency injection (DI) is a process in which objects define their dependencies (that is, other objects they work with) through constructor parameters, factory method parameters, or properties set on the object instance after construction or return from the factory method. The container then injects these dependencies when the bean is created. This process is basically the reverse of the bean itself (hence the name, inversion of control), where the bean itself controls the instantiation or location of its dependencies through the use of a direct construct of the class or the service locator pattern.
Using the DI principle, the code is cleaner, and decoupling is more effective when objects are provided with their dependencies. The object does not look up its dependencies and does not know the location or class of the dependencies. As a result, your classes become easier to test, especially when the dependencies are on interfaces or abstract base classes, which allow stub or mock implementations to be used in unit tests.
There are two main variants of dependency injection: constructor-based and setter-based.Copy the code
Constructor-based dependency injection
Constructor-based DI is accomplished by the container calling the constructor with multiple arguments, each representing a dependency. Calling static factory methods with specific parameters to construct beans is nearly equivalent, and this discussion treats constructors and static factory method parameters as similar. The following example shows classes that can be injected only through constructor injection:
Public class SimpleMovieLister {// SimpleMovieLister dependency MovieFinder private MovieFinder MovieFinder; Constructor so the Spring container can inject MovieFinder public SimpleMovieLister(MovieFinder MovieFinder) {this.movieFinder = MovieFinder; }}Copy the code
Note that there is nothing special about this class. It is a POJO and does not depend on container-specific interfaces, base classes, or annotations.
Constructor parameter parsing
Constructor parameter resolution matching is done by using the type of the parameter. If there is no potential ambiguity in the constructor parameters of the Bean definition, when the Bean is instantiated, the constructor parameters are defined in the order in which they are provided to the appropriate constructor. Consider the following categories:
package x.y; public class ThingOne { public ThingOne(ThingTwo thingTwo, ThingThree thingThree) { // ... }}Copy the code
Assuming that the ThingTwo and ThingThree classes are not related by inheritance, there is no potential ambiguity. Therefore, the following configuration works, and you do not need to explicitly specify the constructor parameter index or type in the element.
<beans>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg ref="beanTwo"/>
<constructor-arg ref="beanThree"/>
</bean>
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
</beans>
Copy the code
When another bean is referenced, the type is known and a match can occur (as in the previous example). True when using simple types (such as), Spring cannot determine the type of the value, and therefore cannot match by type without help. Consider the following categories:
package examples; public class ExampleBean { private int years; private String ultimateAnswer; public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; }}Copy the code
Constructor parameter types match
In this case, the container can use type matching of simple types if the type of the constructor parameter is explicitly specified by using the Type attribute. As shown in the following example:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg type="int" value="7500000"/>
<constructor-arg type="java.lang.String" value="42"/>
</bean>
Copy the code
Constructor parameter index
You can use the index attribute to explicitly specify the index of the constructor argument, as shown in the following example:
<bean id="exampleBean" class="examples.ExampleBean">
<constructor-arg index="0" value="7500000"/>
<constructor-arg index="1" value="42"/>
</bean>
Copy the code
In addition to resolving the ambiguity of multiple simple values, specifying an index resolves ambiguity where the constructor has two parameters of the same type.
The index starts at 0.Copy the code
Constructor parameter name
You can also use constructor parameter names to disambiguate, as shown in the following example:
<bean id="exampleBean" class="examples.ExampleBean"> <constructor-arg name="years" value="7500000"/> <constructor-arg Name ="ultimateAnswer" value="42"/> </bean> Remember that to use this functionality immediately, you must compile the code with the debug flag enabled so that Spring can look up parameter names from the constructor. If you can't or don't want to compile code with the Debug flag, you can explicitly name the constructor parameters using the @ConstructorProperties JDK annotation. Then, the sample class must look like this: Package Examples; public class ExampleBean { @ConstructorProperties({"years", "ultimateAnswer"}) public ExampleBean(int years, String ultimateAnswer) { this.years = years; this.ultimateAnswer = ultimateAnswer; }}Copy the code
Setter-based dependency injection
Setter-based DI is accomplished by calling setter methods on the bean after invoking the parameterless constructor or parameterless static factory method to instantiate the bean.
The following example shows classes that can only be relied on by using pure setter injection. This class is regular Java. It is a POJO and does not depend on container-specific interfaces, base classes, or annotations.
public class SimpleMovieLister { private MovieFinder movieFinder; public void setMovieFinder(MovieFinder movieFinder) { this.movieFinder = movieFinder; }} Instead of writing the above class and registering the bean into the Spring container, you can use setters to inject the following three registered bean methods: autowire = byType, So autowire=byName is going to inject a setter into autowire which is no by default, Detailed rules of injection back < bean id = "simpleMovieLister" class = "org. Springframework. SimpleMovieLister" autowire = "no" / > < bean id="simpleMovieLister" class="org.springframework.SimpleMovieLister" autowire="byType"/> <bean id="simpleMovieLister" class="org.springframework.SimpleMovieLister" autowire="byName"/>Copy the code
ApplicationContext supports constructor-based and setter-based DI for the beans it manages. It also supports setter-based DI, after having injected some dependencies through the constructor method. You configure a dependency in the form of a BeanDefinition, which you use with the PropertyEditor instance to convert properties from one format to another. However, most Spring users do not use these classes directly (that is, programmatically), but instead use XML Bean definitions, annotated components (i.e., annotated classes like @Component, @Controller, etc.), or the @Bean methods in the Java-based @Configuration class. These sources are then internally transformed into instances of BeanDefinition and used to load the entire Spring IoC container instance.
###### constructor-based or setter-based DI? Because you can mix constructor-based DI with setter-based DI, it is a good rule of thumb to use constructors for mandatory dependencies and setter methods or configuration methods for optional dependencies. Note that you can use the @required annotation on setter methods to make this property a Required dependency. However, it is best to use constructor injection with program validation with parameters. The Spring team generally advocates constructor injection because it lets you implement application components as immutable objects and ensures that there are no required null dependencies. In addition, the component that injected the constructor always returns to the client (calling) code in a fully initialized state. As a side note, the large number of constructor arguments is a bad code smell, indicating that the class may be taking on too much responsibility and should be refactored to better address separation of concerns. Setter injection should primarily be used only for optional dependencies that can be assigned reasonable defaults in a class. Otherwise, non-null checks must be performed wherever the code uses dependencies. One benefit of setter injection is that setter methods enable objects of that class to be reconfigured or reinjected later. Therefore, management through JMX MBeans is a compelling use case for setter injection. Use the DI style best suited to the particular class. Sometimes, choices are made for you when dealing with third-party classes for which you do not have source code. For example, if a third-party class does not expose any setter methods, constructor injection may be the only form of DI available.Copy the code
Dependency resolution procedure
The container performs bean dependency resolution as follows:
- Create and initialize the ApplicationContext with configuration metadata that describes all the beans. Configuration metadata can be specified by XML, Java code, or annotations.
- For each bean, the dependency is expressed in the form of a property, constructor parameter, or static-Factory method parameter (if used instead of a normal constructor). These dependencies are provided to the Bean when it is actually created.
- Each property or constructor parameter is an actual definition of the value to be set, or a reference to another bean in the container.
- Each property or constructor parameter as a value is converted from its specified format to the actual type of the property or constructor parameter. By default, Spring can supply values as strings to all the built-in types, such as int, Long, String, Boolean, and so on.
When the container is created, the Spring container validates the configuration of each bean. However, the bean properties themselves are not set until the bean is actually created. When the container is created, the singleton scope is created and set to a pre-instantiated (default) bean. The scope is defined in the Bean scope. Otherwise, the bean is only created when it is requested. Creating a bean can result in the need to create a series of beans or even circular dependent beans because of the need to create and assign the bean’s dependencies and the bean’s dependencies (and so on). Note that parsing mismatches between these dependencies occur when the affected bean is first created.
###### Cyclic dependencies Unlike the typical case (no cyclic dependencies), A cyclic dependency between Bean A and Bean B forces one bean to inject another bean before it is fully initialized (A typical chicken-and-egg scenario). If you use constructor injection primarily, you create a loop dependency scenario that cannot be resolved. For example, class A needs an instance of class B via constructor injection, and class B needs an instance of class A via constructor injection. If you for class A and class B configuration the beans into each other, so the Spring IoC container detects the circular reference at runtime, and throw BeanCurrentlyInCreationException. One possible solution is to edit the source code for some classes so that setter injection is used instead of constructor injection. Alternatively, avoid constructor injection and just use setter injection. In other words, although it is not recommended, you can configure circular dependencies using setter injection.Copy the code
You can usually trust Spring to do the right thing. It detects non-existent beans and cyclic dependencies at container load time. Spring sets the properties and resolves the dependencies as late as possible when the bean is actually created. This means that when a properly loaded Spring container has problems creating an object or its dependencies, it can generate an exception when requesting an object — for example, a bean throwing an exception due to missing or invalid properties. The potential for delayed visibility of certain configuration issues is why the ApplicationContext implementation preinstantiates the singleton bean by default. Because it takes some up-front time and memory to create these beans before they are actually needed, configuration problems are discovered by default when ApplicationContext is created, not later. You can still override this default behavior so that the singleton bean can be lazily initialized rather than pre-instantiated.
If there are no circular dependencies, when one or more collaboration beans are injected into a dependency bean, each collaboration bean is fully configured before being injected into the dependency bean. This means that if Bean A depends on Bean B, the Spring IoC container will fully configure Bean B before calling setter methods of Bean A. In other words, the bean is instantiated (if it is not a singleton pre-instantiated), its setting dependencies, and the associated lifecycle methods (such as the InitializingBean init method or the configuration callback method) are called.
Example of dependency injection
The following example uses XML-based configuration metadata for setter-based DI. A small section of the Spring XML configuration file specifies some bean definitions, as follows:
Here the default autowire attribute for examoleBean is no, which manually specifies the bean to which the attribute corresponds <bean ID ="exampleBean" class=" examoleBean "> <! -- setter injection using the nested ref element --> <property name="beanOne"> <ref bean="anotherExampleBean"/> </property> <! -- setter injection using the neater ref attribute --> <property name="beanTwo" ref="yetAnotherBean"/> <property name="integerProperty" value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/> public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public void setBeanOne(AnotherBean beanOne) { this.beanOne = beanOne; } public void setBeanTwo(YetAnotherBean beanTwo) { this.beanTwo = beanTwo; } public void setIntegerProperty(int i) { this.i = i; }}Copy the code
In the previous example, setters were declared to match the properties specified in the XML file. The following example uses constructor-based DI:
<bean id="exampleBean" class="examples.ExampleBean"> <! -- constructor injection using the nested ref element --> <constructor-arg> <ref bean="anotherExampleBean"/> </constructor-arg> <! -- constructor injection using the neater ref attribute --> <constructor-arg ref="yetAnotherBean"/> <constructor-arg type="int" value="1"/> </bean> <bean id="anotherExampleBean" class="examples.AnotherBean"/> <bean id="yetAnotherBean" class="examples.YetAnotherBean"/> public class ExampleBean { private AnotherBean beanOne; private YetAnotherBean beanTwo; private int i; public ExampleBean( AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) { this.beanOne = anotherBean; this.beanTwo = yetAnotherBean; this.i = i; }}Copy the code
The constructor parameter specified in the Bean definition is used as the constructor parameter ExampleBean. Now consider a variation of this example in which, instead of using a constructor, Spring is told to call the static factory method to return an instance of the object:
<bean id="exampleBean" class="examples.ExampleBean" factory-method="createInstance">
<constructor-arg ref="anotherExampleBean"/>
<constructor-arg ref="yetAnotherBean"/>
<constructor-arg value="1"/>
</bean>
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
private ExampleBean(
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
this.beanOne = anotherBean;
this.beanTwo = yetAnotherBean;
this.i = i;
}
public static ExampleBean createInstance (
AnotherBean anotherBean, YetAnotherBean yetAnotherBean, int i) {
ExampleBean eb = new ExampleBean (...);
// some other operations...
return eb;
}
}
Copy the code
Arguments to the static factory method are provided by the element, just as constructors are actually used. The class type returned by the Factory method need not be the same type as the class containing the StaticFactory method (although it is in this example). Instance (non-static) factory methods can be used in essentially the same way (except using factory-bean attributes instead of using class attributes), so I won’t discuss those details here.