第04章:注入属性和依赖对象
本文内容
1. 设计
在上一章的设计中,按照是否带有参数的构造函数实现了不同的类实例化策略。但是并 没有考虑类中是否有属性,如果类包含属性,那么 在实例化时就需要填充属性信息,这样创建的对象才是完整的。
举个例子,如果在 UserService 类中使用到了 userDao(private UserDao userDao
),那么 在实例化 UserService 时,也需要将 UserDao 实例化,得到 userDao 对象(依赖对象),将其注入进 UserService 类,填充为 UserService 类的属性。
创建对象过程所需填充的属性不只有 int、long、double 等基本数据类型,还包括可能 没有被实例化的对象属性,这些都需要 在创建 Bean 对象的时候填充。
这里暂时不考虑循环依赖问题,后续会有专门的章节解决循环依赖。
由于 属性填充 是在使用 newInstance()
或者 Cglib 创建 Bean 对象后开始执行的,所以可以在 AbstractAutowireCapableBeanFactory 类的 createBean()
方法中添加属性填充的操作 applyPropertyValues(),如下图所示:
- 需要在 bean 定义 BeanDefinition 类中,添加 PropertyValues 信息,方便将属性注入到 BeanDefinition;
- 填充的属性信息还包括了 Bean 的 对象类型,也就是需要再定义一个 BeanReference 引用对象(只用于存储对象类型的 Bean 依赖),在具体实例化时如果遇到对象类型则进行递归创建和填充。
2. 实现
实现属性注入还需要新增加3个类:BeanReference
(类引用)、PropertyValue
(属性值)、PropertyValues
(属性集合),分别用于对象类型和基本类型的属性填充。
另外,需要改动的类主要是 AbstractAutowireCapableBeanFactory
,在 createBean()
方法中添加属性填充部分,这里主要是新增一个属性填充方法 applyPropertyValues()
。
目录结构更新如下:
类图如下:
- 新增 PropertyValue 类,保存属性的名称和值;
- 新增 PropertyValues 类,保存 PropertyValue 集合;
- 新增 BeanReference 类,如果是对象类型,则需要使用该类包装后再存入 PropertyValue;
- 在 bean 定义 BeanDefinition 类中,添加 propertyValues 属性集合,方便将属性注入到 BeanDefinition;
- 在 AbstractAutowireCapableBeanFactory 类中,新增
applyPeopertyValues()
方法,在实例化 Bean 后,给 Bean 填充其依赖的属性。即从 BeanDefinition 中获取 propertyValues,然后遍历每个 propertyValue,如果是基本数据类型则直接填充,如果是对象类型(BeanReference 类型)则先调用getBean()
实例化该对象后再进行填充(遇到传递依赖则会递归实例化对象后再填充)
3. 测试
新增一个 UserDao 类,模拟查询数据库:
public class UserDao {
private static Map<String, String> map = new HashMap<>();
// 模拟数据库中的数据
static {
map.put("10001", "孙悟空");
map.put("10002", "猪八戒");
map.put("10003", "沙悟净");
}
public String queryUserName(String uId) {
return map.get(uId);
}
}
UserService 类,依赖了 UserDao 类,需要先注入 userDao 对象:
public class UserService {
private String uId;
// 依赖 UserDao
private UserDao userDao;
public void queryUserInfo() {
System.out.println("查询用户信息:" + userDao.queryUserName(uId));
}
// setter/getter
}
测试类:
@Test
public void test_BeanFactory() {
// 1. 初始化 BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
// 2. UserDao 注入 bean 容器
beanFactory.registerBeanDefinition("userDao", new BeanDefinition(UserDao.class));
// 3. 为 UserService 设置依赖的属性 [uId, userDao],其中 userDao 又是一个 bean 对象
PropertyValues propertyValues = new PropertyValues();
propertyValues.addPropertyValue(new PropertyValue("uId", "10001"));
propertyValues.addPropertyValue(new PropertyValue("userDao", new BeanReference("userDao")));
// 4. UserService 注入 bean 容器,将它依赖的属性进行填充
BeanDefinition beanDefinition = new BeanDefinition(UserService.class, propertyValues);
beanFactory.registerBeanDefinition("userService", beanDefinition);
// 5. UserService 获取 bean
UserService userService = (UserService) beanFactory.getBean("userService");
userService.queryUserInfo();
}
输出结果:
查询用户信息:孙悟空
Process finished with exit code 0
从测试结果来看,属性填充已经起作用了,因为只有属性填充后,才能调用到 UserDao 的方法 userDao.queryUserName(uId)
。
4. 流程
调用流程如下所示: