好处:大幅度减少 Spring 配置
坏处:依赖不能明确管理,可能会有多个 bean 同时符合注入规则,没有清晰的依赖关系。
在装配的时候会有两种方式,byName
和byType
两种。
byName:根据属性名自动装配。此选项将检查容器并根据名字查找与属性完全一致的 bean,并将其与属性自动装配。
byType:如果容器中存在一个与指定属性类型相同的 bean,那么将与该属性自动装配;如果存在多个该类型 bean,那么抛出异常,并指出不能使用 byType 方式进行自动装配;如果没有找到相匹配的 bean,则什么事都不发生,也可以通过设置
什么是自动装配 自动装配:也就是 Spring
会在容器中自动的查找,并自动的给 bean
装配及其关联的属性
涉及到自动装配 bean
的依赖关系 时,Spring
有多种处理方式。Spring
提供了 4
种自动装配策略
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public interface AutowireCapableBeanFactory extends BeanFactory { int AUTOWIRE_NO = 0 ; int AUTOWIRE_BY_NAME = 1 ; int AUTOWIRE_BY_TYPE = 2 ; int AUTOWIRE_CONSTRUCTOR = 3 ; @Deprecated int AUTOWIRE_AUTODETECT = 4 ; }
什么是依赖注入 依赖注入:当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。然而采用依赖注入的方式,创建被调用者的工作不再由调用者来完成,创建被调用者的实例的工作由 IOC 容器来完成。然后注入调用者,称为依赖注入
控制反转:当一个类的实例需要另一个类的实例协助时,在传统的程序设计过程中,通常由调用者来创建被调用者的实例。然而采用控制反转的方式,创建被调用者的工作不再由调用者来完成,创建被调用者的实例的工作由 IOC 容器来完成。也就是把对象的创建,初始化,销毁的工作交给 spring ioc 容器来做。由 spring ioc 容器来管理对象的生命周期
依赖注入的方式有两种:构造器注入和 setter 方法注入
依赖注入与自动装配的关系 依赖注入的本质就是装配,装配是依赖注入的具体行为
在传统的使用 xml 文件装配 bean 是一件很繁琐的事情,而且还需要找到对应类型的 bean 才能装配,一旦 bean 很多,就不好维护了。为了解决这种问题,spring 使用注解来进行自动装配。自动装配就是开发人员不必知道具体要装配哪个 bean 的引用,这个识别的工作会由 spring 来完成。与自动装配配合的还有“自动检测”,这个动作会自动识别哪些类需要被配置成 bean,进而来进行装配
因此也可以这样理解:自动装配是为了将依赖注入“自动化”的一个简化配置的操作
Spring
中自动装配的策略byName 它的意思是:把与 bean 的属性具有相同名字的其他 bean 自动装配到 bean 的对应属性中
例:在 User 的 bean 中有个属性 Role myRole,再创建一个 Role 的 bean,它的名字如果叫 myRole,那么在 User 中就可以使用 byName 来自动装配
1 2 3 4 5 6 7 8 public class User { private Role myRole; } public class Role { private String id; private String name; }
上面是 bean
的定义,再看配置文件
1 2 3 4 5 6 <bean id ="myRole" class ="com.viewscenes.netsupervisor.entity.Role" > <property name ="id" value ="1001" > </property > <property name ="name" value ="管理员" > </property > </bean > <bean id ="user" class ="com.viewscenes.netsupervisor.entity.User" autowire ="byName" > </bean >
如上所述,只要属性名称和 bean 的名称可以对应,那么在 user 的 bean 中就可以使用 byName 来自动装配。那么,如果属性名称对应不上呢?
byType 它的意思是:把与 bean 的属性具有相同类型的其他 bean 自动装配到 bean 的对应属性中
1 2 3 4 5 6 <bean class ="com.viewscenes.netsupervisor.entity.Role" > <property name ="id" value ="1001" > </property > <property name ="name" value ="管理员" > </property > </bean > <bean id ="user" class ="com.viewscenes.netsupervisor.entity.User" autowire ="byType" > </bean >
还是上面的例子,如果使用 byType,Role bean
的 id
都可以省去
constructor 它是说:把与 bean
的构造器入参具有相同类型的其他 bean
自动装配到 bean
构造器的对应入参中 。值的注意的是,具有相同类型的其他 bean
这句话说明它在查找入参的时候,还是通过 bean
的类型来确定
1 2 3 4 5 6 7 public class User { private Role role; public User (Role role) { this .role = role; } }
1 <bean id ="user" class ="com.viewscenes.netsupervisor.entity.User" autowire ="constructor" > </bean >
autodetect(不推荐使用了) 它首先会尝试使用 constructor
进行自动装配,如果失败再尝试使用 byType
。不过,它在 Spring 3.0
之后已经被标记为 @Deprecated
默认的自动装配策略 默认情况下,default-autowire 属性被设置为 none,标示所有的 bean 都不使用自动装配,除非 bean 上配置了 autowire 属性
如果你需要为所有的 bean 配置相同的 autowire 属性,有个办法可以简化这一操作,在根元素 beans 上增加属性
1 <beans default-autowire ="byType" >
@Autowired 注解 https://hgm.vercel.app/post/7047b0e7/
具体的大家可以看这篇文章,这里补充一些文章没写全的内容
从 Spring 2.5
开始,开始支持使用注解来自动装配 bean
的属性。它允许更细粒度的自动装配,我们可以选择性的标注某一个属性来对其应用自动装配。Spring
支持几种不同的应用于自动装配的注解
Spring
自带的 @Autowired
注解
JSR-330
的 @Inject
注解
JSR-250
的 @Resource
注解
使用 @Autowired
它有几个点需要注意
强制性 默认情况下,它具有强制契约特性,其所标注的属性必须是可装配的。如果没有 bean
可以装配到 @Autowired
所标注的属性或参数中,那么你会看到 NoSuchBeanDefinitionException
的异常信息
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public Object doResolveDependency (DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException { Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(type, "" , descriptor); } return null ; } }
看到上面的源码,我们可以得到这一信息,bean
集合为空不要紧,关键 isRequired
条件不能成立,如果成立就会抛异常。那么,如果我们不确定属性是否可以装配,可以这样来使用 Autowired
1 2 @Autowired(required = false) UserService userService;
装配策略 前几天看到大佬群里有个面试题是这样问的:Autowired
是按照什么策略来自动装配的呢?关于这个问题,不能一概而论,你不能简单的说按照类型或者按照名称。但可以确定的一点的是,它默认是按照类型来自动装配的,即 byType
默认按照类型装配 关键点 findAutowireCandidates
这个方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 protected Map<String, Object> findAutowireCandidates ( String beanName, Class<?> requiredType, DependencyDescriptor descriptor) { String[] candidateNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( this , requiredType, true , descriptor.isEager()); Map<String, Object> result = new LinkedHashMap <String, Object>(candidateNames.length); for (String candidateName : candidateNames) { if (!isSelfReference(beanName, candidateName) && isAutowireCandidate(candidateName, descriptor)) { result.put(candidateName, getBean(candidateName)); } } return result; }
可以看到它返回的是一个列表,那么就表明,按照类型匹配可能会查询到多个实例。到底应该装配哪个实例呢?我看有的文章里说,可以加注解以此规避。比如 @qulifier、@Primary
等,实际还有个简单的办法
比如,按照 UserService
接口类型来装配它的实现类。UserService
接口有多个实现类,分为 UserServiceImpl、UserServiceImpl2
。那么我们在注入的时候,就可以把属性名称定义为 bean
实现类的名称
1 2 @Autowired UserService UserServiceImpl2;
这样的话,spring
会按照 byName
来进行装配。首先,如果查到类型的多个实例,spring
已经做了判断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 public Object doResolveDependency (DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) throws BeansException { Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor); if (matchingBeans.isEmpty()) { if (descriptor.isRequired()) { raiseNoSuchBeanDefinitionException(type, "" , descriptor); } return null ; } if (matchingBeans.size() > 1 ) { String primaryBeanName = determineAutowireCandidate(matchingBeans, descriptor); if (primaryBeanName == null ) { throw new NoUniqueBeanDefinitionException (type, matchingBeans.keySet()); } if (autowiredBeanNames != null ) { autowiredBeanNames.add(primaryBeanName); } return matchingBeans.get(primaryBeanName); } }
可以看出,如果查到多个实例,determineAutowireCandidate
方法就是关键。它来确定一个合适的 bean
返回。其中一部分就是按照 bean
的名称来匹配
1 2 3 4 5 6 7 8 9 10 11 12 13 protected String determineAutowireCandidate (Map<String, Object> candidateBeans, DependencyDescriptor descriptor) { for (Map.Entry<String, Object> entry : candidateBeans.entrySet()) { String candidateBeanName = entry.getKey(); Object beanInstance = entry.getValue(); if (matchesBeanName(candidateBeanName, descriptor.getDependencyName())) { return candidateBeanName; } } return null ; }
最后我们回到问题上,得到的答案就是:@Autowired
默认使用 byType
来装配属性,如果匹配到类型的多个实例,再通过 byName
来确定 bean
主和优先级 上面我们已经看到了,通过 byType
可能会找到多个实例的 bean
。然后再通过 byName
来确定一个合适的 bean
,如果通过名称也确定不了呢?
还是 determineAutowireCandidate
这个方法,它还有两种方式来确定
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 protected String determineAutowireCandidate (Map<String, Object> candidateBeans, DependencyDescriptor descriptor) { Class<?> requiredType = descriptor.getDependencyType(); String primaryCandidate = determinePrimaryCandidate(candidateBeans, requiredType); if (primaryCandidate != null ) { return primaryCandidate; } String priorityCandidate = determineHighestPriorityCandidate(candidateBeans, requiredType); if (priorityCandidate != null ) { return priorityCandidate; } return null ; }
@Primary
注解它的作用是看 bean
上是否包含 @Primary
注解,如果包含就返回。当然了,你不能把多个 bean
都设置为 @Primary
,不然你会得到 NoUniqueBeanDefinitionException
这个异常
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 protected String determinePrimaryCandidate (Map<String, Object> candidateBeans, Class<?> requiredType) { String primaryBeanName = null ; for (Map.Entry<String, Object> entry : candidateBeans.entrySet()) { String candidateBeanName = entry.getKey(); Object beanInstance = entry.getValue(); if (isPrimary(candidateBeanName, beanInstance)) { if (primaryBeanName != null ) { boolean candidateLocal = containsBeanDefinition(candidateBeanName); boolean primaryLocal = containsBeanDefinition(primaryBeanName); if (candidateLocal && primaryLocal) { throw new NoUniqueBeanDefinitionException (requiredType, candidateBeans.size(), "more than one 'primary' bean found among candidates: " + candidateBeans.keySet()); } else if (candidateLocal) { primaryBeanName = candidateBeanName; } } else { primaryBeanName = candidateBeanName; } } } return primaryBeanName; }
@Priority
注解你也可以在 bean
上配置@Priority
注解,它有个 int 类型的属性 value
,可以配置优先级大小。数字越小的,就被优先匹配。同样的,你也不能把多个 bean
的优先级配置成相同大小的数值,否则 NoUniqueBeanDefinitionException
异常照样出来找你
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 protected String determineHighestPriorityCandidate (Map<String, Object> candidateBeans, Class<?> requiredType) { String highestPriorityBeanName = null ; Integer highestPriority = null ; for (Map.Entry<String, Object> entry : candidateBeans.entrySet()) { String candidateBeanName = entry.getKey(); Object beanInstance = entry.getValue(); Integer candidatePriority = getPriority(beanInstance); if (candidatePriority != null ) { if (highestPriorityBeanName != null ) { if (candidatePriority.equals(highestPriority)) { throw new NoUniqueBeanDefinitionException (requiredType, candidateBeans.size(), "Multiple beans found with the same priority ('" + highestPriority + "') " + "among candidates: " + candidateBeans.keySet()); } else if (candidatePriority < highestPriority) { highestPriorityBeanName = candidateBeanName; highestPriority = candidatePriority; } } else { highestPriorityBeanName = candidateBeanName; highestPriority = candidatePriority; } } } return highestPriorityBeanName; }
Priority
的包在 javax.annotation.Priority
,如果想使用它还要引入一个坐标
1 2 3 4 5 <dependency > <groupId > javax.annotation</groupId > <artifactId > javax.annotation-api</artifactId > <version > 1.2</version > </dependency >
总结 本章节重点阐述了 Spring 中的自动装配的几种策略,又通过源码分析了 Autowired 注解的使用方式。 在 Spring3.0 之后,有效的自动装配策略分为byType、byName、constructor
三种方式。注解 Autowired 默认使用 byType 来自动装配,如果存在类型的多个实例就尝试使用 byName 匹配,如果通过 byName 也确定不了,可以通过 Primary 和 Priority 注解来确定。