注入私人,包装或公共领域或提供制定者?

时间:2010-01-07 16:08:52

标签: java dependency-injection scope

我看到许多Java示例使用依赖注入私有字段而没有像这样的公共setter:

public SomeClass {
  @Inject
  private SomeResource resource;
}

但是,当应该手动执行注射时,例如在单元测试中,这是一个坏主意。

有几种可能性来解决这个问题:

  • 添加公开制定者:setSomeResource(SomeResource r)
  • 将该字段设为公开
  • 使字段包受保护

我想避免使用setter,因为它没有真正发生。所以我更喜欢公共或包裹保护。您有什么推荐的吗?

7 个答案:

答案 0 :(得分:14)

避免为字段创建setter的一种方法是使用构造函数注入。这甚至允许您将该字段声明为最终字段。

它是这样的:

public class SomeClass {
    private final SomeResource resource;

    @Inject
    public SomeClass(SomeResource resource) {
        this.resource = resource;
    }
}

答案 1 :(得分:8)

添加setter不是最佳解决方案,因为您要添加不需要的生产代码。

另一种方法是使用Spring的ReflectionTestUtils类使用反射注入测试依赖项,请参阅http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/test/util/ReflectionTestUtils.html

EDIT(2017):然而,反思是比添加setter更糟糕的解决方案。造成这种混乱的原因是Spring可以在没有setter或构造函数的情况下注入值。我目前的立场是坚持使用其中任何一种并避免使用黑魔注射法。

答案 2 :(得分:7)

我更喜欢setter

  • 调试更容易(在setter中放置断点而不是字段访问/修改)
  • 更容易记录
  • 更容易添加一些验证(虽然这并不总是最好的地方)
  • 更容易支持双向维护(尽管IOC容器可以处理)
  • 任何其他“手动AOP”目的

但这只是我的意见

答案 3 :(得分:4)

我建议使用setter。在this question中使用getter和setter是有好处的。

答案 4 :(得分:3)

借助我(与此相关)问题的答案:

How do app servers inject into private fields?

我编写了这个关于如何在没有setter的情况下进行注入的简单示例。 也许有帮助

//......................................................
import java.lang.annotation.*;
import java.lang.reflect.*;

//......................................................
@Target(value = {ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@interface Inject {
}

//......................................................
class MyClass {

    @Inject
    private int theValue = 0;

    public int getTheValue() {
        return theValue;
    }
} // class

//......................................................
public class Example {

    //......................................................
    private static void doTheInjection(MyClass u, int value) throws IllegalAccessException {

        Field[] camps = u.getClass().getDeclaredFields();

        System.out.println("------- fields : --------");
        for (Field f : camps) {
            System.out.println(" -> " + f.toString());
            Annotation an = f.getAnnotation(Inject.class);
            if (an != null) {
                System.out.println("       found annotation: " + an.toString());
                System.out.println("       injecting !");
                f.setAccessible(true);
                f.set(u, value);
                f.setAccessible(false);
            }
        }

    } // ()

    //......................................................
    public static void main(String[] args) throws Exception {

        MyClass u = new MyClass();

        doTheInjection(u, 23);

        System.out.println(u.getTheValue());

    } // main ()
} // class

运行输出:

------- fields : --------
 -> private int MyClass.theValue
       found annotation: @Inject()
       injecting !
23

答案 5 :(得分:0)

使用基于字段的注入,您会遇到通过测试描述的问题。此外,对于基于setter的注入,如果您忘记设置某些依赖项,则在运行测试时可以在不完整的状态下创建类的实例。我最近一直在练习构造函数注入,因为它会强制您在测试期间创建类的实例时设置所有依赖项。 Andre Rodrigues 上面的答案解释了如何实现这一目标。

答案 6 :(得分:0)

可能的解决方案:

  • 使用像JGlue CDI-Unit这样的CDI感知测试框架。这样你就不需要setter了。您只需在测试中定义依赖项 - 通常使用Mockito模拟对象。恕我直言,这是最好的解决方案,因为它不需要你做任何额外的测试。

  • 注入构造函数或setter。没错,你可以注入二传手! More details here.

  • 使用受保护的setter。简单,适用于所有情况。由于它受到保护,您可以从测试类(它应该与测试类具有相同的包定义)访问它,并且没有其他包可以访问它。

  • 使用getter并在测试时覆盖它。在您的测试类中,创建一个新的内部类,扩展测试的类并覆盖getter。但是,这有一个很大的缺点:您的测试类必须在内部使用getter而不是字段。很多可能漏洞的样板...