Better generics handling with adapters
- Modify StateMachineConfiguration and StateMachineFactoryConfiguration so that if you have different states and events classes with multiple machines or factories, you can autowire by types without adding qualifiers. - Manually try to discover generic types from enclosing adapther class and if succesfull pass that information into bean definition as ResolvableType. - Fixes #306
This commit is contained in:
@@ -24,10 +24,13 @@ import org.springframework.beans.factory.BeanNameAware;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.SmartLifecycle;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Scope;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
@@ -39,8 +42,8 @@ import org.springframework.statemachine.config.builders.StateMachineConfigBuilde
|
||||
import org.springframework.statemachine.config.builders.StateMachineConfigurer;
|
||||
import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer;
|
||||
import org.springframework.statemachine.config.model.DefaultStateMachineModel;
|
||||
import org.springframework.statemachine.config.model.ConfigurationData;
|
||||
import org.springframework.statemachine.config.model.DefaultStateMachineModel;
|
||||
import org.springframework.statemachine.config.model.StatesData;
|
||||
import org.springframework.statemachine.config.model.TransitionsData;
|
||||
import org.springframework.statemachine.monitor.StateMachineMonitor;
|
||||
@@ -97,7 +100,15 @@ public class StateMachineConfiguration<S, E> extends
|
||||
beanDefinitionBuilder.addConstructorArgValue(StateMachine.class);
|
||||
beanDefinitionBuilder.addConstructorArgValue(importingClassMetadata.getClassName());
|
||||
beanDefinitionBuilder.addConstructorArgValue(contextEvents);
|
||||
return beanDefinitionBuilder.getBeanDefinition();
|
||||
|
||||
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
|
||||
|
||||
// try to add more info about generics
|
||||
ResolvableType type = resolveFactoryObjectType(enableStateMachineEnclosingClass);
|
||||
if (type != null && beanDefinition instanceof RootBeanDefinition) {
|
||||
((RootBeanDefinition)beanDefinition).setTargetType(type);
|
||||
}
|
||||
return beanDefinition;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -107,6 +118,18 @@ public class StateMachineConfiguration<S, E> extends
|
||||
return types;
|
||||
}
|
||||
|
||||
private ResolvableType resolveFactoryObjectType(Class<?> enableStateMachineEnclosingClass) {
|
||||
ResolvableType type = null;
|
||||
try {
|
||||
Class<?>[] generics = ResolvableType.forClass(enableStateMachineEnclosingClass).getSuperType().resolveGenerics();
|
||||
if (generics != null && generics.length == 2) {
|
||||
type = ResolvableType.forClassWithGenerics(StateMachine.class, generics);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
private static class StateMachineDelegatingFactoryBean<S, E>
|
||||
extends BeanDelegatingFactoryBean<StateMachine<S, E>,StateMachineConfigBuilder<S, E>,StateMachineConfig<S, E>>
|
||||
implements SmartLifecycle, BeanNameAware {
|
||||
|
||||
@@ -27,8 +27,11 @@ import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.RootBeanDefinition;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.core.type.AnnotationMetadata;
|
||||
import org.springframework.statemachine.config.EnableStateMachineFactory;
|
||||
@@ -39,8 +42,8 @@ import org.springframework.statemachine.config.StateMachineFactory;
|
||||
import org.springframework.statemachine.config.builders.StateMachineConfigBuilder;
|
||||
import org.springframework.statemachine.config.common.annotation.AbstractImportingAnnotationConfiguration;
|
||||
import org.springframework.statemachine.config.common.annotation.AnnotationConfigurer;
|
||||
import org.springframework.statemachine.config.model.DefaultStateMachineModel;
|
||||
import org.springframework.statemachine.config.model.ConfigurationData;
|
||||
import org.springframework.statemachine.config.model.DefaultStateMachineModel;
|
||||
import org.springframework.statemachine.config.model.StatesData;
|
||||
import org.springframework.statemachine.config.model.TransitionsData;
|
||||
import org.springframework.util.ClassUtils;
|
||||
@@ -64,6 +67,12 @@ public class StateMachineFactoryConfiguration<S, E> extends
|
||||
@Override
|
||||
protected BeanDefinition buildBeanDefinition(AnnotationMetadata importingClassMetadata,
|
||||
Class<? extends Annotation> namedAnnotation) throws Exception {
|
||||
|
||||
String enableStateMachineEnclosingClassName = importingClassMetadata.getClassName();
|
||||
// for below classloader, see gh122
|
||||
Class<?> enableStateMachineEnclosingClass = ClassUtils.forName(enableStateMachineEnclosingClassName,
|
||||
ClassUtils.getDefaultClassLoader());
|
||||
|
||||
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder
|
||||
.rootBeanDefinition(StateMachineFactoryDelegatingFactoryBean.class);
|
||||
AnnotationAttributes attributes = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(
|
||||
@@ -72,9 +81,29 @@ public class StateMachineFactoryConfiguration<S, E> extends
|
||||
beanDefinitionBuilder.addConstructorArgValue(builder);
|
||||
beanDefinitionBuilder.addConstructorArgValue(importingClassMetadata.getClassName());
|
||||
beanDefinitionBuilder.addConstructorArgValue(contextEvents);
|
||||
return beanDefinitionBuilder.getBeanDefinition();
|
||||
|
||||
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
|
||||
|
||||
// try to add more info about generics
|
||||
ResolvableType type = resolveFactoryObjectType(enableStateMachineEnclosingClass);
|
||||
if (type != null && beanDefinition instanceof RootBeanDefinition) {
|
||||
((RootBeanDefinition)beanDefinition).setTargetType(type);
|
||||
}
|
||||
|
||||
return beanDefinition;
|
||||
}
|
||||
|
||||
private ResolvableType resolveFactoryObjectType(Class<?> enableStateMachineEnclosingClass) {
|
||||
ResolvableType type = null;
|
||||
try {
|
||||
Class<?>[] generics = ResolvableType.forClass(enableStateMachineEnclosingClass).getSuperType().resolveGenerics();
|
||||
if (generics != null && generics.length == 2) {
|
||||
type = ResolvableType.forClassWithGenerics(StateMachineFactory.class, generics);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<Class<? extends Annotation>> getAnnotations() {
|
||||
|
||||
@@ -0,0 +1,255 @@
|
||||
/*
|
||||
* Copyright 2017 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.statemachine.config;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.CoreMatchers.notNullValue;
|
||||
import static org.hamcrest.Matchers.sameInstance;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.statemachine.AbstractStateMachineTests;
|
||||
import org.springframework.statemachine.StateMachine;
|
||||
import org.springframework.statemachine.config.builders.StateMachineStateConfigurer;
|
||||
import org.springframework.statemachine.config.builders.StateMachineTransitionConfigurer;
|
||||
|
||||
public class MachineTypedTests extends AbstractStateMachineTests {
|
||||
|
||||
@Override
|
||||
protected AnnotationConfigApplicationContext buildContext() {
|
||||
return new AnnotationConfigApplicationContext();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutowireMachineByTypeNameMatches() {
|
||||
context.register(Config1.class, Config2.class, MyBean1Config.class);
|
||||
context.refresh();
|
||||
|
||||
MyBean1 myBean1 = context.getBean(MyBean1.class);
|
||||
assertThat(myBean1.machine1, notNullValue());
|
||||
assertThat(myBean1.machine2, notNullValue());
|
||||
assertThat(myBean1.machine1, not(sameInstance(myBean1.machine2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutowireMachineByTypeNameNotMatches() {
|
||||
context.register(Config1.class, Config2.class, MyBean2Config.class);
|
||||
context.refresh();
|
||||
|
||||
MyBean2 myBean2 = context.getBean(MyBean2.class);
|
||||
assertThat(myBean2.someMachine1, notNullValue());
|
||||
assertThat(myBean2.someMachine2, notNullValue());
|
||||
assertThat(myBean2.someMachine1, not(sameInstance(myBean2.someMachine2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutowireMachineFactoryByTypeNameMatches() {
|
||||
context.register(Config3.class, Config4.class, MyBean3Config.class);
|
||||
context.refresh();
|
||||
|
||||
MyBean3 myBean3 = context.getBean(MyBean3.class);
|
||||
assertThat(myBean3.machinefactory1, notNullValue());
|
||||
assertThat(myBean3.machinefactory2, notNullValue());
|
||||
assertThat(myBean3.machinefactory1, not(sameInstance(myBean3.machinefactory2)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAutowireMachineFactoryByTypeNameNotMatches() {
|
||||
context.register(Config3.class, Config4.class, MyBean4Config.class);
|
||||
context.refresh();
|
||||
|
||||
MyBean4 myBean4 = context.getBean(MyBean4.class);
|
||||
assertThat(myBean4.someMachineFactory1, notNullValue());
|
||||
assertThat(myBean4.someMachineFactory2, notNullValue());
|
||||
assertThat(myBean4.someMachineFactory1, not(sameInstance(myBean4.someMachineFactory2)));
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class MyBean1Config {
|
||||
|
||||
@Bean
|
||||
public MyBean1 myBean1() {
|
||||
return new MyBean1();
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class MyBean2Config {
|
||||
|
||||
@Bean
|
||||
public MyBean2 myBean2() {
|
||||
return new MyBean2();
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class MyBean3Config {
|
||||
|
||||
@Bean
|
||||
public MyBean3 myBean3() {
|
||||
return new MyBean3();
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
public static class MyBean4Config {
|
||||
|
||||
@Bean
|
||||
public MyBean4 myBean4() {
|
||||
return new MyBean4();
|
||||
}
|
||||
}
|
||||
|
||||
public static class MyBean1 {
|
||||
|
||||
@Autowired
|
||||
StateMachine<MyTestStates1, MyTestEvents1> machine1;
|
||||
|
||||
@Autowired
|
||||
StateMachine<MyTestStates2, MyTestEvents2> machine2;
|
||||
}
|
||||
|
||||
public static class MyBean2 {
|
||||
|
||||
@Autowired
|
||||
StateMachine<MyTestStates1, MyTestEvents1> someMachine1;
|
||||
|
||||
@Autowired
|
||||
StateMachine<MyTestStates2, MyTestEvents2> someMachine2;
|
||||
}
|
||||
|
||||
public static class MyBean3 {
|
||||
|
||||
@Autowired
|
||||
StateMachineFactory<MyTestStates1, MyTestEvents1> machinefactory1;
|
||||
|
||||
@Autowired
|
||||
StateMachineFactory<MyTestStates2, MyTestEvents2> machinefactory2;
|
||||
}
|
||||
|
||||
public static class MyBean4 {
|
||||
|
||||
@Autowired
|
||||
StateMachineFactory<MyTestStates1, MyTestEvents1> someMachineFactory1;
|
||||
|
||||
@Autowired
|
||||
StateMachineFactory<MyTestStates2, MyTestEvents2> someMachineFactory2;
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine(name = "machine1")
|
||||
public static class Config1 extends EnumStateMachineConfigurerAdapter<MyTestStates1, MyTestEvents1> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<MyTestStates1, MyTestEvents1> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(MyTestStates1.S1)
|
||||
.state(MyTestStates1.S2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<MyTestStates1, MyTestEvents1> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(MyTestStates1.S1).target(MyTestStates1.S2)
|
||||
.event(MyTestEvents1.E1);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachine(name = "machine2")
|
||||
public static class Config2 extends EnumStateMachineConfigurerAdapter<MyTestStates2, MyTestEvents2> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<MyTestStates2, MyTestEvents2> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(MyTestStates2.S1)
|
||||
.state(MyTestStates2.S2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<MyTestStates2, MyTestEvents2> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(MyTestStates2.S1).target(MyTestStates2.S2)
|
||||
.event(MyTestEvents2.E1);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachineFactory(name = "machinefactory1")
|
||||
public static class Config3 extends EnumStateMachineConfigurerAdapter<MyTestStates1, MyTestEvents1> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<MyTestStates1, MyTestEvents1> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(MyTestStates1.S1)
|
||||
.state(MyTestStates1.S2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<MyTestStates1, MyTestEvents1> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(MyTestStates1.S1).target(MyTestStates1.S2)
|
||||
.event(MyTestEvents1.E1);
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableStateMachineFactory(name = "machinefactory2")
|
||||
public static class Config4 extends EnumStateMachineConfigurerAdapter<MyTestStates2, MyTestEvents2> {
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineStateConfigurer<MyTestStates2, MyTestEvents2> states) throws Exception {
|
||||
states
|
||||
.withStates()
|
||||
.initial(MyTestStates2.S1)
|
||||
.state(MyTestStates2.S2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void configure(StateMachineTransitionConfigurer<MyTestStates2, MyTestEvents2> transitions) throws Exception {
|
||||
transitions
|
||||
.withExternal()
|
||||
.source(MyTestStates2.S1).target(MyTestStates2.S2)
|
||||
.event(MyTestEvents2.E1);
|
||||
}
|
||||
}
|
||||
|
||||
public enum MyTestStates1 {
|
||||
S1, S2;
|
||||
}
|
||||
|
||||
public enum MyTestEvents1 {
|
||||
E1;
|
||||
}
|
||||
|
||||
public enum MyTestStates2 {
|
||||
S1, S2;
|
||||
}
|
||||
|
||||
public enum MyTestEvents2 {
|
||||
E1;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user