前言
经过了前面的三章,不知道大家有没有一个感觉,总觉得有些怀念Spring中我们最常用到的那个东西,但是等啊等啊,一直没有提到它。没错,就是我们的@Autowired!单丝不成线,孤木不成林。很多时候我们的一个bean,并不能仅靠自己就完成一项任务,它会需要依赖其他的bean。我们常用的做法,自然就是将依赖的bean注入进来。那么这一章,我们就来解决这件事情:如何解决bean的依赖。虽然还没有达到@Autowired这样,加一个注解就解决问题的程度,但我们还是可以通过这一章,来达成bean之间的依赖问题,以及bean对其他普通属性的依赖问题。
工程结构
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─akitsuki
│ │ │ └─springframework
│ │ │ └─beans
│ │ │ ├─exception
│ │ │ │ BeanException.java
│ │ │ │
│ │ │ └─factory
│ │ │ │ BeanFactory.java
│ │ │ │
│ │ │ ├─config
│ │ │ │ BeanDefinition.java
│ │ │ │ BeanReference.java
│ │ │ │ DefaultSingletonBeanRegistry.java
│ │ │ │ PropertyValue.java
│ │ │ │ PropertyValues.java
│ │ │ │ SingletonBeanRegistry.java
│ │ │ │
│ │ │ └─support
│ │ │ AbstractAutowireCapableBeanFactory.java
│ │ │ AbstractBeanFactory.java
│ │ │ BeanDefinitionRegistry.java
│ │ │ CglibSubclassingInstantiationStrategy.java
│ │ │ DefaultListableBeanFactory.java
│ │ │ InstantiationStrategy.java
│ │ │ SimpleInstantiationStrategy.java
│ │ │
│ │ └─resources
│ └─test
│ └─java
│ └─com
│ └─akitsuki
│ └─springframework
│ └─test
│ │ ApiTest.java
│ │
│ └─bean
│ UserDao.java
│ UserService.java
基本上没有增加什么新的类,这一次的任务不算很难。
包装,包装,把一切都包装起来
我们的bean,需要依赖一些东西,才能完成自己的工作。而依赖的这些东西,实际上都将会作为这个bean的属性。所以,我们需要有一个类来描述这些属性。
package com.akitsuki.springframework.beans.factory.config;
import lombok.Getter;
import lombok.Setter;
/**
* bean中的property名称和值
*
* @author ziling.wang@hand-china.com
* @date 2022/11/8 14:19
*/
@Getter
@Setter
public class PropertyValue {
private String name;
private Object value;
public PropertyValue(String name, Object value) {
this.name = name;
this.value = value;
}
}
很好理解,对吧。这个类里面有两个字段,分别代表依赖属性的名称和值。
那么有了上面这个类用来描述属性,我们知道,一个类很多时候不会只有一个依赖,那我们还需要一个类,用来表述这些属性的集合。
package com.akitsuki.springframework.beans.factory.config;
import java.util.HashMap;
import java.util.Map;
/**
* 用来包装和集合PropertyValue的类
*
* @author ziling.wang@hand-china.com
* @date 2022/11/8 14:22
*/
public class PropertyValues {
private Map<String, PropertyValue> propertyValueMap = new HashMap<>();
public void addPropertyValue(PropertyValue propertyValue) {
propertyValueMap.put(propertyValue.getName(), propertyValue);
}
public PropertyValue getPropertyValue(String name) {
return propertyValueMap.get(name);
}
public PropertyValue[] getPropertyValues() {
return propertyValueMap.values().toArray(new PropertyValue[0]);
}
}
那么我们就可以用 PropertyValues
来表示,一个bean中的所有依赖属性了。
这个时候,我们遇到了一些特殊的情况。如果我们的依赖,是一个普通的字段,比如一个int类型、Spring类型的字段,那我们自然可以在PropertyValue里面,对这个字段进行包装。但是,如果这个依赖是另一个bean呢?这里的value我们是给不出来的,因为bean的实例化是交给Spring来做的。那么这个时候,我们就需要一个标识,来告诉Spring,某个属性,是需要引用其他的bean的,不能把它当作普通的属性来处理。
package com.akitsuki.springframework.beans.factory.config;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
/**
* Bean引用接口,用于指明类中的某个依赖是bean的引用
*
* @author ziling.wang@hand-china.com
* @date 2022/11/8 14:17
*/
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class BeanReference {
private String name;
}
于是我们就有了这个类。当我们bean中的依赖是另一个bean的引用的时候,就可以用这个标识来进行指示。具体怎么用,我们下面再来具体解释。
bean定义,迎来了一次扩充
我们之前的工程中,对于bean的定义,只有bean的class信息。这次我们将bean中的依赖属性也作为bean定义中的一员,扩展bean定义的内容。
package com.akitsuki.springframework.beans.factory.config;
import lombok.Getter;
import lombok.Setter;
/**
* Bean的定义,包含bean的一些基本信息
*
* @author ziling.wang@hand-china.com
* @date 2022/11/7 9:54
*/
@Getter
@Setter
public class BeanDefinition {
/**
* bean的class
*/
private final Class<?> beanClass;
/**
* bean的属性和值
*/
private PropertyValues propertyValues = new PropertyValues();
public BeanDefinition(Class<?> beanClass) {
this.beanClass = beanClass;
}
public BeanDefinition(Class<?> beanClass, PropertyValues propertyValues) {
this.beanClass = beanClass;
this.propertyValues = propertyValues;
}
}
起始之刻,灵魂的注入
开头我们就说,要为依赖的对象注入灵魂。那么,我们在何时注入这个灵魂呢?答案就是在我们创建bean的时候。还记得我们创建bean方法是在哪个类具体实现的吗?对,就是那个名字很奇怪的类:AbstractAutowireCapableBeanFactory
。
package com.akitsuki.springframework.beans.factory.support;
import cn.hutool.core.bean.BeanUtil;
import com.akitsuki.springframework.beans.exception.BeanException;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.config.BeanReference;
import com.akitsuki.springframework.beans.factory.config.PropertyValue;
import com.akitsuki.springframework.beans.factory.config.PropertyValues;
import java.lang.reflect.Constructor;
/**
* 抽象的实例化Bean类,提供创建Bean的能力,创建完成后,放入单例bean map中进行缓存
*
* @author ziling.wang@hand-china.com
* @date 2022/11/7 10:12
*/
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {
/**
* 实例化策略,在构造方法中进行初始化
*/
private final InstantiationStrategy strategy;
protected AbstractAutowireCapableBeanFactory(Class<? extends InstantiationStrategy> clazz) throws InstantiationException, IllegalAccessException {
strategy = clazz.newInstance();
}
@Override
protected Object createBean(String beanName, BeanDefinition beanDefinition, Object[] args) throws BeanException {
Object bean;
try {
bean = createBeanInstance(beanDefinition, beanName, args);
//设置bean属性
applyPropertyValues(beanName, beanDefinition, bean);
//创建好的单例bean,放入缓存
addSingleton(beanName, bean);
} catch (Exception e) {
throw new BeanException("创建bean失败", e);
}
return bean;
}
protected Object createBeanInstance(BeanDefinition beanDefinition, String beanName, Object[] args) throws BeanException {
//拿到所有的构造方法
Constructor<?>[] declaredConstructors = beanDefinition.getBeanClass().getDeclaredConstructors();
//开始循环,找到和参数类型一致的方法
for (Constructor<?> c : declaredConstructors) {
if (c.getParameterTypes().length == args.length) {
boolean flag = true;
for (int i = 0; i < c.getParameterTypes().length; i++) {
if (!args[i].getClass().equals(c.getParameterTypes()[i])) {
flag = false;
break;
}
}
if (flag) {
//调用策略来进行实例化
return strategy.instantiate(beanDefinition, beanName, c, args);
}
}
}
throw new BeanException("创建bean出错,未找到对应构造方法");
}
/**
* 为生成的bean设置属性
*
* @param beanName bean的名称
* @param beanDefinition bean的定义
* @param bean 等待设置属性的bean
*/
protected void applyPropertyValues(String beanName, BeanDefinition beanDefinition, Object bean) throws BeanException {
PropertyValues propertyValues = beanDefinition.getPropertyValues();
for (PropertyValue pv : propertyValues.getPropertyValues()) {
String name = pv.getName();
Object value = pv.getValue();
if (value instanceof BeanReference) {
try {
value = getBean(((BeanReference) value).getName());
} catch (Exception e) {
throw new BeanException("设置bean属性时异常:" + beanName, e);
}
}
BeanUtil.setFieldValue(bean, name, value);
}
}
}
好长好长,我们来一点点分析。
首先自然是看我们的 createBean
方法。可以看到,我们加了一个步骤:设置bean属性。那么我们的流程就变成了:创建bean实例->填充依赖属性->放入缓存->返回。那么我们就来看看这个新增的【设置bean属性】步骤。如果刨开中间对 BeanReference
的这一大段不看,其实很好理解,就是调用工具类,将属性和值按照名称设置进刚才创建好的bean实例中。顺带一提,这里使用的 BeanUtil
,是 hutool
工具包下的一个工具类,可以很方便的操作对象。
那么我们对值是 BeanReference
的属性是怎么操作的?通过上面我们知道,这样的属性,代表这是一个bean的引用。所以我们需要将它的值设为一个bean。那么这个bean从哪里来呢?当然是调用getBean方法了。这个时候就会有2种情况:
- 这个bean已经创建好了。那直接从缓存中返回就可以了
- 这个bean还没有被创建。不用担心,getBean方法里面对没有创建的bean会自动进行创建
这样我们就解决了依赖bean的问题,实现了灵魂的注入。
看到这里,可能会联想到上次的有参构造方法。我们一般会在构造方法中,将对象的一些属性进行初始化。那么这一次,我们实际上解决了不通过构造方法对属性进行初始化的问题,也是我们平时@Autowired所习惯的。
对⭐羁绊⭐进行测试吧!
经过我们的不懈努力,我们终于可以让bean之间可以产生羁绊(依赖)了!那么事不宜迟,赶紧为我们之前孤零零的bean,找一位可以产生羁绊的好伙伴。
package com.akitsuki.springframework.test.bean;
import java.util.HashMap;
import java.util.Map;
/**
* @author ziling.wang@hand-china.com
* @date 2022/11/8 14:42
*/
public class UserDao {
private static final Map<Long, String> userMap = new HashMap<>();
static {
userMap.put(1L, "akitsuki");
userMap.put(2L, "toyosaki");
userMap.put(3L, "kugimiya");
userMap.put(4L, "hanazawa");
userMap.put(5L, "momonogi");
}
public String queryUserName(Long id) {
return userMap.get(id);
}
}
我们之前的bean,命名为UserService。我们知道,一般都会由Service层去调用Dao层,Dao层去操作数据库来获取数据。所以很自然的,我们就有了一个UserDao,作为被依赖的对象。这里为了简化操作,就使用静态数据了。那么,UserService,快点依赖起来罢!
package com.akitsuki.springframework.test.bean;
/**
* @author ziling.wang@hand-china.com
* @date 2022/11/8 14:42
*/
public class UserService {
private String dummyString;
private int dummyInt;
private UserDao userDao;
public void queryUserInfo(Long id) {
System.out.println("dummyString:" + dummyString);
System.out.println("dummyInt:" + dummyInt);
String userName = userDao.queryUserName(id);
if (null == userName) {
System.out.println("用户未找到>_<");
} else {
System.out.println("用户名:" + userDao.queryUserName(id));
}
}
}
终于,它看起来像一个正常的Service了。还添加了两个普通属性作为添头,测试一下普通属性的注入。
我们正式开始测试
package com.akitsuki.springframework.test;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.config.BeanReference;
import com.akitsuki.springframework.beans.factory.config.PropertyValue;
import com.akitsuki.springframework.beans.factory.config.PropertyValues;
import com.akitsuki.springframework.beans.factory.support.CglibSubclassingInstantiationStrategy;
import com.akitsuki.springframework.beans.factory.support.DefaultListableBeanFactory;
import com.akitsuki.springframework.test.bean.UserDao;
import com.akitsuki.springframework.test.bean.UserService;
import org.junit.Test;
/**
* @author ziling.wang@hand-china.com
* @date 2022/11/8 14:42
*/
public class ApiTest {
@Test
public void test() throws Exception {
//初始化bean工厂
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory(CglibSubclassingInstantiationStrategy.class);
//初始化Bean UseDao
BeanDefinition beanDefinition = new BeanDefinition(UserDao.class);
beanFactory.registerBeanDefinition("userDao", beanDefinition);
//初始化Bean UserService
PropertyValue dummyString = new PropertyValue("dummyString", "dummy");
PropertyValue dummyInt = new PropertyValue("dummyInt", 114514);
PropertyValue userDao = new PropertyValue("userDao", new BeanReference("userDao"));
PropertyValues propertyValues = new PropertyValues();
propertyValues.addPropertyValue(dummyInt);
propertyValues.addPropertyValue(dummyString);
propertyValues.addPropertyValue(userDao);
BeanDefinition beanDefinition1 = new BeanDefinition(UserService.class, propertyValues);
beanFactory.registerBeanDefinition("userService", beanDefinition1);
//获取UserService并使用
UserService userService = (UserService) beanFactory.getBean("userService");
userService.queryUserInfo(1L);
System.out.println("---------");
userService.queryUserInfo(5L);
System.out.println("---------");
userService.queryUserInfo(114514L);
}
}
测试结果
dummyString:dummy
dummyInt:114514
用户名:akitsuki
---------
dummyString:dummy
dummyInt:114514
用户名:momonogi
---------
dummyString:dummy
dummyInt:114514
用户未找到>_<
Process finished with exit code 0
看起来很长,实际上大部分的内容都用在了BeanDefinition的制作上。功夫不负有心人,我们的bean依赖成功的被注入进来了。普通属性也正常地进行了注入。
不过这里也体现出一个问题,那就是,我们对BeanDefinition的构建操作,太过于繁琐了。想想我们正常使用Spring的时候,写一写配置文件,加一加注解,Spring自动就帮我们注册好了。那么下一章,我们就来填上这个大坑,让配置文件来帮我们完成Bean的注册!
相关源码可以参考我的gitee:https://gitee.com/akitsuki-kouzou/mini-spring
,这里对应的代码是mini-spring-04