Naplňte mi inštančné premenné beanu z properties súboru [v Springu]!

Nebolo by skvelé, keby sme vedeli konfigurovať springácke beany tak, že hodnoty ich inštančných premenných sa natiahnu z properties súboru?

Napr. mám úžasnú triedu pre “aplikáciu”:

package sk.upjs.ics.novotnyr;

public class ApplicationBean {
    private int version;

    private String title;

    private boolean deployedOnCloud;

    public int getVersion() {
        return version;
    }

    public String getTitle() {
        return title;
    }

    public boolean isDeployedOnCloud() {
        return deployedOnCloud;
    }

    @Override
    public String toString() {
        return "ApplicationBean [version=" + version + ", title=" + title
                + ", deployedOnCloud=" + deployedOnCloud + "]";
    }   
}

Atribúty version, title a deployedOnCloud by sa mohli automaticky načítať z properties súboru application.properties, ktorý vyzerá nasledovne:

app.version=1
app.title=Spring Test
app.deployedOnCloud=true

V bežnej aplikácii by to znamenalo kadejaké načítavanie properties, riešenie problémov s jeho umiestnením, a kadečo.

V Springu je to ízy

Anotácie triedy

Stačí anotovať bean ako springácky @Component a využiť ďalšiu fintivú anotáciu @Value. Tá umožňuje nastavovať hodnoty inštančným premenným na základe pestrých pravidiel.

package sk.upjs.ics.novotnyr;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

@Component
public class ApplicationBean {
    @Value("${app.version}")
    private int version;

    @Value("${app.title}")
    private String title;

    @Value("${app.deployedOnCloud}")
    private boolean deployedOnCloud;

    public int getVersion() {
        return version;
    }

    public String getTitle() {
        return title;
    }

    public boolean isDeployedOnCloud() {
        return deployedOnCloud;
    }

    @Override
    public String toString() {
        return "ApplicationBean [version=" + version + ", title=" + title
                + ", deployedOnCloud=" + deployedOnCloud + "]";
    }   
}

Každá inštančná premenná vyfasovala svoju vlastnú @Value s parametrom. Jeho hodnota je v tvare známom z make/antovských premenných. Napr. ${app.version} sa má vyhodnotiť na hodnotu premennej app.version. Všimnite si, ako sa jej názov zhoduje s názvom kľúča v properties súbore.

Nastavenie aplikačného kontextu

Teraz treba presvedčiť Spring, aby vedel, že “tieto hodnoty” sa načítajú “z tohto súboru.”

Vyrieši sa to jedným riadkom v súbore aplikačného kontextu. Stačí zapnúť automatické vyhodnocovanie propertoidných premenných:

<context:property-placeholder location="classpath:/sk/upjs/ics/novotnyr/application.properties"/>

Tento element priamo umožňuje nastaviť cestu k properties súboru pomocou repertoáru springovských resources. V tomto príklade sa súbor application.properties nachádza v CLASSPATHe hneď vedľa triedy.

Mimochodom, budeme predpokladať automatické vyhľadávanie beanov a ich registráciu v springáckom kontexte, čo rieši element <context:component-scan>.

Celý applicationContext.xml vyzerá:

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
    xmlns:util="http://www.springframework.org/schema/util"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
           http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context-2.5.xsd
            http://www.springframework.org/schema/util
           http://www.springframework.org/schema/util/spring-util-3.0.xsd           
            ">

    <context:component-scan base-package="sk.upjs.ics.novotnyr" />
    <context:property-placeholder location="classpath:/sk/upjs/ics/novotnyr/application.properties"/>
</beans>

Konfigurák môžeme tiež buchnúť vedľa triedy ApplicationBean.

Test!

Celé to môžeme rozbehnúť takto:

package sk.upjs.ics.novotnyr;

import org.springframework.context.support.ClassPathXmlApplicationContext;

public class SpringTest {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml", SpringTest.class);
        ApplicationBean bean = ctx.getBean(ApplicationBean.class);
        System.out.println(bean);
    }
}

A hľa! Výstupom bude (po ignorovaní inicializácie Springu):

ApplicationBean [version=1, title=Spring Test, deployedOnCloud=true]

Čo s chýbajúcimi hodnotami?

Niekedy sa stane, že názov premennej uvedenej v ${...} sa jednoducho nenájde. Skúste si uviesť napr. toto:

@Value("${title}")
private String title;

Ak spustíte test, celé to zhavaruje s tonou výnimiek, kde prapôvodná príčina jest:

Caused by: java.lang.IllegalArgumentException: Could not resolve placeholder 'version' in string value [${title}]

Jedno z riešení je použiť implicitné hodnoty, ktoré možno uviesť za dvojbodku:

@Value("${app.title:N/A}")
private String title;

Ak sa náhodou nenájde premenná s názvom app.title, dosadí sa za ňu implicitná hodnota, v tomto prípade “N/A”.

Bonusovka na záver

Celé správanie je v skutočnosti v zodpovednosti triedy org.springframework.beans.factory.config.PropertyPlaceholderConfigurer. Tá je v Springu od nepamäti (teda pokiaľ nepamäť siaha do roku 2003).