前言
上一章我们实现了一个简化的Bean容器,那么这一章开始,我们就要上一些强度了。这一章主要的目的是,让Spring容器来自动化实现Bean的创建,并且实现单例Bean的复用。在上一章,我们只实现了一个容器,bean的创建,实际上还是我们在注册bean的时候手动new出来的。那么这次,我们就让Spring容器来帮我们做这些工作。
工程结构
├─src
│ ├─main
│ │ ├─java
│ │ │ └─com
│ │ │ └─akitsuki
│ │ │ └─springframework
│ │ │ └─beans
│ │ │ └─factory
│ │ │ │ BeanFactory.java
│ │ │ │
│ │ │ ├─config
│ │ │ │ BeanDefinition.java
│ │ │ │ DefaultSingletonBeanRegistry.java
│ │ │ │ SingletonBeanRegistry.java
│ │ │ │
│ │ │ ├─exception
│ │ │ │ BeanException.java
│ │ │ │
│ │ │ └─support
│ │ │ AbstractAutowireCapableBeanFactory.java
│ │ │ AbstractBeanFactory.java
│ │ │ BeanDefinitionRegistry.java
│ │ │ DefaultListableBeanFactory.java
│ │ │
│ │ └─resources
│ └─test
│ └─java
│ └─com
│ └─akitsuki
│ └─springframework
│ └─test
│ │ ApiTest.java
│ │
│ └─bean
│ UserService.java
光是看工程结构就知道,这次强度比起上次来,要暴涨了!(笑)
一切的开始:竟然是异常?
没有异常的程序,是不存在的。只要是程序,就一定会有不按套路出牌的时候。那对于我们的小Spring框架来说,我们也需要一个属于自己的异常类,这样能够帮助我们更好的对异常进行包装。
package com.akitsuki.springframework.beans.factory.exception;
/**
* Bean相关的异常
*
* @author ziling.wang@hand-china.com
* @date 2022/11/7 10:14
*/
public class BeanException extends RuntimeException {
public BeanException(String info) {
super(info);
}
public BeanException(String info, Exception e) {
super(info, e);
}
}
嗯,一个很简单的运行时异常,但它是属于我们框架自己的异常,所以还是要给予鼓励~
Bean定义的变化
package com.akitsuki.springframework.beans.factory.config;
/**
* Bean的定义,将Bean的Class类型注册到这里来
*
* @author ziling.wang@hand-china.com
* @date 2022/11/7 9:54
*/
public class BeanDefinition {
private final Class<?> beanClass;
public BeanDefinition(Class<?> beanClass) {
this.beanClass = beanClass;
}
public Class<?> getBeanClass() {
return beanClass;
}
}
我们可以看到,BeanDefinition不再保存实例化完成的bean了,而是保存了bean的class。这也让它更加符合【bean定义】这个名称。这样,我们就可以将bean的实例化,放在容器中进行,而不是在创建bean定义时就进行。
单例bean注册接口
package com.akitsuki.springframework.beans.factory.config;
/**
* 单例Bean注册接口
*
* @author ziling.wang@hand-china.com
* @date 2022/11/7 9:56
*/
public interface SingletonBeanRegistry {
/**
* 获取单例Bean
*
* @param beanName bean的名称
* @return 单例bean
*/
Object getSingleton(String beanName);
/**
* 添加一个单例Bean
*
* @param beanName bean的名称
* @param bean 单例bean
*/
void addSingleton(String beanName, Object bean);
}
接下来是一个新玩意儿了。我们知道,接口一般是用来规定一个类应该具有哪些功能的。那么这个接口:单例bean注册接口,自然就是用来规定一个类如果要提供单例bean注册功能,需要提供哪些功能的。这里,有2个方法:获取单例bean
、添加单例bean
,都很好理解。
我们知道,Spring中的bean,在默认情况下和大多数情况下,都是单例的。所以,我们需要一个功能,用来添加和获取这些单例bean,这就是这个接口存在的意义。
单例bean注册接口的默认实现
package com.akitsuki.springframework.beans.factory.config;
import java.util.HashMap;
import java.util.Map;
/**
* 默认的单例Bean注册获取实现
*
* @author ziling.wang@hand-china.com
* @date 2022/11/7 9:58
*/
public class DefaultSingletonBeanRegistry implements SingletonBeanRegistry {
private final Map<String, Object> singletonBeans = new HashMap<>();
@Override
public Object getSingleton(String beanName) {
return singletonBeans.get(beanName);
}
@Override
public void addSingleton(String beanName, Object singletonBean) {
singletonBeans.put(beanName, singletonBean);
}
}
有接口,自然就会有接口对应的实现类。可以看到,这个类用一个map来保存实例化好的单例bean,key是bean的名称。不由得让人想到上次的factory中,用来保存bean定义的那个map。这里也是相同的一种做法。方法的实现本身很浅显易懂,这里就不过多解释了
BeanFactory的变化
在上一次,我们的BeanFactory是一个具体的类。但实际上,我们的BeanFactory应该是各种各样的,不止一种实现。所以,在这一次,我们将BeanFactory变成了一个接口。对于所有的BeanFactory来说,它们都应该拥有的一个功能,自然是获取bean。所以我们在BeanFactory这个接口中,就定义这么一个功能,具体的内容,则由实现了这个接口的子类来完成。
package com.akitsuki.springframework.beans.factory;
import com.akitsuki.springframework.beans.factory.exception.BeanException;
/**
* bean工厂接口,用来获取bean
*
* @author ziling.wang@hand-china.com
* @date 2022/11/7 10:03
*/
public interface BeanFactory {
/**
* 获取Bean
*
* @param beanName bean的名称
* @return bean
* @throws BeanException exception
*/
Object getBean(String beanName) throws BeanException;
}
抽象bean工厂!
这一部分是比较重要的一块。我们先上代码
package com.akitsuki.springframework.beans.factory.support;
import com.akitsuki.springframework.beans.factory.BeanFactory;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.config.DefaultSingletonBeanRegistry;
import com.akitsuki.springframework.beans.factory.exception.BeanException;
/**
* 抽象bean工厂,实现了BeanFactory,提供获取Bean的实现
* 在获取Bean的方法中,调用了两个抽象方法:获取定义、创建Bean
* 这两个抽象方法由子类来实现,这里只定义过程
*
* @author ziling.wang@hand-china.com
* @date 2022/11/7 10:04
*/
public abstract class AbstractBeanFactory extends DefaultSingletonBeanRegistry implements BeanFactory {
@Override
public Object getBean(String beanName) throws BeanException {
//这里先尝试获取单例Bean,如果可以获取到就直接返回
Object bean = getSingleton(beanName);
if (null != bean) {
return bean;
}
//这里是上面获取单例Bean失败的情况,需要先调用抽象的获取bean定义的方法,拿到bean的定义,然后再通过这个来新创建一个Bean
BeanDefinition beanDefinition = getBeanDefinition(beanName);
return createBean(beanName, beanDefinition);
}
/**
* 获取Bean的定义
*
* @param beanName bean的名字
* @return beanDefinition
* @throws BeanException exception
*/
protected abstract BeanDefinition getBeanDefinition(String beanName) throws BeanException;
/**
* 创建Bean
*
* @param beanName bean的名字
* @param beanDefinition bean的定义
* @return bean
* @throws BeanException exception
*/
protected abstract Object createBean(String beanName, BeanDefinition beanDefinition) throws BeanException;
}
有点复杂,我们一点点来分析。
首先,我们可以看到,这是一个抽象类,它继承了上面默认的单例bean注册接口实现类 DefaultSingletonBeanRegistry
,实现了 BeanFactory
接口。那么这个类就具有了以下功能:
- 由BeanFactory接口提供的获取bean功能
- 继承自单例bean注册接口默认实现类的添加、获取单例bean功能
既然是抽象类,自然也会有抽象方法,这个抽象bean工厂也提供了2个抽象方法供子类实现:获取bean定义方法 getBeanDefinition
、创建bean方法 createBean
。
框架介绍完了,我们来思考为什么要这么去设计。这里实际上用到了设计模式中的【模板模式】。模板模式是指,一个抽象类中,有一个【主要的方法】,再定义1~n个方法,可以是抽象的,也可以是实际的。主要方法中会调用这些抽象方法,而这些抽象方法则由具体的子类去实现。那么这些抽象方法,其实就相当于【模板】。主要方法是我们最终想要达成的目的,而达成这个目的,需要一些步骤。在抽象类中,我们只在主要方法里去调用这些步骤,而不管这些步骤具体的实现,具体的实现由具体的子类去完成。就好像要让儿子去打酱油,在主方法中,只需要调用【打酱油】这个抽象方法,而具体打酱油要怎么做,走哪条路,去哪家店,则是由儿子这个子类来实现了。
那我们来具体分析这里的主要方法。很显然,这里的主要方法,自然是实现BeanFactory的获取bean方法。这里首先尝试根据名称,直接获取单例的bean,如果获取到了,那就直接返回。那么如果获取不到呢?我们就要去自己创建一个。怎么创建呢?先要获取bean定义,再根据bean定义来创建。所以下面的步骤就是:根据bean名称,获取bean定义,再通过bean定义来创建一个bean,最终返回创建好的bean。而获取bean定义、创建bean这两个方法都是抽象方法,在这里的感觉就是,步骤已经给你列好了,获取bean都需要经过这几步,具体实现我不管,反正最终能给我一个生产好的bean就行。
让抽象变具体:实现createBean方法!
上面我们的抽象类中定义了createBean这个抽象方法,它总要有人来具体实现。下面这个类,就是用来做这个事情的。
package com.akitsuki.springframework.beans.factory.support;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.exception.BeanException;
/**
* 抽象的实例化Bean类,提供创建Bean的能力,创建完成后,放入单例bean map中进行缓存
*
* @author ziling.wang@hand-china.com
* @date 2022/11/7 10:12
*/
public abstract class AbstractAutowireCapableBeanFactory extends AbstractBeanFactory {
@Override
protected Object createBean(String beanName, BeanDefinition beanDefinition) throws BeanException {
Object bean;
try {
//通过反射来创建Bean
bean = beanDefinition.getBeanClass().newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new BeanException("创建bean异常", e);
}
//放入单例Bean map中,以备调用
addSingleton(beanName, bean);
return bean;
}
}
我们可以看到,它自己本身也是一个抽象类,继承了上面的抽象bean工厂,并且实现了createBean方法。这里槽点有些多,我们一个一个来解释。
- 这个类的名字好奇怪>_<!
确实很奇怪,但这是为了和Spring保持一致而命名的。在Spring中,的确是由这个类来继承AbstractBeanFactory的,但这个类在现阶段过于简化了,没有体现出本身的功能。在这里可以简单的理解为为了实现createBean方法的一个子类(虽然名字有点怪) - 为什么它也是一个抽象类,搞不懂诶>_<!
我们可以看到,它只实现了父类的一个抽象方法。如果它是一个普通的类,那就要把两个方法全部实现了。 - 为什么不把父类的两个抽象方法都实现了啊,是太懒了吗>_<!
这里体现出的是一种分层实现的方式,也是单一职责的一种体现。父类提供的2个抽象方法涉及到2个方面:bean
和bean定义
。这个类只应该负责bean这一块,而bean定义,则交给更适合的人去负责,所以它只实现了创建bean的方法。
这里创建bean的实现,是简单的使用反射来实现的,从bean定义中拿到了class,调用了newInstance方法。这里其实无法处理有参构造方法。但这里为了简化,我们先不考虑这种情况,后面再继续完善。
关于bean定义,要做的事:注册接口
上面我们定义了一个接口单例bean注册接口,这个接口规定了注册一个单例bean,需要拥有哪些功能。那么,对于bean定义,是不是也需要这么一个注册接口呢?
package com.akitsuki.springframework.beans.factory.support;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.exception.BeanException;
/**
* 注册Bean定义的接口
*
* @author ziling.wang@hand-china.com
* @date 2022/11/7 10:21
*/
public interface BeanDefinitionRegistry {
/**
* 获取bean定义
*
* @param beanName bean的名称
* @return bean定义
* @throws BeanException exception
*/
BeanDefinition getBeanDefinition(String beanName) throws BeanException;
/**
* 注册Bean定义
*
* @param beanName bean的名称
* @param beanDefinition bean的定义
*/
void registerBeanDefinition(String beanName, BeanDefinition beanDefinition);
}
由于和单例bean注册接口类似,这里就不过多解释了。bean定义总是需要先注册,才能使用。这个我们在上一次完成小demo的时候,就已经明确了。所以这里我们用接口来规定bean定义注册时需要的功能:获取、注册。
最终的实现类!
上面我们定义了各种接口,也有各种抽象类或者普通类通过分层实现和继承,各自实现了许多功能。在最后,这些功能都需要落实在一个具体的实现类上。可以说,这个类是一个集大成的类,它通过逐级继承,拥有了前面所有描述的功能!
package com.akitsuki.springframework.beans.factory.support;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.exception.BeanException;
import java.util.HashMap;
import java.util.Map;
/**
* 最终集大成的核心类,通过逐级继承获取到了其他的各种bean操作方法
* 同时也实现了BeanDefinitionRegistry的注册bean定义、获取bean定义的方法
*
* @author ziling.wang@hand-china.com
* @date 2022/11/7 10:20
*/
public class DefaultListableBeanFactory extends AbstractAutowireCapableBeanFactory implements BeanDefinitionRegistry {
private final Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
@Override
public BeanDefinition getBeanDefinition(String beanName) throws BeanException {
BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
if (null == beanDefinition) {
throw new BeanException("没有名称为" + beanName + "的Bean定义");
}
return beanDefinition;
}
@Override
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition) {
beanDefinitionMap.put(beanName, beanDefinition);
}
}
终于到了大boss了。但是这个类看起来意外的很简单。因为它所拥有的功能,都被各个父类进行分担了。而对于它自己来说,则是实现bean定义注册接口里面的两个方法即可。可能这就是继承的力量吧(笑)。
而这里的beanDefinition操作,则与上次练习中的beanFactory类似,也比较好理解,就不多解释了。
最后我们来分析一下这个类最终所拥有的功能,再次感受一下继承的力量吧~
- beanDefinition的操作能力:自身实现
BeanDefinitionRegistry
接口所拥有 - 创建bean:继承自
AbstractAutowireCapableBeanFactory
类 - 获取bean:继承自
AbstractBeanFactory
类 - 注册、获取单例bean:继承自
DefaultSingletonBeanRegistry
类
嗯嗯,真是了不起。有了这么个集大成的类,我们这次的demo也就算完成了。下面准备进行测试。顺带一提,这个类的名字也很奇怪,但这也是为了和Spring保持一致,原因参照上面的解释吧~
终于,要开始测试了!
经过了上面这些兜兜转转、逐级继承,最终我们有了DefaultListableBeanFactory这么个集万千宠爱于一身的类。还记得这一章我们的目标是什么吗?对,实现Bean的定义、注册、获取,并且让Spring容器来帮我们进行bean的实例化!
首先,来把我们的bean稍微改造一下吧~
package com.akitsuki.springframework.test.bean;
/**
* @author ziling.wang@hand-china.com
* @date 2022/11/7 10:30
*/
public class UserService {
public UserService() {
System.out.println("userService初始化");
}
public void queryUserInfo() {
System.out.println("查询用户信息");
}
}
其实也没有什么改造,只不过加了个无参的构造方法而已,这样做,是为了更好的看出Spring帮助我们创建bean的过程。
接下来是正式的测试方法!
package com.akitsuki.springframework.test;
import com.akitsuki.springframework.beans.factory.config.BeanDefinition;
import com.akitsuki.springframework.beans.factory.exception.BeanException;
import com.akitsuki.springframework.beans.factory.support.DefaultListableBeanFactory;
import com.akitsuki.springframework.test.bean.UserService;
import org.junit.Test;
/**
* @author ziling.wang@hand-china.com
* @date 2022/11/7 10:31
*/
public class ApiTest {
@Test
public void testBeanFactory() throws BeanException {
//初始化BeanFactory
DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
//注册Bean
BeanDefinition beanDefinition = new BeanDefinition(UserService.class);
beanFactory.registerBeanDefinition("userService", beanDefinition);
//获取Bean
UserService userService = (UserService) beanFactory.getBean("userService");
userService.queryUserInfo();
//第二次获取Bean
userService = (UserService) beanFactory.getBean("userService");
userService.queryUserInfo();
}
}
测试结果:
userService初始化
查询用户信息
查询用户信息
Process finished with exit code 0
可以看到,在这次的测试中,我们首先初始化了我们那个集大成的bean工厂:DefaultListableBeanFactory
。然后,将bean的class作为参数,创建了bean的定义,并注册到了Spring中。接下来,我们只需要简单地对工厂说:我需要一个名为"userService"的bean。就可以等着工厂帮我们生产了。这里从工厂获取了2次,但结果中只初始化了1次。可以看出,在第一次获取bean的时候,工厂会进行新建操作。而第二次进行获取的时候,工厂直接从缓存中将第一次创建的bean进行了返回,这就是单例bean的思想。到这里,我们的第二章手写Spring也要告一段落了。整个工程看起来也要比第一次更加像样了。
相关源码可以参考我的gitee:https://gitee.com/akitsuki-kouzou/mini-spring
,这里对应的代码是mini-spring-02