Spring source code -- Bean instantiation

Keywords: Java Spring Back-end source code source code analysis

Last BeanWrapper We introduced the origin of BeanWrapper. Now let's continue to see how Spring constructs a Bean.

The code is not long and not particularly complex

/**
 * Use the appropriate instantiation strategy to create beans: factorymethod, automatic injection of constructor, or simple parameterless constructor
 */
protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
   // Make sure bean class is actually resolved at this point.
   Class<?> beanClass = resolveBeanClass(mbd, beanName);
	 .......
   Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
   if (instanceSupplier != null) {
      return obtainFromSupplier(instanceSupplier, beanName);
   }

   if (mbd.getFactoryMethodName() != null) {
      return instantiateUsingFactoryMethod(beanName, mbd, args);
   }

   // Shortcut when re-creating the same bean...
   boolean resolved = false;
   boolean autowireNecessary = false;
   if (args == null) {
      synchronized (mbd.constructorArgumentLock) {
         if (mbd.resolvedConstructorOrFactoryMethod != null) {
            resolved = true;
            autowireNecessary = mbd.constructorArgumentsResolved;
         }
      }
   }
   if (resolved) {
      if (autowireNecessary) {
         return autowireConstructor(beanName, mbd, null, null);
      }
      else {
         return instantiateBean(beanName, mbd);
      }
   }

   // Candidate constructors for autowiring?
   Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
   if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
         mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
      return autowireConstructor(beanName, mbd, ctors, args);
   }

   // Preferred constructors for default construction?
   ctors = mbd.getPreferredConstructors();
   if (ctors != null) {
      return autowireConstructor(beanName, mbd, ctors, null);
   }

   // No special handling: simply use no-arg constructor.
   return instantiateBean(beanName, mbd);
}
Class<?> beanClass = resolveBeanClass(mbd, beanName);

This step is to explain that the type corresponding to the bean is interpreted as Class and placed in the BeanDefinition (when creating the BeanDefinition, the className may be set instead of the Class object)

InstanceSupplier

 Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
  return obtainFromSupplier(instanceSupplier, beanName);
}

This step is to get the Supplier object, call the get method of this object to get the created bean, and then build the BeanWrapper and initialize it. Register the corresponding PropertyEditor.

protected BeanWrapper obtainFromSupplier(Supplier<?> instanceSupplier, String beanName) {
   Object instance;

   String outerBean = this.currentlyCreatedBean.get();
   this.currentlyCreatedBean.set(beanName);
   try {
      instance = instanceSupplier.get();
   }
   finally {
      if (outerBean != null) {
         this.currentlyCreatedBean.set(outerBean);
      }
      else {
         this.currentlyCreatedBean.remove();
      }
   }

   if (instance == null) {
      instance = new NullBean();
   }
   BeanWrapper bw = new BeanWrapperImpl(instance);
   initBeanWrapper(bw);
   return bw;
}
protected void initBeanWrapper(BeanWrapper bw) {
  bw.setConversionService(getConversionService());
  registerCustomEditors(bw);
}

How do we register this Supplier?

if (context instanceof GenericApplicationContext) {
    ((GenericApplicationContext) context).registerBean(Service.class,()->{
        System.out.println("create bean in supplier");
        return new Service();
    });
}
Service bean = context.getBean(Service.class);

FactoryMethod

if (mbd.getFactoryMethodName() != null) {
   return instantiateUsingFactoryMethod(beanName, mbd, args);
}
protected BeanWrapper instantiateUsingFactoryMethod(
			String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {
		return new ConstructorResolver(this).instantiateUsingFactoryMethod(beanName, mbd, explicitArgs);
}

The second way to instantiate bean s. What is FactoryMethod?

There are static factories and factory methods in design patterns, and the same is true here. The bean s we declare in the configuration class are similar to this pattern.

@Configuration
public class Config {
    @Bean
    public Service service() {
        return new Service();
    }
    @Bean
    public static Service staticService() {
        return new Service();
    }
}

Step by step analysis code

public BeanWrapper instantiateUsingFactoryMethod(
      String beanName, RootBeanDefinition mbd, @Nullable Object[] explicitArgs) {

   BeanWrapperImpl bw = new BeanWrapperImpl();
   this.beanFactory.initBeanWrapper(bw);

   Object factoryBean;
   Class<?> factoryClass;
   boolean isStatic;

   String factoryBeanName = mbd.getFactoryBeanName();
   if (factoryBeanName != null) {
      if (factoryBeanName.equals(beanName)) {
         throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
               "factory-bean reference points back to the same bean definition");
      }
      factoryBean = this.beanFactory.getBean(factoryBeanName);
      if (mbd.isSingleton() && this.beanFactory.containsSingleton(beanName)) {
         throw new ImplicitlyAppearedSingletonException();
      }
      factoryClass = factoryBean.getClass();
      isStatic = false;
   }
   else {
      // It's a static factory method on the bean class.
      if (!mbd.hasBeanClass()) {
         throw new BeanDefinitionStoreException(mbd.getResourceDescription(), beanName,
               "bean definition declares neither a bean class nor a factory-bean reference");
      }
      factoryBean = null;
      factoryClass = mbd.getBeanClass();
      isStatic = true;
   }
  ................

mbd.getFactoryBeanName() if the method corresponding to the @ Bean annotation is not static, return the beanId of its configuration class, here is config

If the method corresponding to the @ bean annotation is static, null will be returned. This setting process is explained when the ConfigurationClassBeanDefinitionReader is used in the ConfigurationClassPostProcessor to scan the registered beans.

factoryBean is the instance of this configuration class, which is obtained from the Spring container. For static methods, when calling @ Bean modified methods through reflection, the object of invoke can be null. Therefore, when @ Bean modified static methods, factoryBean = null.

.........................
Method factoryMethodToUse = null;
ArgumentsHolder argsHolderToUse = null;
Object[] argsToUse = null;

if (explicitArgs != null) {
   argsToUse = explicitArgs;
}
else {
   Object[] argsToResolve = null;
   synchronized (mbd.constructorArgumentLock) {
      factoryMethodToUse = (Method) mbd.resolvedConstructorOrFactoryMethod;
     // The methods used in the last bean creation are mainly for prototype types
      if (factoryMethodToUse != null && mbd.constructorArgumentsResolved) {
         // Constructor parameters used in the last bean creation
         argsToUse = mbd.resolvedConstructorArguments;
         if (argsToUse == null) {
           // Constructor parameter last used (not yet transformed or requires additional processing)
            argsToResolve = mbd.preparedConstructorArguments;
         }
      }
   }
   if (argsToResolve != null) {
     // For type conversion and attribute editing, see the previous article
      argsToUse = resolvePreparedArguments(beanName, mbd, bw, factoryMethodToUse, argsToResolve, true);
   }
}
.........................

Here is mainly the explanation of parameters and the selection of calling methods. They are mainly obtained from BeanDefinition. If it is the first time, the value will not be obtained from BeanDefinition.

Explicatargs is not null if the value is specified when calling the Spring#getBean method. If this value is specified, even if the bean generation method explained last time in the BeanDefinition will not be used, and only the most appropriate method will be selected again.

@Bean()
@Scope(value = SCOPE_PROTOTYPE)
public static Service staticService() {
    return new Service();
}
public static void main(String[] args) {
        ConfigurableApplicationContext context = SpringApplication.run(JunitSpringBootApplication.class, args);
        Object staticService = context.getBean("staticService");
        staticService = context.getBean("staticService");
    }

When context.getBean("staticService") is called the second time, it will enter the cache created last time.

if (factoryMethodToUse == null || argsToUse == null) {
   // Need to determine the factory method...
   // Try all methods with this name to see if they match the given arguments.
   factoryClass = ClassUtils.getUserClass(factoryClass);

   List<Method> candidates = null;
   if (mbd.isFactoryMethodUnique) {
      if (factoryMethodToUse == null) {
         factoryMethodToUse = mbd.getResolvedFactoryMethod();
      }
      if (factoryMethodToUse != null) {
         candidates = Collections.singletonList(factoryMethodToUse);
      }
   }
   if (candidates == null) {
      candidates = new ArrayList<>();
      Method[] rawCandidates = getCandidateMethods(factoryClass, mbd);
      for (Method candidate : rawCandidates) {
         if (Modifier.isStatic(candidate.getModifiers()) == isStatic && mbd.isFactoryMethod(candidate)) {
            candidates.add(candidate);
         }
      }
   }
   ................			

Judge whether the FactoryMethod of a BeanDefinition is unique according to whether the method name decorated by @ Bean is unique in the configuration class, because the method name is its beanId. If there are two methods with the same method name, whether static or non static, they are not unique. If it is unique, it will directly become a candidate method.

If it is not unique, all methods of the configuration class are obtained by reflection, and then filtered according to the method name and whether it is a static method. There may be multiple candidate methods at this time.

if (candidates.size() == 1 && explicitArgs == null && !mbd.hasConstructorArgumentValues()) {
  Method uniqueCandidate = candidates.get(0);
  if (uniqueCandidate.getParameterCount() == 0) {
    mbd.factoryMethodToIntrospect = uniqueCandidate;
    synchronized (mbd.constructorArgumentLock) {
      mbd.resolvedConstructorOrFactoryMethod = uniqueCandidate;
      mbd.constructorArgumentsResolved = true;
      mbd.resolvedConstructorArguments = EMPTY_ARGS;
    }
    bw.setBeanInstance(instantiate(beanName, mbd, factoryBean, uniqueCandidate, EMPTY_ARGS));
    return bw;
  }
}

If there is only one candidate method, and no parameters are passed in, and the method modified by @ Bean has no parameters, it is very simple to call the method by reflection.

If there is more than one candidate method, it is sorted. Public is higher than non-public, and those with more input parameters are higher than those with less input parameters (Spring always wants to give it the best love for bean s with configuration classes)

if (candidates.size() > 1) {  // explicitly skip immutable singletonList
   candidates.sort(AutowireUtils.EXECUTABLE_COMPARATOR);
}
public static final Comparator<Executable> EXECUTABLE_COMPARATOR = (e1, e2) -> {
  int result = Boolean.compare(Modifier.isPublic(e2.getModifiers()), Modifier.isPublic(e1.getModifiers()));
  return result != 0 ? result : Integer.compare(e2.getParameterCount(), e1.getParameterCount());
};

The BeanDefinition created by @ Bean in all configuration classes is autowire_ Of the constructor.

minTypeDiffWeight represents the difference weight between the method parameter type and the bean parameter type actually found from Spring. If the difference is small, select it as the final candidate method and call it to create the return bean.

ConstructorArgumentValues resolvedValues = null;
boolean autowiring = (mbd.getResolvedAutowireMode() == AutowireCapableBeanFactory.AUTOWIRE_CONSTRUCTOR);
int minTypeDiffWeight = Integer.MAX_VALUE;
Set<Method> ambiguousFactoryMethods = null;

int minNrOfArgs;
if (explicitArgs != null) {
   minNrOfArgs = explicitArgs.length;
}
else {
   // We don't have arguments passed in programmatically, so we need to resolve the
   // arguments specified in the constructor arguments held in the bean definition.
   if (mbd.hasConstructorArgumentValues()) {
      ConstructorArgumentValues cargs = mbd.getConstructorArgumentValues();
      resolvedValues = new ConstructorArgumentValues();
      minNrOfArgs = resolveConstructorArguments(beanName, mbd, bw, cargs, resolvedValues);
   }
   else {
      minNrOfArgs = 0;
   }
}

LinkedList<UnsatisfiedDependencyException> causes = null;

This involves the processing of dependencies. By default, all beannames of the parameter type are found from the BeanFactory. If it is one, the bean is used directly. If it is multiple, the beanId is compared. If no bean is found or cannot be determined as a method input parameter, an exception will be thrown and the for loop will continue to the next candidate method for comparison and filtering. If the type difference is consistent, it will be recorded and the subsequent exit loop will throw an exception in which the correct method cannot be selected.

for (Method candidate : candidates) {
   int parameterCount = candidate.getParameterCount();

   if (parameterCount >= minNrOfArgs) {
      ArgumentsHolder argsHolder;

      Class<?>[] paramTypes = candidate.getParameterTypes();
      if (explicitArgs != null) {
         // Explicit arguments given -> arguments length must match exactly.
         if (paramTypes.length != explicitArgs.length) {
            continue;
         }
         argsHolder = new ArgumentsHolder(explicitArgs);
      }
      else {
         // Resolved constructor arguments: type conversion and/or autowiring necessary.
         try {
            String[] paramNames = null;
            ParameterNameDiscoverer pnd = this.beanFactory.getParameterNameDiscoverer();
            if (pnd != null) {
               paramNames = pnd.getParameterNames(candidate);
            }
            argsHolder = createArgumentArray(beanName, mbd, resolvedValues, bw,
                  paramTypes, paramNames, candidate, autowiring, candidates.size() == 1);
         }
         catch (UnsatisfiedDependencyException ex) {
            if (logger.isTraceEnabled()) {
               logger.trace("Ignoring factory method [" + candidate + "] of bean '" + beanName + "': " + ex);
            }
            // Swallow and try next overloaded factory method.
            if (causes == null) {
               causes = new LinkedList<>();
            }
            causes.add(ex);
            continue;
         }
      }

      int typeDiffWeight = (mbd.isLenientConstructorResolution() ?
            argsHolder.getTypeDifferenceWeight(paramTypes) : argsHolder.getAssignabilityWeight(paramTypes));
      // Choose this factory method if it represents the closest match.
      if (typeDiffWeight < minTypeDiffWeight) {
         factoryMethodToUse = candidate;
         argsHolderToUse = argsHolder;
         argsToUse = argsHolder.arguments;
         minTypeDiffWeight = typeDiffWeight;
         ambiguousFactoryMethods = null;
      }
      
      else if (factoryMethodToUse != null && typeDiffWeight == minTypeDiffWeight &&
            !mbd.isLenientConstructorResolution() &&
            paramTypes.length == factoryMethodToUse.getParameterCount() &&
            !Arrays.equals(paramTypes, factoryMethodToUse.getParameterTypes())) {
         if (ambiguousFactoryMethods == null) {
            ambiguousFactoryMethods = new LinkedHashSet<>();
            ambiguousFactoryMethods.add(factoryMethodToUse);
         }
        // Mo Ling Liangke
         ambiguousFactoryMethods.add(candidate);
      }
   }
}

This concludes the FactoryMethod. Finally, select the most appropriate method to generate bean s

When we overload methods in the configuration class, it will not be the only BeanFactory. If our parameters still depend on the bean's parent class / parent interface, it will become ambiguous and throw an exception

Constructor

// Shortcut when re-creating the same bean...
boolean resolved = false;
boolean autowireNecessary = false;
if (args == null) {
   synchronized (mbd.constructorArgumentLock) {
      if (mbd.resolvedConstructorOrFactoryMethod != null) {
         resolved = true;
         autowireNecessary = mbd.constructorArgumentsResolved;
      }
   }
}
if (resolved) {
   if (autowireNecessary) {
      return autowireConstructor(beanName, mbd, null, null);
   }
   else {
      return instantiateBean(beanName, mbd);
   }
}

This is the operation after caching and interpretation. When your bean is a prototype and is not declared in the configuration class, it will enter the code block the second time you get the bean.

// Candidate constructors for autowiring?
Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
      mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
   return autowireConstructor(beanName, mbd, ctors, args);
}

This code will eventually come to Autowired annotation bean post processor #determinecandidateconstructors

Put aside the Lookup annotation and cache

  1. Find the constructor declared by this class
  2. Find out all constructors that use @ Autowire annotation. If there is already a constructor whose required is true, no second constructor can be modified by @ Autowire. If they are all false, there can be multiple
  3. If there is no constructor with required = true (there is a constructor decorated with @ Autowire), if there is a default constructor, it will also be added to the array and returned once
  4. If there is no @ Autowire decorated constructor, but there is a non default constructor, that is, a constructor with an input parameter greater than 0, it is returned

If the returned constructor array is not null, it enters the ConstructorResolver#autowireConstructor method. According to the passed in constructor, if there is only one, select it to create bean s. If there are multiple, try one by one in order. Logically, it is the same as selecting FactoryMethod.

  1. Sort first (public first, those with many parameters first)
  2. for loop, find parameter objects in Spring, compare the difference of parameter types, and select the constructor with the least difference
  3. Reflection calls the constructor and creates the bean
// Preferred constructors for default construction?
ctors = mbd.getPreferredConstructors();
if (ctors != null) {
   return autowireConstructor(beanName, mbd, ctors, null);
}

// No special handling: simply use no-arg constructor.
return instantiateBean(beanName, mbd);

getPreferredConstructors this method returns null by default. Only one subclass attempts to return. ClassDerivedBeanDefinition this class will be used only when we try to actively register the Supplier (instancesupplier above). In other cases, null is returned

@Override
@Nullable
public Constructor<?>[] getPreferredConstructors() {
   Class<?> clazz = getBeanClass();
  // Kotlin may return non null
   Constructor<?> primaryCtor = BeanUtils.findPrimaryConstructor(clazz);
   if (primaryCtor != null) {
      return new Constructor<?>[] {primaryCtor};
   }
  // public constructor
   Constructor<?>[] publicCtors = clazz.getConstructors();
   if (publicCtors.length > 0) {
      return publicCtors;
   }
   return null;
}

instantiateBean(beanName, mbd); It is very simple to create beans by calling parameterless construction methods through reflection. This involves method injection (replace/lookup), which will be introduced in subsequent articles.

last

The supplier is simple, and the FactoryMethod is also simpler than the Constructor, because there is no need to find the corresponding method, just filter the appropriate method according to the sorting and rules. Of course, this sorting and rules are also applicable to the Constructor.

Constructor needs to select an appropriate constructor, @ Autowire modified or not, and then use the default constructor. When multiple constructors are selected, they are consistent with FactoryMethod.

Posted by dianaqt on Fri, 03 Dec 2021 22:01:04 -0800