使用 Java 配置时,Camel 的 BridgePropertyPlaceholderConfigurer 不工作

Camel's BridgePropertyPlaceholderConfigurer is not working when using Java config

我正在使用 Spring Java 配置并使用一些 Camel 路由编写控制台应用程序。我的应用程序中有几个属性源,所以我使用两个 PropertyPlaceholderConfigurers:

@Configuration
@Import(CamelConfig.class)
@ComponentScan(basePackageClasses = {App.class})
public class Config
{
  final static String ENV = System.getProperty( "ENV" );

  @Bean
  public static BridgePropertyPlaceholderConfigurer properties()
  {
    final BridgePropertyPlaceholderConfigurer result = new BridgePropertyPlaceholderConfigurer();

    result.setOrder( 0 );
    result.setIgnoreUnresolvablePlaceholders( true );
    result.setLocations( new ClassPathResource( "a/b/c/environments/base.properties" ),
      new ClassPathResource( "a/b/c/environments/" + ENV + "/env.properties" ) );

    return result;
  }

  @Bean
  public static BridgePropertyPlaceholderConfigurer dlqAppProperties()
  {
    final YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
    final BridgePropertyPlaceholderConfigurer result = new BridgePropertyPlaceholderConfigurer();

    yaml.setResources( new ClassPathResource( "app.yaml" ) );
    result.setOrder( 1 );
    result.setIgnoreUnresolvablePlaceholders( true );
    result.setProperties( yaml.getObject() );

    return result;
  }
}

根据 this doc,我正在使用 BridgePropertyPlaceholderConfigurer class 使 Spring 属性在 Camel 中可用。它的配置也很简单:

@Configuration
public class CamelConfig extends SingleRouteCamelConfiguration
{
  @Override
  protected CamelContext createCamelContext() throws Exception
  {
    final SpringCamelContext result = new SpringCamelContext( getApplicationContext() );

    return result;
  }

  @Override
  protected void setupCamelContext( CamelContext camelContext ) throws Exception
  {
  }

  @Bean
  @Override
  public RouteBuilder route()
  {
    return (new Routes()).builder();
  }
}

测试路线(Scala DSL)也很简单:

class Routes extends RouteBuilder {
  "timer://{{foo}}?period=2s" ==> {
    process((exchange) => {
      exchange.getIn.setBody("test")
    })
    to("log:test")
  }
}

但上下文不以下列异常开头:

Exception in thread "main" org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'camelContext' defined in class path resource [a/b/c/config/CamelConfig.class]: Invocation of init method failed; nested exception is org.apache.camel.FailedToCreateRouteException: Failed to create route route1: Route(route1)[[From[timer://{{foo}}?period=2s]] -> [process[... because of Failed to resolve endpoint: timer://{{foo}}?period=2s due to: PropertiesComponent with name properties must be defined in CamelContext to support property placeholders.
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1566)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:539)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getObject(AbstractBeanFactory.java:303)
  at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
  at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299)
  at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194)
  at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:755)
  at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757)
  at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480)
  at org.springframework.context.annotation.AnnotationConfigApplicationContext.<init>(AnnotationConfigApplicationContext.java:84)
  at a.b.c.App.main(App.java:13)
  at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
  at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57)
  at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
  at java.lang.reflect.Method.invoke(Method.java:606)
  at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)
Caused by: org.apache.camel.FailedToCreateRouteException: Failed to create route route1: Route(route1)[[From[timer://{{foo}}?period=2s]] -> [process[... because of Failed to resolve endpoint: timer://{{foo}}?period=2s due to: PropertiesComponent with name properties must be defined in CamelContext to support property placeholders.
  at org.apache.camel.model.RouteDefinition.addRoutes(RouteDefinition.java:182)
  at org.apache.camel.impl.DefaultCamelContext.startRoute(DefaultCamelContext.java:770)
  at org.apache.camel.impl.DefaultCamelContext.startRouteDefinitions(DefaultCamelContext.java:1914)
  at org.apache.camel.impl.DefaultCamelContext.doStartCamel(DefaultCamelContext.java:1670)
  at org.apache.camel.impl.DefaultCamelContext.doStart(DefaultCamelContext.java:1544)
  at org.apache.camel.spring.SpringCamelContext.doStart(SpringCamelContext.java:179)
  at org.apache.camel.support.ServiceSupport.start(ServiceSupport.java:61)
  at org.apache.camel.impl.DefaultCamelContext.start(DefaultCamelContext.java:1512)
  at org.apache.camel.spring.SpringCamelContext.maybeStart(SpringCamelContext.java:228)
  at org.apache.camel.spring.SpringCamelContext.afterPropertiesSet(SpringCamelContext.java:104)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1625)
  at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1562)
  ... 16 more
Caused by: org.apache.camel.ResolveEndpointFailedException: Failed to resolve endpoint: timer://{{foo}}?period=2s due to: PropertiesComponent with name properties must be defined in CamelContext to support property placeholders.
  at org.apache.camel.impl.DefaultCamelContext.getEndpoint(DefaultCamelContext.java:477)
  at org.apache.camel.util.CamelContextHelper.getMandatoryEndpoint(CamelContextHelper.java:63)
  at org.apache.camel.model.RouteDefinition.resolveEndpoint(RouteDefinition.java:192)
  at org.apache.camel.impl.DefaultRouteContext.resolveEndpoint(DefaultRouteContext.java:106)
  at org.apache.camel.impl.DefaultRouteContext.resolveEndpoint(DefaultRouteContext.java:112)
  at org.apache.camel.model.FromDefinition.resolveEndpoint(FromDefinition.java:72)
  at org.apache.camel.impl.DefaultRouteContext.getEndpoint(DefaultRouteContext.java:88)
  at org.apache.camel.model.RouteDefinition.addRoutes(RouteDefinition.java:890)
  at org.apache.camel.model.RouteDefinition.addRoutes(RouteDefinition.java:177)
  ... 27 more
Caused by: java.lang.IllegalArgumentException: PropertiesComponent with name properties must be defined in CamelContext to support property placeholders.
  at org.apache.camel.impl.DefaultCamelContext.resolvePropertyPlaceholders(DefaultCamelContext.java:1121)
  at org.apache.camel.impl.DefaultCamelContext.getEndpoint(DefaultCamelContext.java:475)
  ... 35 more

看起来桥接不起作用(但我绝对可以在 Spring 中使用占位符)。可能是什么问题?

尝试重命名您的第一个 BridgePropertyPlaceholderConfigurer bean(在您的案例中是方法的名称)。

看起来如果你想使用 BridgePropertyPlaceholderConfigurer,你需要用 CamelContextFactoryBean 实例化 Camel 上下文。它有 initPropertyPlaceholder 方法:

@Override
protected void initPropertyPlaceholder() throws Exception {
    super.initPropertyPlaceholder();

    Map<String, BridgePropertyPlaceholderConfigurer> beans = applicationContext.getBeansOfType(BridgePropertyPlaceholderConfigurer.class);
    if (beans.size() == 1) {
        // setup properties component that uses this beans
        BridgePropertyPlaceholderConfigurer configurer = beans.values().iterator().next();
        String id = beans.keySet().iterator().next();
        LOG.info("Bridging Camel and Spring property placeholder configurer with id: " + id);

        // get properties component
        PropertiesComponent pc = getContext().getComponent("properties", PropertiesComponent.class);
        // replace existing resolver with us
        configurer.setResolver(pc.getPropertiesResolver());
        configurer.setParser(pc.getPropertiesParser());
        String ref = "ref:" + id;
        // use the bridge to handle the resolve and parsing
        pc.setPropertiesResolver(configurer);
        pc.setPropertiesParser(configurer);
        // and update locations to have our as ref first
        String[] locations = pc.getLocations();
        String[] updatedLocations;
        if (locations != null && locations.length > 0) {
            updatedLocations = new String[locations.length + 1];
            updatedLocations[0] = ref;
            System.arraycopy(locations, 0, updatedLocations, 1, locations.length);
        } else {
            updatedLocations = new String[]{ref};
        }
        pc.setLocations(updatedLocations);
    } else if (beans.size() > 1) {
        LOG.warn("Cannot bridge Camel and Spring property placeholders, as exact only 1 bean of type BridgePropertyPlaceholderConfigurer"
                + " must be defined, was {} beans defined.", beans.size());
    }
}

嗯,现在的问题是要有两座桥,那是另外一回事了..

我遇到了同样的问题。以下是对我有用的方法(受 initPropertyPlaceholder() 方法启发):


import org.apache.camel.component.properties.PropertiesComponent;
import org.apache.camel.spring.javaconfig.CamelConfiguration;
import org.apache.camel.spring.spi.BridgePropertyPlaceholderConfigurer;

@Configuration
@ComponentScan
public class AwesomeConfig extends CamelConfiguration {
    private static final String PROPERTIES_BEAN_NAME = "springProperties";

    @Resource(name = PROPERTIES_BEAN_NAME)
    private BridgePropertyPlaceholderConfigurer springProperties;

    @Bean(PROPERTIES_BEAN_NAME)
    public static BridgePropertyPlaceholderConfigurer springProperties() throws Exception {
        BridgePropertyPlaceholderConfigurer configurer = new BridgePropertyPlaceholderConfigurer();
        configurer.setSystemPropertiesMode(BridgePropertyPlaceholderConfigurer.SYSTEM_PROPERTIES_MODE_OVERRIDE);
        String defaultPropertiesPath = buildProperties().getProperty("properties.path");
        String propertiesPath = System.getProperty(PROPERTY_FILE_SYSTEM_PROPERTY, defaultPropertiesPath);
        configurer.setLocations(new ClassPathResource("META-INF/application.properties"));
        return configurer;
    }

    @Bean
    public PropertiesComponent camelProperties() throws Exception {
        PropertiesComponent camelProperties = new PropertiesComponent();

        springProperties.setParser(camelProperties.getPropertiesParser());
        springProperties.setResolver(camelProperties.getPropertiesResolver());

        camelProperties.setSystemPropertiesMode(springProperties.getSystemPropertiesMode());
        camelProperties.setPropertiesResolver(springProperties);
        camelProperties.setPropertiesParser(springProperties);
        camelProperties.setLocation("ref:" + PROPERTIES_BEAN_NAME);

        return camelProperties;
    }


    @Override
    protected void setupCamelContext(CamelContext camelContext) throws Exception {
        camelContext.addComponent("properties", camelProperties());
    }
}

下面是我的使用方法:

import org.apache.camel.spring.javaconfig.Main;

public class AwesomeMain extends Main {
    setConfigClass(AwesomeConfig.class);
}

public static void main(String... args) throws Exception {
    AwesomeMain main = new AwesomeMain();
    instance = main;
    main.run(args);
}

看看我破解了什么。尚未完全测试但想分享;应该与 Spring 5.x 一起使用。基本上将所有 Environment 复制到 Camel 的 properties,所以我根本不使用 Camel 的 "bridge"。如果我必须将其放入 "initial" 或 "overiding" 属性中,我今天不确定一件事:

@Configuration
public static class CamelConfig extends CamelConfiguration {

    @Autowired
    private ConfigurableEnvironment environment;

    @Bean
    ... some beans ...

    //@Bean -- haven't yet found out if we need it as a bean ...
    private PropertiesComponent camelProperties() throws Exception {
        PropertiesComponent camelProperties = new PropertiesComponent();

        // just brutally copy all the properties form environment
        HashSet<String> propertyNames = new HashSet<String>(100);
        for (PropertySource ps : environment.getPropertySources()) {
            if (ps instanceof MapPropertySource) {
                MapPropertySource mps = (MapPropertySource) ps;
                propertyNames.addAll(Arrays.asList(mps.getPropertyNames()));
            }
        }

        Properties allProps = new Properties();

        for (String prop : propertyNames) {
            allProps.setProperty(prop, environment.getProperty(prop));
        }

        camelProperties.setInitialProperties(allProps);
        // TODO: check it this is better or worse
        //camelProperties.setOverrideProperties(allProps);

        return camelProperties;
    }

    @Override
    protected void setupCamelContext(CamelContext camelContext) throws Exception {

        ... some configs.  ...

        camelContext.addComponent("properties", camelProperties());
    }
}