Field @Autowired背后的魔力是什么?

时间:2015-08-01 20:40:25

标签: spring

我目前正在提高我的Spring知识。我想知道当我在一个字段上使用Spring注释@Autowire时会发生什么。

这是一段代码:

OutputHelper文件

@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无法初始化。

有人能帮我理解吗?

2 个答案:

答案 0 :(得分:4)

让字段@Autowired的想法值得怀疑。它有效,但实施的其他方面(即测试)将很困难。

有三种类型的注射:

  • 字段 - 基本上将反射(Field.set(Object, Object))直接应用于字段:

    @Autowired
    private MyInterface field;
    
  • setter - 使用这种方法,每个依赖项的配置都通过一个属性(spring遍历所有方法并使用Method.invoke(Object, Object...)执行每个注释@Autowired的方法,因此使用{它的二传手如下:

    @Autowired
    public void setField(MyInterface value) { 
        this.field = value;
    }
    
  • 构造函数 - 最后一个,也是我更好的方法,构造函数注入。那个基本上用@Autowired注释一个构造函数,而不是使用方法或字段,你可以直接在你的构造函数上配置你的bean。对于那个春天,将选择一个用于实例化@Component的构造函数,如果存在,则使用@Autowired或使用Constructor.newInstance(Object...)调用空params构造函数。例如:

    @Component
    public class Implementation {
        private MyInterface field;
        @Autowired
        public Implementation(MyInterface value) {
            Assert.notNull(value, "value should not be null");
            this.field = value;
        }
    }
    

控制反转(或依赖注入)背后的一个想法是能够隔离一段代码,以提供合适的测试实现支持。

为了更深入,有必要评论一下,在单元测试期间,您希望该类处于其隔离状态,您将使用该类的所有内容基本上都是针对其依赖性(注入)的模拟。

那么,结果是什么:

  • 如果你进行字段注入,那么在测试期间每次设置bean使用一些反射来配置bean将是非常昂贵的(需要引入另一个逻辑来配置bean到经过测试)。
  • 使用 setter注入方法,您将能够使用自己的bean来配置必要的模拟,以隔离您的实现并测试其功能。
  • 最后,使用构造函数注入方法,您不仅可以获得配置bean的支持,而且还可以要求其依赖项。这意味着对于每个新的依赖项,都会在构造函数上添加一个新参数,这会让您在开发时获得优势,例如,您可以通过引入新的依赖项来查看开发时间单元测试的影响(一次)你的IDE会指出你的)。

答案 1 :(得分:3)

简单回答

实际上,setter没用,因为CDI使用java Reflection来访问字段。

这意味着方法调用不再访问字段。 反射允许迭代一个类的所有字段,并检查是否有带有spécific注释的注释。

在这种情况下,如果类中的某个字段带有@Autowired(或@Inject更多J2E complient),那么如果有一个可用的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依赖项添加到你的项目中。

<dependency>
    <groupId>javax.inject</groupId>
    <artifactId>javax.inject</artifactId>
    <version>1</version>
</dependency>