上篇我们介绍了手写框架的项目结构,这篇我们就从 IOC/DI 开始,先完成 beans 包的内容。
beans 包中一般放的是配置、规范、标准等,关于 IOC 容器的具体逻辑实现是在 context 包中。
1.MYBeanFactory
BeanFactory是顶层设计,相当于规范了IOC容器的功能。
public interface MYBeanFactory {
// 通过beanName获取bean
Object getBean(String beanName) throws Exception;
// 通过Class获取bean
Object getBean(Class<?> beanClass) throws Exception;
}
注意,IOC 容器默认是单例模式。
2.MYFactoryBean
FactoryBean 工厂Bean,在我们的手写框架中没用到,暂时先不写。
public interface MYFactoryBean {
}
关于 BeanFactory 和 FactoryBean 的区别可以看这篇 【Spring】IOC&DI:FactoryBean和BeanFactory分析对比…
3.MYBeanDefinition
用来保存Bean的信息,包括实际类信息和配置信息。在我们平时使用Spring时,xml中有许多能配置的选项,但是为了简单我们这里只包括了 beanName,class,isLazyInit,isSigleton 这四项。
在通过getBean获取Bean实例时,首先要拿到这里实例的类信息(BeanDefinition)。
这里明确下三组对应关系:
- 一个Class对应多个BeanDefinition,比如Abean,Bbean属于同一Class的实例,
- beanName不同:Abean 的 beanName 是类名小写,Bbean 的 beanName 是其它的某个接口首字母小写
- isLazyInit不同:Abean 不是懒加载,Bbean是懒加载
- isSigleton 不同:Abean 是单例模式,Bbean 是多例模式
- 一个 BeanDefinition 对应一个 Bean
- 一个 Bean 有两种获取方式(从 BeanFactory#getBean() 可以看出),所以一个 BeanDefinition 也有两种获取方式
- ByName:Name 来源有两个
- 默认:所属类名(首字母小写)
- 自定义:用户在将 bean 交给 IOC 容器时,注解中自己定义的 beanName
- ByType:当一个 type 下有多个 bean(BeanDefinition)时,只会返回最后一个(覆盖)
注意,对于有接口的类,还需要构建其父接口的 BeanDefinition
- beanName:接口名(首字母小写)
- beanClass:实现类
public class MYBeanDefinition {
// factoryBeanName:即每个类的对象应该用什么具体工厂bean创建 --> 一个Bean对应一个工厂
// 注意:但是在这里,factoryBeanName 用来保存beanName的,同时作为bean的唯一标识!!!
private String factoryBeanName;
// 全类名
// 为了后面通过反射拿到 Class对象,然后创建实例和注解判断
private String beanClassName;
// 懒加载,默认false(后面构建BeanDefinition时就可以不用管了)
private boolean isLazyInit = false;
// 单例模式,默认true
private boolean isSigleton = true;
// getter、setter...
public String getBeanClassName() {
return beanClassName;
}
public void setBeanClassName(String beanClassName) {
this.beanClassName = beanClassName;
}
public String getFactoryBeanName() {
return factoryBeanName;
}
public void setFactoryBeanName(String factoryBeanName) {
this.factoryBeanName = factoryBeanName;
}
public boolean isLazyInit() {
return isLazyInit;
}
public void setLazyInit(boolean lazyInit) {
isLazyInit = lazyInit;
}
public boolean isSigleton() {
return isSigleton;
}
public void setSigleton(boolean sigleton) {
isSigleton = sigleton;
}
}
4.MYBeanDefinitionReader
两个作用:
- 加载配置文件,在Spring中本来是由Resource相关类进行解析,而这里为了简便就都放在这一个类中了
- 将配置文件组装成Beandefinition
为了扩展,这里本来还应有一个统一接口,然后应用策略模式,但为了方便这里只加载properties文件
public class MYBeanDefinitionReader {
// 保存所有Bean的class信息(全类名)
private List<String> registerBeanClasses = new ArrayList<String>();
private Properties config = new Properties();
// 定义Properties文件中要扫描包的 key,相当于一种规范
private final String SCAN_PACKAGE = "scanPackage";
// 构造函数,传入要加载配置文件的路径
public MYBeanDefinitionReader(String... locations) {
// 根据url读取配置文件,加载成io流
// 注:这里因为知道要加载properties文件,所以取[0]
// 删掉classpath是因为,在传入配置文件位置时,常见写法是 classpath:application.Properties
InputStream is = this.getClass().getClassLoader().getResourceAsStream(locations[0].replace("classpath:", ""));
// 通过Properties类,将IO流解析成properties,然后关闭IO流
try {
config.load(is);
} catch (IOException e) {
e.printStackTrace();
} finally {
if (null != is) {
try {
is.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
// 对指定包进行扫描
doScanner(config.getProperty(SCAN_PACKAGE));
}
}
doScanner()
扫描指定包,得到指定包下所有类的全类名,本质是文件操作
private void doScanner(String scanPackage) {
// 1.获取需要扫描包的File对象
// 这里不能直接new File,因为new File需要的是相对路径或绝路径,而上一步得到的包名什么都不是,所以我们的策略是先获取URL对象(里面封装了File对象)。
// 注:getResource方法返回的是URL对象,用来获取指定类或包的绝对路径
// getResource获取绝对路径,首先要将包名转化成项目相对路径。而最前面 / 表示从根路径中寻找(显然要找到这个包不能从当前目录下寻找)
URL url = this.getClass().getClassLoader().
getResource("/" + scanPackage.replaceAll("\\.", "/"));
// 通过getFile获取到要扫描包的File对象(文件夹)
File classpath = new File(url.getFile());
// 2.遍历文件夹,寻找class文件
for (File file : classpath.listFiles()) {
if (file.isDirectory()) {
// 这里是通过递归遍历文件夹,还是包就再执行上述步骤(解析路径->创建目录->遍历)
doScanner(scanPackage + "." + file.getName());
} else {
// 不是class文件的不管
if (!file.getName().endsWith("class")) {continue;}
// 这里要保存全类名(包.类名),因为后面要通过反射Class.forName获取Class对象
String className = (scanPackage + "." + file.getName()).replace(".class", "");
registerBeanClasses.add(className);
}
}
}
loadBeanDefinitions()
将Scanner方法读取出来的类(名)转换成 BeanDefinition
public List<MYBeanDefinition> loadBeanDefinitions() {
// 保存构建的所有 BeanDefinition
List<MYBeanDefinition> result = new ArrayList<MYBeanDefinition>();
try {
// 扫描所有加载进来的类
for (String className : registerBeanClasses) {
Class<?> clazz = Class.forName(className);
// 接口不能实例化(相当于不能有factoryBean),不处理
if (clazz.isInterface()) continue;
// 一个Class对应多个BeanDefinition,其中还有一个原因就是 beanName 这里可以分为三类
// 1.beanName是对应类的类名(默认)
result.add(doCreateBeanDefinition(toLowerFirstCase(clazz.getSimpleName()), clazz.getName()));
// 2.beanName是接口名(当前类存在接口时)
Class<?>[] interfaces = clazz.getInterfaces();
for (Class<?> i : interfaces) {
// 注:这里若一个接口有多个实现类,那么后扫描会覆盖先扫描的(相当于只能保存一个实现类的)
// 这种情况下,可以通过注入Bean时指定name解决
result.add(doCreateBeanDefinition(i.getName(), clazz.getName()));
}
// TODO
// 3.beanName是用户自定义的(一般配合@Resource注解)
}
} catch (Exception e) {
e.printStackTrace();
}
return result;
}
// 再封装了构建 BeanDefinition 的方法
private MYBeanDefinition doCreateBeanDefinition(String factoryBeanName, String beanClassName) {
MYBeanDefinition myBeanDefinition = new MYBeanDefinition();
myBeanDefinition.setBeanClassName(beanClassName);
myBeanDefinition.setFactoryBeanName(factoryBeanName);
return myBeanDefinition;
}
// 将首字母转为小写
private String toLowerFirstCase(String simpleName) {
char [] chars = simpleName.toCharArray();
chars[0] += 32;
return String.valueOf(chars);
}
getConfig()
返回保存了配置文件内容的Properties对象,拿到该对象相当于拿到了配置文件的内容
public Properties getConfig() {
return this.config;
}
后面 aop 的切面等信息和 mvc 的页面模板信息都在配置文件中
5.MYBeanWrapper
在IOC容器中,Spring 并不会把最原始的对象进去,而是会用一个BeanWrapper来进行一次包装
public class MYBeanWrapper {
// 实例 Bean
private Object wrappedInstance;
// 还要保存 Class,目的为了多例模式服务
private Class<?> wrappedClass;
public MYBeanWrapper(Object wrappedInstance) {
this.wrappedInstance = wrappedInstance;
}
public Object getWrappedInstance() {
return this.wrappedInstance;
}
// 这里没有通过构造函数或者set方法进行注入,而是直接通过instance获取到Class
public Class<?> getWrappedClass() {
return this.wrappedInstance.getClass();
}
}
6.MYBeanPostProcessor
事件处理器,它的作用主要是如果我们需要在Spring 容器完成 Bean 的实例化、配置和其他的初始化前后添加一些自己的逻辑处理。
注意,在Spring中BeanPostProcessor是一个接口,我们可以定义一个或者多个 BeanPostProcessor 接口的实现,然后注册到容器中。但这里我们只是为了模仿,并且出于在 ApplicationContext#getBean 中能够进行实例化的目的,此处就直接写成了 class。
public class MYBeanPostProcessor {
// 为在Bean的初始化前提供回调入口
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return null;
}
// 为在Bean的初始化之后提供回调入口
public Object postProcessAfterInitialization(Object bean, String beanName) {
return null;
}
}