Spring自动装配的源码分析

前言

说到Spring框架自动装配就是一个绕不开的话题,那么Spring中自动装配到底内部是怎么做的呢?本篇博客就到Spring源码里一探究竟。更多Spring内容进入【Spring解读系列目录】

Spring是怎么做自动装配的

虽然说大多数时候我们使用@Autowire@Resource就完成了自动装配,但是到底是哪个类在初始化对做的呢?这个时候就必须提到一个类GenericBeanDefinition,故名思及就是产生BeanDefinition的类。这个类有继承了AbstractBeanDefinition,在这个父类里面有一个设置自动注解模式的方法AbstractBeanDefinition#setAutowireMode(),我们所使用的自动注入正是用这个方法进行的注入。

setAutowireMode方法

为了说清楚这个事情,先举一个例子。创建一个PersonClazz依赖了NameClazz并且二者都在Spring容器中注册了,再加一个测试类如下。在这个例子里,我试图拿到PersonClazz中的NameClass对象,如果直接运行一定拿到一个null

@Component
public class PersonClazz {
    private NameClazz nameClazz;
    public NameClazz getNameClazz() {
        return nameClazz;
    }
    public void setNameClazz(NameClazz nameClazz) {
        this.nameClazz = nameClazz;
    }
}
@Component
public class NameClazz {
}
public class AutoTest {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext anno = new AnnotationConfigApplicationContext(AppConfig.class);
        System.out.println(anno.getBean(PersonClazz.class).getNameClazz()+"-------------------");
    }
}
输出结果,没有拿到NameClazz
null-------------------

因为没有没有设置,所以Spring不认为这是一个依赖,也不会给NameClazz自动装配。于是我们强行把NameClazz依赖进去又会如何呢?看过笔者以前帖子的同学知道,我们要憋大招了祭出ImportBeanDefinitionRegistrar这个接口,知识点参考【Spring框架中ImportBeanDefinitionRegistrar的应用】
创建一个AutoWireRegistrar类把我们要的功能注册到Spring容器中,在Import到配置类中:

public class AutoWireRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry){
		//拿到personClazz的BD
        GenericBeanDefinition gbd= (GenericBeanDefinition) registry.getBeanDefinition("personClazz");
		//设置为byType
		gbd.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
		//重新注册到Spring容器中
        registry.registerBeanDefinition("personClazz",gbd);
    }
}
@Import(AutoWireRegistrar.class)
public class AppConfig { }
运行结果,NameClazz对象被打印出来了
com.demo.spring.NameClazz@46fa7c39-------------------

这里做了什么操作导致我们拿到了这个对象呢,首先获取到personClazzBeanDefinition,然后强制设置为byType自动注入,重新注册到Spring容器中。这里想说明一个什么东西呢?

自动注入的模型

是想告诉大家,其实Spring在自动注入的时候是有几个模型的,一个就是刚刚使用的AUTOWIRE_BY_TYPE,还有AUTOWIRE_BY_NAME,其实还有一个AUTOWIRE_NO(还有AUTOWIRE_CONSTRUCTORAUTOWIRE_AUTODETECT,和本篇文章主旨无关,不做详解):

/**
 * Constant that indicates no external autowiring at all.
 * @see #setAutowireMode
 */
public static final int AUTOWIRE_NO = AutowireCapableBeanFactory.AUTOWIRE_NO;
/**
 * Constant that indicates autowiring bean properties by name.
 * @see #setAutowireMode
 */
public static final int AUTOWIRE_BY_NAME = AutowireCapableBeanFactory.AUTOWIRE_BY_NAME;
/**
 * Constant that indicates autowiring bean properties by type.
 * @see #setAutowireMode
 */
public static final int AUTOWIRE_BY_TYPE = AutowireCapableBeanFactory.AUTOWIRE_BY_TYPE;

这三个都叫做自动装配。一般意义上来说,我们理解的给某个属性上加上@Autowired就会根据类型ByType装配,加上@Resource就是ByName,其实并不是。那么什么是自动装配呢,只要一个类里面有属性,就会被自动装配,只不过这个装配的规则为Autowired_No,也就是说不进行装配的动作。

Spring自动装配的机制

最简单来说当设置为@Autowired或者@Resource的时候。如果我们仅仅说使用ByType,其实什么都不用加,因为Spring会自动根据目标Bean里面有没有setter方法进行装配。如果有就自动装配,如果没有就不装配。换句话说Spring是怎么判断一个属性要不要自动装配呢?就是根据这个属性有没有setter方法。为了验证这点我们接着修改我们的PersonClazz

@Component
public class PersonClazz {
    //private NameClazz nameClazz;
    public NameClazz getNameClazz() {
        return null;
    }
    public void setNameClazz(NameClazz nameClazz) {
        //this.nameClazz = nameClazz;
        System.out.println("NameClazz.class");
    }
}

如果我之前说的没错,那么即便我这里没有一个叫做NameClazz的属性,Spring一样识别并运行setter方法,试图进行装配。

运行输出,果然Spring上当了
NameClazz.class
null-------------------

为了进行横向对比,我再改造一下PersonClazz,我们把setter方法删了,看看Spring在我们明确告知要使用AUTOWIRE_BY_TYPE的时候会做什么事情:

@Component
public class PersonClazz {
    private NameClazz nameClazz;
    public NameClazz getNameClazz() {
        return nameClazz;
    }
}
运行输出
null-------------------

可以看到我们删除setter方法以后Spring并没有给NameClazz进行自动装配,这也解释了为什么最新版本中的Spring强烈建议我们使用setter作为@Autowired的使用地点。说白了自动装配就是根据setter方法来的,和属性没有一点关系。上面的代码换成AUTOWIRE_BY_NAME效果是一样的。所以要记住一点,Spring自动装配的只有那些有setter方法的属性。

被Spring嫌弃的类型

Spring虽然有自动装配,但是Spring并不会给每一个属性都进行自动装配。首先被Spring嫌弃的就是没有setter方法的类型,也没有@Autowired或者@Resource注解的类型。其次就是没有通过Spring检测的类型,比如Class类型,比如Spring。具体的方法就是AbstractAutowireCapableBeanFactory#unsatisfiedNonSimpleProperties()

protected String[] unsatisfiedNonSimpleProperties(AbstractBeanDefinition mbd, BeanWrapper bw) {
   Set<String> result = new TreeSet<>();
   PropertyValues pvs = mbd.getPropertyValues();
   PropertyDescriptor[] pds = bw.getPropertyDescriptors();
   for (PropertyDescriptor pd : pds) {
	//过滤每一个属性
      if (pd.getWriteMethod() != null && !isExcludedFromDependencyCheck(pd) && !pvs.contains(pd.getName()) &&
            !BeanUtils.isSimpleProperty(pd.getPropertyType())) {
         result.add(pd.getName());
      }
   }
   return StringUtils.toStringArray(result);
}

看到for循环的if语句里面的条件

  1. 第一个条件:pd.getWriteMethod() != null,也就是说首先这个属性必须有写方法否则不行。
  2. 第二个条件:!isExcludedFromDependencyCheck(pd),就是@Import中被排除的类作为属性也不行。
  3. 第三个条件:!pvs.contains(pd.getName()),就是说没有包含在PropertyValues中的属性也不行,这是啥呢?就是从当前类中拿出来的属性值,拿出来的都没有更不可能去装配。
  4. 第四个条件:BeanUtils.isSimpleProperty(pd.getPropertyType())而且还不能是Sample类型的。

什么是Sample类型呢?就是数组Array加上Java中属于Sample类型。

public static boolean isSimpleProperty(Class<?> type) {
   Assert.notNull(type, "'type' must not be null");
   return isSimpleValueType(type) || (type.isArray() && isSimpleValueType(type.getComponentType()));
}

Spring中认为的Sample类型:

public static boolean isSimpleValueType(Class<?> type) {
   return (Void.class != type && void.class != type &&
         (ClassUtils.isPrimitiveOrWrapper(type) ||
         Enum.class.isAssignableFrom(type) ||
         CharSequence.class.isAssignableFrom(type) ||
         Number.class.isAssignableFrom(type) ||
         Date.class.isAssignableFrom(type) ||
         Temporal.class.isAssignableFrom(type) ||
         URI.class == type ||
         URL.class == type ||
         Locale.class == type ||
         Class.class == type));
}

脱离Spring的扩展

为什么要说这个问题呢,因为虽然Spring是一个非常好用的框架,但是在技术日新月异的情况下,我们应该尽可能的把代码和一个框架解耦,如果每一个类的属性都有@Autowired注解,那么写出来的内容就会和Spring牢牢地绑定在一起。比如我们开发了一个通用工具类,如果其中有一个属性和Spring绑定,那么即便对方不需要引入Spring一样必须引入Spring的包才能进行解析。但是如果使用的是外部扩展,那么你的工具类就和Spring没有任何关系,任何人拿到这个工具类不需要任何依赖就可以使用这个工具类,这样编程是不是就灵活了很多。

总结

Spring怎么认为一个字段是属性的:

  1. 凡是具有ReadMethod ,也就是get/is的方法的都是属性。
  2. 凡是具有WriteMethod ,set的方法的方法的都是属性。

Spring认为一个字段是应该自动装配的:

  1. 有set方法。
  2. 没有被检查方法unsatisfiedNonSimpleProperties()过滤掉的。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值