【Spring】重构--仿写Spring核心逻辑(三)实现IOC/DI(context包)

系列文章:

在上一篇我们已经实现了 beans 包中的相关类,本篇就来实现 context 中关于容器的具体逻辑。

在这里插入图片描述

1.MYAbstractApplicationContext

IOC容器顶层设计,是最顶层容器的规范,不管是 XmlApplication 还是 AnnotationApplication 都必须去实现。这种设计也便于我们日后扩展新容器。

public abstract class MYAbstractApplicationContext {
    // 受保护,只提供给子类重写(最少知道原则)
    protected void refresh() throws Exception {}
}

2.MYDefaultListableBeanFactory

上面有了 MYAbstractApplicationContext 那我们就可以多种选择,但是也必须提供 IOC 容器的默认实现。这就好比平常我们手机支付时可以用微信、支付宝、银行卡等,但是在具体的支付页面它都会设置好一个默认支付选项。

注意,这个类其实是在 beans 包中,因为 beans包更多放的是规范、配置、标准等。放到 context 包中说是为了更直观的看到 AbstractApplicationContext、DefaultListableBeanFactory、ApplicationContext 的关系。

public class MYDefaultListableBeanFactory extends MYAbstractApplicationContext {
    // 伪IOC容器,保存了BeanDefinition(类信息)
    // 这里的key是factoryBeanName,即beanName(对于一种Bean而言factoryName是唯一的)
    protected final Map<String, MYBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, MYBeanDefinition>();
}

3.MYApplicationContext

// 实现 BeanFactory 接口,继承 DefaultListableBeanFactory
public class MYApplicationContext extends MYDefaultListableBeanFactory implements MYBeanFactory {

    private String[] configLocations;
    private MYBeanDefinitionReader reader;

    // 通用IOC容器,存的是 BeanWrapper
    private Map<String, MYBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<String, MYBeanWrapper>();
    // 单例的IOC容器,存的是实例对象(相当于缓存,避免重复创建Bean)
    private Map<String, Object> factoryBeanObjectCache = new ConcurrentHashMap<String, Object>();

    // 构造函数,要传入加载的配置文件,然后调用refresh方法
    public MYApplicationContext(String... configLocations) {
        this.configLocations = configLocations;
        try {
            refresh();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
	
	//...
}

我们再来看一遍继承关系:

在这里插入图片描述

refresh()

在 AbstractApplicationContext 定义的模板方法,是IOC容器初始化的入口

@Override
protected void refresh() throws Exception {
   // 1.定位,定位配置文件
   reader = new MYBeanDefinitionReader(configLocations);

   // 2.加载,加载配置文件到内存(BeanDefinition)
   List<MYBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();

   // 3.注册,注册配置信息到容器里面(伪IOC容器)
   doRegisterBeanDefinition(beanDefinitions);

   // 4.把不是延时加载的类,提前初始化
   // 一般不开启懒加载,即在IOC容器初始化时就完成实例化与注入
   doAutowired();
}

doRegisterBeanDefinition()

将BeanDefinition们注入容器(伪IOC容器)。该方法执行完后,IOC容器初始化就完成了

private void doRegisterBeanDefinition(List<MYBeanDefinition> beanDefinitions) throws Exception {
    for (MYBeanDefinition beanDefinition : beanDefinitions) {
        if (super.beanDefinitionMap.containsKey(beanDefinition.getFactoryBeanName())) {
            throw new Exception("The “" + beanDefinition.getFactoryBeanName() + "” is exists!!");
        }
        super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
    }
}

doAutowired()

对于非延时加载的bean,在IOC容器初始化时就将Bean创建出来并完成依赖注入

private void doAutowired() {
    // 遍历BeanDefinition,看哪个是延时加载了
    for (Map.Entry<String, MYBeanDefinition> entry : super.beanDefinitionMap.entrySet()) {
        String beanName = entry.getKey();
        // 这里默认isLazyInit=false,即先提前将Bean放入IOC容器中,即这里所有BeanDefinition都会创建一个bean
        if (!entry.getValue().isLazyInit()) {
            try {
                getBean(beanName);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

getBean() *

初始化Bean并完成依赖注入:

  1. 初始化:读取BeanDefinition,通过反射创建Bean实例,包装成BeanWrapper,并放入IOC容器
  2. 注入:对IOC容器管理的Bean进行依赖注入

Spring中调用getBean的时机:

  1. DispatchServlet 创建 IOC容器:refresh --> doAutowired
  2. DispatchServlet 创建 HandlerMapping,要拿出所有Bean
  3. 手动调用 getBean 方法去获取Bean,比如上面doAutowired()在IOC容器初始化时调用
@Override
public Object getBean(String beanName) throws Exception {
	
	// 根据 beanName 拿出 BeanDefinition
    MYBeanDefinition myBeanDefinition = this.beanDefinitionMap.get(beanName);
    Object instance = null;

    // 创建事件处理器
    MYBeanPostProcessor beanPostProcessor = new MYBeanPostProcessor();
    // 在创建bean之前进行一些动作
    beanPostProcessor.postProcessBeforeInitialization(instance, beanName);

    // 判断是否是Spring管理的对象
    // 在创建BeanDefinition时,不是Spring管理的就没有BeanDefinition(这里是通过Properties配置scanPackage不是注解)
    if (myBeanDefinition == null) {
        throw new Exception("This Bean not exists!");
    }

    // 1.初始化
    // 注:所有Bean实例都要封装成BeanWrapper,然后在BeanWrapper中再取出Bean实例
    MYBeanWrapper beanWrapper = instantiteBean(beanName, myBeanDefinition);

    // 2.将拿到的BeanWrapper放入IOC容器
    this.factoryBeanInstanceCache.put(beanName, beanWrapper);
	
	// 在创建bean之后进行一些动作
    beanPostProcessor.postProcessAfterInitialization(instance, beanName);

    // 3.注入
    populateBean(beanName, new MYBeanDefinition(), beanWrapper);

    Object o = this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
    // 注:即使是单例模式有单例IOC容器,但获取Instance也要先封装为Wrapper,然后再在通用容器中取
    return this.factoryBeanInstanceCache.get(beanName).getWrappedInstance();
}

// 通过类型获取bean的方法跟beanName获取大同小异,这里就不实现了
@Override
public Object getBean(Class<?> beanClass) throws Exception {
     return null;
}

循环注入:class A{ B b; } --> class B{ A a; }。因此要分成初始化与注入两个方法,即先将对象创建出来,然后再将依赖注入

instantiteBean()

创建 Bean 实例,并封装成 BeanWrapper

private MYBeanWrapper instantiteBean(String beanName, MYBeanDefinition myBeanDefinition) {
    // 1.拿到要实例化的类名
    String className = myBeanDefinition.getBeanClassName();
    // 2.通过反射进行实例化
    Object instance = null;
    try {
        // 默认所有对象都是单例的,即都可以通过单例IOC容器中获取Bean 
        if (this.factoryBeanObjectCache.containsKey(className)) {
            instance = this.factoryBeanObjectCache.get(className);
        } else {
            Class<?> clazz = Class.forName(className);
            instance = clazz.newInstance();
            
            this.factoryBeanObjectCache.put(myBeanDefinition.getFactoryBeanName(), instance);
            // 注:这里还要根据className(全类名)放一个
            // 因为在通过类型进行getBean时,BeanDefinition只封装了接口做factoryName
            this.factoryBeanObjectCache.put(className, instance);
        }
    } catch (Exception e) {
        e.printStackTrace();
    }
    // 3.封装BeanWrapper
    // 注:无论单例多例,都要先封装成 BeanWrapper
    MYBeanWrapper beanWrapper = new MYBeanWrapper(instance);
    return beanWrapper;
}

populateBean()

对IOC中的Bean进行依赖注入:

  1. byName:指定 beanName(首字母小写,接口,自定义) -----> @Resource时
  2. byType:类型注入(默认) ----> @Autowired
private void populateBean(String beanName, MYBeanDefinition myBeanDefinition, MYBeanWrapper myBeanWrapper) {
    Class<?> clazz = myBeanWrapper.getWrappedClass();
    // 只有容器管理的bean才会给他依赖注入
    if (! clazz.isAnnotationPresent(MYController.class ) || clazz.isAnnotationPresent(MYService.class)) { return; }

    Object instance = myBeanWrapper.getWrappedInstance();
    // 注:这里是getDeclaredFields,getFields只能获取到public字段
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        if (!field.isAnnotationPresent(MYAutowired.class)) { continue; }
		
		// 1.获取注解中指定注入的beanName(byName注入)
        MYAutowired annotation = field.getAnnotation(MYAutowired.class);
        String autowiredBeanName = annotation.value().trim();

        // 2.没有指定beanName的话,通过类型进行注入(byType注入)
        // 注意:类型 = 自身类型 || 接口类型。在BeanDefinitionReader#loadBeanDefinitions已经对接口创建过BeanDefinition了(当一个接口有多个实现类时,后扫描的会覆盖先扫描的)
        if ("".equals(autowiredBeanName)) {
            // 除了simpleName,通过class拿到的都是全类名
            // 前面初始化时已经用className向容器中注入过wrapper了
            autowiredBeanName = field.getType().getName();
        }

        field.setAccessible(true);

        try {
            // 因为要给当前Bean注入时,可能要注入的Bean还没初始化,因此就暂时不给这个字段注入
            // 但是当正式使用时还会getBean一次,这时所有bean都初始化完成了,就可以注入了
            if(this.factoryBeanInstanceCache.get(autowiredBeanName) == null){ continue; }

            // 获取具体Bean实例:这里是在通用IOC容器中获取,因为可能有多例情况
            field.set(instance, this.factoryBeanInstanceCache.get(autowiredBeanName).getWrappedInstance());
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }
    }
}

getBeanDefinitionNames()

返回所有的 beanName

public String[] getBeanDefinitionNames() {
    return this.beanDefinitionMap.keySet().toArray(new String[this.beanDefinitionMap.size()]);
}

getConfig()

获取配置文件的内容,实际上调用到 MYBeanDefinition#getConfig 方法

public Properties getConfig() {
    return this.reader.getConfig();
}

4.MYApplicationContextAware

通过解耦方式获得IOC容器的顶层设计。后面将通过一个监听器去扫描所有的类,只要实现了此接口,将自动调用setApplicationContext()方法,从而将IOC容器注入到目标类中。

public interface MYApplicationContextAware {

    void setApplicationContext(MYApplicationContext applicationContext);
}

结果演示

IOC和DI模块到此就写完了,下面我们写个测试类来看看能否运行。我们首先在 MyAction 中增加一个 test 方法

public void test(String name) {
        System.out.println(queryService.query(name));
    }

然后再 test 包下新建一个 Test 类

public class Test {

    public static void main(String[] args) {
    	// 初始化 IOC 容器
        MYApplicationContext context = new MYApplicationContext("classpath:application.properties");
        try {
        	// 测试 IOC 
            Object bean = context.getBean("myAction");
            System.out.println(bean);
            // 测试 DI,即 myAction 对象中是否成功注入 QueryService 对象
            MyAction myAction = (MyAction)bean;
            myAction.test("張三");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

结果如下:

在这里插入图片描述
可以看到 IOC 容器中已经有了 myAction 对象,并且也成功注入 QueryService!

完整代码我放到 GitHub 上了,可以点击这里跳转…

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A minor

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值