37

我们有一个现有的 Spring Web 应用程序作为 WAR 文件部署到 Amazon Elastic Beanstalk 中。目前,我们将属性文件加载为 http 资源,以便为我们提供属性占位符配置解析的单一来源。我正在研究用新的 Spring Cloud 配置服务器替换它,以便为我们提供 git 版本控制等的好处。

但是文档(http://cloud.spring.io/spring-cloud-config/spring-cloud-config.html)似乎只描述了一个 Spring Boot 客户端应用程序。是否可以在现有 Web 应用程序中设置 Spring Cloud Config Client?我是否需要手动设置 Bootstrap 父应用程序上下文等 - 有没有这样的例子?我们当前的 spring 配置是基于 XML 的。

4

5 回答 5

7

参考:https ://wenku.baidu.com/view/493cf9eba300a6c30d229f49.html

Root WebApplicationContext并且Servlet WebApplicationContext使用 Environment 并根据 spring 配置文件初始化 PropertySources。对于非 Spring Boot 应用程序,我们需要自定义这些以从 Config Server 获取属性,并在属性更改时刷新 bean。以下是使配置在 SpringMVC 中工作所需的更改。您还需要一个系统属性spring.profile.active

  1. 创建一个CustomBeanFactoryPostProcessor并将lazyInit所有 bean 定义设置为 true 以延迟初始化所有 bean,即 bean 仅在请求时初始化。

    @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. 创建一个自定义类,扩展StandardServletEnvironment并覆盖initPropertySources加载其他 PropertySource(来自配置服务器)的方法。

     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. 创建一个自定义ApplicatonContextInitializer并覆盖initialize方法来设置custom Enviroment而不是StandardServletEnvironment.

    public class ConfigAppContextInitializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
    
    @Override
    public void initialize(ConfigurableApplicationContext applicationContext) {
        applicationContext.setEnvironment(new CloudEnvironment());
      }
    }
    
  4. 修改为对和web.xml使用此自定义上下文初始值设定项。application contextservlet context

    <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. 要刷新 bean 创建了一个刷新端点,您还需要刷新application Context.

    @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();
    }
}
于 2018-01-24T15:53:00.270 回答
5

我有类似的要求;我有一个使用 Spring XML 配置来定义一些 bean 的 Web 应用程序,属性的值存储在 .property 文件中。要求是开发时从硬盘加载配置,生产环境从Spring Cloud Config服务器加载。

我的想法是对 PropertyPlaceholderConfigurer 有两个定义;第一个将用于从硬盘加载配置:

        <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>

第二个将从 Spring Config Server 加载 .properties :

    <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>
于 2015-09-22T12:02:26.347 回答
2

与 Spring Boot 一起“正常工作”的一切实际上只不过是一些配置。归根结底,这只是一个 Spring 应用程序。因此,我相信您可能可以手动设置 Boot 自动为您执行的所有操作,但我不知道有人真正尝试过这个特定的角度。创建引导应用程序上下文当然是首选方法,但根据您的用例,如果您确保足够早地执行属性源定位器,您可能会使其与单个上下文一起工作。

非 Spring(或非 Spring Boot)应用程序可以访问配置服务器中的纯文本或二进制文件。例如,在 Spring 中,您可以将 a@PropertySource与作为 URL 的资源位置一起使用,例如http://configserver/{app}/{profile}/{label}/application.propertieshttp://configserver/{app}-{profile}.properties. 用户指南中涵盖了所有内容。

于 2015-02-07T15:44:36.467 回答
2

根据此处提供的想法https://wenku.baidu.com/view/493cf9eba300a6c30d229f49.html,我找到了在没有 Spring Boot 的情况下使用 spring-cloud-zookeeper 的解决方案

它应该很容易更新以满足您的需求并使用 Spring Cloud Config Server(需要更新 CloudEnvironement 类以从服务器而不是 Zookeeper 加载文件)

首先,创建一个 CloudEnvironement 类,该类将创建一个 PropertySource(来自 Zookeeper):

云环境.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()); 
  } 

}

然后创建一个自定义的 XmlWebApplicationContext 类:当您的 Web 应用程序启动时,它将启用从 Zookeeper 加载 PropertySource 并替换 Spring Boot 的引导魔法:

MyConfigurableWebApplicationContext.java

public class MyConfigurableWebApplicationContext extends XmlWebApplicationContext { 

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

最后,在您的web.xml文件中添加以下上下文参数以使用您的 MyConfigurableWebApplicationContext 类并引导您的 CloudEnvironement。

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

如果您使用标准的属性文件配置器,它仍然应该被加载,这样您就可以在本地文件和 Zookeeper 中拥有属性。

为了使所有这些工作,您需要在您的类路径中有 spring-cloud-starter-zookeeper-config 和 curator-framework jar 及其依赖关系,如果您使用 maven,您可以将以下内容添加到您的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>
于 2017-07-28T14:34:16.950 回答
1

发布作为答案是因为我没有足够的分数来评论 Dave Syer 的出色答案。我们已经在生产中实施了他提出的解决方案,并且按预期工作。我们较新的应用程序是使用 Boot 编写的,而我们的旧应用程序使用 Spring,但不是 boot。我们能够使用 Spring Cloud Config 创建一个为两者提供属性的属性服务。变化很小。我将遗留属性文件从 war 文件移到属性服务 git 存储库,并将属性定义从类路径引用更改为 Dave 描述的 URL,并插入我们的系统环境变量,就像我们为类路径所做的那样。这既简单又有效。

<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" />
于 2017-07-11T15:49:32.740 回答