1
votes

I am using Spring Boot 1.5.7 and Apache Camel 2.19.3, using Spring Boot AutoConfiguration provided by spring-boot-starter-camel

It is pretty basic Spring Boot and Camel initialized as in their tutorial, so we have a RouteBuilder component that does exactly that.

@Component
public class CIFRoutes extends  RouteBuilder {

  @Override
  public void configure() throws Exception {
  // build routes
  }
}

We have a Configuration that defines some beans we need in our application

@Configuration
public class MyConfiguration {

  @Bean
  public void Map<String, Object> map() {
    return new HashMap<>()
  }
}

Finally, we have a custom InflightRepository implementation that should be scanned by auto-configuration and added to the CamelContext which basically works, but for some reason, the component doesn't get initialized properly. Means, its dependencies are not initialized but the bean is instantiated and injected in my Application.

@Component
public class MyCustomInflightRepository extends DefaultInflightRepository {

  @Autowired
  private Map<String, Object> map;

  @Override
  public void add(Exchange exchange) {
  super.addExchange(exchange);
   // ....
  }
}

The problem now is that map remains (null), I also tried adding a @PostConstruct initializer method but it doesn't get called.

As far as I was able to reconstruct, it seems to be connected to premature in CamelAutoConfiguration where the CamelContext bean gets instantiated (done in private method afterPropertiesSet.

InflightRepository inflightRepository = getSingleBeanOfType(applicationContext, InflightRepository.class);
if (inflightRepository != null) {
     LOG.info("Using custom InflightRepository: {}", inflightRepository);
            camelContext.setInflightRepository(inflightRepository);
}

If MyCustomInflightRepository doesn't implement InflightRepository, the bean is initialized correctly, but indeed not recognized by Camel. When disabling auto-configuration, the bean's dependencies are injected.

So, either I'm doing the impossible by Spring standards or there's something fishy with the Camel component for Spring.

2
It is really weird but for some reason it doesn't inject the map if camel-starter-http is on the classpath, which is reproducible when adding/removing the dependency in the given sample on Github: github.com/DoNuT1985/camel-dependency-sample Indeed, makes no sense in the given sample but we're using HTTP components in the fully grown application where this issues occurs.DoNuT

2 Answers

1
votes

I'm a bit quick on resolving this (I wanted to post this two days ago already^^), but a colleague figured out what could be the problem.

When using CamelAutoConfiguration the InflightRepository bean (or practicially everything for which Camel tries to get a matching bean here), the context is accessed before property resolvers are fully initialized which leads to the bean being initialized (and cached in context) before any auto-wired properties can be resolved.

I'm not a Spring expert but this behavior is a bit problematic in my opinion because uninitialized beans are pulled into the CamelContext when you rely on Spring DI for your custom components.

To be sure, I'll raise this with the maintainers...

By the way, my simple solution was to manually setting the in-flight repository in context configuration (as suggested)

@Bean
public CamelContextConfiguration camelConfig() {
    return new CamelContextConfiguration() {
        @Override
        public void beforeApplicationStart(CamelContext context) {

            context.setInflightRepository(new MyCustomInflightRepository(/* Dependencies go here */ ));
        }

        @Override
        public void afterApplicationStart(CamelContext camelContext) {

        }
    };
}

Also it seems to be an issue when use camel-http-starter in your project which isn't recommended, they claim it is deprecated.

So, either don't do DI (regardless if via property or constructor injection) for your camel-managed beans or skip that starter.

0
votes

The problem is that a Map<String,Object> is too vague for Spring to be able to understand what you want; I think the default behavior is that it'll give you all beans keyed by name.

Instead, be more specific, or possibly provide the necessary parameters as constructor arguments and configure them explicitly in an @Bean method (it's a good idea to always use constructor injection anyway).