Ehcache Fichier de configuration

De EjnTricks

Le framework Ehcache se configure à l'aide d'un fichier de paramètre au format XML, fichier ehcache.xml.

Traditionnellement le fichier est placé dans le classpath de l'application et tout se déroule correctement. Or dans le cadre d'un projet, il a été demandé de placer ce fichier dans un répertoire qui ne figurait pas dans le classpath. De nombreuses discussions sont visibles sur internet concernant cet emplacement. Cependant l'utilisation est principalement faite avec Spring, qui propose des éléments de configuration, masquant le fonctionnement de Ehcache.

Cet article présente comment il est possible de spécifier l'emplacement du fichier de configuration au démarrage du programme.

Attention, cette analyse a été réalisée sur la version 2.5.1.

Hand-icon.png Votre avis

Nobody voted on this yet

 You need to enable JavaScript to vote


Study icon.png Etude

Une petite étude des sources va permettre de comprendre rapidement le mode de fonctionnement. Le projet étant OpenSource, cela va être particulièrement simple. En fait, il suffit de chercher la chaîne ehcache.xml, le fichier recherché par défaut, dans les sources et la classe net.sf.ehcache.config.ConfigurationFactory ressort rapidement.

Dans un premier temps, nous y trouvons deux constantes intéressantes.

    private static final String DEFAULT_CLASSPATH_CONFIGURATION_FILE = "/ehcache.xml";
    private static final String FAILSAFE_CLASSPATH_CONFIGURATION_FILE = "/ehcache-failsafe.xml";

Il suffit donc de chercher où sont elles utilisées et du code statique, exécuté au chargement de la classe, ressort.

    /**
     * Configures a bean from an XML file in the classpath.
     */
    public static Configuration parseConfiguration() throws CacheException {
        ClassLoader standardClassloader = ClassLoaderUtil.getStandardClassLoader();
        URL url = null;
        if (standardClassloader != null) {
            url = standardClassloader.getResource(DEFAULT_CLASSPATH_CONFIGURATION_FILE);
        }
        if (url == null) {
            url = ConfigurationFactory.class.getResource(DEFAULT_CLASSPATH_CONFIGURATION_FILE);
        }
        if (url != null) {
            LOG.debug("Configuring ehcache from ehcache.xml found in the classpath: " + url);
        } else {
            url = ConfigurationFactory.class.getResource(FAILSAFE_CLASSPATH_CONFIGURATION_FILE);

            LOG.warn("No configuration found. Configuring ehcache from ehcache-failsafe.xml "
                    + " found in the classpath: {}", url);

        }
        Configuration configuration = parseConfiguration(url);
        configuration.setSource(ConfigurationSource.getConfigurationSource());
        return configuration;
    }

Cependant, ce code ne présente aucune configuration possible. Or, la fonction exécute le même type de fonction acceptant une URL en argument.

    /**
     * Configures a bean from an XML file available as an URL.
     */
    public static Configuration parseConfiguration(final URL url) throws CacheException {
        LOG.debug("Configuring ehcache from URL: {}", url);
        Configuration configuration;
        InputStream input = null;
        try {
            input = url.openStream();
            configuration = parseConfiguration(input);
        } catch (Exception e) {
            throw new CacheException("Error configuring from " + url + ". Initial cause was " + e.getMessage(), e);
        } finally {
            try {
                if (input != null) {
                    input.close();
                }
            } catch (IOException e) {
                LOG.error("IOException while closing configuration input stream. Error was " + e.getMessage());
            }
        }
        configuration.setSource(ConfigurationSource.getConfigurationSource(url));
        return configuration;
    }

En recherchant les appels à cette dernière, deux classes peuvent ressortir net.sf.ehcache.hibernate.SingletonEhCacheProvider et net.sf.ehcache.hibernate.EhCacheProvider.

Pour la première, la fonction start présente l'utilisation d'une variable système.

    /**
     * The Hibernate system property specifying the location of the ehcache configuration file name.
     * <p/
     * If not set, ehcache.xml will be looked for in the root of the classpath.
     * <p/>
     * If set to say ehcache-1.xml, ehcache-1.xml will be looked for in the root of the classpath.
     */
    public static final String NET_SF_EHCACHE_CONFIGURATION_RESOURCE_NAME = "net.sf.ehcache.configurationResourceName";

    /**
     * Callback to perform any necessary initialization of the underlying cache implementation
     * during SessionFactory construction.
     * <p/>
     *
     * @param properties current configuration settings.
     */
    public final void start(Properties properties) throws CacheException {
        String configurationResourceName = null;
        if (properties != null) {
            configurationResourceName = (String) properties.get(NET_SF_EHCACHE_CONFIGURATION_RESOURCE_NAME);
        }
        if (configurationResourceName == null || configurationResourceName.length() == 0) {
            manager = CacheManager.create();
            referenceCount++;
        } else {
            URL url;
            try {
                url = new URL(configurationResourceName);
            } catch (MalformedURLException e) {
                if (!configurationResourceName.startsWith("/")) {
                    configurationResourceName = "/" + configurationResourceName;
                        LOG.debug("prepending / to {}. It should be placed in the rootof the classpath rather than in a package.", 
                                configurationResourceName);
                }
                url = loadResource(configurationResourceName);
            }
            final Configuration configuration = ConfigurationFactory.parseConfiguration(url);
            manager = CacheManager.create(overwriteCacheManagerIfConfigured(configuration, properties));
            referenceCount++;
        }
        mbeanRegistrationHelper.registerMBean(manager, properties);
    }

Pour la deuxième, le même type de fonctionnement est utilisé.

    /**
     * The Hibernate system property specifying the location of the ehcache configuration file name.
     * <p/>
     * If not set, ehcache.xml will be looked for in the root of the classpath.
     * <p/>
     * If set to say ehcache-1.xml, ehcache-1.xml will be looked for in the root of the classpath.
     */
    public static final String NET_SF_EHCACHE_CONFIGURATION_RESOURCE_NAME = "net.sf.ehcache.configurationResourceName";

    /**
     * Callback to perform any necessary initialization of the underlying cache implementation
     * during SessionFactory construction.
     * <p/>
     *
     * @param properties current configuration settings.
     */
    public final void start(Properties properties) throws CacheException {
        if (manager != null) {
            LOG.warn("Attempt to restart an already started EhCacheProvider. Use sessionFactory.close() " +
                    " between repeated calls to buildSessionFactory. Using previously created EhCacheProvider." +
                    " If this behaviour is required, consider using SingletonEhCacheProvider.");
            return;
        }
        try {
            String configurationResourceName = null;
            if (properties != null) {
                configurationResourceName = (String) properties.get(NET_SF_EHCACHE_CONFIGURATION_RESOURCE_NAME);
            }
            if (configurationResourceName == null || configurationResourceName.length() == 0) {
                manager = new CacheManager();
            } else {
                URL url;
                try {
                    url = new URL(configurationResourceName);
                } catch (MalformedURLException e) {
                    url = loadResource(configurationResourceName);
                }
                manager = new CacheManager(overwriteCacheManagerIfConfigured(HibernateUtil.loadAndCorrectConfiguration(url), properties));
            }
            mbeanRegistrationHelper.registerMBean(manager, properties);
        } catch (net.sf.ehcache.CacheException e) {
            if (e.getMessage().startsWith("Cannot parseConfiguration CacheManager. Attempt to create a new instance of " +
                    "CacheManager using the diskStorePath")) {
                throw new CacheException("Attempt to restart an already started EhCacheProvider. Use sessionFactory.close() " +
                        " between repeated calls to buildSessionFactory. Consider using SingletonEhCacheProvider. Error from " +
                        " ehcache was: " + e.getMessage());
            } else {
                throw e;
            }
        }
    }


Cependant, elles sont dépréciées pour cette version. Une nouvelle recherche sur la chaîne de caractère net.sf.ehcache.configurationResourceName permet d'identifier la classe net.sf.ehcache.hibernate.AbstractEhcacheRegionFactory, qui contient deux extensions.

  • net.sf.ehcache.hibernate.EhCacheRegionFactory, dont la méthode start est la suivante.
    /**
     * {@inheritDoc}
     */
    public void start(Settings settings, Properties properties) throws CacheException {
        this.settings = settings;
        if (manager != null) {
            LOG.warn("Attempt to restart an already started EhCacheRegionFactory. Use sessionFactory.close() " +
                    " between repeated calls to buildSessionFactory. Using previously created EhCacheRegionFactory." +
                    " If this behaviour is required, consider using SingletonEhCacheRegionFactory.");
            return;
        }

        try {
            String configurationResourceName = null;
            if (properties != null) {
                configurationResourceName = (String) properties.get(NET_SF_EHCACHE_CONFIGURATION_RESOURCE_NAME);
            }
            if (configurationResourceName == null || configurationResourceName.length() == 0) {
                Configuration configuration = ConfigurationFactory.parseConfiguration();
                manager = new CacheManager(configuration);
            } else {
                URL url;
                try {
                    url = new URL(configurationResourceName);
                } catch (MalformedURLException e) {
                    url = loadResource(configurationResourceName);
                }
                Configuration configuration = HibernateUtil.loadAndCorrectConfiguration(url);
                manager = new CacheManager(HibernateUtil.overwriteCacheManagerIfConfigured(configuration, properties));
            }
            mbeanRegistrationHelper.registerMBean(manager, properties);
        } catch (net.sf.ehcache.CacheException e) {
            if (e.getMessage().startsWith("Cannot parseConfiguration CacheManager. Attempt to create a new instance of " +
                    "CacheManager using the diskStorePath")) {
                throw new CacheException("Attempt to restart an already started EhCacheRegionFactory. " +
                        "Use sessionFactory.close() between repeated calls to buildSessionFactory. " +
                        "Consider using SingletonEhCacheRegionFactory. Error from ehcache was: " + e.getMessage());
            } else {
                throw new CacheException(e);
            }
        }
    }
  • net.sf.ehcache.hibernate.SingletonEhCacheRegionFactory
    /**
     * {@inheritDoc}
     */
    public void start(Settings settings, Properties properties) throws CacheException {
        try {
            String configurationResourceName = null;
            if (properties != null) {
                configurationResourceName = (String) properties.get(NET_SF_EHCACHE_CONFIGURATION_RESOURCE_NAME);
            }
            if (configurationResourceName == null || configurationResourceName.length() == 0) {
                manager = CacheManager.create();
                REFERENCE_COUNT.incrementAndGet();
            } else {
                URL url;
                try {
                    url = new URL(configurationResourceName);
                } catch (MalformedURLException e) {
                    if (!configurationResourceName.startsWith("/")) {
                        configurationResourceName = "/" + configurationResourceName;
                            LOG.debug("prepending / to {}. It should be placed in the root of the classpath rather than in a package.",
                                    configurationResourceName);
                    }
                    url = loadResource(configurationResourceName);
                }
                Configuration configuration = HibernateUtil.loadAndCorrectConfiguration(url);
                manager = CacheManager.create(HibernateUtil.overwriteCacheManagerIfConfigured(configuration, properties));
                REFERENCE_COUNT.incrementAndGet();
            }
            mbeanRegistrationHelper.registerMBean(manager, properties);
        } catch (net.sf.ehcache.CacheException e) {
          throw new CacheException(e);
        }
    }



Finalement, le chargement de la configuration est rapidement compréhensible et se déroule en quelques étapes.

  • Récupération de la variable d'environnement net.sf.ehcache.configurationResourceName. Si celle-ci est renseignée, elle est utilisée pour charger la ressource à l'aide de la classe java.net.URL.
  • Si la valeur fournie ne correspond pas à une URL, c'est considéré comme une resource devant être disponible dans le classpath, en forçant le premier caractère avec /.
  • Si la variable d'environnement n'est pas positionnée, le fichier ehcache.xml est recherché dans le classpath à l'aide des contructeurs de classe par défaut.


Warning-icon.png Conclusion

Afin de spécifier un emplacement alternatif pour le fichier de configuration, il suffit de positionner la variable d'environnement net.sf.ehcache.configurationResourceName. Cependant, cette valeur est chargée à l'aide de la classe java.net.URL. Il est donc impératif de préfixer l'emplacement du fichier par file:. Sinon, le fichier sera recherché dans le classpath de l'application.

Ainsi, la valeur /var/opt/study/ehcache/myehcache.xml entraînera la recherche du fichier myehcache.xml dans le paquet var.opt.study.ehcache. Il faut donc spécifier file:/var/opt/study/ehcache/myehcache.xml.


Examples-icon.png Exemple implémentation

Les variables d'environnement peuvent être positionnées dans la ligne de commande Java pour démarrer l'application. Dans le cadre de l'exemple précédant, celle-ci serait alors la suivante.

#java -Dnet.sf.ehcache.configurationResourceName="file:/var/opt/study/ehcache/myehcache.xml" -cp ... net.jouvinio.study.ehcache.MyCache

net.jouvinio.study.ehcache.MyCache serait la classe de démarrage.