Description
Sam Brannen opened SPR-9493 and commented
Overview
Spring users often run into issues with autowiring by type for beans that are defined in XML configuration using a factory-method
that is parameterized.
Although this situation arrises frequently when using dynamic mocks to replace production-specific beans in tests, this situation is not particular to tests or mocks. One can also have parameterized factory methods for generating proxies or dynamic implementations of components in production code.
Specifically, if a factory-method
is parameterized (i.e., the method itself, not the enclosing class
or interface
), Spring always predicts the return type to be Object
, even if the type can be explicitly inferred from the method signature and supplied arguments (which are available in the bean definition).
Note, however, that the order in which beans are defined plays a role here. If the bean created by the factory is instantiated by the Spring container before the autowired dependent, then there is no issue, since the concrete type of the factory-created bean is already known.
Problems arise when the ordering is reversed: if the autowired dependent bean is created before the factory-created bean is created, then the concrete type of the factory-created bean is not yet known (i.e., it will be predicted to be simply Object
). Consequently, autowiring by type will fail. Similarly, a call to getBeansOfType(<type of factory-created bean>\)
on the BeanFactory
or ApplicationContext
will also fail to return the factory-created bean if it has not yet been created.
Dynamic Mock Example
Consider a parameterized factory-method
declaration such as EasyMock's createMock()
method which has the following signature.
public static <T> T createMock(Class<T> toMock)
Ideally Spring should be able to predict the type T
before the factory method is invoked. However, as of Spring 3.1, the predicted type is unfortunately Object
instead of T
. For details, see the implementation of the getTypeForFactoryMethod()
method in AbstractAutowireCapableBeanFactory
. In short, getTypeForFactoryMethod()
does not infer the return type based on the parameterized type supplied as an argument to the factory method (i.e., Class<T> toMock
in the EasyMock example). In such cases the return type is simply predicted to be Object
.
Test Case
The following test case can be used to verify both the current and the proposed behavior.
With the current behavior the test passes as is.
If you comment out the three getBean() lines, the test will then fail. With the changes proposed by this issue, the test would no longer fail with those three lines removed.
@Test
public void parameterizedFactoryMethod() {
RootBeanDefinition rbd = new RootBeanDefinition(EasyMock.class);
rbd.setFactoryMethodName("createMock");
rbd.getConstructorArgumentValues().addGenericArgumentValue(Runnable.class);
DefaultListableBeanFactory bf = new DefaultListableBeanFactory();
bf.registerBeanDefinition("easyMock", rbd);
// NOTE: commenting out the following three lines will prevent the
// subsequent call to getBeansOfType() from finding the Runnable mock.
Object mock = bf.getBean("easyMock");
assertNotNull(mock);
assertTrue(mock instanceof Runnable);
Map<String, Runnable> beans = bf.getBeansOfType(Runnable.class);
assertEquals(1, beans.size());
}
Alternatives
In testing scenarios, most developers end up implementing their own FactoryBean
that circumvents the issues outlined above by explicitly returning the mock object's type in FactoryBean.getObjectType()
(e.g., in a MockitoFactoryBean
, EasyMockFactoryBean
, etc.). There is also an open source project called Springockito that addresses this issue for Spring and Mockito.
These are viable work-arounds in testing scenarios when you are explicitly overriding a bean definition with a dynamic mock, because in that case your test configuration is decidedly only for tests.
However, these work-arounds do not address the general need for reliable autowiring by type for beans created by a parameterized factory-method
.
Analysis
If a factory method returns T
and an argument to the factory method is either parameterized with Class<T>
or of type T
, then we should have enough information to infer the exact return type from the method metadata and bean definition.
Returning to the EasyMock example, if we pass java.lang.Runnable
to the createMock()
method, then in the getTypeForFactoryMethod()
method in AbstractAutowireCapableBeanFactory
we actually have access to all of the following information:
Description | Value |
---|---|
Factory Class | org.easymock.EasyMock |
Factory Method | createMock |
Factory Method Generic Signature | public static <T> T org.easymock.EasyMock.createMock(java.lang.Class<T>\) |
Factory Method Type Parameters | {T} |
Factory Method Generic Return Type | T |
Factory Method Generic Parameter Types | {java.lang.Class<T>} |
Bean Definition Generic Constructor Args | \[java.lang.Runnable\] |
With that information we should be able to reliably predict the return type to be java.lang.Runnable
.
Deliverables
- Add a new method to
GenericTypeResolver
that can accurately predict the target return type of a parameterized factory method in most use cases. - Modify the
getTypeForFactoryMethod()
method inAbstractAutowireCapableBeanFactory
so that it properly delegates to the method introduced in the previous deliverable, falling back to the current algorithm if the factory method is not parameterized.
Affects: 2.5 final, 3.0 GA, 3.1 GA
Issue Links:
- Introduce FactoryBean to create mock beans with EasyMock [SPR-9130] #13769 Introduce FactoryBean to create mock beans with EasyMock
- Return type prediction for generic factory method fails if type conversion of method arguments is necessary [SPR-10411] #15044 Return type prediction for generic factory method fails if type conversion of method arguments is necessary
- @Autowired dependencies against bean definitions with type-inspecific factory-methods may fail [SPR-8769] #13412
@Autowired
dependencies against bean definitions with type-inspecific factory-methods may fail ("supersedes")
1 votes, 5 watchers