How does Spring automatically inject multiple types?

Posted by sbunse on Tue, 02 Nov 2021 09:37:59 +0100

Grasp the small tail at the end of October to explore how the internal fields of SpringBean inject multiple types

There is often a small problem around me. How does Spring load field characters? Why does Spring support so many types such as Collection, List, Map and String? Are there any tips worth learning in the process of Spring injection? With this doubt, let's explore it.

This paper is based on springboot v2.5.6 and spring v5.3.12. Different versions may be different, please note ha

To understand the above problems, there is a small requirement, that is, to understand the life cycle of spring beans (such as a Bean with Get). Of course, we can also take this doubt and look in the code together.

code search

1.1 inlet analysis

To start exploring the code, we certainly need to find an entrance. Where do we start? Of course, start with the startup function. The startup code is as follows:

public static void main(String[] args) {  ConfigurableApplicationContext context = SpringApplication.run(SpringTestApplication.class, args);  // Get a bean context. GetBean ("fattyca1bean") from the container;}

Copy code

After executing SpringApplication.run, we can get an ApplicationContext, and then we can GetBean. We can continue to look at GetBean.

1.2 in depth

1.2.1

Click getBean to enter org.springframework.context.support.AbstractApplicationContext#getBean(java.lang.String). The code is as follows:

  @Override  public Object getBean(String name) throws BeansException {    // Get the current BeanFactory, and then in getBean return getbeanfactory(). GetBean (name);}

Copy code

Here is to get the current BeanFactory and then in getBean. Here we are a little interested in getBeanFactory(). Why? That is, we want to understand what the current BeanFactory is. Don't say much. Let's just click in.

1.2.2

When clicking in, I found that org.springframework.context.support.AbstractApplicationContext#getBeanFactory() has two implementation classes, namely:

  • org.springframework.context.support.AbstractRefreshableApplicationContext

  • org.springframework.context.support.GenericApplicationContext

Let's enter the class to see how to implement BeanFactory

  1. org.springframework.context.support.AbstractRefreshableApplicationContext

To view the code, there is a method. As follows:

protected DefaultListableBeanFactory createBeanFactory() {  return new DefaultListableBeanFactory(getInternalParentBeanFactory());}

Copy code

  1. org.springframework.context.support.GenericApplicationContext

View code, constructor:

public GenericApplicationContext() {  this.beanFactory = new DefaultListableBeanFactory();}

Copy code

We can see a common feature. The BeanFactory finally implemented is org.springframework.beans.factory.support.DefaultListableBeanFactory. Well, now we know that getBean is finally implemented through org.springframework.beans.factory.support.DefaultListableBeanFactory.

However, another question came. what? The GetBean of ApplicationContext is realized by combining org.springframework.beans.factory.support.DefaultListableBeanFactory. What is the relationship between ApplicationContext and org.springframework.beans.factory.support.DefaultListableBeanFactory? What's the difference? This question remains here, ha ha.

1.2.3

According to the old rules of Spring, xxx() is the method and doXxx() is the real implementation method. We click all the way in, from getBean - > dogetbean - > createbean - > docreatebean();

The doCreateBean has the following code:

protected Object doCreateBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args){    ...slightly    // Initialize the bean instance.    Object exposedObject = bean;    try {/ / populate bean populatebean (beanname, MBD, instancewrapper);}... Omitted}

Copy code

There is a populateBean method. Students familiar with the spring life cycle know that this is the place where the Bean attribute value is filled after the Bean initialization is completed. Of course, we can also see from the method annotation.

Populate the bean instance in the given BeanWrapper with the property values from the bean definition.

1.2.4

Go inside the populateBean method. The code is as follows:

protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {    ...slightly    PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
    int resolvedAutowireMode = mbd.getResolvedAutowireMode();    if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {      MutablePropertyValues newPvs = new MutablePropertyValues(pvs);      // Add property values based on autowire by name if applicable.      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;    }
    ...slightly}

Copy code

As we wish, here we see two autowire in all capitals_ BY_ NAME&AUTOWIRE_ BY_ Type, aren't these two types automatically injected? It seems to be the key point. Then click in and have a look

1.2.5

1.2.5.1 autowireByName

// Fill in any missing property values with references to other beans in this factory if autowire is set to "byname". Protected void autowirebyname (string beanname, abstractbeandefinition MBD, beanwrapper BW, mutablepropertyvalues PVS) {/ / get the name of the property string [] propertynames = unsatisfied nonsimpleproperties (MBD, BW); for (string propertyName: propertynames) {/ / is it a Bean if (containsbean (propertyName)) {object Bean = getBean (propertyName); / / correspond propertyName to Bean pvs.add(propertyName, bean); / / Associate Property with Bean registerDependentBean(propertyName, beanName);}}}

Copy code

In the method, we see that name injection is achieved by getting the associated bean through the geatBean. Click the method to find that it is a doll. The getBean code is as follows:

@Overridepublic Object getBean(String name) throws BeansException {  return doGetBean(name, null, null, false);}

Copy code

Don't we go back to the origin again? Let's go to another way

1.2.5.2 autowireByType

Enter zoom in, the code is as follows:

protected void autowireByType(      String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {    ...slightly    Set<String> autowiredBeanNames = new LinkedHashSet<>(4);    String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);    for (String propertyName : propertyNames) {      try {        // Reflection property descriptorpd = BW. Getpropertydescriptor (propertyname); / / don't try autowiring by type for type object: never makes sense, / / even if it technically is an unsatisfied, non simple property. If (object. Class! = PD. Getpropertytype()) {/ / get the setter method methodparameter of the property. Methodparameter = BeanUtils. Getwritemethodparameter (PD); / / do not allow reader init for type matching in case of a prioritized post processor. / / hungry? Judge whether to lazy load Boolean reader =! (BW. Getwrappedinstance() instanceof priorityordered) ; / / attribute description DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager); / / resolve dependent (key) object autowiredaregument = resolvedependency (DESC, beanname, autowiredbeannames, converter); if (autowiredaregument! = null) {pvs.add (propertyname, autowiredargument);} for (string autowiredbeanname: autowiredbeannames) {/ / associate the property with the corresponding bean registerdependentbean (autowiredbeanname, beanname);} autowiredbeannames. Clear();}}... Omitted}}

Copy code

The code here is relatively clear. Spring has done several operations, namely:

1.2.5.1.1 reflection acquisition properties

We can also see from the name that we have obtained the PropertyDescriptor. This class mainly obtains the Get and Setter methods (writeMethod and readMethod) of the property, and then constructs a DependencyDescriptor through the method parameters to record some parameter information. See the details.

1.2.5.1.2 resolving dependencies (key)

Let's go into specific methods. The code is as follows:

  public Object resolveDependency(DependencyDescriptor descriptor, @Nullable String requestingBeanName,      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    descriptor.initParameterNameDiscovery(getParameterNameDiscoverer());    // Is it Optional class if (Optional. Class = = descriptor. Getdependencytype()) {return createoptionaldependency (descriptor, requestbeanname);} / / is it objectfactory, objectprovider 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) {/ / real resolution result = doresolvedependency (descriptor, requestingbeanname, autowiredbeannames, TypeConverter);} return result;}}

Copy code

In this method, several types are judged, such as Optional, ObjectFactory, ObjectProvider, Java.Inject.Provider, and common classes. Different classes have different processing methods. Of course, according to the old rules, we still enter doResolveDependency, which is a real specific analysis operation. Let's go in and have a look.

public Object doResolveDependency(DependencyDescriptor descriptor, @Nullable String beanName,      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) throws BeansException {
    try {      ...slightly
      Class<?> type = descriptor.getDependencyType();      // The auto injected parser obtains the value. The default implementation is null. Object value = getautowirecandideresolver(). Getsuggestedvalue (descriptor); if (value! = null) {/ / character type judgment if (value instanceof string) {string strval = resolveembeddedvalue ((string) value); beandefinition BD= (beanname! = null & & containsbean (beanname)? Getmergedbeandefinition (beanname): null); / / parse value value = evaluateBeanDefinitionString(strVal, bd);} / / get converter TypeConverter converter = (TypeConverter! = null? TypeConverter: gettypeconverter()); try {/ / convert the type to the value of the corresponding type return converter.convertifnecessary (value, type, descriptor. Gettypedescriptor());} catch (unsupported operationexception Ex) {/ / a custom TypeConverter which does not support TypeDescriptor resolution... Return (descriptor. Getfield()) ! = null? Converter.convertifnecessary (value, type, descriptor. Getfield()): converter.convertifnecessary (value, type, descriptor. Getmethodparameter());}} / / multi dependent bean object multiplebeans = resolvemultiplebeans (descriptor, beanname, autowiredbeannames, TypeConverter); if (multiplebeans! = null) {return multiplebeans;}... Omitted}

Copy code

Look at the code. First, get a wave of values through getautowirecandideresolver(). getSuggestedValue (descriptor); but follow up the code. The default implementation of getautowirecandideresolver() is org.springframework.beans.factory.support.simpleautowirecandideresolver, and the return value of getSuggestedValue is null.

public Object getSuggestedValue(DependencyDescriptor descriptor) {  return null;}

Copy code

Next, we look down to resolveMultipleBeans. At first glance, the name may be the method to resolve multiple beans. It tastes a little like that. Multiple Bean resolution may make what we are looking for. Let's continue.

private Object resolveMultipleBeans(DependencyDescriptor descriptor, @Nullable String beanName,      @Nullable Set<String> autowiredBeanNames, @Nullable TypeConverter typeConverter) {
    Class<?> type = descriptor.getDependencyType();
    if (descriptor instanceof StreamDependencyDescriptor) {      ...slightly    }    else if (type.isArray()) {      ...slightly    }    else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {      ...slightly    }    else if (Map.class == type) {      ...slightly    }    else {      return null;    }  }

Copy code

Come in a little bit, good guy. The code, if.. else, if.. else, looks at the judgment. Isn't it the type injection of Collection and Map that we want? Let's find a specific method, such as Map. The code is as follows:

else if (Map.class == type) {      // Get the generic type resolvabletype maptype = descriptor. Getresolvabletype(). Asmap(); class <? > keytype = maptype. Resolvegeneric (0); / / judge whether the type of key is string if (string. Class! = keytype) {return null;} class <? > ValueType = maptype. Resolvegeneric (1); if (ValueType = = null) {return null;} / / find a qualified Bean and return map < beanname, Bean > type map < string, Object > matchingbeans = findautowirecandidates (beanname, ValueType, new multielementdescriptor (descriptor)); if (matchingbeans. Isempty()) {return null;} if (autowiredbeannames! = null) {autowiredbeannames. Addall (matchingbeans. Keyset());} / / returns the result return matchingBeans;}

Copy code

Get the generic type of the type to be injected through reflection (ResolvableType is provided by reflection in Spring, and there are instructions on the comments, so you can see it yourself). Then judge the type of key. There is a small problem here. If the KeyType is not of String type, it will directly return Null. This is what we need to pay attention to when using registered beans.

Then, judge the valueType, use the findAutowireCandidates method to find all Bean types of Class, directly encapsulate them into a Map type structure, and then return them directly.

So far, we know that spring has done too much for us in our automatic assembly. The initialization of the designed spring automatically helps to assemble into maps, lists, arrays, etc. during injection. Spring is still careful~

Topics: Java Spring Back-end