Spring 5.x 源码之旅-55自动装配的秘密一

作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO

联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬

学习必须往深处挖,挖的越深,基础越扎实!

阶段1、深入多线程

阶段2、深入多线程设计模式

阶段3、深入juc源码解析


阶段4、深入jdk其余源码解析


阶段5、深入jvm源码解析

码哥源码部分

码哥讲源码-原理源码篇【2024年最新大厂关于线程池使用的场景题】

码哥讲源码【炸雷啦!炸雷啦!黄光头他终于跑路啦!】

码哥讲源码-【jvm课程前置知识及c/c++调试环境搭建】

​​​​​​码哥讲源码-原理源码篇【揭秘join方法的唤醒本质上决定于jvm的底层析构函数】

码哥源码-原理源码篇【Doug Lea为什么要将成员变量赋值给局部变量后再操作?】

码哥讲源码【你水不是你的错,但是你胡说八道就是你不对了!】

码哥讲源码【谁再说Spring不支持多线程事务,你给我抽他!】

终结B站没人能讲清楚红黑树的历史,不服等你来踢馆!

打脸系列【020-3小时讲解MESI协议和volatile之间的关系,那些将x86下的验证结果当作最终结果的水货们请闭嘴】

 

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名字装配是可能装配不上的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值