Spring Dependency Injection - Private fields - Anti Pattern? Why does it even work?

允我心安 提交于 2021-01-29 21:33:07

问题


I am generally a c# developer but working on Java now and then I see a lot of dependency injection using Spring on private properties, with no public way of setting the value. I was surprised this actually works, but I guess it’s possible via reflection?

Surely this is terrible practice?! I can't see how anyone unit testing or inspecting the class would possibly know that a private member needs to be set from some external framework.

How would you even set the property when you are unit testing? Or just using the class stand alone?

I guess you have to use spring in your unit tests which seems really overkill. Surely you should be able to unit test without your IOC container? The class becomes completely dependent on spring...

Have I missed anything here?

Should dependency injection not always involve a public setter of some kind, and preferably use the constructor if possible? Or is there something about Java I am missing...?

Thanks


回答1:


You can always mock injected beans even if you have private fields. You should have a look on @MockBean from Spring documentation. Essentially, you could do the following:

@ExtendWith({SpringExtension.class})
class MyServiceTest{

    @MockBean
    private RepositoryInterface repository;

    @Autowired
    private MyService service;

}

Supposing that RepositoryInterface is an interface (and not a concrete class) that is injected in MyService. What happens is that the SpringExtension for JUnit5, which should be already in your dependencies if you created your pom.xml from Spring Initialzr, will build a mock for that interface using another framework that is called Mockito (maybe have a look to it). Then Spring IoC will inject the created mock in the service. This works for field injection:

@Service
public class MyService{

    @Autowired
    private RepositoryInterface repositoryInterface
}

setter injection:

@Service
public class MyService{

    private RepositoryInterface repositoryInterface

    @Autowired
    public void setRepository(RepositoryInterface repositoryInterface){
        this.repositoryInterface = repositoryInterface;
    }
}

or constructor injection:

@Service
public class MyService{

    private RepositoryInterface repositoryInterface

    public MyService(RepositoryInterface repositoryInterface){
        this.repositoryInterface = repositoryInterface;
    }
}

Essentially, the last one is the recommended one because in this way your service's dependencies will be explicit. It's more on code style. Field injection is not recommended because iy hides your class dependencies. So, the recommended way of building a test using the constructor injection would be the following:

@ExtendWith({SpringExtension.class})
class MyServiceTest{

    @MockBean
    private RepositoryInterface repository;

    private MyService service;

    @BeforeEach
    void setup(){
        service = new MyService(repository);
    }
}

Hope this helps your understanding.




回答2:


There is field based injection, setter based injection, annotation based injection, and constructor based injection. Constructor based injection is best for testing simply because you can easily mock the dependencies needed. It's always nice to define services with final fields where possible.

class MyService {

    private final MyDependency dependency;

    @Autowired // not needed, explicit just for this example
    public MyService(MyDependency dependency) {
        this.dependency = dependency;
    }

}



回答3:


Yes, it works. Some testing frameworks allow inject private fields.

And yes, it's antipattern, adds technical debt - easy to write, but hard to maintain such code, instead of compile-time errors you'll have runtime errors. Don't do that. Use constructor injection.



来源:https://stackoverflow.com/questions/63600604/spring-dependency-injection-private-fields-anti-pattern-why-does-it-even-wo

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!