I can deal with both, but just a preference.
In general, dynamic integrations like IoC, etc make things harder to troubleshoot/debug as well.
Inversion of Control is really good and there are no reasons not to use it. When you have something that can be done in multiple ways, make a small interface and several implementations for it.
The trick now is how to decide what to use. The simplest way is just to parse your configuration file or application flags/arguments in main and instantiate the concrete implementation of your classes there, then pass them down all the way until you need them. This is explicit, simple and clean.
Spring makes you (at least) do two things wrong:
1. Configure the concrete implementations the wrong way. You don't want your users to decide which class implements a certain interface. You want them to decide something higher level, like for example "get crash data via e-mail" or "get crash data in logs". The rest is implementation details.
2. Annotations for autowiring: my feeling is that those are just to go around Java verboseness. Instead of typing "VeryLongName veryLongName = new VeryLongName()", "@autowire" it easier to type. I have always seen it misused (used for stuff that is not Inverted/Configurable) and it makes code more magic. Even worse, you are back at having classes pick their dependencies, even if they are interface implementations. If your classes take their dependencies as constructor parameters, everything is much more explicit.
This is the guidance that Spring team members give, to the point that Spring now defaults to injecting constructors without annotations.
I agree that field/setter autowiring is Satan's handicraft.
Not all of this is Spring's fault. Java has the JavaBeans standard, which created a universe in which setter injection is common. JPA, for example, requires no-arg constructors. It makes me sad.
The problem with the cult of DI fanatics is that they can sometimes take it too far. Sometimes code is tightly coupled to the implementation by necessity, and it doesn't add any value to have additional layers of misdirection. Although I'll admit that this is a bit of a strawman, since IoC can be summed up as "give things what they need", which is generally a sensible approach.
At test-runtime, one will pass mock objects to the SUT's constructor using the new keyword. These mock objects are used to rig up specific preconditions that drive the scenario to be tested. Mocks can also be used to assert that the SUT called the dependency as expected with parameters.
At runtime, the IoC container handles instantiation of the module and injects the dependencies as appropriate constructor params.
I assume you asked this because Spring Framework is the poster child of dependency injection and IoC in the Java world. With all the Aspect-Oriented Programming and proxification and scope twizzling that occasionally blooms in the Spring ecosystem that is all wired-up using Spring DI, I think Spring sometimes gets a bad rep. Only abusers of those features get bad reps with me. Spring as a whole is a trove of nicely built systems with lots of classy 3rd party integrations. I personally prefer using Google Guice for DI because it is a standalone system, it eschews doing classpath scanning to identify injectable classes which I think makes it faster especially at startup, and it has decent integrations with 3rd party libraries.
[0] http://olivergierke.de/2013/11/why-field-injection-is-evil/
[1] https://github.com/spring-projects/spring-framework/wiki/Wha...
Past a threshold of codebase size and complexity, I see the value of course, but IoC frameworks get pitched too often as a thing that everyone should be using IMHO. Even for testing we already have mocking tools that handle replacing component objects with mocked versions just fine, and we don't need IoC tools for that.
Well, that place needs those objects to do its job.
> When / why is that ever a thing that someone needs to do so often that it becomes a concern?
In web applications, at least, you find yourself with a few controllers, which all need a few services which all need a few repositories to access the database... I guess you can see where this is going. Declaring in your class' constructor that instances need an object of `Type` is the less repetitive way of saying that an instance of a class needs something. Writing the code that builds objects and passes them to your constructors would be quite repetitive.
Maybe something like the mediator pattern? There's definitely use cases for it, but I think a lot of the time the cure isn't any better than the disease.
Note that DI is just a form of IoC and both are separate from Spring. You can "manually" do DI by passing objects to constructors yourself. Some people have a complex web of dependencies and Spring can help make this manageable. Other people don't but have a hatred for passing arguments to constructors.
This is especially useful when implementing tests because it is easy to inject mock implementations of dependencies.
Personally I'm deeply suspicious of mocking as an approach to testing code. I much prefer a functional approach with explicit data flow between parts of the system, rather than indirecting control flow through vtables using complex ad-hoc protocols that become part of your specification by getting baked into mocks in tests. IoC and mocking promotes dataless Verber, Doer and Handler classes that have no real place in an OO model of a system.
We shouldn't forget that decoupling isn't good for its own sake. Things that change together are already coupled; decoupling them at the implementation level is usually an obstacle in every way, whether to understanding, effort, performance, whatever. Abstraction use vs programmer experience generally follows a hump shaped curve; beginners know nothing, intermediates abstract everything, while experts use fewer more powerful, more composable abstractions and code more directly elsewhere. Mocking IMO optimizes for the middle case of too many non-reusable abstractions.
Also, you don't need to swap everything, your application (in fact) has hard dependencies. And mocking everything is not always the best way to test system (sometimes it's totally okay to mock the whole db)
Also, Java is statically-typed language and DIC are a way to do more dynamic stuff (@Transactional proxies) - such things are possible without DI in some other languages.