Jednou z největších výhod Springu je bezesporu podpora anotací, kterými se můžeme vyhnout zdlouhavé konfiguraci pomocí XML. Jak ale vložit property z externího souboru do anotované třídy bez toho, aniž bychom duplikovali konfiguraci?

Ačkoliv jsou možnosti autowiringu ve Springu velké, na autowiring vlastností z externího souboru se jaksi zapomnělo (a vypadá to, že ani verze 3.0 tuto podporu nepřinese). V našich projektech ovšem právě takovéto properties velmi často používáme, a tak pro nás bylo důležité nalézt řešení, které by nám pomohlo tento problém vyřešit.

Naštěstí jsme nemuseli hledat dlouho – řešení již vymyslel Alexander V. Zinin. Jak tedy na to? Řešení spočívá ve vytvoření vlastní třídy rozšiřující PropertyPlaceholderConfigurer, která bude vkládat properties na místo naší speciální anotace. To celé se bude dít po instanciaci beany.

Nejprve vytvoříme novou anotaci (name udává jméno property, kterou budeme chtít do naší třídy vložit):


@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface PropertyAutowired {

    String name();
}

Dále je třeba vytvořit již zmiňované rozšíření třídy PropertyPlaceholderConfigurer:


public class CustomPropertyPlaceholderConfigurer extends PropertyPlaceholderConfigurer {

    private Properties props;

    protected void loadProperties(Properties props) throws IOException {
        super.loadProperties(props);
        this.props = props;
    }

    public Properties getProps() {
        return props;
    }
}

Dálším krokem je vytvoření třídy, která rozšiřuje InstantiationAwareBeanPostProcessorAdapter a která se bude starat o samotné vložení property po instanciaci beany:


@Component
public class PropertyInjectBeanPostProcessor extends InstantiationAwareBeanPostProcessorAdapter {

    @Autowired
    private CustomPropertyPlaceholderConfigurer propertyPlaceholderConfigurer;

    private SimpleTypeConverter typeConverter = new SimpleTypeConverter();

    public boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
        findPropertyAutowiringMetadata(bean);
        return true;
    }

    private void findPropertyAutowiringMetadata(final Object bean) {
        ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {

            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                PropertyAutowired annotation = field.getAnnotation(PropertyAutowired.class);
                if (annotation != null) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        throw new IllegalStateException("PropertyAutowired annotation is not " +
                                "supported on static fields");
                    }

                    Object strValue = propertyPlaceholderConfigurer.getProps().get(annotation.name());

                    if (strValue != null) {
                        Object value = typeConverter.convertIfNecessary(strValue, field.getType());
                        ReflectionUtils.makeAccessible(field);
                        field.set(bean, value);
                    }
                }
            }
        });
    }
}

Nakonec zbývá upravit definici bean propertyPlaceholderConfigurer v XML konfiguraci Springu, která má za úkol spravovat properties z externího souboru (v našem případě config.properties):


<bean id="propertyPlaceholderConfigurer" 
    class="cz.foo.CustomPropertyPlaceholderConfigurer" autowire="byName">
    <property name="locations">
        <list>
            <value>/WEB-INF/config.properties</value>
        </list>
    </property>
    <property name="fileEncoding" value="utf-8" />
    <property name="ignoreUnresolvablePlaceholders" value="true" />
</bean>

Nyní nám již zbývá použít námi definovanou anotaci pro vložení požadované property:


@Controller("fooController")
public class FooController extends ParameterizableViewController {

    @PropertyAutowired(name = "example.param")
    private String exampleParameter;
    
    ...
}


Tímto článkem tedy děkujeme panu Zininovi za to, že vymyslel tak elegantní řešení. Budeme rádi, když nám v diskuzi popíšete vaše zkušenosti s anotacemi a úskalí, na která jste narazili vy.