我目前正在提高我的 Spring 知识。我想知道当我在字段上使用 Spring 注释 @Autowire 时到底会发生什么。
这是一段代码:
输出帮助文件
@Component
public class OutputHelper {
@Autowired
@Qualifier("csvOutputGenerator")
private IOutputGenerator outputGenerator;
public void setOutputGenerator(IOutputGenerator outputGenerator) {
this.outputGenerator = outputGenerator;
}
// I can focus only on what my code do because my objects are injected
public void generateOutput(){
outputGenerator.generateOutput();
}
}
CsvOutputGenerator 文件
@Component
public class CsvOutputGenerator implements IOutputGenerator {
public void generateOutput(){
System.out.println("Csv Output Generator");
}
}
申请文件
public static void main(String[] args) {
// Create the spring context
ApplicationContext context = new ClassPathXmlApplicationContext("META-INF/spring/spring-module.xml");
// Get the configured OutpuHelper from the spring-module.xml
OutputHelper output = (OutputHelper) context.getBean("outputHelper");
// Display output from the output configured
output.generateOutput();
}
我的配置文件只包含
<context:component-scan base-package="com.xxx.xxx.output"/>
当我执行此代码时,一切正常。但让我惊讶的是,当我删除 OutPutHelper 文件中的 setOutputGenerator 时,我的代码仍然有效。我认为通过此配置,OutputHelper 首先使用默认构造函数创建并使用 setter 初始化。
我预计会出现错误,因为变量outputGenerator无法初始化。
有人可以帮助我理解吗?
拥有字段
@Autowired
的想法是有问题的。它有效,但会给您实施的其他方面(即测试)带来困难。
有3种注射方式:
字段 - 基本配置为直接将反射 (Field.set(Object, Object)) 应用于字段:
@Autowired
private MyInterface field;
setters - 通过这种方法,每个依赖项的配置都通过一个属性(spring 遍历所有方法并使用
Method.invoke(Object, Object...)执行每个用
@Autowired
注释的方法,因此其值为使用其设置器配置如下:
@Autowired
public void setField(MyInterface value) {
this.field = value;
}
构造函数 - 最后也是我更喜欢的方法,构造函数注入。该方法基本上用
@Autowired
注释构造函数,您可以直接在构造函数上配置 bean,而不是使用方法或字段。为此,spring 将选择一个构造函数来实例化您的 @Component
,并且它将使用 @Autowired
(如果存在)或空的 params 构造函数,并使用 Constructor.newInstance(Object...) 调用它。示例:
@Component
public class Implementation {
private MyInterface field;
@Autowired
public Implementation(MyInterface value) {
Assert.notNull(value, "value should not be null");
this.field = value;
}
}
控制反转(或依赖注入)背后的想法之一是能够隔离一段代码,以提供良好的测试实现支持。
为了更深入,有必要评论一下,在单元测试期间,您希望该类处于隔离形式,您将与该类一起使用的所有内容基本上都是其依赖项(注入)的模拟。
那么,结果是什么:
实际上,setter 是没有用的,因为
CDI
使用 java Reflection 来访问字段。
这意味着字段不再通过方法调用来访问。 反射允许迭代类的所有字段并检查是否有特定注释的注释。
在这种情况下,如果类中的字段使用
@Autowired
(或 @Inject
,更符合 J2E)进行注释,则容器将迭代搜索是否存在适合当前属性的注册 bean。
当上下文启动时,容器会迭代类并搜索所有用
@Inject
或 @Autowired
注释的字段。
对于这些字段,它会搜索可用的 bean。
这是一个必须简单的例子:
public class SpringClassInChargeOfDependencyInjection {
public void handdleInjections(T objectWithInjectableField) {
Class<T> clazz = objectWithInjectableField.class;
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.isAnnotationPresent(Autowired.class) || field.isAnnotationPresent(Inject.class)) {
//find a bean for the type;
Object injectableBean = getAvailablebean(field.getType());
field.setAccessible(true);
//inject the value into the class, this line explain why the setter is not necessary
field.set(objectWithInjectableField, injectableBean);
}
}
}
}
这是一个非工作示例,只是为了解释它是如何工作的。
您可以考虑使用
@Inject
代替@Autowired
,后者是由Spring创建的,@Inject
是JSR-330的一部分。 Spring 也理解 @Inject
,您只需将 javax.inject
jar 依赖项添加到您的项目中即可。如果稍后你想从 spring 切换到其他东西(例如 guice),你不必更改所有 @Autowired
注释
<dependency>
<groupId>javax.inject</groupId>
<artifactId>javax.inject</artifactId>
<version>1</version>
</dependency>