web-dev-qa-db-fra.com

Client Spring Cloud Config sans démarrage Spring

Une application Web Spring existante est déployée en tant que fichier WAR dans Amazon Elastic Beanstalk. Actuellement, nous chargeons les fichiers de propriétés sous forme de ressources http afin de nous fournir une source unique de résolution de configuration d'espace réservé pour les propriétés. J'envisage de le remplacer par le nouveau serveur de configuration cloud Spring afin de nous donner les avantages du contrôle de version git, etc. 

Cependant, la documentation ( http://cloud.spring.io/spring-cloud-config/spring-cloud-config.html ) ne semble décrire qu'une application cliente Spring Boot. Est-il possible de configurer Spring Cloud Config Client dans une application Web existante? Dois-je configurer manuellement le contexte de l'application parent Bootstrap, etc.? Existe-t-il des exemples? Notre configuration printanière actuelle est basée sur XML.

29
David Geary

Tout ce qui "fonctionne" avec Spring Boot n’est en réalité qu’une configuration. Ce n'est qu'une application de printemps en fin de journée. Donc, je pense que vous pouvez probablement tout configurer manuellement, ce que Boot fait automatiquement pour vous, mais je ne suis au courant de personne qui ait réellement essayé cet angle particulier. La création d'un contexte d'application d'amorçage est certainement l'approche préférée, mais selon votre cas d'utilisation, vous pouvez le faire fonctionner avec un seul contexte si vous vous assurez que les localisateurs de source de propriété sont exécutés suffisamment tôt.

Les applications non Spring (ou Spring Spring) peuvent accéder aux fichiers texte ou binaires du serveur de configuration. Par exemple. au printemps, vous pourriez utiliser un @PropertySource avec un emplacement de ressource qui était une URL, comme http://configserver/{app}/{profile}/{label}/application.properties ou http://configserver/{app}-{profile}.properties. Tout est couvert dans le guide de l'utilisateur.

3
Dave Syer

J'ai des exigences similaires J'ai une application Web qui utilise la configuration XML Spring pour définir des beans. La valeur des propriétés est stockée dans des fichiers .property. Il est impératif que la configuration soit chargée à partir du disque dur pendant le développement et d'un serveur Spring Cloud Config dans l'environnement de production.

Mon idée est d'avoir deux définitions pour le PropertyPlaceholderConfigurer; le premier sera utilisé pour charger la configuration à partir du disque dur:

        <bean id="resources" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" doc:name="Bean">
        <property name="locations">
            <list>
                <value>dcm.properties</value>
                <value>post_process.properties</value>
            </list>
        </property>
    </bean>

Le second chargera les .properties à partir du serveur Spring Config:

    <bean id="resources" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer" doc:name="Bean">
        <property name="locations">
            <list>
                <value>http://localhost:8888/trunk/dcm-qa.properties</value>
            </list>
        </property>
    </bean>
3
Radwan Nizam

Référencé: https://wenku.baidu.com/view/493cf9eba300a6c30d229f49.html

Racine WebApplicationContext et le servlet WebApplicationContext utilise Environment et initialise PropertySources en fonction du profil de ressort. Pour les applications de démarrage non-printanières, nous devons les personnaliser pour obtenir les propriétés de Config Server et actualiser les beans chaque fois qu’une propriété est modifiée. Vous trouverez ci-dessous les modifications apportées pour que la configuration fonctionne dans SpringMVC. Vous aurez également besoin d'une propriété système pour spring.profile.active

  1. Créez un CustomBeanFactoryPostProcessor et définissez lazyInit sur toutes les définitions de beans sur true pour initialiser tous les beans paresseusement, c.-à-d. Que les beans sont initialisés uniquement sur demande.

    @Component
    public class AddRefreshScopeProcessor implements BeanFactoryPostProcessor, ApplicationContextAware {
    
    private static ApplicationContext applicationContext;
    
    @SuppressWarnings("unchecked")
    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
    
        String[] beanNames = applicationContext.getBeanDefinitionNames();
        for(int i=0; i<beanNames.length; i++){
            BeanDefinition beanDef = beanFactory.getBeanDefinition(beanNames[i]);
            beanDef.setLazyInit(true);
            beanDef.setScope("refresh");
        }
    }
    
    @Override
    public void setApplicationContext(ApplicationContext context)
            throws BeansException {
        applicationContext = context;
    }
    
    /**
     * Get a Spring bean by type.
     * 
     * @param beanClass
     * @return
     */
    public static <T> T getBean(Class<T> beanClass) {
        return applicationContext.getBean(beanClass);
    }
    
    /**
     * Get a Spring bean by name.
     * 
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName) {
        return applicationContext.getBean(beanName);
      }
    }
    
  2. Créez une classe personnalisée qui étend StandardServletEnvironment et redéfinit la méthode initPropertySources pour charger des PropertySources supplémentaires. (depuis le serveur de configuration)

     public class CloudEnvironment extends StandardServletEnvironment {
    
      @Override
        public void initPropertySources(ServletContext servletContext, ServletConfig servletConfig) {
     super.initPropertySources(servletContext,servletConfig);
     customizePropertySources(this.getPropertySources());
       }
    
    @Override
      protected void customizePropertySources(MutablePropertySources propertySources) {
        super.customizePropertySources(propertySources);
        try {
          PropertySource<?> source = initConfigServicePropertySourceLocator(this);
          propertySources.addLast(source);
    
        } catch (
    
        Exception ex) {
          ex.printStackTrace();
        }
      }
    
      private PropertySource<?> initConfigServicePropertySourceLocator(Environment environment) {
    
        ConfigClientProperties configClientProperties = new ConfigClientProperties(environment);
        configClientProperties.setUri("http://localhost:8888");
        configClientProperties.setProfile("dev");
        configClientProperties.setLabel("master");
        configClientProperties.setName("YourApplicationName");
    
        System.out.println("##################### will load the client configuration");
        System.out.println(configClientProperties);
    
        ConfigServicePropertySourceLocator configServicePropertySourceLocator =
            new ConfigServicePropertySourceLocator(configClientProperties);
    
        return configServicePropertySourceLocator.locate(environment);
        }
    
      }
    
  3. Créez un ApplicatonContextInitializer personnalisé et substituez la méthode initialize pour définir l'environnement personnalisé au lieu de StandardServletEnvironment.

    public class ConfigAppContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        applicationContext.setEnvironment(new CloudEnvironment());
      }
    }
    
  4. Modifiez web.xml pour utiliser cet initialiseur de contexte personnalisé pour le contexte d'application et le contexte de servlet.

    <servlet>
        <servlet-name>dispatcher</servlet-name>
            <servlet-class>
                org.springframework.web.servlet.DispatcherServlet
            </servlet-class>
        <init-param>
            <param-name>contextInitializerClasses</param-name>
            <param-value>com.my.context.ConfigAppContextInitializer</param-value>
        </init-param>
        <load-on-startup>1</load-on-startup>
    </servlet>
    
    
    <servlet-mapping>
        <servlet-name>dispatcher</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>
    
    <listener>
     <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    </listener>
    
    <context-param>
        <param-name>contextInitializerClasses</param-name>
        <param-value>com.my.context.ConfigAppContextInitializer</param-value>
    </context-param>
    
    <context-param>
        <param-name>contextConfigLocation</param-name>
        <param-value>/WEB-INF/dispatcher-servlet.xml</param-value>
    </context-param>
    

  5. Pour actualiser les beans créés par un noeud final d'actualisation, vous devez également actualiser le contexte de l'application.

    @Controller
    public class RefreshController {
    
    @Autowired
    private RefreshAppplicationContext refreshAppplicationContext;
    
    @Autowired
    private RefreshScope refreshScope;
    
    @RequestMapping(path = "/refreshall", method = RequestMethod.GET)
    public String refresh() {
        refreshScope.refreshAll();
        refreshAppplicationContext.refreshctx();
        return "Refreshed";
    }
    

    }

RefreshAppplicationContext.Java

@Component
public class RefreshAppplicationContext implements ApplicationContextAware {

    private ApplicationContext applicationContext;
    public void setApplicationContext(ApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }


    public void refreshctx(){
        ((XmlWebApplicationContext)(applicationContext)).refresh();
    }
}
2
Grinish Nepal

Poster comme réponse parce que je n'ai pas assez de points pour commenter l'excellente réponse de Dave Syer. Nous avons mis en œuvre sa solution proposée en production et cela fonctionne comme prévu. Nos applications les plus récentes sont écrites à l'aide de Boot, alors que nos applications héritées utilisent Spring, mais pas de démarrage. Nous avons pu utiliser Spring Cloud Config pour créer un service de propriété qui sert des propriétés à la fois. Les changements étaient minimes. J'ai déplacé les fichiers de propriétés hérités du fichier war vers le référentiel git du service de propriétés et modifié la définition de la propriété d'une référence de chemin de classe en une URL comme décrit par Dave. C'était facile et efficace.

<util:properties id="envProperties" location="https://properties.me.com/property-service/services-#{envName}.properties" />
<context:property-placeholder properties-ref="envProperties" ignore-resource-not-found="true" ignore-unresolvable="true" order="0" />
<util:properties id="defaultProperties" location="https://properties.me.com/property-service/services-default.properties" />
<context:property-placeholder properties-ref="defaultProperties" ignore-resource-not-found="true" ignore-unresolvable="true" order="10" />
1
Tschuss

J'ai trouvé une solution pour utiliser spring-cloud-zookeeper sans Spring Boot, basée sur l'idée proposée ici https://wenku.baidu.com/view/493cf9eba300a6c30d229f49.html

Il doit être facilement mis à jour pour répondre à vos besoins et à l'aide d'un serveur Spring Cloud Config (la classe CloudEnvironement doit être mise à jour pour charger le fichier à partir du serveur au lieu de Zookeeper).

Commencez par créer une classe CloudEnvironement qui créera un PropertySource (ex de Zookeeper):

CloudEnvironement.Java

  public class CloudEnvironment extends StandardServletEnvironment { 

  @Override 
  protected void customizePropertySources(MutablePropertySources propertySources) { 
    super.customizePropertySources(propertySources); 
    try { 
      propertySources.addLast(initConfigServicePropertySourceLocator(this)); 
    } 
    catch (Exception ex) { 
      logger.warn("failed to initialize cloud config environment", ex); 
    } 
  } 

  private PropertySource<?> initConfigServicePropertySourceLocator(Environment environment) { 
    ZookeeperConfigProperties configProp = new ZookeeperConfigProperties(); 
    ZookeeperProperties props = new ZookeeperProperties(); 
    props.setConnectString("myzookeeper:2181"); 
    CuratorFramework fwk = curatorFramework(exponentialBackoffRetry(props), props); 
    ZookeeperPropertySourceLocator propertySourceLocator = new ZookeeperPropertySourceLocator(fwk, configProp); 
    PropertySource<?> source= propertySourceLocator.locate(environment); 
    return source ; 
  } 

  private CuratorFramework curatorFramework(RetryPolicy retryPolicy, ZookeeperProperties properties) { 
    CuratorFrameworkFactory.Builder builder = CuratorFrameworkFactory.builder(); 
    builder.connectString(properties.getConnectString()); 
    CuratorFramework curator = builder.retryPolicy(retryPolicy).build(); 
    curator.start(); 
    try { 
      curator.blockUntilConnected(properties.getBlockUntilConnectedWait(), properties.getBlockUntilConnectedUnit()); 
    } 
    catch (InterruptedException e) { 
      throw new RuntimeException(e); 
    } 
    return curator; 
  } 

  private RetryPolicy exponentialBackoffRetry(ZookeeperProperties properties) { 
    return new ExponentialBackoffRetry(properties.getBaseSleepTimeMs(), 
        properties.getMaxRetries(), 
        properties.getMaxSleepMs()); 
  } 

}

Créez ensuite une classe XmlWebApplicationContext personnalisée: elle permettra de charger le PropertySource à partir de Zookeeper lorsque votre application Web démarre et remplace la magie d’amorçage de Spring Boot:

MyConfigurableWebApplicationContext.Java 

public class MyConfigurableWebApplicationContext extends XmlWebApplicationContext { 

  @Override 
  protected ConfigurableEnvironment createEnvironment() { 
    return new CloudEnvironment(); 
  } 
}

Enfin, dans votre fichier web.xml, ajoutez le paramètre de contexte suivant pour utiliser votre classe MyConfigurableWebApplicationContext et amorcer votre CloudEnvironement.

<context-param>           
      <param-name>contextClass</param-name> 
      <param-value>com.kiabi.config.MyConfigurableWebApplicationContext</param-value> 
    </context-param> 

Si vous utilisez une configuration de fichier de propriétés standard, celle-ci doit quand même être chargée afin que vous puissiez avoir les propriétés à la fois dans un fichier local et dans Zookeeper.

Pour que tout cela fonctionne, vous devez avoir les fichiers spring-cloud-starter-zookeeper-config et curator-framework dans votre chemin de classe avec leur dépendance, si vous utilisez maven, vous pouvez ajouter ce qui suit à votre pom.xml

<dependencyManagement>
        <dependencies>
            <dependency>
                <groupId>org.springframework.cloud</groupId>
                <artifactId>spring-cloud-zookeeper-dependencies</artifactId>
                <version>1.1.1.RELEASE</version>
                <type>pom</type>
                <scope>import</scope>
            </dependency>
        </dependencies>
    </dependencyManagement>

    <dependencies>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-zookeeper-config</artifactId>
        </dependency>
        <dependency>
            <groupId>org.Apache.curator</groupId>
            <artifactId>curator-framework</artifactId>
        </dependency>
    </dependencies>
1
loicmathieu