关注我,紧跟本系列专栏文章,咱们下篇再续!
作者简介:魔都架构师,多家大厂后端一线研发经验,在分布式系统设计、数据平台架构和AI应用开发等领域都有丰富实践经验。
各大技术社区头部专家博主。具有丰富的引领团队经验,深厚业务架构和解决方案的积累。
负责:
- 中央/分销预订系统性能优化
- 活动&券等营销中台建设
- 交易平台及数据中台等架构和开发设计
- 车联网核心平台-物联网连接平台、大数据平台架构设计及优化
- LLM Agent应用开发
- 区块链应用开发
- 大数据开发挖掘经验
- 推荐系统项目
目前主攻市级软件项目设计、构建服务全社会的应用系统。
用依赖注入特性,须思考:
- 对象从哪注入
- 咋创建
- 为啥注入这个对象
虽然编写框架,是为了让开发无需关心太多底层细节,专注业务逻辑开发,但作为开发不能真无脑用框架。务必学会注入集合等高级用法,让自己有所提升!
现有需求:存在多个用户Bean,找出来存储到一个List。
1 注入方式
1.1 收集装配
多个用户Bean定义:
@Configuration
public class UserConfig {
@Bean
public User user1() {
return createUser(1, "java");
}
@Bean
public User user2() {
return createUser(2, "edge");
}
private User createUser(int id, String name) {
return new User(id, name);
}
}
有了集合类型的自动注入后,即可收集零散的用户Bean:
@RestController
@Slf4j
public class UserController {
private List<User> users;
public UserController(List<User> users) {
this.users = users;
}
@GetMapping(path = "users")
public String listUsers() {
return users.toString();
}
}
即可完成集合类型注入:
但当持续增加一些user时,可能就不喜欢用上述的注入集合类型,而是这样:
1.2 直接装配
@Bean
public List<User> users() {
User user3 = createUser(3, "WeChat");
User user4 = createUser(4, "QQ");
return Arrays.asList(user3, user4);
}
分开玩,大家应该不会有啥问题。
若两种方式共存,会咋样?运行程序后发现直接装配方式的未生效:
why?
2 源码解析
就得精通两种注入风格的底层实现。
2.1 收集装配
看DefaultListableBeanFactory#resolveMultipleBeans
private Object resolveMultipleBeans(DependencyDescriptor descriptor, String beanName, Set<String> autowiredBeanNames, TypeConverter typeConverter) {
// interface java.util.List
final Class<?> type = descriptor.getDependencyType();
...
// 装配集合
else if (Collection.class.isAssignableFrom(type) && type.isInterface()) {
// 获取集合的元素类型
Class<?> elementType = descriptor.getResolvableType().asCollection().resolveGeneric();
// 根据元素类型查找所有的bean
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, elementType,
new MultiElementDescriptor(descriptor));
// 转化查到的所有bean放置到集合并返回
TypeConverter converter = (typeConverter != null ? typeConverter : getTypeConverter());
Object result = converter.convertIfNecessary(matchingBeans.values(), type);
// ...
return result;
}
}
1 获取集合类型的elementType
目标类型定义为List<User> users
,所以元素类型为User:
2 根据元素类型找出所有Bean
据elementType找出所有Bean:
3 将匹配的所有的Bean按目标类型转化
上一步获取的所有的Bean都以java.util.LinkedHashMap.LinkedValues
存储,和目标类型大不相同,所以最后按需转化。
本案例需转化为List:
2.2 直接装配
定位源码:DefaultListableBeanFactory#findAutowireCandidates,不再赘述。
最后就是根据目标类型直接寻找匹配Bean名称为users的List<user>
装配给userController#users属性。
2.3 同时满足
两种装配方式时,Spring咋处理?定位源码:DefaultListableBeanFactory#doResolveDependency
Object multipleBeans = resolveMultipleBeans(descriptor, beanName, autowiredBeanNames, typeConverter);
if (multipleBeans != null) {
return multipleBeans;
}
Map<String, Object> matchingBeans = findAutowireCandidates(beanName, type, descriptor);
if (matchingBeans.isEmpty()) {
if (isRequired(descriptor)) {
raiseNoMatchingBeanFound(type, descriptor.getResolvableType(), descriptor);
}
return null;
}
显然两种装配集合方式不能同存,结合本案例:
- 用收集装配时,能找到任一对应Bean,则返回
- 若一个都没找到,才采用直接装配
所以后期以List方式直接添加的user Bean都不生效!
3 修正
务必避免两种方式共存去装配集合!只选用一种方式即可。如只用直接装配:
@Bean
public List<User> users() {
User user1 = createUser(1, "java");
User user2 = createUser(2, "edge");
User user3 = createUser(3, "WeChat");
User user4 = createUser(4, "QQ");
return Arrays.asList(user1, user2,user3, user4);
}
只使用收集方式:
@Bean
public User user1() {
return createUser(1, "java");
}
@Bean
public User user2() {
return createUser(2, "edge");
}
@Bean
public User user3() {
return createUser(3, "WeChat");
}
@Bean
public User user4() {
return createUser(4, "QQ");
}
4 咋让用户2先输出?
控制spring bean加载顺序:
- Bean上用@Order,如@Order(2)。数值越小,优先级越高。默认优先级最低
- @DependsOn,可使依赖的Bean若未被初始化,则被优先初始化
- 加@Order(number),number越小优先级越高,越靠前
- 声明user这些Bean时将id=2的user提到id=1前