Original 001 | Get on SpringBoot Auto Injection Source Code Analysis

Preface

If this is the second time you see the teacher's article, you are coveting my beauty! O() Ohaha~

Applause + Focus and Look Again, Get into the habit

Nothing else means you need your peek screen

This series is the SpringBoot Deep Source Private Car series, the first start!

Special Car Introduction

This train is for Spring Boot Auto Injection Principle Source Code Analysis

Special Car Problem

  • When does Spring Boot inject the properties of the @Autowired label?
  • What happens if there are multiple Spring Boot s for injection type beans?

Special Car Example

  • Define interfaces
public interface PersonService {

    String hello(String name);
}
  • Define an implementation of the interface
@Service(value = "studentService")
public class StudentServiceImpl implements PersonService {


    @Override
    public String hello(String name) {
        return "[student service] hello " + name;
    }
}
  • Define another implementation of the interface
@Service(value = "teacherService")
public class TeacherServiceImpl implements PersonService {

    @Override
    public String hello(String name) {
        return "[teacher service] hello " + name;
    }
}
  • Define Controller
@RestController
public class TestController {

    @Autowired
    private PersonService studentService;

    @Autowired
    private PersonService teacherService;

    @GetMapping("/hello")
    public String hello(@RequestParam(name = "name") String name) {
        return studentService.hello(name) + "=======>" + teacherService.hello(name);
    }
}

The example code above is simple, creating an interface with two implementation classes, then injecting the implementation class into the controller to complete the call to the business method.Next we'll start analyzing the source code

Special Car Analysis

Before analyzing the code, let's recall the steps for manipulating objects:

  • First we'll instantiate an object
  • Then call the object's set method to set the object's properties

With that basics in mind, the next step is to uncover the mystery

Find Entrance

The most critical step in analyzing the source code is to find the entrance to the program. With the entrance we are half successful, so how do we find the entrance to the program?For the source analysis here, we can break a point on the TestController class and look at the call chain

Based on the call link, we see a doCreateBean method that is used to create bean s, which is the Instantiated Object section we mentioned above

Instantiate Bean

AbstractAutowireCapableBeanFactory#doCreateBean

protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args)
        throws BeanCreationException {

    // Instantiate the bean.
    BeanWrapper instanceWrapper = null;
    if (mbd.isSingleton()) {
        instanceWrapper = this.factoryBeanInstanceCache.remove(beanName);
    }
    if (instanceWrapper == null) {
        // Create bean s
        instanceWrapper = createBeanInstance(beanName, mbd, args);
    }
    final Object bean = instanceWrapper.getWrappedInstance();
    Class<?> beanType = instanceWrapper.getWrappedClass();
    // ...omit part of the code
    // Initialize the bean instance.
    Object exposedObject = bean;
    try {
        // Fill in the bean, which is the set method of the calling object we mentioned above to set the object properties
        populateBean(beanName, mbd, instanceWrapper);
        exposedObject = initializeBean(beanName, exposedObject, mbd);
    }
    // ...omit part of the code
    return exposedObject;
}

Fill in bean s

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
    // ...omit code
    PropertyDescriptor[] filteredPds = null;
    if (hasInstAwareBpps) {
        if (pvs == null) {
            pvs = mbd.getPropertyValues();
        }
        // Traverse all post processors
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                // From breakpoint analysis we can see that the call here is AutowiredAnnotationBeanPostProcessor#postProcessProperties
                PropertyValues pvsToUse = ibp.postProcessProperties(pvs, bw.getWrappedInstance(), beanName);
                if (pvsToUse == null) {
                    if (filteredPds == null) {
                        filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
                    }
                    pvsToUse = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                    if (pvsToUse == null) {
                        return;
                    }
                }
                pvs = pvsToUse;
            }
        }
    }
    if (needsDepCheck) {
        if (filteredPds == null) {
            filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
        }
        checkDependencies(beanName, mbd, filteredPds, pvs);
    }

    if (pvs != null) {
        applyPropertyValues(beanName, mbd, bw, pvs);
    }
}

Processing Properties

AutowiredAnnotationBeanPostProcessor#postProcessProperties

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    // Find the metadata information that the current bean needs to be injected into. For example, with TestController, the two properties studentService and teacherService need to be injected
    InjectionMetadata metadata = findAutowiringMetadata(beanName, bean.getClass(), pvs);
    try {
        // Injection Properties
        metadata.inject(bean, beanName, pvs);
    }
    catch (BeanCreationException ex) {
        throw ex;
    }
    catch (Throwable ex) {
        throw new BeanCreationException(beanName, "Injection of autowired dependencies failed", ex);
    }
    return pvs;
}

Injection Property AutowiredAnnotationBeanPostProcessor.AutowiredFieldElement#inject

protected void inject(Object bean, @Nullable String beanName, @Nullable PropertyValues pvs) throws Throwable {
    // Get the property, where the property is studentService
    Field field = (Field) this.member;
    // Property corresponding value
    Object value;
    if (this.cached) {
        value = resolvedCachedArgument(beanName, this.cachedFieldValue);
    }
    else {
        DependencyDescriptor desc = new DependencyDescriptor(field, this.required);
        desc.setContainingClass(bean.getClass());
        Set<String> autowiredBeanNames = new LinkedHashSet<>(1);
        Assert.state(beanFactory != null, "No BeanFactory available");
        TypeConverter typeConverter = beanFactory.getTypeConverter();
        try {
            // Resolve Attribute Dependencies
            value = beanFactory.resolveDependency(desc, beanName, autowiredBeanNames, typeConverter);
        }
        catch (BeansException ex) {
            throw new UnsatisfiedDependencyException(null, beanName, new InjectionPoint(field), ex);
        }
        synchronized (this) {
            if (!this.cached) {
                if (value != null || this.required) {
                    this.cachedFieldValue = desc;
                    registerDependentBeans(beanName, autowiredBeanNames);
                    if (autowiredBeanNames.size() == 1) {
                        String autowiredBeanName = autowiredBeanNames.iterator().next();
                        if (beanFactory.containsBean(autowiredBeanName) &&
                                beanFactory.isTypeMatch(autowiredBeanName, field.getType())) {
                            this.cachedFieldValue = new ShortcutDependencyDescriptor(
                                    desc, autowiredBeanName, field.getType());
                        }
                    }
                }
                else {
                    this.cachedFieldValue = null;
                }
                this.cached = true;
            }
        }
    }
    if (value != null) {
        ReflectionUtils.makeAccessible(field);
        // Set value to property to complete injection function
        field.set(bean, value);
    }
}

Resolution property depends on DefaultListableBeanFactory#resolveDependency

public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,
        @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

    descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());
    if (Optional.class == descriptor.getDependencyType()) {
        return createOptionalDependency(descriptor, requestingBeanName);
    }
    else if (ObjectFactory.class == descriptor.getDependencyType() ||
            ObjectProvider.class == descriptor.getDependencyType()) {
        return new DependencyObjectProvider(descriptor, requestingBeanName);
    }
    else if (javaxInjectProviderClass == descriptor.getDependencyType()) {
        return new Jsr330Factory().createDependencyProvider(descriptor, requestingBeanName);
    }
    else {
        Object result = getAutowireCandidateResolver().getLazyResolutionProxyIfNecessary(
                descriptor, requestingBeanName);
        if (result == null) {
            // Resolve Dependencies
            result = doResolveDependency(descriptor, requestingBeanName, autowiredBeanNames, typeConverter);
        }
        return result;
    }
}

Resolution property depends on DefaultListableBeanFactory#doResolveDependency

public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,
        @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {

    InjectionPoint previousInjectionPoint = ConstructorResolver.setCurrentInjectionPoint(descriptor);
    try {
        // ...omit code
        
        // Parse multiple Bean s, such as Array, List, Map types, and be interested in viewing the analysis yourself
        Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
        if (multipleBeans != null) {
            return multipleBeans;
        }
        
        // Get candidates by type, for studentService PersonService is the type of property
        // PersonService has two implementation classes, StudentServiceImpl and TeacherServiceImpl
        // So here we get the results as StudentServiceImpl objects and TeacherServiceImpl objects
        Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
        if (matchingBeans.isEmpty()) {
            if (isRequired(descriptor)) {
                raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
            }
            return null;
        }

        String autowiredBeanName;
        Object instanceCandidate;
        // Focus on processing if there are multiple matching bean s
        if (matchingBeans.size() > 1) {
            // Select a matching bean from the already matched beans
            autowiredBeanName = determineAutowireCandidate(matchingBeans, descriptor);
            if (autowiredBeanName == null) {
                // Throw an exception if the bean must be injected or if there are multiple matching beans
                if (isRequired(descriptor) || !indicatesMultipleBeans(type)) {
                    return descriptor.resolveNotUnique(descriptor.getResolvableType(), matchingBeans);
                }
                else {
                    // In case of an optional Collection/Map, silently ignore a non-unique case:
                    // possibly it was meant to be an empty collection of multiple regular beans
                    // (before 4.3 in particular when we didn't even look for collection beans).
                    return null;
                }
            }
            // Get an example based on the bean name
            instanceCandidate = matchingBeans.get(autowiredBeanName);
        }
        else {
            // We have exactly one match.
            Map.Entry<String, Object> entry = matchingBeans.entrySet().iterator().next();
            autowiredBeanName = entry.getKey();
            instanceCandidate = entry.getValue();
        }

        if (autowiredBeanNames != null) {
            autowiredBeanNames.add(autowiredBeanName);
        }
        if (instanceCandidate instanceof Class) {
            instanceCandidate = descriptor.resolveCandidate(autowiredBeanName, type, this);
        }
        Object result = instanceCandidate;
        if (result instanceof NullBean) {
            if (isRequired(descriptor)) {
                raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
            }
            result = null;
        }
        if (!ClassUtils.isAssignableValue(type, result)) {
            throw new BeanNotOfRequiredTypeException(autowiredBeanName, type, instanceCandidate.getClass());
        }
        // Return the corresponding sample object
        return result;
    }
    finally {
        ConstructorResolver.setCurrentInjectionPoint(previousInjectionPoint);
    }
}

Here, all matching beans are obtained based on their type. If there are more than one matching beans, a qualified bean name is selected and the corresponding bena instance is returned, the set method is called for injection, and the principle of this injection is finished.But how does Spring Boot select the right beans?

Select a qualified bean DefaultListableBeanFactory#determineAutowireCandidate

protected String determineAutowireCandidate(Map<String, Object> candidates, DependencyDescriptor descriptor) {
    Class<?> requiredType = descriptor.getDependencyType();
    // Returns the name of the bean if its corresponding primary property is true
    String primaryCandidate = determinePrimaryCandidate(candidates, requiredType);
    if (primaryCandidate != null) {
        return primaryCandidate;
    }
    // Returns the name of the high priority bean if the candidate bean is labeled with javax.annotation.Priority
    String priorityCandidate = determineHighestPriorityCandidate(candidates, requiredType);
    if (priorityCandidate != null) {
        return priorityCandidate;
    }
    // Fallback
    // Returns the name of the matching bean if the name of the matching bean matches the name of the attribute that needs to be injected
    for (Map.Entry<String, Object> entry : candidates.entrySet()) {
        String candidateName = entry.getKey();
        Object beanInstance = entry.getValue();
        if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||
                matchesBeanName(candidateName, descriptor.getDependencyName())) {
            return candidateName;
        }
    }
    return null;
}

Get a qualifying bean name summary:

  • Depending on Bean's primary property
  • According to javax.annotation.Priority
  • Depending on the name of the injection property

Special Vehicle Summary

  • When the Bean instantiation is complete, populate the Bean
  • Call AutowiredAnnotationBeanPostProcessor#postProcessProperties to process properties
  • Get all the properties that need to be injected
  • Find matching instances from IOC containers based on the type of injection properties
  • If there are multiple matching instances, filter by the primary attribute - > javax.annotation.Priority annotation - > injection attribute name, and return the qualified Bean name
  • After filtering, if there is a qualified Bean name, the corresponding instance is returned, otherwise an exception is thrown

Special Vehicle Review

Review the first two questions:

  • When does Spring Boot inject the properties of the @Autowired label?
  • What happens if there are multiple Spring Boot s for injection type beans?

First question: inject the properties of the @Autowired label when the Bean is populated after it is instantiated

Second question: If there are multiple types of beans, they are filtered by primary--->javax.annotation.Priority--->name to get the final matching bean name

Tags: Java Spring Attribute SpringBoot

Posted on Sat, 07 Dec 2019 08:40:22 -0500 by Wright