java.lang.IllegalStateException: Bean factory must be instance of ListableBeanFactory, was null - spring-statemachine

I'm creating a state machine object using the provided builder as follows:
10.2 State Machine via Builder
I'm seeing the following exception:
ERROR org.springframework.statemachine.support.StateMachineObjectSupport - Unable to initialize annotation handlers
java.lang.IllegalStateException: Bean factory must be instance of ListableBeanFactory, was null
The exception isn't preventing the state machine from functioning as expected. However, I would like to get down to the bottom of why I'm seeing this.
Anyone know how I can stop this exception from showing?
Thanks.

Managed to get a solution for this.
I autowired the the Spring application context into my class. I then extracted the AutowireCapableBeanFactory from this and set this up inside the builder. As follows:
#Autowired
private ApplicationContext appContext;
private void buildStateMachine() throws Exception {
Builder<EnquiryStatus, Event> builder = StateMachineBuilder.builder();
builder.configureConfiguration().withConfiguration().beanFactory(appContext.getAutowireCapableBeanFactory());
}

Just add this:
builder.configureConfiguration().withConfiguration().beanFactory(new StaticListableBeanFactory());

Related

Why do spring tests do not reuse the same Context with Embedded Mongo

I am doing integration tests and unit tests with spring boot. I made three test classes.
ApplicationRunnterTest: to test contextLoads
DecisionTreeServiceTest: to test the save function of the service
DecisionTreeRepositoryTest: to test the methods of the repo
I added the embdded mongo packages (flapdoodle.embed.mongo). What i have observed is that, when all three tests are run in this order 1) => 2) => 3)
What happens is when ApplicationRunnerTest loads the context then the second test fails (the service) and the third is success (repository). The problem is with the context being used or something in the test that fails it says "Error to create bean embedded mongo"
The solution i found is to add the annotation #DirtiesContext() and from what i understand is for each test class, it will make a new context and it worked.
My questions are the following :
Why doesnt spring use the same context for all the classes
if it does, why there is an ambiguity or an error ?
is there a better way instead of #DirtiestContext() since i guess its a lot to load a new context for each test class.
Here are the classes and the erroes.
#RunWith(SpringRunner.class)
#SpringBootTest
#ContextConfiguration()
public class ApplicationRunnerTest {
#Test
public void contextLoads(){
}
}
Service class
#RunWith(SpringRunner.class)
#SpringBootTest
//#DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
public class DecisionTreeServiceTest {
#Autowired
private DecisionTreeRepository decisionTreeRepository;
#MockBean
private HistorySupervisionService historySupervisionService;
#Autowired
private DecisionTreeService decisionTreeService;
#Autowired
private DecisionTreeMapper decisionTreeMapper;
private DecisionTree[] trees;
#Before
public void init() throws IOException {
// get test data
JacksonMapper jacksonMapper = new JacksonMapper();
trees = jacksonMapper.loadJsonFileToObject("src/test/java/com/almerys/cpm/back/utils/decisionTree/decisionTreeObject",
DecisionTree[].class
);
//clear databse first
this.decisionTreeRepository.deleteAll();
// save object 2 and object 3
this.decisionTreeRepository.save(trees[1]);
this.decisionTreeRepository.save(trees[2]);
}
#Test
public void saveTest(){
// TEST CREATE SCENARIO
DecisionTreeDTO decisionTree = this.decisionTreeMapper.toDto(trees[0]);
DecisionTree savedDecisionTree = trees[0];
savedDecisionTree.setId("1");
savedDecisionTree.setInternalId(4);
// Mockito.when(decisionTreeRepository.findFirstByOrderByInternalIdDesc()).thenReturn(Optional.of(trees[2]));
// Mockito.when(decisionTreeRepository.save(Mockito.any())).thenReturn(savedDecisionTree);
Mockito.when(historySupervisionService.getLastVersionByContext(Mockito.anyString())).thenReturn("13.0");
DecisionTreeDTO createdDecisionTree= decisionTreeService.save(decisionTree);
assertThat(createdDecisionTree).isNotNull();
assertThat(createdDecisionTree.getInternalId()).isEqualTo(4);
assertThat(createdDecisionTree.getFactoryVersion()).contains("13.1");
// TEST UPDATE SCENARIO
}
Repo Class
#RunWith(SpringRunner.class)
#DataMongoTest
public class DecisionTreeRepositoryTest{
#Autowired
private DecisionTreeRepository decisionTreeRepository;
private void initData() throws IOException {
//clean embedded database first.
this.decisionTreeRepository.deleteAll();
// retrieve data from json
JacksonMapper jacksonMapper = new JacksonMapper();
DecisionTree[] createDecisionTree= jacksonMapper.loadJsonFileToObject("src/test/java/com/almerys/cpm/back/utils/decisionTree/decisionTreeObject",
DecisionTree[].class
);
// persist data
Arrays.stream(createDecisionTree).forEach(tree-> this.decisionTreeRepository.save(tree));
}
#Before
public void init() throws IOException {
this.initData();
}
#Test
public void findByFactoryCategoryInternalId(){
DecisionTree decisionTree = this.decisionTreeRepository.findByFactoryCategoryInternalIdAndFactoryVersion(1,"11.0");
assertThat(decisionTree).isNotNull();
}
}
Error
Unsatisfied dependency expressed through field 'mongoTemplate'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'mongoTemplate' defined in class path resource [org/springframework/boot/autoconfigure/data/mongo/MongoDbFactoryDependentConfiguration.class]: Unsatisfied dependency expressed through method 'mongoTemplate' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'mongoDbFactory' defined in class path resource [org/springframework/boot/autoconfigure/data/mongo/MongoDbFactoryConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.data.mongodb.core.MongoDbFactorySupport]: Factory method 'mongoDbFactory' threw exception; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'embeddedMongoServer' defined in class path resource [org/springframework/boot/autoconfigure/mongo/embedded/EmbeddedMongoAutoConfiguration.class]: Invocation of init method failed; nested exception is java.io.IOException: Could not start process: <EOF>
Why doesnt spring use the same context for all the classes
The simple answer is: because one of the test uses #MockBean.
When using mock bean, you're asking Spring to replace the original bean instance with a mock. Obviously, other beans might depend on that bean, it might be involved in their initialization etc., so any previously created context needs to be scrapped in order for the test environment to be reliable.
Also, do all the test reside in the same package? If not, I think the consequence of using #ContextConfiguration will be that only components residing in the same package and sub-packages are going to be picked up. This implies the test context for each of the tests will not be the same, so obviously the test context cannot be reused.
why there is an ambiguity or an error ?
Hard to say without looking at the actual configuration, but my guess would be the embedded Mongo doesn't cleanup after itself properly.

What is the JNDI name should I use to lookup for a remote interface deployed in websphere using Spring Boot?

I have a remote interface deployed in websphere 8.5.5 and I want to look up for this in spring boot application. I have made similar interface in my spring boot as common interface RMI I also used SimpleRemoteStatelessSessionProxyFactoryBean but the proxy returned is null and it was throwing null pointer in proxy.invokeMethod()
#Configuration
public class Config {
private static final String INITIAL_CONTEXT_FACTORY = "com.ibm.websphere.naming.WsnInitialContextFactory";
private static final String PROVIDER_URL = "corbaname:iiop:localhost:2809/NameServiceServerRoot";
#Primary
#Bean
public static AdminService adminService() {
Properties jndiProps = new Properties();
Properties properties = new Properties();
properties.put(Context.INITIAL_CONTEXT_FACTORY, INITIAL_CONTEXT_FACTORY);
properties.put(Context.PROVIDER_URL, PROVIDER_URL);
SimpleRemoteStatelessSessionProxyFactoryBean factory = new SimpleRemoteStatelessSessionProxyFactoryBean();
factory.setJndiEnvironment(jndiProps);
factory.setJndiName("java:global/[AppName]/[ModuleName]/ejb/[BeanName]![RemoteInterface]");
factory.setBusinessInterface(AdminService.class);
factory.setResourceRef(true);
AdminService proxy = (AdminService) factory.getObject();
try {
proxy.invokeMethod();
}catch(RemoteException e) {
e.printStackTrace();
}
return proxy;
}
}
Now it's throwing this error:
org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'AdminService' defined in class path resource [...Config.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [...AdminService]: Factory method 'adminEJBService' threw exception; nested exception is javax.naming.NameNotFoundException: Name [global/[AppName]/[ModuleName]/ejb/[BeanName]![RemoteInterface] is not bound in this Context. Unable to find [global].
You must replace everything in [] with the actual name, so not [AppName], but MyApp, etc. If not sure, you can determine the exact lookup string by looking in SystemOut.log file of the WebSphere 8.5.5 server for the message CNTR0167I. For example, the actual message would look like this:
CNTR0167I: The server is binding the javax.management.j2ee.ManagementHome interface of the Management enterprise bean in the mejb.jar module of the ManagementEJB application. The binding location is: java:global/ManagementEJB/mejb/Management!javax.management.j2ee.ManagementHome

EmbeddedKafka kafka streams test with SpringBootTest finds two StreamsBuilderFactoryBeans

Following the advice here, I'm trying to use an embedded Kafka to test my Spring Boot Streams application.
However, simply creating the given configuration
#Configuration
#EnableKafkaStreams
public class StreamsTestConfiguration {
#Value("${" + EmbeddedKafkaBroker.SPRING_EMBEDDED_KAFKA_BROKERS + "}")
private String brokerAddresses;
#Bean(name = KafkaStreamsDefaultConfiguration.DEFAULT_STREAMS_CONFIG_BEAN_NAME)
public KafkaStreamsConfiguration kStreamsConfigs() {
Map<String, Object> props = new HashMap<>();
props.put(StreamsConfig.APPLICATION_ID_CONFIG, "testStreams");
props.put(StreamsConfig.BOOTSTRAP_SERVERS_CONFIG, this.brokerAddresses);
return new KafkaStreamsConfiguration(props);
}
}
and a simple test
#RunWith(SpringRunner.class)
#SpringBootTest
#EmbeddedKafka(topics = { "topic" })
public class EmbeddedKafkaTest {
#Autowired
private MyBean tested;
#Autowired
private EmbeddedKafkaBroker kafkaBroker;
#Test
public void loaded() {}
}
fails to run:
Parameter 0 of method kafkaStreamsFactoryBeanConfigurer in org.springframework.boot.autoconfigure.kafka.KafkaStreamsAnnotationDrivenConfiguration required a single bean, but 2 were found:
- &defaultKafkaStreamsBuilder: defined by method 'defaultKafkaStreamsBuilder' in class path resource [org/springframework/kafka/annotation/KafkaStreamsDefaultConfiguration.class]
- &stream-builder-process: defined in null
[...]
Caused by: org.springframework.context.ApplicationContextException: Failed to start bean 'outputBindingLifecycle'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'kafkaStreamsFactoryBeanConfigurer' defined in org.springframework.boot.autoconfigure.kafka.KafkaStreamsAnnotationDrivenConfiguration: Unsatisfied dependency expressed through method 'kafkaStreamsFactoryBeanConfigurer' parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'org.springframework.kafka.config.StreamsBuilderFactoryBean' available: expected single matching bean but found 2: &defaultKafkaStreamsBuilder,&stream-builder-process
If I remove the #SpringBootTest from the test class, the problem disappears, but then the actual bean under test fails to autowire.
I don't define a StreamBuilderFactoryBean myself, where are they coming from?
Also: is this this setup even worthwhile for testing a stream that is used to feed a KTable that is later being queried? It's not like I can "use a different topic for each test" since the stream will always use the same topic. My hope is that I can get around that with proper test case design, or am I going to hit a wall I can't see yet?
According to you stack trace, you also use Spring Cloud Stream with Kafka Streams Binder. Please, add an appropriate tag.
Consider to remove an explicit #EnableKafkaStreams since Binder is going to take care about infrastructure for you.

Spring boot #SpyBean tries to instantiate a new bean instead of spying the one in context

I'm trying to unit-testing a Spring batch job inside Spring boot using JUnit.
I wrote this test class where I want to spy the bean ItemReader :
#RunWith(SpringRunner.class)
#SpringBootTest(webEnvironment=WebEnvironment.NONE)
#ActiveProfiles({"dev", "batch", "test-jobs"})
public class BatchJobTest {
#Autowired
private JobLauncherTestUtils jobLauncherTestUtils;
private #Autowired #Qualifier("contactDownloadAckJob") Job contactDownloadAckTaskJob;
#SpyBean
private ItemReader<CrsOscContact> reader;
#Test
public void testJob() throws Exception {
given(this.reader.read()).willReturn(new CrsOscContact());
//... blah blah blah
}
}
When I run this test, it seems that the #SpyBean annotation does not do its job, that should be proxying the ItemReader bean that's already present in the context, and so I obtain the (correct) exception because, as per definition, if the bean is not found it tries to instantiate a new bean of that type (and I have specified an interface) :
org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.batch.item.ItemReader]: Specified class is an interface
I'm pretty sure that the bean (of ItemReader type) is already in the context because :
debugging, I see that the target bean instantiation is correctly processed
if I change the #SpyBean annotation to an #Autowired annotation, the instance of previous point is correctly injected
Any hint? Thank you
It was a Spring Core issue, now fixed.
https://github.com/spring-projects/spring-boot/issues/7625
https://jira.spring.io/browse/SPR-15011

Spring - Autowire fails when adding an aspect-backed annotation to overridden method

I have an authentication service implementing org.springframework.security.core.userdetails.UserDetailsService and defined as:
#Service
public class AuthService implements UserDetailsService {
// Autowires ..
#Override
//#Logged(successMessage = "User %s logged in")
public UserDetails loadUserByUsername(String username) { .. }
}
It is autowired into an extension of WebSecurityConfigurerAdapter and used to configure authentication:
// Annotations ...
public class SecurityConfig extends WebSecurityConfigurerAdapter {
#Autowired
private AuthService authService;
#Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
auth.userDetailsService(authService).passwordEncoder(encoder);
}
// Other security config stuff ...
}
And last, I have created a custom annotation called #Logged, that is picked up by an aspect that applies around advice to the annotated method and performs some logging logic. Lets just say that if you pass only one argument to it, it will log successful returns.
Applying the #Logged annotation to the overriden AuthService.loadUserByUsername method causes the application to crash on startup, displaying the message about not being able to autowire AuthService into the SecurityConfig. Everything works flawlessly if I don't put the annotation to that overriden method - authentication works (complete with autowiring) and logging annotation works in other parts of the system.
Question
Why is this happening and can it be fixed?
I would really like to log that exact method using my fancy logging anotation.
Thanks :)
Technical details
Spring 4 with Boot, Jetty 9
Stack trace (part of):
2014-09-22 12:41:05 WARN AbstractLifeCycle - FAILED org.springframework.boot.context.embedded.jetty.ServletContextInitializerConfiguration$InitializerListener#8bb473: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire method: public void org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.setFilterChainProxySecurityConfigurer(org.springframework.security.config.annotation.ObjectPostProcessor,java.util.List) throws java.lang.Exception; nested exception is org.springframework.beans.factory.BeanExpressionException: Expression parsing failed; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'securityConfig': Injection of autowired dependencies failed; nested exception is org.springframework.beans.factory.BeanCreationException: Could not autowire field: private com.bluepixelflag.services.AuthService com.bluepixelflag.config.SecurityConfig.authService; nested exception is java.lang.IllegalArgumentException: Can not set com.bluepixelflag.services.AuthService field com.bluepixelflag.config.SecurityConfig.authService to com.sun.proxy.$Proxy90
Spring is using a proxy to add aspects to AuthService. Without the annotation it would just use a simple class.
Change
private AuthService authService;
to
private UserDetailsService authService;
and it ought to work. Or, use cglib proxies instead of JDK dynamic proxies.
Aside: the point of using aspects for logging is to try to avoid one line of code per logging call. If you use an aspect annotation for every method call you want logged, you haven't saved any complexity (in fact you've increased it), and might as well just call a logger in code.

Resources