I have a prototype-scope bean, which I want to be injected by @Autowired annotation. In this bean, there is also @PostConstruct method which is not called by Spring and I don't understand why.
My bean definition:
package somepackage;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@Component
@Scope("prototype")
public class SomeBean {
public SomeBean(String arg) {
System.out.println("Constructor called, arg: " + arg);
}
@PostConstruct
private void init() {
System.out.println("Post construct called");
}
}
JUnit class where I want to inject bean:
package somepackage;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringRunner;
@RunWith(SpringRunner.class)
@ContextConfiguration("classpath*:applicationContext-test.xml")
public class SomeBeanTest {
@Autowired
ApplicationContext ctx;
@Autowired
@Value("1")
private SomeBean someBean;
private SomeBean someBean2;
@Before
public void setUp() throws Exception {
someBean2 = ctx.getBean(SomeBean.class, "2");
}
@Test
public void test() {
System.out.println("test");
}
}
Spring configuration:
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<context:component-scan base-package="somepackage"/>
</beans>
The output from execution:
Constructor called, arg: 1
Constructor called, arg: 2
Post construct called
test
When I initialize bean by calling getBean
from ApplicationContext
everything works as expected. My question is why injecting bean by @Autowire
and @Value
combination is not calling @PostConstruct
method
Why is @Value used instead of @Autowired?
The
@Value
annotation is used to inject values and normally has as destination strings, primitives, boxed types and java collections.Acording to Spring's documentation:
Value
receives a string expression which is used by spring to handle the conversion to the destination object. This conversion can be through the Spring's type conversion, the java bean property editor, and the Spring's SpEL expresions. The resulting object of this conversion, in principle, is not managed by spring (even though you can return an already managed bean from any of this methods).By the other hand, the AutowiredAnnotationBeanPostProcessor is a
This class handles the field injection, resolves the dependencies, and eventually calls the method doResolveDependency, is in this method where the 'priority' of the injection is resolved, springs checks if a sugested value is present which is normally an expression string, this sugested value is the content of the annotation
Value
, so in case is present a call to the class SimpleTypeConverter is made, otherwise spring looks for candicate beans and resolves the autowire.Simply the reason
@Autowired
is ignored and@Value
is used, is because the injection strategy of value is checked first. Obviously always has to be a priority, spring could also throw an exception when multiple conflicting annotations are used, but in this case is determined by that previous check to the sugested value.I couldn't find anything related to this 'priority' is spring, but simple is because is not intended to use this annotations together, just as for instance, its not intended to use
@Autowired
and@Resource
together either.Why does @Value creates a new intance of the object
Previously I said that the class
SimpleTypeConverter
was called when the suggested value was present, the specific call is to the method convertIfNecessary, this is the one that performs the conversion of the string into the destination object, again this can be done with property editor or a custom converter, but none of those are used here. A SpEL expression isn't used either, just a string literal.Spring checks first if the destination object is a string, or a collection/array (can convert e.g. comma delimited list), then checks if the destination is an enum, if it is, it tries to convert the string, if is not, and is not an interface but a class, it checks the existance of a
Constructor(String)
to finally create the object (not managed by spring). Basically this converter tries many different ways to convert the string to the final object.This instantiation will only work using a string as argument, if you use for instance, a SpEL expression to return a long
@Value("#{2L}")
, and use an object with aConstructor(Long)
it will throw anIllegalStateException
with a similar message:Possible Solution
Using a simple @Configuration class as a supplier.
You could define MyBean as a static class in MyBeanSupplier class if its the only method it would have. Also you cannot use the proxy mode ScopedProxyMode.TARGET_CLASS, because you'll need to provide the arguments as beans and the arguments passed to
getMyBean
would be ignored.With this approach you wouldn't be able to autowire the bean itself, but instead, you would autowire the supplier and then call the get method.
You can also create the bean using the application context.
And the
@PostConstruct
method should be called no matter which one you use, but@PreDestroy
is not called in prototype beans.