Skip to content

Infer return type of parameterized static factory methods [SPR-9493] #14127

Closed
@spring-projects-issues

Description

@spring-projects-issues

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

  1. Add a new method to GenericTypeResolver that can accurately predict the target return type of a parameterized factory method in most use cases.
  2. Modify the getTypeForFactoryMethod() method in AbstractAutowireCapableBeanFactory 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:

1 votes, 5 watchers

Metadata

Metadata

Assignees

Labels

in: coreIssues in core modules (aop, beans, core, context, expression)in: testIssues in the test moduletype: enhancementA general enhancement

Type

No type

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions