Why doesn't Spring support direct field dependency injection (except for autowired)?
I am interested in direct field dependency injection. Traditionally, Spring supports both constructor injection (supplying arguments to constructors) and setter-based injection (calling setters on a call).
However, Spring is also capable of direct field injection (setting member fields of an object without a setter method), as evidenced by annotating fields with @Autowired
. Autowiri开发者_如何学Gong is limited to just "beans", so primitive values cannot be injected (although this can somewhat be circumvented by creating beans of class "java.lang.String" - this works, but has the normal caveats of autowiring.) In addition to this, Spring supports @Value
to directly set values to member fields from properties etc.
Yet, Spring does not allow properties to be directly set to member fields (without autowiring).
My question is: why?
It is obviously capable of doing so, so why doesn't it? Are there any big negative side-effects that prevent this? Or is the capability somehow limited so that only autowiring makes sense? Does it need some bigger hacks than calling setters?
Note that I do not wish to discuss the relative merits of having setters and getters in general, just the reasons why Spring has made this choice.
The @Autowired annotation uses reflection to make private fields accessible (see this related question). I can see three things why it isn't used in Spring configuration files.
- Since configuration happens by bean properties (getters and setters) you can't really tell - in the likely case that both exist - if you want to e.g. call setValue or set the member value.
- It breaks encapsulation. Your Spring configuration has no reason to know about private member variables. With an annotation that's ok since it is already right there in the source code.
- Security concerns. A more restrictive security manager might not allow making private fields accessible via reflection.
I think I found the answer myself. I went over to the Spring source code and saw how the features were actually implemented. This is what I found:
Setting properties via XML is probably the oldest part of Spring, and it relies very heavily on "java.beans" classes for introspection, property enumeration, etc. And quite obviously, those do not support field introspection at all. On top of this is the type conversion machinery which determines how the property value can be converted to a suitable value for the property in question. There are no neatly separable pieces here.
All the @Autowired etc. stuff is implemented in a BeanPostProcessor, which has it's own type matching mechanic, which has nothing to do with the type conversion. That is also why it only injects beans. Same thing pretty much for @Value, it is just something that is resolved on the spot there and has nothing to do with properties.
So, adding field injection support, for properties in particular, is not a trivial engineering effort as the parts of the code that do one or the other are pretty much completely separate.
This doesn't exactly answer "Why?", but I think this is a more compelling explanation as to why Spring hasn't added direct field dependency injection than the other explanations I've heard. Unless they have something fundamental against it (which I doubt, considering that they want to allow configuration of existing third party classes, not just JavaBeans), then it's just a matter of engineering effort to get the functionality in.
It does support this through JSR-250 @Resource annotation (in Spring 3.0+)
I personally prefer this to setter injection, and have mixed feelings with this when considering constructor injection. Here are the considerations that I have thought about FWIW:
1) Constructor injection is a nice way to self-document your bean dependencies (pro) but creates a lot of DRY violations: (a) private field, (b) constructor argument, (c) constructor code to set the field from the parameter, (d) the additional code in the bean configuration (either in @Configuration classes or in xml). That's a LOT of DRY violations just for the sake of some encapsulation purity, which I don't even care about. This is barely a violation of encapsulation -- but it does create a large dependency on some injection container that respects JSR-250 annotations (see next)
2) Creates a dependency on JSR-250 compliant container. I have mixed feelings about this. When I first heard about @Resource I wrote it off saying it will make my system harder to test. However, I ended up using spring in my tests anyways. I can still use mock beans like I would anyways so it was never really a problem. And the question is outside of testing, when would you actually want to reuse this. In my mind if you're designing the system to take advantage of a container, then embrace it and use it. Are the dry violations actually worth the flexibility of not running inside of a container? At least with JSR-250 annotations you could run in any JEE6 environment and get injection like you want it.
3) Can create some not so great debugging scenarios if you instantiate one outside of the container: i.e. you will get null pointer exceptions instead of something nice. This is a trade-off. I personally think its not horrible -- I mean if you get a NPE on a line with a @Resource then you quickly realize it wasn't injected.
精彩评论