I dislike annotation-driven code, as well as opinionated frameworks like Spring.
Throwing out Spring (or similar) is probably not going to happen if your team uses it.
I have found that you can greatly reduce your coupling to Spring (and other annotations) while still letting it work for you.
To do so, you use inheritance (which in general I dislike, but in this case - two wrongs make a right?)
Split your FooService into two: {SpringFooService and FooService}, where SFS inherits from FS.
* SFS may contain annotations, but FS must not. * FS must contain all your business logic. SFS must be a thin 1:1 wrapper over FS.
This means that: * SFS can be autowired, and managed by Spring. * Your business logic remains "simply-instantiable" with _new_ for the purposes of unit testing.
However, my first reason for doing it this way is that I want to be able to see at a glance whether or not there's Spring in my class.
I look at the import list and ask myself: Is this code doing what it says? Or is Spring going to attempt (and possibly fail) some behind-the-back magic (subject to the order in which annotations are declared in some unrelated package?)
I want to be able to look at my business logic, and know it's going to do what's written in the file. It is not worth it to me to including @Component (in an otherwise non-Spring class) to make instantiation slightly more streamlined[*].
The second reason is that of needless coupling. People change frameworks - or more accurately - people don't change frameworks, because they declare that they'll never need to, then couple themselves tightly to one framework, then when a new better framework comes along, they don't change because it would be too much work, because of all the coupling. So their prediction that they'll never change framework is self-fulfilling.
[*] I also don't want the streamlined instantiation. The IOC container is shared global state, and @Singleton used to be a known antipattern. At work we are currently migrating between Kafka providers, and some of the hardest work I've had to do in the last year has been cutting our singletons apart. The original developer's assumption would be that there's one kind of EventReader and one kind of ConfigFactory and one kind of Config, etc. That idea is spread across (no joke) 60+ repositories. I just want to _new_ one Kafka consumer with config, and _new_ another consumer with a different config.*