Spring依赖注入
Spring注入方式
Spring有以下两种注入方式:
- 手动注入
- 自动注入
手动注入
在XML中定义Bean时, 就是手动注入,因为程序员手动给某个属性指定了值
<bean id="orderService" class="com.fanqiechaodan.service.OrderService"></bean>
<bean id="userService" class="com.fanqiechaodan.service.UserService" >
<property name="orderService" ref="orderService"/>
</bean>
上面这种方式底层就是通过 set方法 进行注入,UserService里面必须要有orderService的set方法.没有会报错
<bean id="orderService" class="com.fanqiechaodan.service.OrderService"></bean>
<bean id="userService" class="com.fanqiechaodan.service.UserService" >
<constructor-arg index="0" ref="orderService"/>
</bean>
上面这种底层通过 构造方法 进行注入,UserService里面要有对应的构造方法,没有会报错
所以手动注入的底层也就是分为两种:
- set方法注入
- 构造方法注入
自动注入
自动注入分为以下两种:
- XML的autowire自动注入
- @Autowired注解的自动注入
XML的autowire自动注入
在XML中,可以定义一个Bean时去指定这个Bean的自动注入模式:
- byType
- byName
- constructor
- default
- no
<bean id="orderService" class="com.fanqiechaodan.service.OrderService"></bean>
<bean id="userService" class="com.fanqiechaodan.service.UserService" autowire="byType"></bean>
这么写,表示Spring会自动给UserService中所有属性自动赋值,不需要这个属性上有@Autowired注解,但需要这个属性有对应的set方法
在创建Bean的过程中,在填充属性时,Spring回去解析当前类,把当前类的所有方法都解析出来,Spring会去解析每个方法得到对应的PropertyDescriptor对象,PropertyDescriptor中有几个属性:
- name:这个name并不是方法的名字,而是拿方法名字进行处理后的
- 如果方法名字以"get"开头,例如"getXXX",name=XXX
- 如果方法名字以"is"开头,例如"isXXX",name=XXX
- 如果方法名字以"set"开头,例如"setXXX",name=XXX
- readMethodRef:表示get方法的Method
- readMethodName:表示get方法的名字
- writeMethodRef:表示set方法的Method
- writeMethodName:表示set方法的名字
- propertyTypeRef: 如果有get方法对应的就是返回值的类型,如果是set方法对应的就是set方法中唯一参数的类型
get方法的定义是:方法参数个数为0,并且方法名字以get开头或者方法名字以is开头并且方法返回值类型为boolean
set方法的定义是:方法参数个数为1,并且方法名字以set开头方法返回类型为void
所以,Spring在通过byName的自动填充属性时流程是:
- 找到所有set方法所对应的XXX部分的名字
- 根据名字去获取Bean
Spring在通过byType的自动填充属性时流程是:
- 获取到set方法中唯一参数的参数类型,并且根据该类型去容器中获取Bean
如果找到多个,会报错
如果是constructor,那么就可以不写set方法了,当某个Bean是通过构造方法来注入时,Spring利用构造方法参数信息从Spring容器中去找Bean,找到Bean之后作为参数传给构造方法,从而实例化得到一个Bean对象,并完成属性赋值,其实构造方法注入相当于byType+byName,普通的byType是根据set方法中的参数类型去找Bean.找到多个会报错,而constructor就是通过构造方法中的参数类型去找Bean,如果找到多个会根据参数名确定.
另外两个:
- no:关闭autowire
- default:默认值,如果在
<bean>
设置了autowire为default,则会使用<beans>
中设置的autowire
@Autowired注解的自动注入
从本质上讲,@Autowired注解提供了与autowire相同的功能,但是拥有 更细粒度的控制 和广泛的适用性
XML中的autowire控制的是整个Bean的所有属性,而@Autowired注解是直接写在某个属性,某个set方法,某个构造方法上的.如果一个类有多个构造方法,XML的autowire=constructor,无法指定用那个构造方法,用@Autowired注解可以指定构造方法.
同时,使用@Autowired注解,还可以控制,那些属性想被自动注入,那些属性不想,这也是细粒度的控制.
但是@Autowired无法区分byType和byName,@Autowired是先byType,如果找到多个再byName
@Autowired注解可以写在:
- 属性上(属性注入):先根据 属性类型 去找Bean,如果找到多个再根据 属性名 确定一个
- set方法上(set方法注入):先根据方法 参数类型 去找Bean,如果找到多个再根据 参数名 确定一个
- 构造方法上(构造方法注入): 先根据方法 参数类型 去找Bean,如果找到多个再根据 参数名 确定一个
寻找注入点
在创建一个Bean的过程中,Spring会利用org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition找到注入点并缓存,找注入点的流程为:
- 遍历当前类的所有属性字段Field
- 查看字段上是否存在@Autowired,@Value中的任意一个存在,则认为该字段是一个注入点
- 如果字段是static关键字修饰的,则不进行注入
- 获取@Autowired中required的值
- 将字段信息构造成一个 AutowiredFieldElement 对象,作为一个 注入点 对象添加到currElements中
- 遍历当前类所有的方法Method
- 判断当前Method是否是 桥接方法,如果是找到原方法
- 查看方法上是否存在@Autowired,@Value中的任意一个存在,则认为该字段是一个注入点
- 如果方法是static关键字修饰的,则不进行注入
- 获取@Autowired中的required属性的值
- 将方法信息构造成一个 AutowiredMethodElement 对象,作为一个 注入点 对象天机道currElements中
- 遍历完当前类的方法和字段后, 将 遍历父类 的,知道没有父类
- 最后将currElements集合封装成一个InjectionMetadata对象,作为当前Bean对应的注入点集合对象,并缓存
对应的源码实现
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessMergedBeanDefinition
@Override
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
InjectionMetadata metadata = findAutowiringMetadata(beanName, beanType, null);
metadata.checkConfigMembers(beanDefinition);
}
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#findAutowiringMetadata
private InjectionMetadata findAutowiringMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
// Fall back to class name as cache key, for backwards compatibility with custom callers.
String cacheKey = (StringUtils.hasLength(beanName) ? beanName : clazz.getName());
// Quick check on the concurrent map first, with minimal locking.
InjectionMetadata metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
synchronized (this.injectionMetadataCache) {
metadata = this.injectionMetadataCache.get(cacheKey);
if (InjectionMetadata.needsRefresh(metadata, clazz)) {
if (metadata != null) {
metadata.clear(pvs);
}
metadata = buildAutowiringMetadata(clazz);
this.injectionMetadataCache.put(cacheKey, metadata);
}
}
}
return metadata;
}
org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#buildAutowiringMetadata
private InjectionMetadata buildAutowiringMetadata(final Class<?> clazz) {
if (!AnnotationUtils.isCandidateClass(clazz, this.autowiredAnnotationTypes)) {
return InjectionMetadata.EMPTY;
}
List<InjectionMetadata.InjectedElement> elements = new ArrayList<>();
Class<?> targetClass = clazz;
do {
final List<InjectionMetadata.InjectedElement> currElements = new ArrayList<>();
ReflectionUtils.doWithLocalFields(targetClass, field -> {
MergedAnnotation<?> ann = findAutowiredAnnotation(field);
if (ann != null) {
if (Modifier.isStatic(field.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static fields: " + field);
}
return;
}
boolean required = determineRequiredStatus(ann);
currElements.add(new AutowiredFieldElement(field, required));
}
});
ReflectionUtils.doWithLocalMethods(targetClass, method -> {
Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
if (!BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
return;
}
MergedAnnotation<?> ann = findAutowiredAnnotation(bridgedMethod);
if (ann != null && method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
if (Modifier.isStatic(method.getModifiers())) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation is not supported on static methods: " + method);
}
return;
}
if (method.getParameterCount() == 0) {
if (logger.isInfoEnabled()) {
logger.info("Autowired annotation should only be used on methods with parameters: " +
method);
}
}
boolean required = determineRequiredStatus(ann);
PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
currElements.add(new AutowiredMethodElement(method, required, pd));
}
});
elements.addAll(0, currElements);
targetClass = targetClass.getSuperclass();
}
while (targetClass != null && targetClass != Object.class);
return InjectionMetadata.forElements(elements, clazz);
}
static的字段或方法为什么不支持?
再Spring中,依赖注入主要针对对象级别的依赖关系进行管理,即在对象创建时通过构造函数,Setter方法或者字段注入等方式将依赖项注入到对象中.而静态字段属于类级别的属性,通常用于保存类级别的状态或者常量值,而不是对象之间的依赖关系.同样静态方法同样属于类级别的方法,它们可以直接通过类名调用,而不需要创建对象的实例.因此Spring的设计理念主要集中在对象之间的依赖关系管理,而对于静态字段/方法并不是其主要关注点.不支持static的字段/方法进行注入.
注入点进行注入
Spring在org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor#postProcessProperties方法中,遍历所找到的注入点,依次进行注入.
字段注入
- 遍历所有的 AutowiredFieldElement对象
- 查找当前是否存在缓存的 cachedFieldValue,存在使用缓存,不存在解析字段
- 将对应的字段封装成 DependencyDescriptor对象
- 调用beanFactory.resolveDependency方法,传入 DependencyDescriptor对象,进行依赖查找,找到当前字段匹配的Bean对象
- 将 DependencyDescriptor对象 和所找到的 结果对象beanName 封装成一个 ShortcutDependencyDescriptor对象 作为缓存,比如如果当前Bean是原型Bean,那么下次再来创建Bean时,就可以直接拿缓存的结果对象beanName去BeanFactory中去拿bean对象了,不用再次进行查找了
- 利用反射将结果对象赋值给字段
set方法注入
- 遍历所有的 AutowiredMethodElement
- 获取方法参数数组,如果缓存有就从缓存中获取,缓存没有就解析方法参数
- 遍历方法参数,将每个参数封装成 MethodParameter对象
- 将 MethodParameter对象 封装成 DependencyDescriptor对象
- 调用beanFactory.resolveDependency,传入 DependencyDescriptor对象,进行依赖查找,找到当前方法参数所匹配的Bean对象
- 将 DependencyDescriptor对象 和所找到的 结果对象beanName 封装成一个 ShortcutDependencyDescriptor对象 作为缓存,比如如果当前Bean是原型Bean,那么下次再来创建Bean时,就可以直接拿缓存的结果对象beanName去BeanFactory中去拿bean对象了,不用再次进行查找了
- 利用反射将找到的所有结果对象传给当前方法,并执行
原文地址:https://blog.csdn.net/qq_43135259/article/details/139111982
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!