That DI layer (speaking for Spring) is pretty handy for large projects, though, because I can switch out implementations based on configurations centrally, I can use factories without change of injection patterns, ...
I've heard that, but I don't understand it. Is the idea "I don't have to deal with static typing"? In vanilla Go code I can change out implementations super easily as well (via interfaces, which are less tedious than Java interfaces due to structural subtyping). We can also use factories, but this is precisely the kind of stuff I prefer to avoid except in certain very rare cases ("but with Spring, no one is forcing you to use factories!"--maybe not technically, but if it's idiomatic or otherwise culturally accepted then you can guarantee that your coworkers will reach for them).
What I am referring to with factories in particular is that I can have a bean injected in Spring automagically using an annotation, have an interface and an implementation behind it.
Now, I can go ahead and make the concrete implementation dependent on configuration, e.g. by providing multiple implementations annotating them with a condition that is evaluated using the application configuration. I can also switch to a factory method that creates and sets up the concrete implementation for the interface, I have not to change any place in the application that is using that piece, though.
The same should be possible in Go with reflect and an IoC container, but I am not sure if there's a solid implementation for that out there.
I'm a bit ignorant, but it sounds like you're re-inventing "functions" in any programming language. Maybe if you could give a simple, concrete example it would help.
The code base at work has 100s of thousands of lines in several services based on Spring Boot. There's not a single factory in our code.
We also do not use XML to configure DI. Just add a annotation to a class and you can inject it automatically in any constructor without any additional config/annotations.
If this is too much "magic" for someone's taste, I will not argue even if I have a different view. But please do not make absolute statements based on a bad experience in some companies.
Modern Java and frameworks have evolved a lot in recent years.
My take on this is that just injecting stuff in a constructor is an antipattern. Why not pass the data through methods instead of accessing it through a singleton in a constructor?
I understand the need for DI, sometimes the order of instantiation is necessary but for everything else I get lost in the DI constructor injection stuff.
There’s typically a separation between “services” and “data” in this style of app. Data is typically a request or some result obtained from another service such as the database. A service can have a lifetime distinct from the data, such as a database service that uses a connection pool, which is scoped to the lifetime of the application.
In a functional language you could use a reader or environment monad to abstract the dependencies, but you don’t have that ability in a language like Java (and it’s not worth torturing the language to do it because it’s not idiomatic). So DI in Java ends up constructor-injecting service dependencies and using the method parameters to declare data dependencies.
Edit: another benefit of DI is that it allows for multiple lifetime scopes beyond application and method call. Spring has request and transaction scopes, for instance. Managing all that in Java without DI is a nightmare.