Few weeks ago, I worked on an interesting task. I had to find out how to migrate my customer’s proprietary framework to Spring. The framework was quite similar to Spring, although there were some differences. They used combination of XML and annotation configuration. Every property had special annotation saying that it is a property. XML schema for configuration was generated based on this annotation. So basically you had XML configuration that said what to inject to the annotated fields. The problem was that they did not have set methods for the fields.
And of course, the customer did not want to change all their classes just to migrate to Spring. It meant that they needed configure all the beans using Spring, but inject the dependencies directly, not through set methods.
I know that it is ugly and not recommended but in this case I think I have good excuse. It is a customer’s requirement. The question of course is how to do it.
You can try to use Spring post-processor. If you register instance of InstantiationAwareBeanPostProcessor you can use method postProcessPropertyValues to manipulate with property values just before Spring attempts to inject them. So you can inject the values into fields by yourself. And it works. The trouble is that you have to do property instantiation and conversion by yourself too. I almost managed to do it when I encountered a big problem – inner beans. In Spring, you can define a bean inside another bean. In order to inject the inner bean to the outer bean, the inner bean has to be already instantiated. But postProcessPropertyValues
method is called before inner bean instantiation. So that is a blind alley.
OK, post-processor can not be used, let’s do it more hard-core. Spring has something called BeanWrapper
. It is used by Spring for setting bean properties. Great, that is the point where I can force Spring to inject into the fields. The only thing I need to do, is to inject my BeanWrapper implementation into Spring. There is only one small trouble. It is not possible to do it. Unsurprisingly, dependency injection does not work inside the dependency injection code. I tried hard, but did not find any simple and safe way how to replace default BeanWrapper implementation. (If you know how to do it, please let me know)
So, if it is not possible to force Spring to inject without set method, let’s give him what he is asking for. The final solution is simple. When instantiating beans, I do not instantiate the original class but a subclass that is generated in the runtime and has all needed set methods. For the class generation I use Javassist, it is simple and powerful. To make the integration with Spring as simple as possible, I use factory bean. So the Spring XML config file looks like this
<bean id="beanFactory" class="net.krecan.beanfactory.SetMethodGeneratingBeanFactory"/> <bean id="sampleClass" factory-bean="beanFactory" factory-method="createBean"> <constructor-arg value="net.krecan.beanfactory.Sample"/> <property name="textProperty" value="test.txt"/> <property name="intProperty" value="123"/> <property name="simpleSample"> <bean factory-bean="beanFactory" factory-method="createBean"> <constructor-arg value="net.krecan.beanfactory.SimpleSample"/> <property name="textProperty" value="hallo"/> </bean> </property> </bean>
Instead of providing class name in the bean definition, factory-bean attribute is used. Spring than calls method createBean on the factory bean with parameter taken from the constructor-arg element. It contains the class name of the bean. The result of the method call is used as a bean instance and all the properties are set by Spring.
Of course, you do not have to write it this way every time you want to instantiate this kind of bean. Simple custom namespace can be created in order to simplify the syntax.
In the future, when my customer realize that setters are a good thing, the factory can be easily replaced by a dummy implementation.
To reiterate, direct injection of dependencies from XML to the fields without using set methods is not supported by Spring. You can choose between XML based configuration using set methods or annotation based configuration. It is extremely hard to change default Spring behavior. And maybe it is a good thing. So if you need to do ugly XML based field injection, your only choice is to generate set methods in the runtime. Or maybe something else, but I have no idea what.