前言
说到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-------------------
这里做了什么操作导致我们拿到了这个对象呢,首先获取到personClazz
的BeanDefinition
,然后强制设置为byType
自动注入,重新注册到Spring容器中。这里想说明一个什么东西呢?
自动注入的模型
是想告诉大家,其实Spring在自动注入的时候是有几个模型的,一个就是刚刚使用的AUTOWIRE_BY_TYPE
,还有AUTOWIRE_BY_NAME
,其实还有一个AUTOWIRE_NO
(还有AUTOWIRE_CONSTRUCTOR
,AUTOWIRE_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语句里面的条件
- 第一个条件:
pd.getWriteMethod() != null
,也就是说首先这个属性必须有写方法否则不行。 - 第二个条件:
!isExcludedFromDependencyCheck(pd)
,就是@Import
中被排除的类作为属性也不行。 - 第三个条件:
!pvs.contains(pd.getName())
,就是说没有包含在PropertyValues
中的属性也不行,这是啥呢?就是从当前类中拿出来的属性值,拿出来的都没有更不可能去装配。 - 第四个条件:
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怎么认为一个字段是属性的:
- 凡是具有ReadMethod ,也就是get/is的方法的都是属性。
- 凡是具有WriteMethod ,set的方法的方法的都是属性。
Spring认为一个字段是应该自动装配的:
- 有set方法。
- 没有被检查方法unsatisfiedNonSimpleProperties()过滤掉的。