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:
Janne Valkealahti
2017-02-10 11:50:28 +00:00
parent f3cd30532b
commit 1389360950
3 changed files with 311 additions and 4 deletions

View File

@@ -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 {

View File

@@ -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() {

View File

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