手写Spring框架第三天

本文详细描述了如何按照Spring框架的实现方式,从BeanDefinitionMap中加载Bean到单例池,并创建自定义注解进行依赖注入,处理单例与非单例对象及循环引用问题。

目录

前言

从BeanDefinitionMap中加载bean到单例池

创建自定义注解

创建initSingletonObjects方法

完整ApplicationContext代码

测试


前言

之前实现了封装Bean信息,接下来实现依赖注入,同时在第一天时,我是将Bean创建好后直接放入SingletonObjects中,但在Spring的实现中,是将所有的Bean读取到BeanDefinitionMap中后再从Map集合中加载到SingletonObjects中的。

今天对之前的代码进行修改,按Spring的实现方式,进行加载Bean,同时实现依赖注入。

从BeanDefinitionMap中加载bean到单例池

在init方法中,只初始化了BeanDefinitionMap集合。接下来,我们要实现从Map集合中获取单例的bean加载到单例池,添加一个新的初始化单例池的方法

创建自定义注解

要实现依赖注入,那么一定要存在于一个注解,在这里我只实现了按bean名称注入,因此我们采用Spring中的@Resource的依赖注入方式 

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyResource {
    String value() default "";
}

创建initSingletonObjects方法

初始化SingletonObjects方法应该在初始化完BeanDefinitionMap之后执行,同时我们应该注意单例对象的注入以及非单例对象的注入方式不同。

    private void initSingletonObjects() {
        //从BeanDefinitionMap当中获取信息并加载单例对象到单例池中
        Set<Map.Entry<String, BeanDefinition>> entries = this.beanDefinitionMap.entrySet();
        for (Map.Entry<String, BeanDefinition> entry : entries) {
            createBean(entry);
        }
    }

遍历BeanDefinitionMap集合获取单个元素后执行createBean()方法,主要是去实现Bean对象的创建以及依赖注入问题,同时我们需要规避循环引用的问题,具体实现如下

    /**
     * 实现依赖注入
     *
     * @param entry
     */
    private void createBean(Map.Entry<String, BeanDefinition> entry) {
        //获取到类
        Object o = null;
        String beanName = entry.getKey();
        if (cacheSingleton.containsKey(beanName)) {
            o = cacheSingleton.get(beanName);
        } else {
            Class<?> clazz = entry.getValue().getClazz();
            try {
                o = clazz.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        //获取该类中的字段
        Field[] declaredFields = o.getClass().getDeclaredFields();
        for (Field declaredField : declaredFields) {
            //判断字段上是否存在@MyResource注解
            if (declaredField.isAnnotationPresent(MyResource.class)) {
                MyResource annotation = declaredField.getAnnotation(MyResource.class);
                //需要被注入的名称
                String setBeanName = annotation.value();
                if ("".equals(setBeanName)) {
                    //如果MyResource没有指定,那么以字段名为注入名
                    setBeanName = declaredField.getName();
                }
                if (!beanDefinitionMap.containsKey(setBeanName)) {
                    throw new RuntimeException("不存在该bean名称:" + setBeanName);
                }
                //开放权限
                declaredField.setAccessible(true);
                BeanDefinition beanDefinition = beanDefinitionMap.get(setBeanName);
                try {
                    if (beanDefinition.getType().equals("Singleton")) {
                        //如果是单例bean
                        if (!cacheSingleton.containsKey(setBeanName)) {
                            //如果缓存单例池不存在,那么去单例池寻找
                            if (singletonObjects.containsKey(setBeanName)) {
                                //单例池存在的情况
                                Object o1 = singletonObjects.get(setBeanName);
                                declaredField.set(o, o1);
                            } else {
                                //单例池中也不存在
                                Object o1 = beanDefinition.getClazz().newInstance();
                                //存放在缓存单例池中
                                cacheSingleton.put(setBeanName, o1);
                                declaredField.set(o, o1);
                            }
                        } else {
                            //如果已经创建过了,但未完成创建
                            Object o1 = cacheSingleton.get(setBeanName);
                            declaredField.set(o, o1);
                        }
                    } else {
                        Object o1 = beanDefinition.getClazz().newInstance();
                        declaredField.set(o, o1);
                    }
                } catch (IllegalAccessException | InstantiationException e) {
                    e.printStackTrace();
                }
            }
        }
        //到这里执行结束,清除缓存单例池信息
        cacheSingleton.remove(beanName);
        singletonObjects.put(beanName, o);
    }

以上代码实现了将单例Bean加载到单例池。对于非单例的Bean不需要添加到单例池中,其次加入到单例池的对象是已经注入好属性的对象,在createBean方法中也解决了循环依赖的问题,因为我又创建了一个cacheObjects属性用来保存没有创建完成的bean,在Spring中采用的是三级缓存,但是我只采用了一个。

完整ApplicationContext代码

public class SpringApplication2 {
    //配置类属性
    private Class<?> configClass;
    //单例池
    private ConcurrentHashMap<String, Object> singletonObjects;
    //BeanDefinitionMap
    private Map<String, BeanDefinition> beanDefinitionMap;

    private Map<String, Object> cacheSingleton;

    //每创建一个容器都会有各自的单例池
    {
        singletonObjects = new ConcurrentHashMap<>(256);
        beanDefinitionMap = new ConcurrentHashMap<>(256);
        cacheSingleton = new ConcurrentHashMap<>(256);
    }

    public SpringApplication2(Class<?> configClass) throws Exception {
        this.configClass = configClass;
        this.init();
        this.initSingletonObjects();
    }

    private void init() throws Exception {
        //判断该类是否是配置类
        MyConfiguration configuration = configClass.getAnnotation(MyConfiguration.class);
        if (configuration == null) {
            //说明不是配置类
            throw new RuntimeException("该类不是配置类,目前只支持传入配置类");
        }
        //如果是配置类,那么获取需要扫描的类路径
        MyComponentScan scan = configClass.getAnnotation(MyComponentScan.class);
        if (scan == null) {
            throw new RuntimeException("不存在扫描注解");
        }
        String[] paths = scan.value();
        //遍历需要扫描的路径
        for (String path : paths) {
            path = URLDecoder.decode(path, "UTF-8");
            //path这里还有作用,因此不建议将path接收replace的结果
            // com/zmt
            String newPath = path.replace(".", "/");
            //获取根路径,并去除最前面的/
            String classPath = configClass.getResource("/").getPath().substring(1);
            classPath = URLDecoder.decode(classPath, "UTF-8");
            System.out.println("classPath:" + classPath);

            //获取被扫描包的绝对路径
            String absolutePath = configClass.getResource("/" + newPath).getPath();
            absolutePath = URLDecoder.decode(absolutePath, "UTF-8");
            System.out.println(absolutePath);

            //遍历绝对路径的子包,寻找.class结尾的文件
            File rootFile = new File(absolutePath);
            if (rootFile.isFile()) {
                throw new RuntimeException("不支持扫描具体类");
            }
            //说明指定路径是一个文件夹
            //获取所有的类名称
            List<String> listClass = dirFile(rootFile);
            for (String absoluteClassPath : listClass) {
                //完整类名,通过Class.forName()创建出该类
                String fullClassName = absoluteClassPath.replace(classPath, "").replace("/", ".");
                //加载出该类
                ClassLoader classLoader = configClass.getClassLoader();
                Class<?> aClass = classLoader.loadClass(fullClassName);
                MyComponent myComponent = aClass.getAnnotation(MyComponent.class);
                if (myComponent == null) {
                    //该类不应该被加载
                    continue;
                }

                //获取Bean名称
                String beanName = getBeanName(fullClassName, aClass, myComponent);

                //判断是否添加了@MyScope注解,通知判断该注解的值
                BeanDefinition beanDefinition = new BeanDefinition();
                beanDefinition.setType("Singleton");
                if (aClass.isAnnotationPresent(MyScope.class)) {
                    String value = aClass.getAnnotation(MyScope.class).value();
                    if (!"Singleton".equals(value) && "Prototype".equals(value)) {
                        //说明是不是非单例的对象
                        beanDefinition.setType("Prototype");
                    }
                }
                beanDefinition.setClazz(aClass);
                beanDefinitionMap.put(beanName, beanDefinition);

            }
        }
    }

    private void initSingletonObjects() {
        //从BeanDefinitionMap当中获取信息并加载单例对象到单例池中
        Set<Map.Entry<String, BeanDefinition>> entries = this.beanDefinitionMap.entrySet();
        for (Map.Entry<String, BeanDefinition> entry : entries) {
            createBean(entry);
        }
    }

    /**
     * 实现依赖注入
     *
     * @param entry
     */
    private void createBean(Map.Entry<String, BeanDefinition> entry) {
        //获取到类
        Object o = null;
        String beanName = entry.getKey();
        if (cacheSingleton.containsKey(beanName)) {
            o = cacheSingleton.get(beanName);
        } else {
            Class<?> clazz = entry.getValue().getClazz();
            try {
                o = clazz.newInstance();
            } catch (InstantiationException | IllegalAccessException e) {
                e.printStackTrace();
            }
        }
        //获取该类中的字段
        Field[] declaredFields = o.getClass().getDeclaredFields();
        for (Field declaredField : declaredFields) {
            //判断字段上是否存在@MyResource注解
            if (declaredField.isAnnotationPresent(MyResource.class)) {
                MyResource annotation = declaredField.getAnnotation(MyResource.class);
                //需要被注入的名称
                String setBeanName = annotation.value();
                if ("".equals(setBeanName)) {
                    //如果MyResource没有指定,那么以字段名为注入名
                    setBeanName = declaredField.getName();
                }
                if (!beanDefinitionMap.containsKey(setBeanName)) {
                    throw new RuntimeException("不存在该bean名称:" + setBeanName);
                }
                //开放权限
                declaredField.setAccessible(true);
                BeanDefinition beanDefinition = beanDefinitionMap.get(setBeanName);
                try {
                    if (beanDefinition.getType().equals("Singleton")) {
                        //如果是单例bean
                        if (!cacheSingleton.containsKey(setBeanName)) {
                            //如果缓存单例池不存在,那么去单例池寻找
                            if (singletonObjects.containsKey(setBeanName)) {
                                //单例池存在的情况
                                Object o1 = singletonObjects.get(setBeanName);
                                declaredField.set(o, o1);
                            } else {
                                //单例池中也不存在
                                Object o1 = beanDefinition.getClazz().newInstance();
                                //存放在缓存单例池中
                                cacheSingleton.put(setBeanName, o1);
                                declaredField.set(o, o1);
                            }
                        } else {
                            //如果已经创建过了,但未完成创建
                            Object o1 = cacheSingleton.get(setBeanName);
                            declaredField.set(o, o1);
                        }
                    } else {
                        Object o1 = beanDefinition.getClazz().newInstance();
                        declaredField.set(o, o1);
                    }
                } catch (IllegalAccessException | InstantiationException e) {
                    e.printStackTrace();
                }
            }
        }
        //到这里执行结束,清除缓存单例池信息
        cacheSingleton.remove(beanName);
        singletonObjects.put(beanName, o);
    }

    private String getBeanName(String fullClassName, Class<?> aClass, MyComponent myComponent) {
        //如果注解中没有没有给定值,那么采用类名首字母小写的方式
        String beanName = myComponent.value();
        if ("".equals(beanName)) {
            System.out.println(aClass.getName() + "没有指定bean名称");
            String className = fullClassName.substring(fullClassName.lastIndexOf(".") + 1);
            beanName = className.substring(0, 1).toLowerCase() + className.substring(1);
        }
        return beanName;
    }


    /**
     * 遍历文件夹,获取所有类绝对路径集合
     *
     * @param rootFile
     * @return
     */
    private List<String> dirFile(File rootFile) {
        List<String> fileList = new ArrayList<>();
        if (rootFile.isDirectory()) {
            File[] files = rootFile.listFiles();
            for (File file : files) {
                fileList.addAll(dirFile(file));
            }
        } else {
            //走到这说明已经不是文件夹了
            String filePath = rootFile.getPath();
            if (filePath.endsWith(".class")) {
                //类的绝对路径
                String classPath = filePath.replace("\\", "/").replace(".class", "");
                System.out.println(classPath);
                fileList.add(classPath);
            }
        }
        return fileList;
    }

    public Object getBean(String beanName) {
        Object o = this.singletonObjects.get(beanName);
        if (o == null) {
            throw new RuntimeException("不存在bean为:" + beanName);
        }
        return o;
    }

}

测试

public class SpringStart {
    public static void main(String[] args) throws Exception {
        SpringApplication2 springApplication = new SpringApplication2(SpringConfig.class);
        UserController userController = (UserController) springApplication.getBean("userController");
        UserServiceImpl userService = (UserServiceImpl) springApplication.getBean("userServiceImpl");
        System.out.println(userController.getUserDao() == userService.getUserDao());
    }
}

在上面测试代码中,只有UserDao上添加了@MyScope注解并值为Prototype,而Controller与Service类中均依赖了Dao,测试两个类中的Dao是否是同一个对象。其次,Controller与Service又互相引用对方,我们测试能否执行测试通过

 由此可以看出,实现了单例与非单例的加载也实现了依赖注入。并且循环引用的问题也可以得到解决。 

评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

zmbwcx2003

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

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

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

打赏作者

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

抵扣说明:

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

余额充值