10k

Spring Cookbook2 - Self Injection

Self Injection!

We know that there are dependency injection in Spring, who helps us to do the bean instantiation. The self injection , you can tell from its name, is a bean inject itself in its class, to overcome the limitation described below of AOP.

Why

  1. Start from @Transactional

```java @Service public class MyService { public void doSomething() { // ... doSomethingElse(); }

@Transactional
public void doSomethingElse() {
    // ...
}

} ```

When doSomething() is called, the transactional annotation doesn't take effect. -> when you call another method in a method(self invoking method) , and both methods belongs to the same class, the @Transactional will not take effective.

  1. @Transactional, annotation in Spring is based on Aspect oriented programming. The original class will be wrapped as a new proxy class. -> to avoid the repeat execution of such code,

  2. calling class method directly will not take effect -> so when doSomething call doSomethingElse, the aspect code in the proxy class won't be executed.

  3. e.g.

    1. The code that java generate agent class

      ```java public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

      // transactional stuff, maybe add lock
        Object result = method.invoke(proxiedObject, args);
      

      // transactional stuff, maybe release lock return result; } ```

How self injection avoid this

```java @Service public class MyService { @Autowired private MyService service; public void doSomething() { // ... service.doSomethingElse(); }

@Transactional public void doSomethingElse() { // ... } } ```

After self injection, the call of doSomethingElse() will be regarded as another instance function so the aspect code will take effect.

Risk

  1. Circular dependencies between modules. (APPENDIX 1)
  2. Memory leak(same class instance being managed by the GC) and performance issue(additional instance is created)

Conclusion

Use carefully

Referece

  1. Self-Injection In Spring
  2. 为什么Spring可以“自己注入自己

Appendix 1

Spring 4.3 handle the circular dependencies cause by self injection

/**
    * Find bean instances that match the required type.
    * Called during autowiring for the specified bean.
    * @param beanName the name of the bean that is about to be wired
    * @param requiredType the actual type of bean to look for
    * (may be an array component type or collection element type)
    * @param descriptor the descriptor of the dependency to resolve
    * @return a Map of candidate names and candidate instances that match
    * the required type (never {@code null})
    * @throws BeansException in case of errors
    * @see #autowireByType
    * @see #autowireConstructor
    */
   protected Map<String, Object> findAutowireCandidates(
           @Nullable String beanName, Class<?> requiredType, DependencyDescriptor descriptor) {

       String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors(
               this, requiredType, true, descriptor.isEager());
       Map<String, Object> result = CollectionUtils.newLinkedHashMap(candidateNames.length);
       for (Map.Entry<Class<?>, Object> classObjectEntry : this.resolvableDependencies.entrySet()) {
           Class<?> autowiringType = classObjectEntry.getKey();
           if (autowiringType.isAssignableFrom(requiredType)) {
               Object autowiringValue = classObjectEntry.getValue();
               autowiringValue = AutowireUtils.resolveAutowiringValue(autowiringValue, requiredType);
               if (requiredType.isInstance(autowiringValue)) {
                   result.put(ObjectUtils.identityToString(autowiringValue), autowiringValue);
                   break;
               }
           }
       }
       for (String candidate : candidateNames) {
           if (!***isSelfReference***(beanName, candidate) && isAutowireCandidate(candidate, descriptor)) {
               addCandidateEntry(result, candidate, descriptor, requiredType);
           }
       }
       if (result.isEmpty()) {
           boolean multiple = indicatesMultipleBeans(requiredType);
           // Consider fallback matches if the first pass failed to find anything...
           DependencyDescriptor fallbackDescriptor = descriptor.forFallbackMatch();
           for (String candidate : candidateNames) {
               if (!isSelfReference(beanName, candidate) && isAutowireCandidate(candidate, fallbackDescriptor) &&
                       (!multiple || getAutowireCandidateResolver().hasQualifier(descriptor))) {
                   addCandidateEntry(result, candidate, descriptor, requiredType);
               }
           }
           if (result.isEmpty() && !multiple) {
               // Consider self references as a final pass...
               // but in the case of a dependency collection, not the very same bean itself.
               for (String candidate : candidateNames) {
                   if (isSelfReference(beanName, candidate) &&
                           (!(descriptor instanceof MultiElementDescriptor) || !beanName.equals(candidate)) &&
                           isAutowireCandidate(candidate, fallbackDescriptor)) {
                       addCandidateEntry(result, candidate, descriptor, requiredType);
                   }
               }
           }
       }
       return result;
   }

关键点在:isSelfReference, beanName 是需要注入的self bean,candidateName是当前正在初始化的bean:

/**
    * Determine whether the given beanName/candidateName pair indicates a self reference,
    * i.e. whether the candidate points back to the original bean or to a factory method
    * on the original bean.
    */
   private boolean isSelfReference(@Nullable String beanName, @Nullable String candidateName) {
       return (beanName != null && candidateName != null &&
               (beanName.equals(candidateName) || (containsBeanDefinition(candidateName) &&
                       beanName.equals(getMergedLocalBeanDefinition(candidateName).getFactoryBeanName()))));
   }

考虑到requiredType有可能是接口类,故通过BeanFactoryUtils.beanNamesForTypeIncludingAncestors方法可以拿到具体的需要注入的beanNames,并放在candidateNames。

Thoughts? Leave a comment