自己实现spring框架核心原理

332 阅读6分钟

“我报名参加金石计划1期挑战——瓜分10万奖池,这是我的第1篇文章,点击查看活动详情

根据spring框架底层原理,自己动手写一个,这里实现的只是一些简单的功能。

spring创建bean的流程:

  • 1.加载配置类。
  • 2.根据配置类配置的扫描路径,扫描带有@Component 注解的类。
  • 3.将扫描到的类,定义为beanDefinition(封装有类的信息:属性,类型,是否是单例等等。)
  • 4.创建bean.

1.参考spring代码仿写

毕竟是模拟,我们先看spring是怎么写的。

public class Test {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);

        CompanyService companyService = (CompanyService)context.getBean("companyService");
        companyService.test();
    }
}

我们可以看到:

  • 1.创建了一个AnnotationConfigApplicationContext(conetxt) 对象,
  • 2.用的是有参构造方法。
  • 3.使用context调用getBean()方法,
  • 4.传了一个beanName,获取一个bean. 那么我们就可以仿着来
  • 1.创建一个类似AnnotationConfigApplicationContext的类。
  • 2.这个类里有:接收配置类的属性,一个有参方法,
  • 3.还需要一个getBean()方法,
public class StartApplicationContext{
    // 接收配置类的属性
    private Class configClass;

    public StarsApplicationContext(Class configClass) {
        this.configClass = configClass;
    }
    //getBean
    public Object  getBean(String name){
        return null;
    }
}

public class Test {

    public static void main(String[] args) {
        StartApplicationContext context = new StartApplicationContext(AppConfig.class);
        CompanyService companyService = (CompanyService) context.getBean("companyService");
    }
}

在spring框架中,在写配置类的时候要加一个@ComponentScan注解配置扫描路径,还需要注解@Component来判断一个类是否要spring来加载为bean。 创建一个@ComponentScan注解。

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {

    String value() default "";

}
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface Component {

    String value() default "";

}

2.扫描

梳理下spring加载配置类的大致流程:

  1. 扫描配置类
  • 1.判断配置类上是否加了@ComponentScan 注解
  • 2.获取配置类上配置的扫描路径,就是注解的value值。
  • 3.获取扫描路径下的类文件。
  • 4.加载类,判断类上面是否有@Component注解,等等。
  1. 创建单例bean。
public class StartApplicationContext {

    private Class configClass;
    
    public StartApplicationContext(Class configClass) {
        this.configClass = configClass;
        // 扫描 到 创建bean
        // 扫描的过程
        // 判断传进来得配置类,是否加了 @ComponentScan
        // 获取注解的值,定义的扫描包路径
        // 获取扫描包路径下的类
        // 判断类上面是否有 Component 注解。
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String pathValue = componentScanAnnotation.value();
            String replace = pathValue.replace(".", "/");
            System.out.println(replace);
            ClassLoader classLoader = StartApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(replace);
            File file = new File(resource.getFile());
            boolean a = false;
            if(file.isDirectory()){
                for (File f : file.listFiles()){
                    String path = f.getAbsolutePath();
                    System.out.println(path);
                    path = path.substring(path.indexOf("com"), path.indexOf(".class"));
                    path = path.replace("/",".");
                    System.out.println(path);

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

                        if(loadClass.isAnnotationPresent(Component.class)){
                            if(loadClass.isAnnotationPresent(Scope.class)){

                            }else {

                                //单例
                            }
                        }

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

    public Object getBean(String name) {
        return null;
    }

上面这个流程(如上面的示例代码) 是在 public StartApplicationContext(Class configClass) 这个方法中,那么还有一个getBean(String name) 方法,如果只是按照上面这个流程,那么getBean(String name )的时候还要再重复去判断这些类的信息。

所以这里会引入bean定义的概念:就是在扫描的时候获取的这些类的信息,封装成一个对象,存到缓存中。

类的信息包括:是否是单里,是否是懒加载等等。

public class BeanDefinition {

    private Class type;
    private String scope;
    private boolean isLazy;

    public Class getType() {
        return type;
    }

    public void setType(Class type) {
        this.type = type;
    }

    public String getScope() {
        return scope;
    }

    public void setScope(String scope) {
        this.scope = scope;
    }

    public boolean isLazy() {
        return isLazy;
    }

    public void setLazy(boolean lazy) {
        isLazy = lazy;
    }
}
public class StartApplicationContext {

    private Class configClass;
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

    public StartApplicationContext(Class configClass) {
        this.configClass = configClass;
        scan(configClass);

    }

    private void scan(Class configClass) {
        // 扫描 到 创建bean
        // 扫描的过程
        // 判断传进来得配置类,是否加了 @ComponentScan
        // 获取注解的值,定义的扫描包路径
        // 获取扫描包路径下的类
        // 判断类上面是否有 Component 注解。
        // 封装bean定义存到缓存中
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String pathValue = componentScanAnnotation.value();
            String replace = pathValue.replace(".", "/");
            System.out.println(replace);
            ClassLoader classLoader = StartApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(replace);
            File file = new File(resource.getFile());
            boolean a = false;
            if (file.isDirectory()) {
                for (File f : file.listFiles()) {
                    String path = f.getAbsolutePath();
                    System.out.println(path);
                    path = path.substring(path.indexOf("com"), path.indexOf(".class"));
                    path = path.replace("/", ".");
                    System.out.println(path);

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

                        if (loadClass.isAnnotationPresent(Component.class)) {
                            Component annotation = loadClass.getAnnotation(Component.class);
                            String beanName = annotation.value();
                            
                     //如果没有定义bean的名字,默认生成一个bean的名字。
                            String beanName = annotation.value();
                            if("".equals(beanName)){
                                beanName = Introspector.decapitalize(loadClass.getSimpleName());

                            }
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setType(loadClass);

                            if (loadClass.isAnnotationPresent(Scope.class)) {
                                Scope scopeAnnotation = loadClass.getAnnotation(Scope.class);
                                String value = scopeAnnotation.value();
                                beanDefinition.setScope(value);
                            } else {
                                beanDefinition.setScope("singleton");
                            }
                            beanDefinitionMap.put(beanName, beanDefinition);
                        }

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

    public Object getBean(String name) {


        return null;
    }

}

这个是引入bean定义后修改的。到现在,我已经通过扫描拿到了相关bean的信息。

3.创建bean

bean 的相关信息已经缓存起来,接下来就该创建bean了。

  1. 从缓存中拿到bean信息,
  2. 判断这个bean是否是单例,
  3. 如果是单例bean则创建bean,然后存入单例缓存中。
  4. 如果不是单例则每次获取单例的时候都要创建。

如下示例;

package com.star.spring;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

public class StartApplicationContext {

    private Class configClass;
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    private Map<String, Object> singletonObjectMap = new HashMap<>();

    public StartApplicationContext(Class configClass) {
        this.configClass = configClass;
        //扫描
        scan(configClass);
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String key = entry.getKey();
            BeanDefinition value = entry.getValue();
            if ("singleton".equals(value.getScope())) {
                Object bean = createBean(key, value);
                singletonObjectMap.put(key, bean);
            }
        }
    }

    public Object createBean(String beanName, BeanDefinition beanDefinition) {
        Class aClass = beanDefinition.getType();
        Object object = null;
        try {
            object =  aClass.getConstructor().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return object;
    }


    public Object getBean(String name) {
        if (!beanDefinitionMap.containsKey(name)) {
            throw new NullPointerException();
        }

        BeanDefinition beanDefinition = beanDefinitionMap.get(name);
        String scope = beanDefinition.getScope();
        if ("singleton".equals(scope)) {
            Object singletonObject = singletonObjectMap.get(name);
            return singletonObject;
        } else {
            Object bean = createBean(name, beanDefinition);
            return bean;
        }
    }


    private void scan(Class configClass) {
        // 扫描 到 创建bean
        // 扫描的过程
        // 判断传进来得配置类,是否加了 @ComponentScan
        // 获取注解的值,定义的扫描包路径
        // 获取扫描包路径下的类
        // 判断类上面是否有 Component 注解。
        // 封装bean定义存到缓存中
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String pathValue = componentScanAnnotation.value();
            String replace = pathValue.replace(".", "/");
            System.out.println(replace);
            ClassLoader classLoader = StartApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(replace);
            File file = new File(resource.getFile());
            boolean a = false;
            if (file.isDirectory()) {
                for (File f : file.listFiles()) {
                    String path = f.getAbsolutePath();
                    System.out.println(path);
                    path = path.substring(path.indexOf("com"), path.indexOf(".class"));
                    path = path.replace("/", ".");
                    System.out.println(path);

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

                        if (loadClass.isAnnotationPresent(Component.class)) {
                            Component annotation = loadClass.getAnnotation(Component.class);
                            String beanName = annotation.value();
                            if("".equals(beanName)){
                                beanName = Introspector.decapitalize(loadClass.getSimpleName());
                                
                            }
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setType(loadClass);


                            if (loadClass.isAnnotationPresent(Scope.class)) {
                                Scope scopeAnnotation = loadClass.getAnnotation(Scope.class);
                                String value = scopeAnnotation.value();
                                beanDefinition.setScope(value);
                            } else {
                                beanDefinition.setScope("singleton");
                            }
                            beanDefinitionMap.put(beanName, beanDefinition);
                        }

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


}

4.属性赋值

spring框架中的属性值是很复杂的,我们这里就简单实现好了:使用名字匹配。

属性赋值很显然,我们要在对象创建完之后,给属性赋值。

步骤:

  1. 获取对象中的所有属性循环
  2. 判断属性是否有@Autowired 注解。
  3. 根据属性字段的名字给属性赋值。
public class StartApplicationContext {

    private Class configClass;
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    private Map<String, Object> singletonObjectMap = new HashMap<>();

    public StartApplicationContext(Class configClass) {
        this.configClass = configClass;
        //扫描
        scan(configClass);
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String key = entry.getKey();
            BeanDefinition value = entry.getValue();
            if ("singleton".equals(value.getScope())) {
                Object bean = createBean(key, value);
                singletonObjectMap.put(key, bean);
            }
        }
    }

    public Object createBean(String beanName, BeanDefinition beanDefinition) {
        Class aClass = beanDefinition.getType();
        Object object = null;
        try {
            object =  aClass.getConstructor().newInstance();
            //属性赋值
            for (Field field : aClass.getDeclaredFields()){
                if(field.isAnnotationPresent(Autowired.class)){
                  field.setAccessible(true);
                  field.set(object,getBean(field.getName()));
                }
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return object;
    }

    public Object getBean(String name) {
        if (!beanDefinitionMap.containsKey(name)) {
            throw new NullPointerException();
        }

        BeanDefinition beanDefinition = beanDefinitionMap.get(name);
        String scope = beanDefinition.getScope();
        if ("singleton".equals(scope)) {
            Object singletonObject = singletonObjectMap.get(name);
            //属性赋值的时候,获取对象可能会为空
            if(singletonObject == null){
                singletonObject = createBean(name, beanDefinition);
                singletonObjectMap.put(name,singletonObject);
            }
            return singletonObject;
        } else {
            Object bean = createBean(name, beanDefinition);
            return bean;
        }
    }


    private void scan(Class configClass) {
        // 扫描 到 创建bean
        // 扫描的过程
        // 判断传进来得配置类,是否加了 @ComponentScan
        // 获取注解的值,定义的扫描包路径
        // 获取扫描包路径下的类
        // 判断类上面是否有 Component 注解。
        // 封装bean定义存到缓存中
        if (configClass.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
            String pathValue = componentScanAnnotation.value();
            String replace = pathValue.replace(".", "/");
            System.out.println(replace);
            ClassLoader classLoader = StartApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(replace);
            File file = new File(resource.getFile());
            boolean a = false;
            if (file.isDirectory()) {
                for (File f : file.listFiles()) {
                    String path = f.getAbsolutePath();
                    System.out.println(path);
                    path = path.substring(path.indexOf("com"), path.indexOf(".class"));
                    path = path.replace("/", ".");
                    System.out.println(path);

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

                        if (loadClass.isAnnotationPresent(Component.class)) {
                            Component annotation = loadClass.getAnnotation(Component.class);
                            String beanName = annotation.value();
                            if("".equals(beanName)){
                                beanName = Introspector.decapitalize(loadClass.getSimpleName());

                            }
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setType(loadClass);


                            if (loadClass.isAnnotationPresent(Scope.class)) {
                                Scope scopeAnnotation = loadClass.getAnnotation(Scope.class);
                                String value = scopeAnnotation.value();
                                beanDefinition.setScope(value);
                            } else {
                                beanDefinition.setScope("singleton");
                            }
                            beanDefinitionMap.put(beanName, beanDefinition);
                        }

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

以上就是这次模拟spring框架创建bean 给属性赋值。 里面有需要注意的地方:

  1. 判断是否实现了哪个注解,isAnnotationPresent();
  2. 类加载器:ClassLoader classLoader = StartApplicationContext.class.getClassLoader();
  3. 反射创建对象: aClass.getConstructor().newInstance();
  4. 获取一个对象的所有属性。aClass.getDeclaredFields()
  5. 还有就是,创建bean的整个流程