Circular dependencies when application is deployed as docker container

I have been having a heck of a time with somewhat random circular dependency issues that produce the follow error in the logs:

`

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘categoryController’ defined in URL [jar:file:/app.jar!/BOOT-INF/classes!/com/fedexx/product/category/rest/CategoryController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘queryGateway’ defined in class path resource [org/axonframework/springboot/autoconfig/AxonAutoConfiguration.class]: Unsatisfied dependency expressed through method ‘queryGateway’ parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘queryBus’ defined in class path resource [org/axonframework/springboot/autoconfig/AxonServerAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.axonframework.axonserver.connector.query.AxonServerQueryBus]: Factory method ‘queryBus’ threw exception; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name ‘axonMessageInterceptorsConfig’: Unsatisfied dependency expressed through method ‘registerInterceptors’ parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name ‘commandBus’ defined in class path resource [org/axonframework/springboot/autoconfig/AxonServerAutoConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.axonframework.axonserver.connector.command.AxonServerCommandBus]: Factory method ‘commandBus’ threw exception; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name ‘authCorrelationDataProvider’: Requested bean is currently in creation: Is there an unresolvable circular reference?

`

I never get this error when I deploy the application through my IDE (Spring Tools Suite), but when I deploy the application as a docker container I sometimes get the issue and sometimes I can clear all the cached docker images from my system and rebuild and the problem goes away. But presently I can’t get the issue to go away with this approach and now I’m starting to believe it has less to do with a corrupt docker image layer (which was my previous thought, albeit a little weak) and more to do with the timing of things when the application is deployed as a docker container.

Below is the configuration that defines the authCorrelationDataProvider bean reported in the error above:

`

package com.fedexx;

import static com.fedexx.api.AxonMessageMetadataKeys.USER_INFO;

import java.util.Collections;

import org.axonframework.axonserver.connector.command.AxonServerCommandBus;
import org.axonframework.axonserver.connector.query.AxonServerQueryBus;
import org.axonframework.commandhandling.CommandBus;
import org.axonframework.messaging.Message;
import org.axonframework.messaging.MessageDispatchInterceptor;
import org.axonframework.messaging.MessageHandlerInterceptor;
import org.axonframework.messaging.correlation.CorrelationDataProvider;
import org.axonframework.messaging.correlation.SimpleCorrelationDataProvider;
import org.axonframework.queryhandling.QueryBus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;

import com.fedexx.ResourceServerConfigurer.AuthToken;
import com.fedexx.api.values.UserInfo;

@Configuration
public class AxonMessageInterceptorsConfig {

@Bean
public CorrelationDataProvider authCorrelationDataProvider() {
return new SimpleCorrelationDataProvider(USER_INFO);
}

@Autowired
public void registerInterceptors(CommandBus commandBus, QueryBus queryBus) {
Assert.notNull(commandBus, “Invalid configuration, commandBus is null!”);
Assert.notNull(queryBus, “Invalid configuration, queryBus is null!”);

if (AxonServerCommandBus.class.isAssignableFrom(commandBus.getClass())) {
AxonServerCommandBus.class.cast(commandBus).registerDispatchInterceptor(authorizationDispatchInterceptor());
AxonServerCommandBus.class.cast(commandBus).registerHandlerInterceptor(authorizationHandlerInterceptor());
}
if (AxonServerQueryBus.class.isAssignableFrom(queryBus.getClass())) {
AxonServerQueryBus.class.cast(queryBus).registerDispatchInterceptor(authorizationDispatchInterceptor());
AxonServerQueryBus.class.cast(queryBus).registerHandlerInterceptor(authorizationHandlerInterceptor());
}
}

private MessageDispatchInterceptor<? super Message<?>> authorizationDispatchInterceptor() {
return list -> {
AuthToken auth = (AuthToken) SecurityContextHolder.getContext().getAuthentication();

if (auth != null) {
UserInfo userInfo = auth.getPrincipal();
userInfo.validate();
return (index, message) -> message.andMetaData(Collections.singletonMap(USER_INFO, userInfo));
}

return (index, message) -> message;
};
}

private MessageHandlerInterceptor<? super Message<?>> authorizationHandlerInterceptor() {
return (unitOfWork, interceptorChain) -> {
UserInfo userInfo = (UserInfo) unitOfWork.getMessage().getMetaData().get(USER_INFO);
if (userInfo == null) {
throw new SecurityException(“User information not available!”);
}
return interceptorChain.proceed();
};
}
}

`

Anyone else experiencing anything like this?

I made the following modification to my interceptor configuration:

`

@Bean

@DependsOn({“commandBus”, “queryBus”})
public CorrelationDataProvider authCorrelationDataProvider() {
return new SimpleCorrelationDataProvider(USER_INFO);
}

`

I just added the @DependsOn annotation to specify that my configuration depends on the command and query buses.

Now, the application fails with the following error:

`

recover-api_1 | ***************************
recover-api_1 | APPLICATION FAILED TO START
recover-api_1 | ***************************
recover-api_1 |
recover-api_1 | Description:
recover-api_1 |
recover-api_1 | The dependencies of some of the beans in the application context form a cycle:
recover-api_1 |
recover-api_1 | axonMessageInterceptorsConfig
recover-api_1 | ┌─────┐
recover-api_1 | | commandBus defined in class path resource [org/axonframework/springboot/autoconfig/AxonServerAutoConfiguration.class]
recover-api_1 | └─────┘
recover-api_1 |

`

So I may have resolved the issue, but I will only know in time if the issue is really gone. However, the solution isn’t very intuitive.

What I did is I moved the authCorrelationDataProvider bean out of my AxonMessageInterceptorsConfig and put it in my base AxonConfig, which is where I configure the TokenStore, SagaStore, MongoTemplate and that sort of thing. So in other words, I have two axon classes in my application annotate with @Configuration and as I move a bean definition between the two configuration classes, spring’s cycle detection algorithm alternately detects a cycle or doesn’t. For completeness, I will post the current state of both configurations (which seem to be working now):

Base configuration:

`

package com.fedexx;

import static com.fedexx.api.AxonMessageMetadataKeys.USER_INFO;

import org.axonframework.config.EventProcessingConfigurer;
import org.axonframework.eventhandling.PropagatingErrorHandler;
import org.axonframework.eventhandling.tokenstore.TokenStore;
import org.axonframework.extensions.mongo.DefaultMongoTemplate;
import org.axonframework.extensions.mongo.MongoTemplate;
import org.axonframework.extensions.mongo.eventhandling.saga.repository.MongoSagaStore;
import org.axonframework.extensions.mongo.eventsourcing.tokenstore.MongoTokenStore;
import org.axonframework.messaging.correlation.CorrelationDataProvider;
import org.axonframework.messaging.correlation.MessageOriginProvider;
import org.axonframework.messaging.correlation.SimpleCorrelationDataProvider;
import org.axonframework.modelling.saga.repository.SagaStore;
import org.axonframework.serialization.Serializer;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.mongodb.MongoClient;

// TODO: handle tracking event processor initialization. See the axon mailing list thread:
// https://groups.google.com/forum/#!topic/axonframework/eyw0rRiSzUw
// In that thread there is a discussion about properly initializing the token store to avoid recreating query models.
// I still need to understand more about this…

@Configuration
public class AxonConfig {

@Autowired
public void configure(ObjectMapper objectMapper) {
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}

@Autowired
public void configure(EventProcessingConfigurer configurer) {
// *******************************************************************
// This will configure just the navi integration tracking processor
// *******************************************************************
// configurer.registerListenerInvocationErrorHandler(AbstractEventHandler.class.getPackage().getName(),
// c -> PropagatingErrorHandler.INSTANCE);

// *******************************************************************
// This will configure all tracking processors
// *******************************************************************
configurer.registerDefaultListenerInvocationErrorHandler(c -> PropagatingErrorHandler.INSTANCE);
}

@Bean
public TokenStore tokenStore(Serializer serializer, MongoClient client) {
return MongoTokenStore.builder().mongoTemplate(mongoTemplate(client)).serializer(serializer).build();
}

@Bean
public SagaStore sagaStore(Serializer serializer, MongoClient client) {
return MongoSagaStore.builder().mongoTemplate(mongoTemplate(client)).serializer(serializer).build();
}

MongoTemplate mongoTemplate(MongoClient client) {
return DefaultMongoTemplate.builder().mongoDatabase(client).build();
}

@Bean
public CorrelationDataProvider messageOriginProvider() {
return new MessageOriginProvider();
}

@Bean
public CorrelationDataProvider authCorrelationDataProvider() {
return new SimpleCorrelationDataProvider(USER_INFO);
}
}

`

Interceptor Configuration:

`

package com.fedexx;

import static com.fedexx.api.AxonMessageMetadataKeys.USER_INFO;

import java.util.Collections;

import org.axonframework.axonserver.connector.command.AxonServerCommandBus;
import org.axonframework.axonserver.connector.query.AxonServerQueryBus;
import org.axonframework.commandhandling.CommandBus;
import org.axonframework.messaging.Message;
import org.axonframework.messaging.MessageDispatchInterceptor;
import org.axonframework.messaging.MessageHandlerInterceptor;
import org.axonframework.queryhandling.QueryBus;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.Assert;

import com.fedexx.ResourceServerConfigurer.AuthToken;
import com.fedexx.api.values.UserInfo;

@Configuration
public class AxonMessageInterceptorsConfig {

@Autowired
public void registerInterceptors(CommandBus commandBus, QueryBus queryBus) {
Assert.notNull(commandBus, “Invalid configuration, commandBus is null!”);
Assert.notNull(queryBus, “Invalid configuration, queryBus is null!”);

if (AxonServerCommandBus.class.isAssignableFrom(commandBus.getClass())) {
AxonServerCommandBus.class.cast(commandBus).registerDispatchInterceptor(authorizationDispatchInterceptor());
AxonServerCommandBus.class.cast(commandBus).registerHandlerInterceptor(authorizationHandlerInterceptor());
}
if (AxonServerQueryBus.class.isAssignableFrom(queryBus.getClass())) {
AxonServerQueryBus.class.cast(queryBus).registerDispatchInterceptor(authorizationDispatchInterceptor());
AxonServerQueryBus.class.cast(queryBus).registerHandlerInterceptor(authorizationHandlerInterceptor());
}
}

private MessageDispatchInterceptor<? super Message<?>> authorizationDispatchInterceptor() {
return list -> {
AuthToken auth = (AuthToken) SecurityContextHolder.getContext().getAuthentication();

if (auth != null) {
UserInfo userInfo = auth.getPrincipal();
userInfo.validate();
return (index, message) -> message.andMetaData(Collections.singletonMap(USER_INFO, userInfo));
}

return (index, message) -> message;
};
}

private MessageHandlerInterceptor<? super Message<?>> authorizationHandlerInterceptor() {
return (unitOfWork, interceptorChain) -> {
UserInfo userInfo = (UserInfo) unitOfWork.getMessage().getMetaData().get(USER_INFO);
if (userInfo == null) {
throw new SecurityException(“User information not available!”);
}
return interceptorChain.proceed();
};
}
}

`

Hi Troy,

glad you found it, and sorry for not being able to send you a message earlier.
The problem is indeed caused by the CorrelationDataProvider being defined in the same class as the @Autowired configuration method. The problem is that Axon uses the correlation data provider bean to register it to the Command Bus creation process. But it can’t do that just yet, because the @Autowired methods needs to invoked first. But that method needs a Commands bus, which we can only create if the get the CorrelationDataProvider. But to get to that bean, we need to… and we have the circular dependency.

So indeed, splitting the bean definition and @Autowired methods in different configuration classes will help.

Thanks for the clarification!