54、Spring5.x源码之旅五十五自动装配的秘密一

  • AUTOWIRE_NO按类型装配
    • 如果发现有多个同类型的处理方法
    • DefaultListableBeanFactory的determineAutowireCandidate再次候选
    • 总结
  • AUTOWIRE_BY_NAME按set的方法名装配
    • AbstractAutowireCapableBeanFactory的autowireByName根据名字装配
    • PropertyInfo的get

AUTOWIRE_NO按类型装配

我们了解的自动装配就是@Autowired吧,基本上我们会在类的属性上用这个,其实这个默认的装配模式是AUTOWIRE_NO也就是用注入的方式,用的是AutowiredAnnotationBeanPostProcessorpostProcessProperties方法处理的,这个确实是以Type为主,比如DefaultListableBeanFactorydoResolveDependency方法,他会先获取响应的类型:
*
然后后面是根据这个type去寻找的:
*
里面就是根据type去找到相应的bean名字:
*
不过最终都是这样:
*
所以我们常说Autowired注解是根据类型的,而且找不到类型的话可能会直接报错的,或者返回了,并不会再去找bean名字:
*

如果发现有多个同类型的处理方法

比如有同一个接口不同的实现类,这个时候要怎么装配呢,其实他会先进行候选,按照PrimaryPriority优先级选,其实就是注解啦,如果都没有就看要依赖的名字和候选类的名字(首字母小写)是不是一样,一样的话就可以,否则还是找不到,返回null
*

DefaultListableBeanFactory的determineAutowireCandidate再次候选

这里就是刚才说的候选原则,也就是说,Primary的优先,Priority次之,最后再看名字对不对的上。

@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();
			if ((beanInstance != null && this.resolvableDependencies.containsValue(beanInstance)) ||
					matchesBeanName(candidateName, descriptor.getDependencyName())) {
   
     
				return candidateName;
			}
		}
		return null;
	}

这里要注意@Primary只能再一个类上,不然会报异常,具体的可以去看determinePrimaryCandidate源码,还有@Priority(xx)可以在多个类上,只要xx不一样就行,越小优先级越高,一样也会报异常。这两种一样就还是不知道要用哪个,最后才会看名字跟类名是不是一样。

总结

所以常说的Autowired注解是先看类型再看名字,其实不太严谨,还有考虑优先级,而且名字也要跟类名对的上。

AUTOWIRE_BY_NAME按set的方法名装配

这个就是按setXXX方法名,也就是set后的XXX首字母小写,xXX名字进行装配。
我们可以改变自动装配的模式,比如用ImportBeanDefinitionRegistrar

public class AutoWiredTypeRegistrar implements ImportBeanDefinitionRegistrar {
   
     
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
   
     
        RootBeanDefinition autowiredBean = (RootBeanDefinition) registry.getBeanDefinition("getAutowiredBean");
        autowiredBean.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_NAME);
    }
}

我们的配置类MyConfig

@Configuration
@ComponentScan(value = {
   
     "com.ww.postprocessors"})
@Import({
   
     AutoWiredTypeRegistrar.class})
public class MyConfig {
   
     
    @Bean
    public AutowiredBean getAutowiredBean() {
   
     
        return new AutowiredBean();
    }
}

我们看看他会怎么做,在填充注入的AbstractAutowireCapableBeanFactorypopulateBean中有两种方式:
*

AbstractAutowireCapableBeanFactory的autowireByName根据名字装配

我们以这个类AutowiredBean为例:

public class AutowiredBean {
   
     
    private Bean1 autoBean1;

    public Bean1 getBean12() {
   
     
        return autoBean1;
    }

    public void settYBean1(Bean1 bean1) {
   
     
        System.out.println(bean1);
    }

    public void setmyBean1(Bean1 bean1) {
   
     
        System.out.println(bean1);
    }

    public Bean1 getReturnBean1(int i) {
   
     
        return null;
    }

    public static void setStaticBean1(Bean1 bean1) {
   
     
        System.out.println(bean1);
    }

    public void setBean1(Bean1 bean1) {
   
     

        System.out.println(bean1);
    }
}

其实就是获取set开头的方法名字,然后去找是否有这个名字的bean定义,如果有就直接获取,没有就没有了。
*

PropertyInfo的get

最终会调用到这里,这个才是真正的寻找的方法,代码比较多,我截取了主要的,这里所有的读写方法都会保存,这里就会筛选出一些方法:

	...
 for (Method method : methods) {
   
     
            if (!Modifier.isStatic(method.getModifiers())) {
   
     
                Class<?> returnType = method.getReturnType();
                String name = method.getName();
                switch (method.getParameterCount()) {
   
     
                    case 0:
                        if (returnType.equals(boolean.class) && isPrefix(name, "is")) {
   
     
                            PropertyInfo info = getInfo(map, name.substring(2), false);
                            info.read = new MethodInfo(method, boolean.class);
                        } else if (!returnType.equals(void.class) && isPrefix(name, "get")) {
   
     
                            PropertyInfo info = getInfo(map, name.substring(3), false);
                            info.readList = add(info.readList, method, method.getGenericReturnType());
                        }
                        break;
                    case 1:
                        if (returnType.equals(void.class) && isPrefix(name, "set")) {
   
     
                            PropertyInfo info = getInfo(map, name.substring(3), false);
                            info.writeList = add(info.writeList, method, method.getGenericParameterTypes()[0]);
                        } else if (!returnType.equals(void.class) && method.getParameterTypes()[0].equals(int.class) && isPrefix(name, "get")) {
   
     
                            PropertyInfo info = getInfo(map, name.substring(3), true);
                            info.readList = add(info.readList, method, method.getGenericReturnType());
                        }
                        break;
                    case 2:
                        if (returnType.equals(void.class) && method.getParameterTypes()[0].equals(int.class) && isPrefix(name, "set")) {
   
     
                            PropertyInfo info = getInfo(map, name.substring(3), true);
                            info.writeList = add(info.writeList, method, method.getGenericParameterTypes()[1]);
                        }
                        break;
                }
            }
            ...

最终可以拿到,里面的class方法,是objectgetClass
*
接下去就是过滤啦,过滤出所有写方法,还有不要简单的类型,还有数组内的类型,比如下面的一些类型是不要的:
*

最终过滤出了4set方法:
*
可惜他们既不在单例缓存中,也不在bean定义中,下面的条件都是返回false,所以不会自动装配:
*
所以按AUTOWIRE_BY_NAME名字装配是可能装配不上的。

好了,今天就到这里了,希望对学习理解有帮助,大神看见勿喷,仅为自己的学习理解,能力有限,请多包涵。