Mini-Spring之创建Bean

引言

我们上一篇文章中,完成了BeanDefinition的创建,这个类中保存了我们需要实例化bean的信息,这篇文章就是模拟一个创建bean的底层实现,让我们的mini-spring有一个完整的注入功能。

ok,我们开始吧!😁

本文所有的代码都在这个项目工程里,大家需要的时候可以随时取用。传送门

创建Bean

目前为止,我们已经完成从@ComponentScan注解定义的value中取到扫描包的位置,然后根据这个位置取出所有包含@Component注解的类,并将其信息存储到BeanDefinition中去。

接下来,我们要做的就非常简单了,那就是创建bean。我们需要在MiniSpringApplicationContext生产BeanDefinition之后直接创建bean,然后保存以供后续使用,具体代码如下:

package com.zhu.spring;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class MiniSpringApplicationContext {

    private Class configClass;

    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();

    public MiniSpringApplicationContext(Class configClass) {
        this.configClass = configClass;

        //scan the class decorate by @ComponentScan
        if(configClass.isAnnotationPresent(ComponentScan.class)){

            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            //scan path, eg: com.zhu.service
            String path = componentScanAnnotation.value();
            //  com.zhu.service  ----> com/zhu/service
            path = path.replace(".", "/");

            //find absolute path from MiniSpringApplicationContext context
            ClassLoader classLoader = MiniSpringApplicationContext.class.getClassLoader();
            // get url , /Users/knight/IdeaProjects/mini-spring/out/production/mini-spring/com/zhu/service
            URL resource = classLoader.getResource(path);

            File file = new File(resource.getFile());
            if(file.isDirectory()){
                File[] files = file.listFiles();
                for (File f : files) {
                    String absolutePath = f.getAbsolutePath();
                    if(absolutePath.endsWith(".class")){
                        //real load class


                            // /Users/knight/IdeaProjects/mini-spring/out/production/mini-spring/com/zhu/service/UserService ---> com.zhu.service.UserService

                            //com/zhu/service/UserService
                            String className = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"));

                            //com.zhu.service.UserService
                            className = className.replace("/", ".");

                        try {
                            Class<?> clazz = classLoader.loadClass(className);

                            if(clazz.isAnnotationPresent(Component.class)){

                                Component componentAnnotation = clazz.getAnnotation(Component.class);
                                String beanName = componentAnnotation.value();

                                //generate BeanDefinition
                                BeanDefinition beanDefinition = new BeanDefinition();
                                beanDefinition.setType(clazz);
                                if (clazz.isAnnotationPresent(Scope.class)) {
                                    Scope scopeAnnotation = clazz.getAnnotation(Scope.class);
                                    beanDefinition.setScope(scopeAnnotation.value());
                                }else{
                                    beanDefinition.setScope("singleton");
                                }
                                beanDefinitionMap.put(beanName, beanDefinition);
                            }

                        } catch (ClassNotFoundException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

        }

        //create bean  生成beandefinition之后,完成bean的初始化
        for (String beanName : beanDefinitionMap.keySet()) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
            if(beanDefinition.getScope().equals("singleton")){
                  //单例的时候创建,然后放到单例池,即一个map中去
                Object bean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }

    }

      //具体初始化的代码,很简单利用Java的反射机制
    private Object createBean(String beanName, BeanDefinition beanDefinition){
        Class clazz = beanDefinition.getType();
        Object o = null;
        try {
            o = clazz.getConstructor().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return o;
    }

    public Object getBean(String beanName){
                //从beandefinitionMap中获取对应类的信息
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

        if(beanDefinition == null){
            throw new RuntimeException("class not found with bean name:"+beanName);
        }

        String scope = beanDefinition.getScope();
        if("singleton".equals(scope)){
              //单例的话先判断单例池中有没有,没有的话需要创建,有的话直接返回
            Object bean = singletonObjects.get(beanName);
            if(bean == null){
                Object createdBean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, createdBean);
                return createdBean;
            }
            return bean;
        }else{
              //多例的话每次直接创建
            return createBean(beanName, beanDefinition);
        }
    }
}

自此,我们基本模拟完成了spring创建bean的一个过程,简单实现了一个单例或者多例的功能。

ok,我们可以验证一下,修改UserService里面@Scope注解的值,查看生产的对象的地址是不是一致,即可验证单例功能是否生效,具体代码如下:

@Component("userService")
@Scope("prototype")
public class UserService {
}

package com.zhu.service;

import com.zhu.spring.MiniSpringApplicationContext;


public class Test {
    public static void main(String[] args) {
        MiniSpringApplicationContext miniSpringApplicationContext = new MiniSpringApplicationContext(AppConfig.class);
                //经过测试发现地址都不一样,符合多例的初衷。
        System.out.println(miniSpringApplicationContext.getBean("userService"));
        System.out.println(miniSpringApplicationContext.getBean("userService"));
        System.out.println(miniSpringApplicationContext.getBean("userService"));
        System.out.println(miniSpringApplicationContext.getBean("userService"));
        System.out.println(miniSpringApplicationContext.getBean("userService"));
    }
}

总结:

目前为止,我们实现了简单类(就只有一个类,不依赖其他类)的注入。但是,这在我们实际生产中很少有这种简单的bean,一般都是需要依赖其他的bean。所以,我们下一步的目标就是完成依赖注入的流程。