Inject into private, package or public field or pr

2019-02-08 09:56发布

I see many Java examples using dependency injection with private fields without a public setter like this:

public SomeClass {
  @Inject
  private SomeResource resource;
}

But that is a bad idea when the injection should be performed manually for example in unit tests.

There are several possibilities to solve this:

  • add a public setter: setSomeResource(SomeResource r)
  • make the field public
  • make the field package protected

I'd like to avoid the setter, since nothing really happens in it. So I'd prefer public or package protected. What do you recommend?

7条回答
甜甜的少女心
2楼-- · 2019-02-08 10:38

I prefer the setter

  • it is easier to debug (put a breakpoint in a setter rather than on field access / modification)
  • easier to log
  • easier to add some validation (although this is not always the best place)
  • easier to support bidirectional maintainance (though IOC container can take care of that)
  • any other "manual AOP" purpose

But that's just my opinion

查看更多
爷的心禁止访问
3楼-- · 2019-02-08 10:43

One way to avoid creating a setter for the field is using constructor injection. This even allows you to declare the field as final.

It goes like this:

public class SomeClass {
    private final SomeResource resource;

    @Inject
    public SomeClass(SomeResource resource) {
        this.resource = resource;
    }
}
查看更多
倾城 Initia
4楼-- · 2019-02-08 10:44

I recommend using setter. In this question are the benefits of using getters and setters.

查看更多
Anthone
5楼-- · 2019-02-08 10:51

Possible solutions to this:

  • Use a CDI-aware testing framework like JGlue CDI-Unit. This way you need no setter. You only define the dependency inside your tests - usually using a Mockito mock object. IMHO this is the best solution, since it doesn't require you to do anything extra for testing.

  • Inject into Constructor or setter. That's right, you can inject into setters! More details here.

  • Use a protected setter. Simple and works in every case. Since it's protected, you can access it from your test class (which should have the same package definition as your tested class), and no other packages can access it.

  • Use a getter and override it when testing. In your test class, create a new inner class that extends the tested class and override the getter. This, however, has a big disadvantage: your test class has to use the getter internally instead of the field. Lots of potentially bugged boilerplate...

查看更多
Lonely孤独者°
6楼-- · 2019-02-08 10:52

Adding setters is not an optimal solution, since you are adding production code which is not needed.

An alternative is to use Spring's ReflectionTestUtils class to inject your test dependencies using reflection, see http://static.springsource.org/spring/docs/2.5.x/api/org/springframework/test/util/ReflectionTestUtils.html

EDIT (2017): However, reflection is an even worse solution than adding setters. The cause of this mess is the fact that Spring makes it possible to inject values without setters or constructors. My current stance is to stick to using either of those and avoid using black magic injection practices.

查看更多
我命由我不由天
7楼-- · 2019-02-08 10:52

With the help of the answer to my (related to this one) question:

How do app servers inject into private fields?

I coded this simple example on how to inject without setters. Perhaps it helps

//......................................................
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

Run output:

------- fields : --------
 -> private int MyClass.theValue
       found annotation: @Inject()
       injecting !
23
查看更多
登录 后发表回答