Though called Dependency Injection, the book is much more than that: A guide to good practices in object-oriented programming. The authors analyze usual practices against the SOLID principles and others, such as Command Query Segregation (CQS) and Reused Abstraction Principle. It has many references to other books and blogs from where the ideas originate or where more in-depth discussion can be found. Though all the book is high quality, the cherry on the top is chapter 10, starting with an example of an all-too-common Service interface, how it violates 3 out of 5 SOLID principles, and how to correct this by refactoring it following those same principles and CQS, reaching a set of general interfaces for queries and commands that enable aspect oriented programming without the need of tooling (using Pure DI, i.e. dependency injection without a DI container). These set of interfaces for a command query responsibility segregation (CQRS) pattern seem to be somewhat known in the C# world, but not that common in Java as far as my experience goes.
However, though hesitant, I cannot give it 5 stars. In part because it is too C# centric, and longer than it needs to be. Part 4 consists of a long, repetitive comparison of 3 DI frameworks in C#. It could for the most part be removed, lowering the lenght of the book by around 150 pages. I can see it being useful for someone trying to choose which DI framework to use in C#, but to any other reader not needing to have an in-depth comparison of those 3 C# DI frameworks, it just bloats the book. For someone not coming from a C# background, reading through it was mostly tedious without much gain.
Aside from that, some other things merit discussion. The authors add guard clauses to constructor arguments in most examples at the beginning of the book. Though I agree with the spirit (attempting to disallow invalid state by prohibiting null for required dependencies), the guard clauses for constructors are mostly unnecessary and even counter-productive. Unnecessary because (though not strictly required) the most common is using a DI container, not pure DI, and DI containers usually don't inject null unless explicitly told to do so. For instance, in Java, Spring will throw an exception if there is no appropriate dependency to autowire, not inject null. You can inject null, but then you need to explicitly do so (e.g. in an XML configuration, using @Autowired(required = false), or in some other way). So, for the most part, the guard clause is redundant. Counter-productive because it can be annoying when testing. Sometimes you want to test some subset of the functionality, for which not all the dependencies are needed. In that case, simply instantiating the class with some null dependencies is totally fine, and can also communicate "this dependency doesn't matter for this particular use-case". If all are covered by guard clauses, then we are forced to instantiate those dependencies, which might simply be "noise" for a particular test. I first heard this argument from Miško Hevery in one of his "Google Clean Code" talks.
Another thing that caught my attention were the repeated mentions in Part 4 about enums being a code smell. I get the point of dispatching on different classes instead of having if-else-if chains based on enum constants, but a lot of the times doing so simply overcomplicates things or mixes responsibilities, while enums are a perfectly fine solution.
But such things aren't to be taken into consideration for an otherwise wonderful book. Overall, I highly recommend it to new to mid-range developers, maybe even experienced ones looking for a coherent and fresh view on commonly known OOP best practices.