原文链接: dryydrydrdconstructor-injection-in-spring
1. Introduction
Arguably one of the most important development principles of modern software design is Dependency Injection (DI), which quite naturally flows out of another critically important principle: Modularity.
可以说,在现代的软件设计中,最重要的开发准则就是依赖注入DI,它也能自然而然的实现一个重要的编程概念:模块化。
This quick tutorial will explore a specific type of DI technique within Spring called Constructor-Based Dependency Injection, which simply put, means that we pass the required components into a class at the time of instantiation.
在这篇文章中,我们将探究一种Spring框架中名为基于构造函数依赖注入的一种技术,简单来说,就是在初始化阶段,把bean对象的所需的依赖设置上。
To get started, we need to import the spring-context dependency in our pom.xml:
为了达到上述上的,需要在pom.xml文件中加入spring-context的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
Then we need to set up a Configuration file. This file can be either a POJO or an XML file, based on preference.
之后,我们需要定义一个配置文件,可以是一个Java类或XML文件,看你的喜好了。
2. Annotation Based Configuration 基于注解的配置方式
Java configuration files look similar to Java objects with some additional annotations:
Java配置类,看起来就像普通的java对象,只不过多了一些额外的注解罢了。
@Configuration
@ComponentScan("com.baeldung.constructordi")
public class Config {
@Bean
public Engine engine() {
return new Engine("v8", 5);
}
@Bean
public Transmission transmission() {
return new Transmission("sliding");
}
}
Here we're using annotations to notify Spring runtime that this class provides bean definitions (@Beanannotation), and that the package com.baeldung.spring needs to perform a context scan for additional beans. Next, we define a Car class:
在上面的代码中,使用了@Bean注解来告知Spring框架,这个配置类提供了bean的定义,还有,包com.baeldung.spring需要被扫描,以便加载更多的bean。后来,定义一个Car类
@Component
public class Car {
@Autowired
public Car(Engine engine, Transmission transmission) {
this.engine = engine;
this.transmission = transmission;
}
}
Spring will encounter our Car class while doing a package scan, and will initialize its instance by calling the @Autowired annotated constructor.
在扫描包的过程中,Spring框架会遇到我们的Car类,将会调用被@Autowired注解标记的构造函数来初始化它的实例。
By calling the @Bean annotated methods of the Config class, we will obtain instances of Engine and Transmission. Finally, we need to bootstrap an ApplicationContext using our POJO configuration:
通过调用Config类中被@Bean注解标记的方法,我们将会得到Engine和Transmission的实例,它们会做为参数参与到Car类的实例构造过程中。最后,需要给ApplicationContext指定我们的配置类来启动Spring框架,示例如下
ApplicationContext context = new AnnotationConfigApplicationContext(Config.class);
Car car = context.getBean(Car.class);
3. Implicit Constructor Injection 不明确的构造函数参数注入
As of Spring 4.3, classes with a single constructor can omit the @Autowired annotation. This is a nice little bit of convenience and boilerplate removal.
截止到Spring4.3,只有一个构造函数的类可以不用@Autowired进行注解,就可以被Spring框架加载初始化。这是一个比较便捷的点,去除了僵硬的样板化的代码
On top of that, also starting with 4.3, we can leverage the constructor-based injection in @Configuration annotated classes. In addition, if such a class has only one constructor, we can omit the @Autowired annotation as well.
基于这一点,从4.3开始,我们也可以在Java配置类中,在基于构造函数的依赖注入模式中使用这一特性。除此之处,如果只有一个构造函数,同样可以省略@Autowired注解。
4. XML Based Configuration 基于XML的配置
Another way to configure Spring runtime with constructor-based dependency injection is to use an XML configuration file:
另一种使用Spring框架,基于它构造函数进行依赖注入的配置方式是通过XML文件,示例如下
<bean id="toyota" class="com.baeldung.constructordi.domain.Car">
<constructor-arg index="0" ref="engine"/>
<constructor-arg index="1" ref="transmission"/>
</bean>
<bean id="engine" class="com.baeldung.constructordi.domain.Engine">
<constructor-arg index="0" value="v4"/>
<constructor-arg index="1" value="2"/>
</bean>
<bean id="transmission" class="com.baeldung.constructordi.domain.Transmission">
<constructor-arg value="sliding"/>
</bean>
Note that constructor-arg can accept a literal value or a reference to another bean, and that an optional explicit index and type can be provided. We can use Type and index attributes to resolve ambiguity (for example if a constructor takes multiple arguments of the same type).
需要注意的是,构造函数同样能接收值类型和引用类型参数,在这种情况下,可以使用可选的index和type参数来明确指定参数顺序,以此来解决参数的不确定性。比如,可以解决构造函数中,有多个同和类型的参数,这时使用index和type就很有必要了。
name attribute could also be used for xml to java variable matching, but then your code must be compiled with debug flag on.
name属性同样可以当做参数匹配的一种方式,但前提是必须使用debug模式,只有在debug模式下,才能获取到参数的name,用于参数匹配。
In this case, we need to bootstrap our Spring application context using ClassPathXmlApplicationContext:
使用XML配置的日志上传,需要使用ClassPathXmlApplicationContext来加载Spring,如下
ApplicationContext context = new ClassPathXmlApplicationContext("baeldung.xml");
Car car = context.getBean(Car.class);
5. Pros and Cons 构造函数依赖注入模式的好处与坏处
Constructor injection has a few advantages compared to field injection.
对比字段属性注入,构造函数这种方式有更多优点。
The first benefit is testability. Suppose we're going to unit test a Spring bean that uses field injection:
第一个优点当数可测试性,试想一下,当我们使用字段注入进行单元测试时,示例如下
public class UserService {
@Autowired
private UserRepository userRepository;
}
During the construction of a UserService instance, we can't initialize the userRepository state. The only way to achieve this is through the Reflection API, which completely breaks encapsulation. Also, the resulting code will be less safe compared to a simple constructor call.
这种情况,在构造USerService实例对象时,我们不能对它的依赖userRepository进行修改,唯一可行的是只能通过反射,但反射又打破了封闭这一原则。所以,这种方式远不如一个简单的构造函数来的简单。
Additionally, with field injection, we can't enforce class-level invariants, so it's possible to have a UserService instance without a properly initialized userRepository. Therefore, we may experience random NullPointerExceptions here and there. Also, with constructor injection, it's easier to build immutable components.
除此之外,使用字段进入依赖注入,我们无法保证类对象不会被修改变更,所以,很有可能出现,UserService被初始化了,但通过修改依赖字段,使其变为空引用。基于上述情况,在程序运行中,随时可能出现NullPointException。
Moreover, using constructors to create object instances is more natural from the OOP standpoint.
更甚的是,从面向对象角度来说,使用构造函数进行依赖注入更自然。
On the other hand, the main disadvantage of constructor injection is its verbosity, especially when a bean has a handful of dependencies. Sometimes it can be a blessing in disguise, as we may try harder to keep the number of dependencies minimal.
另一方面,构造函数依赖注入的主要缺点是它让构造函数显示冗长,特别是当一个bean对象有一系列的依赖的时候。不过,有时这也是一个好处,可以观察到一个bean对象是否兼有过多的职能,可以通过这种方式,把它的依赖减少到最少。
6. Conclusion 最后
This brief article has showcased the basics of two distinct ways to use Constructor-Based Dependency Injection using the Spring framework.
这篇文章为大家展示了两种方式,来使用Spring框架中的构造函数依赖注入,一种是基于Java注解,另一种是XML文件。
多多点赞关注哈