Sam Brannen opened SPR-8450 and commented
Background
Autowiring a bean with an instance of itself is not something one would normally do, but there are cases where it might be useful -- for example, to route method calls through the proxy that wraps the bean. There are of course alternatives to this, such as using load-time weaving with AspectJ proxies instead of JDK dynamic proxies.
Note that self-autowiring by name via @Resource
is permitted by the framework; whereas, self-autowiring by type is not permitted by the framework as can be seen in the following code snippet from DefaultListableBeanFactory
's findAutowireCandidates(String, Class, DependencyDescriptor)
method.
for (String candidateName : candidateNames) {
if (!candidateName.equals(beanName) && isAutowireCandidate(candidateName, descriptor)) {
result.put(candidateName, getBean(candidateName));
}
}
The name of the bean (i.e., the bean that's trying to autowire itself) is beanName
. That bean is in fact an autowire candidate, but the above if-condition returns false
(since candidateName
equals the beanName
). Thus you simply cannot autowire a bean with itself by type (at least not as of Spring 3.1 M2).
Goal
Add support for self-autowiring using @Autowired
on fields and methods.
Stack Overflow Discussion
This topic was brought to our attention via a discussion on Stack Overflow.
Affects: 3.0.5
Issue Links:
- #21186 Self autowiring does not work for transactional beans
- #19532 Self reference fallback in 4.3 is not meant to apply to collection elements
- #16794 @Autowired
does not work for target bean of type Collection
- #18973 Define and document rules for @Autowired
self-injection in case of ambiguity
18 votes, 23 watchers
Comment From: spring-projects-issues
Chris Beams commented
On review of the StackOverflow thread, I wonder whether the "workaround" approach of using @Autowired
to inject the enclosing ApplicationContext
and look the bean up isn't actually a better idea. It reveals the intention more clearly that the user wants the bean as managed by Spring, e.g. post-proxying, etc. It would probably never be intuitive for someone to see a FooService
that uses @Autowired
to inject a FooService
into itself. The fact that @Resource
works here is more an inadvertent bonus than it is a feature by design.
I understand that this approach complicates unit-testability somewhat, but the use cases for this (self-injection) are probably few and far between and thus a reasonable tradeoff.
Placing within the General Backlog where it can get voted up and receive further comments, but there is no immediate intention to resolve at this time.
Comment From: spring-projects-issues
Fred Clausen commented
This is a bonus to me. In my specific case, it means I don't have to mess with the default cache proxying:
@Service(value="fooService")
public JPAFooService implements FooService {
@Resource(name="fooService") FooService fooService; // only works if injected by name
@Cacheable
List<Foo> getAll() {
return dao.getFoos();
};
boolean hasFoo(Foo f) {
List<Foo> all = getAll(); // THIS WILL BYPASS THE CACHE
List<Foo> all2 = fooService.getAll(); // THIS WILL USE THE CACHE
}
}
Comment From: spring-projects-issues
Chris Beams commented
Hi Fred,
Your example helps make clear why this issue is probably best considered as an advertisement for AspectJ-based advice (as opposed to the proxy approach). By using load- or compile-time weaving and spring-aspect's built in AJ aspects, you can forget about this proxy business entirely.
The proxy approach hits about 80% of the use cases, and that's why we advocate it first. But cases like this are really where bytecode-weaving shines.
Comment From: spring-projects-issues
Ben Fagin commented
On the one hand, calling a method on a reference to your 'real' self could probably be described as an anti-pattern. On the other hand, any day that I can avoid bytecode weaving is a good day. The idea of using a self
reference is certainly present in other languages. Used judiciously, perhaps in an application transitioning from proxying to weaving, autowiring a self reference can be helpful.
Is autowiring oneself by type safe though? Is there a risk of autowiring some other bean entirely?
Comment From: spring-projects-issues
Sam Brannen commented
Is autowiring oneself by type safe though?
No, it is potentially dangerous (see below).
Is there a risk of autowiring some other bean entirely?
Yes, autowiring by type could potentially result in a different bean of the same type being injected. That's why Fred Clausen, in his example above, explicitly references the bean by name in order to ensure that the bean gets a reference to itself (potentially proxied). He does this using the bean name/ID in conjunction with @Resource
; however, the same could be achieved using qualifiers (i.e., via @Qualifier
).
Comment From: spring-projects-issues
Niels Bech Nielsen commented
Given that the use case for this problem is self proxy invocation, wouldn't it be simpler to address the use case through an explicit annotation, (e.g @AutowireSelf
, @AutowireProxy
, or something). It would probably be easy for the BeanFactory to resolve the case correctly, and it would probably be more explanatory than both autowire-by-name and autowire-by-type.
I have found the use case a few times and keeps being amazed at how people work around the problem usually in some less than elegant way. Some by autowire of some kind or by massive delegation or using static holders. Usually the use case have been identified through a massive debugging effort, because the developer was unaware of the problem in the first place.
With a specific annotation it would be easy to document the effect in proxy annotations:
"Should you wish to call a method annotated with @xxxProxyAnnotationOfSomeKind
from another method in the same class, you must use a self-proxy(link to annotation)."
Comment From: spring-projects-issues
Premraj Motling commented
@Juergen
Will this be supported in coming version? I liked the idea of doing this explicitly (like having special annotation @AutowireSelf
)
Comment From: spring-projects-issues
Juergen Hoeller commented
For 4.2, I intend to research the options and suggest a recommended solution for this scenario... A dedicated annotation may indeed be the best compromise.
Juergen
Comment From: spring-projects-issues
Eduardo Simioni commented
I have a use case affected by this problem, where I think the solution of having a specific annotation wouldn't work, or maybe it would, but then it would have to consider collections as well, holding self and others.
In my applications there is an event mechanism, to put it very simple:
All @ServiceS
extend AbstractService, with the abstract having a:
@Autowired
private List\
The AbstractService triggers events on some common situations in my application.
A @Service
can be EventListener, meaning that it can potentially be injected in itself through the eventListeners, since it extends AbstractService.
Now, naturally I'm not handling events from a service inside itself, but I need this to be implemented this way so that it is generic and duplication free.
The injection works normally, but there are some corner cases that I don't recall right now where it doesn't. If you want more details or the exact corner cases, I could investigate and provide.
Comment From: spring-projects-issues
Ben Fagin commented
I have been using a @Self
annotation in my own code to handle these situations. I usually find it necessary when I want to ensure that my declarative transaction and caching semantics are preserved. In a perfect world the weaving would take care of this, but I have never been able to get that to work 100% of the time.
This is the code I use in my own projects. While not the absolute best, it has worked so far.
@Component
public class SelfWiringBeanPostProcessor implements BeanPostProcessor, SmartLifecycle, BeanFactoryAware {
private final List<Callable<Void>> injections = new ArrayList<>();
private BeanFactory beanFactory;
private boolean isRunning = false;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = beanFactory;
}
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
injections.add(() -> {
// find all fields marked @Self
ReflectionUtils.doWithFields(bean.getClass(),
// get the latest bean and inject it
field -> {
Object ref = beanFactory.getBean(beanName);
ReflectionUtils.makeAccessible(field);
field.set(bean, ref);
},
// filter by annotation
field -> field.isAnnotationPresent(Self.class)
);
return null;
});
return bean;
}
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
// ------------------------------------------------------------------- //
@Override
public boolean isAutoStartup() {
return true;
}
@Override
public void stop(Runnable callback) {
stop();
callback.run();
}
@Override
public void start() {
for (Callable<Void> method : injections) {
try {
method.call();
} catch (Exception ex) {
throw Throwables.propagate(ex);
}
}
isRunning = true;
}
@Override
public void stop() {
isRunning = false;
}
@Override
public boolean isRunning() {
return isRunning;
}
@Override
public int getPhase() {
return Integer.MIN_VALUE;
}
}
Comment From: spring-projects-issues
Othon Crelier commented
It will be really interesting to have this solved.
Particularly interested in @Async
and @Transactional
use cases.
Comment From: spring-projects-issues
Sam Brannen commented
FYI: this has been resolved in the following GitHub commit:
https://github.com/spring-projects/spring-framework/commit/4a0fa69ce469cae2e8c8a1a45f0b43f74a74481d
Comment From: spring-projects-issues
Peter Rader commented
Not working well in 4.3.5.RELEASE!
{{ /** * Authorithy to decide what movements are disallowed.
-
\
-
Not responsible in case of movements that are blessed in past (i.e. part of a
-
loading-process of mementos, deserialization, loading a.s.).
-
\
-
Notice that this council is only responsible for non-headless-environments. */
@Named
public final class LayerDropAuthorithy extends LayerDragNDropTransferHandler implements DropVetoCouncilor { public LayerDropAuthorithy() { System.out.println(SpringVersion.getVersion());} /** * The council to decide where to drop elements. */ // @Inject public final DropVetoCouncilor[] dropVetoCouncil = null; @Inject public final ApplicationContext ac = null; @Override public boolean allowsMove(final VectorPublishNode target, final Set<VectorPublishNode> nodesConcerned) { boolean blocked = false; for (DropVetoCouncilor dropVetoCouncilor : dropVetoCouncil) { blocked |= dropVetoCouncilor.blockMove(nodesConcerned, target); } return blocked; } @Override public boolean blockMove(Set<VectorPublishNode> nodesConcerned, VectorPublishNode target) { // Block if root-element shall be moved. for (VectorPublishNode vectorPublishNode : nodesConcerned) { if (vectorPublishNode.getParent() == null) { return true; } } return false; } @PostConstruct public void test() { System.out.println(SpringVersion.getVersion()); System.out.println("dddddddddd->" + ac.getBeansOfType(DropVetoCouncilor.class).size()); }
}
}}
gives: {{4.3.5.RELEASE dddddddddd->1}}
but uncomment // @Inject
gives {{
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'addSquare': Unsatisfied dependency expressed through field 'history'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'historyImpl': Unsatisfied dependency expressed through field 'layer'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'layerImpl': Unsatisfied dependency expressed through field 'dragNDropHandler'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'layerDropAuthorithy': Unsatisfied dependency expressed through field 'dropVetoCouncil'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'net.vectorpublish.desktop.vp.api.layer.dnd.DropVetoCouncilor[]' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@javax
.inject.Inject()}
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'addSquare': Unsatisfied dependency expressed through field 'history'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'historyImpl': Unsatisfied dependency expressed through field 'layer'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'layerImpl': Unsatisfied dependency expressed through field 'dragNDropHandler'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'layerDropAuthorithy': Unsatisfied dependency expressed through field 'dropVetoCouncil'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'net.vectorpublish.desktop.vp.api.layer.dnd.DropVetoCouncilor[]' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@javax
.inject.Inject()}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1225)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:552)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:759)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
at org.springframework.context.annotation.AnnotationConfigApplicationContext.\@javax
.inject.Inject()}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1225)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:552)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:207)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1136)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1064)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
... 46 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'layerImpl': Unsatisfied dependency expressed through field 'dragNDropHandler'; nested exception is org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'layerDropAuthorithy': Unsatisfied dependency expressed through field 'dropVetoCouncil'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'net.vectorpublish.desktop.vp.api.layer.dnd.DropVetoCouncilor[]' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@javax
.inject.Inject()}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1225)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:552)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:207)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1136)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1064)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
... 59 more
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'layerDropAuthorithy': Unsatisfied dependency expressed through field 'dropVetoCouncil'; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'net.vectorpublish.desktop.vp.api.layer.dnd.DropVetoCouncilor[]' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@javax
.inject.Inject()}
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:588)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1225)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:552)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:202)
at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:207)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1136)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1064)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
... 72 more
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'net.vectorpublish.desktop.vp.api.layer.dnd.DropVetoCouncilor[]' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@javax
.inject.Inject()}
at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1474)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1102)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1064)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredFieldElement.inject(AutowiredAnnotationBeanPostProcessor.java:585)
... 85 more
}}
Comment From: spring-projects-issues
Peter Rader commented
Ignore last message please, using 4.3.4.RELEASE solved the problem. Looks like a different bug.
Comment From: ankitkpd
Looks like after the fix findAutowireCandidates()
will either return candidates that are not selfReferenced or candidates that are Collection/Map beans and are self referenced.
But it won't return both i.e. non self referenced candidates that are being added with
for (String candidate : candidateNames) {
if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
addCandidateEntry(result, candidate, descriptor, requiredType);
}
}
AND candidates that are Collection/Map beans & are self referenced being added with the fix i.e.
for (String candidate : candidateNames) {
if (isSelfReference(beanName, candidate) &&
(!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&
isAutowireCandidate(candidate, fallbackDescriptor)) {
addCandidateEntry(result, candidate, descriptor, requiredType);
}
}
Because if condition if (result.isEmpty())
would prevent it to do so.
Is there a way to get autowired candidates of both types?
Comment From: ankitkpd
@spring-projects-issues appreciate you feedback about https://github.com/spring-projects/spring-framework/issues/13096#issuecomment-2073032069 Thanks