Spring - automatic injection?

Keywords: Java Back-end

The way we often inject is similar to this

@Service
public class HelloService {
    @Autowired
    private BeanFactory beanFactory;
    @Autowired
    public HelloService(ApplicationContext applicationContext) {
    }
    @Autowired
    public void setEnvironment(Environment environment) {   
    }
}

Whether constructor injection or attribute injection, we can call it explicit injection.

Let's take another look at our commonly used annotation @ Bean

public @interface Bean {

   @AliasFor("name")
   String[] value() default {};

   @AliasFor("value")
   String[] name() default {};

   @Deprecated
   Autowire autowire() default Autowire.NO;

   boolean autowireCandidate() default true;

   String initMethod() default "";

   String destroyMethod() default AbstractBeanDefinition.INFER_METHOD;

}

Although the annotation of the attribute autowire we are concerned about has been abandoned, it does not prevent us from understanding it.

  • NO is the default value, which means it will not be injected automatically
  • BY_NAME, automatic injection through beanName
  • BY_TYPE, automatic injection by parameter type

Autowire.NO

@Configuration
public class Config {
    @Bean(autowire = Autowire.NO)
    public HelloService helloService() {
        return new HelloService();
    }
}

public class HelloService {
    public void setEnvironment(Environment environment) {
        System.out.println("invoke " + environment);
    }
}

We use the default configuration without automatic injection of HelloService. There is no doubt that setEnvironment will not be invoked by the Spring framework.

Autowire.BY_NAME

We changed the above code to

@Bean(autowire = Autowire.BY_NAME)

You will find that setEnvironment is called and the parameter environment is the standardservlet environment in Spring

In this step, if you are impressed with the Spring bean acquisition process Spring acquiring singleton process (3) You can know that it is called in AbstractAutowireCapableBeanFactory#populateBean

if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
   MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
   // Put the automatically injected value into newPvs
   if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
      autowireByName(beanName, mbd, bw, newPvs);
   }
   // Add property values based on autowire by type if applicable.
   if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
      autowireByType(beanName, mbd, bw, newPvs);
   }
   pvs = newPvs;
}
..........
if (pvs != null) {
            applyPropertyValues(beanName, mbd, bw, pvs);
}

The corresponding code is also very simple

protected void autowireByName(
      String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
    
  // Obtain the main beanName and explain the setter method according to the setter method
   String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
   for (String propertyName : propertyNames) {
     // If it is not in BeanFactory, it will be ignored directly and no error will be reported
      if (containsBean(propertyName)) {
         Object bean = getBean(propertyName);
        // Add to pvs
         pvs.add(propertyName, bean);
         registerDependentBean(propertyName, beanName);
          
      }
      else {
        // If the bean does not exist, no error will be reported and ignored directly
      }
   }
}

You can see BY_NAME is obtained by interpreting the setter method name. If beanName does not exist in BeanFactory, it is ignored directly, similar to @ Autowired(required=false). Not mandatory injection

However, your method name must start with setXxx, comply with certain naming rules, and be similar to the rules of BeanInfo( Java introspection ), but it is looser than it. There is no limit here. The return value must be void

Autowire.BY_TYPE

autowireByType

protected void autowireByType(
      String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {

       .......
   Set<String> autowiredBeanNames = new LinkedHashSet<>(4);
   String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
  // Iterate over the setter method interpretation to get its property name
   for (String propertyName : propertyNames) {
      try {
         PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
         
         if (Object.class != pd.getPropertyType()) {
            MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);
            boolean eager = !(bw.getWrappedInstance() instanceof PriorityOrdered);
           // AutowireByTypeDependencyDescriptor is a key class, and its returned getDependencyName is always null
            DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager);
            Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);
            if (autowiredArgument != null) {
               pvs.add(propertyName, autowiredArgument);
            }
            for (String autowiredBeanName : autowiredBeanNames) {
               registerDependentBean(autowiredBeanName, beanName);
            }
            autowiredBeanNames.clear();
         }
      }
      catch (BeansException ex) {
         throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);
      }
   }
}

With BY_NAME is very similar. If the object I automatically inject does not exist in BeanFactory, will it not throw exceptions? The answer is yes. This is mainly because the class AutowireByTypeDependencyDescriptor is used

private static class AutowireByTypeDependencyDescriptor extends DependencyDescriptor {

   public AutowireByTypeDependencyDescriptor(MethodParameter methodParameter, boolean eager) {
      super(methodParameter, false, eager);
   }

   @Override
   public String getDependencyName() {
      return null;
   }
}
// Parent class
public DependencyDescriptor(MethodParameter methodParameter, boolean required, boolean eager) {
        super(methodParameter);
      .......
        this.required = required;
        .......
    }

The first key point is that required is false. When the dependent bean does not exist BeanFactory, false will not throw an exception.

The second key point is that if there are two identical beans and neither bean declares Primary or priorityorded, it will throw an exception instead of matching and filtering out the appropriate beans according to beanName, because getDependencyName always returns null

This situation is different from @ Autowired because BY_TYPE does not depend on beanName

@Nullable
protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
   Class<?> requiredType = descriptor.getDependencyType();
   String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
   if (primaryCandidate != null) {
      return primaryCandidate;
   }
   String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
   if (priorityCandidate != null) {
      return priorityCandidate;
   }
   // Fallback
   for (Map.Entry<String, Object> entry : candidates.entrySet()) {
      String candidateName = entry.getKey();
      Object beanInstance = entry.getValue();
     // resolvableDependencies 
      if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||
          // BY_ Type matchsbeanname returned false
            matchesBeanName(candidateName, descriptor.getDependencyName())) {
         return candidateName;
      }
   }
   return null;
}

Key value stored in resolvable dependencies

summary

Compared with explicit injection, automatic injection is less practical in actual scenarios, but understanding the process will still let you gain some relevant knowledge and processes

Cheer for yourself and all the students who see this article ~!!

WeChat official account: CoderLi

Posted by kane007 on Sun, 05 Dec 2021 14:01:29 -0800