Accueil > Non classé > Une configuration pas aProxymative

Une configuration pas aProxymative

Introduction

La configuration d’une application grâce à des fichiers properties est une technique très souvent rencontrée dans le monde Java. Que ce soit pour configurer des utilisateurs (kermit/thefrog :D), des loggers ou pour des propriétés propres à une application, ils sont partout. Leur format est pratique (“clé=valeur” dans le cas le plus simple), ils se chargent aisément et se prêtent bien à l’internationalisation via les ResourceBundle.

Pourtant, ils ont aussi leurs contraintes :

  • il est facile de se tromper dans un nom de clé
  • en général on ne peut récupérer que des String, qu’il faut ensuite interpréter ou convertir à la main
  • c’est la galère dès qu’on veut renommer une propriété dans le fichier, il faut penser à changer toutes les références dans le code Java

Voyons comment améliorer ça avec une simple interface et un petit framework !

L’idée

Supposons que nous travaillions sur une application configurable via un fichier properties. Notre properties contient des clés/valeurs classiques :

1
ApplicationName=Hello World application

Nous allons représenter ce fichier properties dans le code Java grâce à une interface, avec un getter par clé. On veut que ça soit typesafe, et qu’on ne puisse pas faire une faute de frappe en écrivant le nom de la clé (par exemple Integer integer = config.getInteger("nubmer") si on utilise Apache commons-configuration).

1
2
3
public interface ApplicationConfig {
    public String getApplicationName();
}

L’idée des méthodes est donc bonne, si la méthode appelée n’existe pas dans l’interface, le compilateur nous le rappelle rapidement. Pour récupérer une valeur du properties, idéalement nous souhaitons utiliser un code de ce genre (du JUnit, pour l’exemple) :

1
2
3
4
5
@Test
public void simpleGetReturnsValue() {
    ApplicationConfig configuration = uneMethodeMysterieuseQuiRenvoieUneInstanceDApplicationConfig();
    assertEquals("Hello World application", configuration.getApplicationName());
}

L’implémentation

Quel est donc ce code mystérieux qui se caché derrière la méthode uneMethodeMysterieuseQuiAUnNomTropLong() ? Et pourquoi pas un dynamic proxy ? Le principe a déjà été présenté dans un précedent article traitant de dynamic finders en Java. L’idée est d’intercepter tous les appels aux méthodes de notre interface, en déduire un nom de clé correspondant et ainsi retrouver auto-magiquement la valeur désirée dans un fichier properties :).

Utilisation du Proxy

Voyons comment coder une implémentation naïve de notre concept :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
package com.excilys.blog.proxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ResourceBundle;

public enum ConfigurationFactory {

    INSTANCE;

    private static final String propertiesPath = "configuration/app-config";
    private static final String METHOD_PREFIX = "get";
    private final ResourceBundle resourceBundle;

    private ConfigurationFactory() {
        resourceBundle = ResourceBundle.getBundle(propertiesPath);
    }

    public <T> static T getConfiguration(Class<T> configClass) {
        return INSTANCE.proxify(configClass);
    }

    @SuppressWarnings("unchecked")
    private <T> T proxify(Class<T> configClass) {
        Object proxy = Proxy.newProxyInstance(configClass.getClassLoader(), new Class[]{configClass}, new InvocationHandler() {
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();

                if (methodName.startsWith(METHOD_PREFIX)) {
                    String key = methodName.substring(METHOD_PREFIX.length());

                    return resourceBundle.getString(key);
                }

                return null;
            }
        });

        return (T) proxy;
    }
}

Voici donc une factory (sous forme de singleton grâce à une enum) qui possède une unique méthode publique (et statique), qui prend en paramètre une classe et retourne une instance de cette classe. Du point de vue de l’utilisateur, pas besoin de se compliquer la vie en sachant que derrière les fagots un proxy est utilisé. Ce qu’on cherche à avoir, c’est une instance de notre ApplicationConfig pour pouvoir appeler les méthodes permettant de récupérer notre configuration.

De notre point de vue de fidèle lecteur du blog, on remarquera tout de même la méthode proxify(), qui comme son nom l’indique va créer un proxy autour de l’interface fournie. La méthode invoke() de l’InvocationHandler intercepte les appels aux méthodes de l’interface, et réagit s’il s’agit d’un getter. Dans ce cas, le nom de la clé est extrait depuis le nom de la méthode, et utilisé comme clé de recherche dans un ResourceBundle configuré pour accéder à notre fichier properties.

Si l’on revient dans notre test JUnit, on peut maintenant remplacer notre méthode mystérieuse :

1
2
3
4
5
@Test
public void simpleGetReturnsValue() {
    ApplicationConfig configuration = ConfigurationFactory.getConfiguration(ApplicationConfig.class);
    assertEquals("Hello World application", configuration.getApplicationName());
}

Un petit coup de triangle vert dans notre IDE, et hop !

Les limitations

Parfait, notre proof of concept fonctionne, mais puisque ce n’est qu’une démo, elle a forcément des limites :

  • les propriétés sont case sensitive et en général CamelCase (sauf si vous utilisez des underscores dans vos noms de méthdodes o_O)
  • ResourceBundle et Properties ne gèrent que des String, donc les méthodes de l’interface ne peuvent retourner que des String
  • le retour de la méthode est silencieux si aucune clé n’a été trouvée (ce qui en général est signe d’une configuration incomplète)
  • on est limités à des properties ; peut-être qu’on est des oufs et qu’on aimerait retourner n’importe quel objet récupéré dans un arbre JNDI par exemple :P
  • etc.

Le cadeau bonux

Reprenons notre PoC et ajoutons-y quelques concepts intéressants :

  • des method name transformers, qui peuvent transformer un nom de méthode tel que “getMaProperty” en “MaProperty”, “maProperty”, “ma.property”, “ma_property” etc pour s’adapter à d’autres conventions de nommage de clés
  • des type casters, capables de “caster” (ou transformer) une instance de String en une instance d’un quelconque autre objet (int, Long, Date, MonObjetCustom)
  • une annotation permettant de spécifier une valeur par défaut si la propriété n’a pas été trouvée, ainsi qu’une exception si jamais on ne peut trouver aucune valeur à retourner (même pas une valeur par défaut)
  • des configuration sources, permettant de répondre à ces deux questions : “connais-tu cette clé ?” et “donne-moi la valeur correspondant à cette clé” ; on peut ainsi charger la configuration depuis une autre source qu’un properties

Avec tout ça on obtient un début de framework permettant d’avoir une interface de ce type :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public interface ApplicationConfig {

    // Standard access
    public String getApplicationName();

    // Non-String return type
    public int getApplicationVersion();

    // User-defined key name
    @ConfigurationKey("developer.names")
    public List<String> getDevelopers();

    // Default value if not found in the source
    @Default("2012-12-21")
    public Date getDoomsDay();
}
1
2
3
4
application.name=My superb app
application.version=1
developer.names=Foo, Bar, Baz
# dooms.day is missing

Mais d’où te vient ce style fluide, cette technique ?

Je dois l’avouer, l’idée ne vient pas de moi, mais est sortie lors d’une discussion que j’ai eue avec un architecte durant ma mission actuelle. Cependant, elle a déjà été utilisée depuis quelques années par d’autres développeurs, qui ont créé des frameworks plus ou moins comple{t,xe}s. L’un de ces pionniers a même nommé la technique utilisée “PICA” (pour Proxied Interfaces Configured with Annotations).

Par curiosité et pour dépoussiérer mes connaissances sur les proxies et la réflexion en Java, j’ai commencé un framework reprenant toutes les concepts présentés auparavant. Il est disponible sur github: proxy-config.

Conclusion

Configuration made easy. Le code est type-safe, les appels à la configuration sont centralisés dans une ou plusieurs interfaces, et si le framework offre un peu de flexibilité il est possible d’ajouter facilement ses propres types de retour (via des converters) et ses propres sources de configuration (pour charger depuis une base de données au lieu d’un arbre JNDI, pour ceux qui suivent ;)).

Pour aller plus loin

Share
  1. Denis VAUMORON
    19/10/2012 à 20:51 | #1

    Une autre approche pour être sur d’éviter la faute de frappe entre le nom de la méthode et la clé de la propriété est celle d’android ou de play, la compilation de la configuration, poc est dispo là https://github.com/dvaumoron/compiluration

  2. 06/03/2015 à 16:46 | #2

    Une autre approche pour être sur d’éviter la faute de frappe entre le nom de la méthode et la clé de la propriété est celle d’android ou de play, la compilation de la configuration, poc est dispo là http://www.filmrally.com/

  1. Pas encore de trackbacks