问题
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