Featured image of post Java工程师 SpringIoC编码实现II

Java工程师 SpringIoC编码实现II

🌏Java工程师 SpringIoC原理实现 🎯 这篇文章用于记录 对SpringIoC的原理实现实践 包括 1️⃣原理分析 分析在使用Spring框架的时候 IoC是如何为我们工作的 分析IoC的实现原理 2️⃣流程分析 分析整个IoC容器的工作流程 3️⃣过程实现 编码模拟实现IoC容器

🎄实现剩下的需要的注解

SpringIoC原理实现I中 已经实现了两个注解 分别是 @ComponentScan@Scope 接着SpringIoC原理实现I现在来实现剩下的需要的注解

📑 @Component用于标识一个类是一个Bean

当IoC容器扫描包的时候发现一个Class对象被@Component注解修饰 则应该将其创建并放入singletonObjects容器进行管理

package bbm.com.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
@author Liu Xianmeng
@createTime 2023/9/14 23:33
@instruction @Component注解用于标识一个Java类是一个Bean 标识后 它将被IoC容器管理
*/

@SuppressWarnings({"all"})
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface Component {
    String name() default ""; // 指定bean的名字
}

📑 @Autowired 标识一个属性应该被注入

package bbm.com.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
@author Liu Xianmeng
@createTime 2023/9/14 23:37
@instruction @Autowired修饰Bean中的变量域 如果类中的某变量被@Autowired修饰
             这个变量对应的类对象就应该被注入到这个类中
             所谓的注入 其实就是将singletonObjects中此变量的类对象的引用赋值给这个变量
*/

@SuppressWarnings({"all"})
@Target(ElementType.FIELD) // 用于修饰变量域
@Retention(RetentionPolicy.RUNTIME)
public @interface Autowired {
    String name() default ""; // 指定注入的变量的名字
}

🎄创建要被IoC管理的Beans

接下来创建要被IoC管理的Beans 事实上 IoC容器的实现现在已经可以开始了 因为实现的逻辑是固定的 只不过先创建一些Beans 在实现的时候会更加清晰 逻辑步骤能对应到相应的Bean

对应于JavaEE开发的后端模式 Bean包括pojo类、Dao类(Data Access Object)、Service类、Controller类 下面一一进行创建

📑Duck鸭子pojo类 pojo类

package bbm.com.component;

/**
@author Liu Xianmeng
@createTime 2023/9/14 23:55
@instruction 鸭子pojo类 pojo类不作为组件进行管理
*/

public class Duck {
    String name; // 鸭子的名字
    int age; // 鸭子的年龄
    public Duck() {
    }
    public Duck(String name, int age) {
        this.name = name;
        this.age = age;
    }
    public String getName() {
        return name;
    }
    public int getAge() {
        return age;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void setAge(int age) {
        this.age = age;
    }
    @Override
    public String toString() {
        return "Duck{" +
            "name='" + name + '\'' +
            ", age=" + age +
            '}';
    }
}

📑DuckDao

package bbm.com.component;

import bbm.com.annotation.Component;

import java.util.concurrent.ConcurrentHashMap;

/**
@author Liu Xianmeng
@createTime 2023/9/15 0:03
@instruction DuckDao将作为一个组件被放入IoC容器 名字为duckDao
*/

@Component(name = "duckDao")
@Scope("singleton") // 指定为单例对象
public class DuckDao {

    // 用一个ConcurrentHashMap模拟数据库的数据
    public static ConcurrentHashMap<String, Duck> data;

    // 构造器初始化数据
    public DuckDao() {
        data = new ConcurrentHashMap<>();
        data.put("littleYellow", new Duck("littleYellow", 2));
        data.put("diandian", new Duck("diandian", 1));
        data.put("littleBlack", new Duck("littleBlack", 3));
    }

    // 通过鸭子的名字获取鸭子对象并返回
    public Duck getOneByName(String duckName) {
        return data.get(duckName);
    }
}

📑DuckService

package bbm.com.component;

/**
@author Liu Xianmeng
@createTime 2023/9/15 0:13
@instruction DuckService将作为一个组件被放入IoC容器 名字为duckService
*/

import bbm.com.annotation.Autowired;
import bbm.com.annotation.Component;

@Component(name = "duckService")
@Scope("singleton") // 指定为单例对象
public class DuckService {
    
    // 注意使用🎯private进行修饰
    @Autowired
    private DuckDao duckDao; // 注入容器中此对象
    
    // 通过鸭子的名字获取鸭子对象并返回
    public Duck getOneByName(String duckName) {
        // 使用注入的duckDao属性
        return duckDao.getOneByName(duckName);
    }
}

📑DuckController

package bbm.com.component;

import bbm.com.annotation.Autowired;
import bbm.com.annotation.Component;

/**
@author Liu Xianmeng
@createTime 2023/9/15 0:17
@instruction duckController将作为一个组件被放入IoC容器 名字为duckController
*/

@Component(name = "duckController")
@Scope("singleton") // 指定为单例对象
public class DuckController {

    // 注意使用🎯private进行修饰
    @Autowired
    private DuckService duckService;

    // 通过鸭子的名字获取鸭子对象并返回
    public Duck getOneByName(String duckName) {
        // 使用注入的duckService属性
        return duckService.getOneByName(duckName);
    }
}

到了这里 整个IoC实现的目录框架就搭建好了 爆红是因为 BigBigMengAnnotationApplicationContext IoC容器类还没有实现

🎄实现IoC容器类

🎯接下来就是重头戏了 来实现IoC容器类

实现之前 先来回顾一下BigBigMengAnnotationApplicationContext类的结构 具体的注释可见上一篇:

目前还只是伪代码

/**
@author Liu Xianmeng
@createTime 2023/9/14 17:27
@instruction IoC容器类 这个类就是要模拟实现额IoC容器类
            下面将BigBigMengAnnotationApplicationContext的类名简称为 "IoC容器"
*/

public class BigBigMengAnnotationApplicationContext { // IoC容器
    private Class configClass; // 配置类Class对象
    private ConcurrentHashMap<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(); // Bean的定义信息
    private ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>(); // 单例池对象
    public BigBigMengAnnotationApplicationContext(Class configClass) {
        [1]beanDefinitionsByScan(configClass);
        [2]initialSingletonObjects();
    }
    public void beanDefinitionsByScan(Class configClass) {
        // 通过配置configClass对象获取扫描包并执行扫描
    }
    public void initialSingletonObjects(){}
    private Object createBean(BeanDefinition beanDefinition) {}
    public Object getBean(String name) {}
}

🍭梳理实现思路

构造函数中初始化IoC容器 先完成beanDefinitionMap的初始化 这个任务是beanDefinitionsByScan函数完成的 然后根据beanDefinitionMap完成singletonObjects的初始化 过程中会使用到getBeancreateBean两个辅助函数

使用自顶向下的顺序来实现:

[1] BigBigMengAnnotationApplicationContext(Class configClass)

[2] beanDefinitionsByScan(Class configClass)

[3] initialSingletonObjects()

[4] getBean(String name)createBean(BeanDefinition beanDefinition)

接下来就一步步进行实现

📑实现BigBigMengAnnotationApplicationContext构造函数

/**
 * IoC容器的构造器
 *
 * 构造器中完成两个任务
 * (1)初始化Beans的定义信息beanDefinitionMap
 * (2)遍历Beans的定义信息beanDefinitionMap初始化单例池singletonObjects
 *
 * @param configClass 在创建一个IoC容器对象的时候 需要传入配置类的Class对象
 *                    配置类的Class对象 上文已经提到 IoC容器
 */
public BigBigMengAnnotationApplicationContext(Class configClass) {
    this.configClass = configClass;
    beanDefinitionsByScan(this.configClass);
    initialSingletonObjects();
}

📑实现beanDefinitionsByScan函数

/**
 * beanDefinitionsByScan方法完成对指定包的扫描 并将Bean信息封装到beanDefinitionMap
 */
public void beanDefinitionsByScan(Class configClass) {

    /*********** 获取要扫描的包 ***********/

    // 首先先获取配置类的注解
    ComponentScan scanAnnotation =
        (ComponentScan) this.configClass.getDeclaredAnnotation(ComponentScan.class);
    // 然后通过componentScan的value值获取要扫描的包
    // value()是ComponentScan注解的一个属性 带括号显得有些别扭 但这是注解的规范
    String packageName = scanAnnotation.value();
    System.out.println("C IoC M beanDefinitionsByScan() -> IoC要扫描的包 = " + packageName);

    /*********** 根据类加载器获取要扫描的资源 ***********/

    // 获取类加载器
    ClassLoader classLoader = BigBigMengAnnotationApplicationContext.class.getClassLoader();
    // 通过类的加载器获取到要扫描的包的资源
    String path = packageName.replace(".", "/");
    URL resource = classLoader.getResource(path);
    System.out.println("C IoC M beanDefinitionsByScan() -> 包的完整路径 =" + resource);

    /*********** 遍历指定包下的所有文件 找出.class结尾的文件 将其信息封装到beanDefinitionMap ***********/

    // 获取指定包所在的包文件夹
    File file = new File(resource.getFile());
    // 遍历文件夹下所有的文件
    if(file.isDirectory()) {
        File[] files = file.listFiles();
        for(File subFile : files) {
            // 获取此subFile的全路径
            String absolutePath = subFile.getAbsolutePath();
            // 只处理.class文件
            if(absolutePath.endsWith(".class")) {
                // 获取类名 注意这里的类并不一定需要注入 还需要判断它是否被@Component、@Service、@Mapper等注解修饰
                String className = absolutePath.substring(absolutePath.lastIndexOf("\\") + 1, absolutePath.indexOf(".class"));
                // 获取全类名
                String classFullName = packageName + "." + className;

                try{
                    // 获取classFullName对应的class对象
                    Class<?> clazz = classLoader.loadClass(classFullName);
                    // 判断该class对象是否被@Component、@Service、@Mapper等注解修饰
                    // 我在模拟实现的过程中只定义了一个标识Bean的注解 即@Component
                    if(clazz.isAnnotationPresent(Component.class)) {
                        System.out.println("C IoC M beanDefinitionsByScan() -> " + classFullName + "是一个Spring bean, 将其注入IoC容器进行管理");

                        /*********** 将其信息封装到beanDefinitionMap ***********/

                        // 获取该class对象的@Component
                        Component componentAnnotation = clazz.getDeclaredAnnotation(Component.class);
                        // 获取该bean的名字
                        String beanName = componentAnnotation.name();
                        if ("".equals(beanName)) { // 如果没有配置名字
                            // 则将该类的类名首字母小写作为beanName
                            beanName = StringUtils.uncapitalize(className);
                        }
                        // 将Bean的信息封装到BeanDefinition对象
                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setClazz(clazz);
                        // 获取@Scope并设置beanDefinition对象的scope信息
                        if(clazz.isAnnotationPresent(Scope.class)) {
                            Scope scope = clazz.getDeclaredAnnotation(Scope.class);
                            beanDefinition.setScope(scope.value());
                        } else {
                            beanDefinition.setScope("singleton");
                        }
                        // 将beanDefinition放入beanDefinitionMap
                        beanDefinitionMap.put(beanName, beanDefinition);
                    } else {
                        System.out.println("C IoC M beanDefinitionsByScan() -> " + classFullName + "不是一个Spring bean 不需要注入IoC容器进行管理");
                    }
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

📑实现initialSingletonObjects函数

/**
 * initialSingletonObjects方法beanDefinitionMap单例池的初始化
 */
public void initialSingletonObjects(){
    // 获取所有beans的定义信息keys
    Enumeration<String> keys = beanDefinitionMap.keys();
    while (keys.hasMoreElements()) {
        // 得到beanName
        String beanName = keys.nextElement();
        // 通过beanName 得到对应的beanDefinition对象
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        // 判断该bean是singleton还是prototype 只初始化创建单例对象放入singletonObjects单例池
        if ("singleton".equalsIgnoreCase(beanDefinition.getScope())) {
            //将该bean实例放入到singletonObjects 集合
            Object bean = createBean(beanDefinition);
            singletonObjects.put(beanName, bean);
        }
    }
}

📑实现createBean函数

说明 getBeancreateBean函数会项目引用 所以先实现哪个都行

/**
 * createBean方法完成Bean对象的创建
 *
 * @param beanName          传入Bean的名字(目前用不到这个看参数 这个参数会在实现AOP的时候用到)
 * @param beanDefinition    传入Bean的定义信息
 * @return                  返回创建的Bean对象
 */
private Object createBean(BeanDefinition beanDefinition) {
    // 获取要创建的bean的Class对象 使用反射创建该对象
    Class clazz = beanDefinition.getClazz();
    try {
        // 获取构造方法并创建对象
        Constructor constructor = clazz.getDeclaredConstructor();
        Object bean = constructor.newInstance();

        // 接下来创建其属性
        for(Field field : clazz.getDeclaredFields()) {
            // 只有这个属性被@Autowired注解修饰 才需要被注入
            if(field.isAnnotationPresent(Autowired.class)){
                /* 按照名字进行组装 */
                String name = field.getName();

                // 通过getBean方法来获取要组装对象
                Object fieldBean = getBean(name);

                // 进行组装
                field.setAccessible(true); // 因为属性都是修饰的所以需要设置可访问
                field.set(bean, fieldBean);
            }
        }

        // 所有的属性注入完成后返回当前的Bean对象
        return bean;

    }catch (Exception e) {
        System.out.println("不存在该构造方法");
    }

    // 创建失败则返回null
    return null;
}

📑实现getBean函数

/**
 * getBean 根据传入的Bean的name返回singletonObjects中的bean对象
 *
 * 这个方法会在给Bean注入属性对象的时候用到 注入属性对象的时候
 * 先检查singletonObjects中是否已经存在要注入的Bean 如果已经存在则直接取出用
 * 如果还未创建 则创建一个属性对象的Bean放入singletonObjects并将其引用赋值给要注入的属性
 *
 * @param   name
 * @return  返回singletonObjects中的bean对象
 */
public Object getBean(String name) {
    // 先判断该name是否存在于beanDefinitionMap的KEYS当中
    if(beanDefinitionMap.containsKey(name)) {
        // 存在则获取该bean的定义信息
        BeanDefinition beanDefinition = beanDefinitionMap.get(name);
        // 然后判断是该bean的Scope是单例还是多例
        if("singleton".equals(beanDefinition.getScope())) {
            // 如果是单例 则直接从单例池获取
            Object o = singletonObjects.get(name);
            // 如果获取不到 则先创建
            if(o == null) {
                o = createBean(beanDefinition);
                // 创建后放入单例池并返回
                singletonObjects.put(name, o);
                return o;
            }
            // 不为空则直接返回
            return o;
        } else {
            // 如果是多例 则每次都使用createBean返回一个新的Bean返回
            return createBean(beanDefinition);
        }
    } else {
        // 不存在则抛出空指针异常
        throw new NullPointerException("该Bean不存在");
    }
}

📑最终的包结构

🎄测试运行

📑编写Main函数进行测试

package bbm.com;

import bbm.com.component.Duck;
import bbm.com.component.DuckController;
import bbm.com.ioc.BigBigMengAnnotationApplicationContext;
import bbm.com.ioc.BigBigMengConfig;

/**
@author Liu Xianmeng
@createTime 2023/9/15 10:54
@instruction
*/

@SuppressWarnings({"all"})
public class Main {
    public static void main(String[] args) {
        // 获取IoC容器
        BigBigMengAnnotationApplicationContext ioc = new BigBigMengAnnotationApplicationContext(BigBigMengConfig.class);
        // 从IoC容器中获取DuckController对象
        DuckController duckController = (DuckController) ioc.getBean("duckController");
        // 调用duckControllerd的getOneByName方法
        Duck littleBlack = duckController.getOneByName("littleBlack");
        // 打印获取到的Duck对象
        System.out.println(littleBlack);
    }
}

🌈运行结果

🎄总结

到此为止 一个基于注解的简单的IoC容器的模拟实现就完成啦 非常nice✨