使用Java配置时,Camel的BridgePropertyPlaceholderConfigurer无法正常工作

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了使用Java配置时,Camel的BridgePropertyPlaceholderConfigurer无法正常工作相关的知识,希望对你有一定的参考价值。

我正在使用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类在Camel中提供Spring属性。它的配置也很简单:

@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$1.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,你需要用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);
}
另一答案

尝试重命名您的第一个BridgePropertyPlaceholderConfigurer bean(在您的情况下方法的名称)。

另一答案

看看我搞砸了什么。尚未完全测试但想分享;应该使用Spring 5.x.基本上将所有Environment复制到Camel的properties,所以我根本不使用Camel的“桥”。我今天不确定的一件事,如果我必须把它放入“初始”或“重叠”属性:

@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());
    }
}

以上是关于使用Java配置时,Camel的BridgePropertyPlaceholderConfigurer无法正常工作的主要内容,如果未能解决你的问题,请参考以下文章

Camel:使用 spring-boot 配置的数据源

如何在 Spring Boot 中为 Camel 配置 Jackson ObjectMapper

如何在使用Java DSL的camel Header中设置没有扩展名的文件名?

如何使用 Apache Camel 从 Java 类访问 JMS 队列?

Camel 和rabbitmq 集成处理

用于在 java 中将 CamelCase 转换为 camel_case 的正则表达式