【spring源码】二、手写模拟Spring加载bean底层原理

在上一篇文章中,我们用了一个个简单的Demo去体验了一下Spring几个核心知识点的简单流程和原理。在这篇文章中,我们来按照上篇文章的流程大致写一个简单的启动过程来模拟一下Spring的启动流程,加深一下感觉。

我们继续来看一下上节文章中的这个入口代码:

    public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(BeanConfig.class);
//        ClassPathXmlApplicationContext context1 = new ClassPathXmlApplicationContext("spring.xml");
        final UserService userService = (UserService)context.getBean("userService");
        userService.test();
    }
  • AnnotationConfigApplicationContext:
    • 上文中讲到过,该类构造方法通过加载一个class类来获取配置信息(例如,通过@ComponentScan来获取扫描路径)作用与ClassPathXmlApplicationContext通过加载xml文件类似。
    • 通过加载的配置类来获取扫描获取文件资源进行加载。
    • 该类中还会提供一个getBean方法,接收一个参数,返回类型为Object。

本文简单介绍一下主要任务,大致是这么一个作用,当前Spring本身所做的远远不止这些,我们在在上篇文章中的基础之上在来体验一下Spring的加载过程。

先看下自定义的CustomerAnnotationConfigApplicationContext完整代码:

public class CustomerAnnotationConfigApplicationContext {

    private Class configClass;

    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    //单例池
    private Map<String, Object> singletonObjects = new HashMap<>();

    public CustomerAnnotationConfigApplicationContext(Class configClass) {
        this.configClass = configClass;
        //注解扫描
        scanner(configClass);

        //扫描beanDefinitionMap,对单例的进行创建bean
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            if (beanDefinition.getScope().equals("singleton")) {
                Object bean = createBean(beanName, beanDefinition);

                singletonObjects.put(beanName, bean);
            }
        }

    }


    private void scanner(Class configClass) {
        if (configClass.isAnnotationPresent(CustomerComponentScan.class)) {
            //如果存在CustomerComponentScan注解,则获取注解信息得到value
            CustomerComponentScan customerComponentScan = (CustomerComponentScan) configClass.getAnnotation(CustomerComponentScan.class);

            //得到需要扫描的包路径
            String path = customerComponentScan.value();
            //将com.xxx.xx替换成 com/xxx/xx文件格式
            path = path.replace(".", "/");

            //获取CustomerAnnotationConfigApplicationContext类资源
            ClassLoader classLoader = CustomerAnnotationConfigApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(path);
            //因为我本地目录存在中文,因此这里进行了解码。
            File file = new File(URLDecoder.decode(resource.getFile()));


            if (file.isDirectory()) {
                //循环目录下文件
                for (File f : file.listFiles()) {
                    //获取绝对路径
                    String absolutePath = f.getAbsolutePath();
                    //将文件路径转换为 com.xxx.xx
                    absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class")).replace("\\", ".");


                    try {
                        //加载文件资源
                        final Class<?> clazz = classLoader.loadClass(absolutePath);
                        //判断类上是否存在CustomerComponent注解
                        if (clazz.isAnnotationPresent(CustomerComponent.class)) {

                            //如果存在,则表示为一个bean,则封装BeanDefinition,这是重点。
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setType(clazz);


                            //该类上是 单例模型 还是 原型模式
                            if (clazz.isAnnotationPresent(CustomerScope.class)) {
                                String value = clazz.getAnnotation(CustomerScope.class).value();
                                beanDefinition.setScope(value);
                            } else {
                                beanDefinition.setScope("singleton");
                            }

                            //将封装的beanDefinition放入map

                            //获取map的key
                            String beanName = clazz.getAnnotation(CustomerComponent.class).value();

                            if ("".equals(beanName)) {
                                //首字母小写处理
                                 beanName =  beanName = Introspector.decapitalize(clazz.getSimpleName());
                            }
                            beanDefinitionMap.put(beanName, beanDefinition);

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

                }

            }
        }
    }


    public Object getBean(String beanName) {

        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new NullPointerException("没有找到" + beanName + "的bean对象");
        }

        final BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

        if (beanDefinition.getScope().equals("singleton")) {
            //单例bean直接吃单例池中返回。
            Object singletonBean = singletonObjects.get(beanName);
            return singletonBean;
        } else {
            //原型bean
            Object prototypeBean = createBean(beanName, beanDefinition);
            return prototypeBean;
        }
    }

    private Object createBean(String beanName, BeanDefinition beanDefinition) {

        Class clazz = beanDefinition.getType();
        Object instance = null;
        try {
            //这里会涉及到推断构造方法,这里演示就拿取无参构造方法。
            instance = clazz.getConstructor().newInstance();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return instance;
    }
}

我们大致来梳理一下构造方法的流程:代码上也有较全的注释,可以阅读理解一下。

  1. scanner方法:
    1. 该方法首先判断是否存在自定义的CustomerComponentScan注解,如果存在则获取该注解上的包路径,通过处理得到包下的.class文件(当然这里包应该是递归查找,这里方法理解不做过多代码堆积)
    2. 当加载到.class文件后,判断是否存在CustomerComponent注解(可以是@Controoler、@Bean、@Servicer等等注解),然后进行封装成一个BeanDefinition对象(这里比较重要,实际在Spring中也确实是这么去做的,下文会解释到)。
  2. 遍历:
    1. 会进行BeanDefinitionMap遍历,将BeanDefinitionMap中的是单例(singleton)属性的进行创建bean对象。

解释一下BeanDefinition

public class BeanDefinition {

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

}

为什么需要beanDefintion,它主要用来定义一些bean的属性,这里简单描述了 scope(singleton、prototype)、是否懒加载,为什么需要这些呢。其实可以想象一下,如果一些类是懒加载的,也就是在getBean()的时候会去创建对象,那么在getBean的时候我们如何去知道该beanName对象的bean是否存在是懒加载、单例啊等等信息呢,他是不是又得通过扫描注解,加载文件资源等等信息重复判断,这些其实在CustomerAnnotationConfigApplicationContext中就已经去做了这些事情,然而CustomerAnnotationConfigApplicationContext中只会去创建单例的bean,因此方便后续的一些操作,Spring定义这一类对象用来存储一些bean的属性信息方便使用。

我们来看一下测试是否和上篇文章达到了同样的效果:

    public static void main(String[] args) {
        CustomerAnnotationConfigApplicationContext context = new CustomerAnnotationConfigApplicationContext(BeanConfig.class);
        UserService userService  = (UserService) context.getBean("userService");
        userService.test();
    }
//console
spring
true
@CustomerComponent
@CustomerScope(value = "prototype") //singleton
public class UserService {


    @CustomerAutowired
    private AbService abService;

    public void test(){
        System.out.println("spring");
        System.out.println(abService == null);
    }
}

可以发现能正常打印,说明我们简单的完成了AnnotationConfigApplicationContext的功能。但是我们可以发现,System.out.println(abService == null); 是没有值的,也就是自定义的CustomerAutowired注解,没有进行依赖注入,我们在来解决一下

依赖注入

修改一下CustomerAnnotationConfigApplicationContext的createBean方法:

   private Object createBean(String beanName, BeanDefinition beanDefinition) {

        Class clazz = beanDefinition.getType();
        Object instance = null;
        //这里会涉及到推断构造方法,这里演示就拿取无参构造方法。
        try {
            instance = clazz.getConstructor().newInstance();

            for (Field field : clazz.getDeclaredFields()) {
                if(field.isAnnotationPresent(CustomerAutowired.class)){

                    field.setAccessible(true);
                    field.set(instance,getBean(field.getName()));
                }
            }
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
        return instance;
    }

我们在得到Object对象的时候通过反射的机制去获取属性了,并判断是否存在CustomerAutowired是否进行属性赋值(依赖注入)。但是需要注意这一行field.set(instance,getBean(field.getName()));getBean我们实际上通过上面的代码我们是从单例池中去获取的,会出现这样一个问题,也能在进行此依赖注入的时候,Spring还没有加到到目标的value值,也就是在单例池中还不存在这样的一个bean对象。因此我们队getBean代码修改一下,如下:

public Object getBean(String beanName) {

        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new NullPointerException("没有找到" + beanName + "的bean对象");
        }

        final BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

        if (beanDefinition.getScope().equals("singleton")) {
            //单例bean直接吃单例池中返回。
            Object singletonBean = singletonObjects.get(beanName);
            if(singletonBean == null){
                singletonBean = createBean(beanName,beanDefinition);
                singletonObjects.put(beanName,singletonBean);
            }

            return singletonBean;
        } else {
            //原型bean
            Object prototypeBean = createBean(beanName, beanDefinition);
            return prototypeBean;
        }
    }

如果singletonBean为空,我们就会去主动创建这个bean这样就解决了这个问题。当然Spring本身需要考虑远远不止这些,这里就简单演示这么一个问题
这样再次进行测试,就可以发现abservice的属性不为空了。
在上篇文章中我们还提到一个InitializingBean这样一个处理。我们也来模拟一下。

InitializingBean

在createBean方法中增加处理

 private Object createBean(String beanName, BeanDefinition beanDefinition) {

        Class clazz = beanDefinition.getType();
        Object instance = null;
        //这里会涉及到推断构造方法,这里演示就拿取无参构造方法。
        try {
            instance = clazz.getConstructor().newInstance();

            for (Field field : clazz.getDeclaredFields()) {
                if(field.isAnnotationPresent(CustomerAutowired.class)){

                    field.setAccessible(true);
                    /**
                     * 这里我直接从单例池中获取值,这样是存在问题,有可能在做这一步操作的时候,单例池
                     * 中并没有还加载此bean
                     */
                    field.set(instance,getBean(field.getName()));
                }
            }

            if(instance instanceof CustomerInitializingBean){
                ((CustomerInitializingBean)instance).afterPropertiesSet();
            }


        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }
@CustomerComponent
@CustomerScope(value = "prototype") //singleton
public class UserService implements CustomerInitializingBean {


    @CustomerAutowired
    private AbService abService;

    public void test(){
        System.out.println("spring");
        System.out.println(abService == null);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("CustomerInitializingBean afterPropertiesSet ");
    }
}

再次测试:

console如下:
CustomerInitializingBean afterPropertiesSet
spring
false

BeanPostProcessor

Spring提供的BeanPostProcessor接口中提供两个方法:

  • postProcessBeforeInitialization 初始化操作
  • postProcessAfterInitialization 初始化后操作

在spring中,BeanPostProcessor的作用是非常重要的,比如我们常见的AOP,setbeanName()、setBeanClassLoader()、setBeanFactory()等Aware处理也就是在这里进行处理的,我们简单来用该功能来模拟一下aop代理对象创建。
看例子:

@CustomerComponent
public interface CustomerBeanPostProcessor {

    default Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }

    default Object postProcessAfterInitialization(Object bean, String beanName)  {
        return bean;
    }
}
@CustomerComponent
public class HyBeanPostProcessImpl implements CustomerBeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if("userService".equals(beanName)){
            final Object newProxyInstance = Proxy.newProxyInstance(HyBeanPostProcessImpl.class.getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() {
                @Override
                public Object invoke(Object o, Method method, Object[] objects) throws Throwable {
                    //执行切面逻辑

                    return method.invoke(bean,objects);
                }
            });
            return newProxyInstance;
        }
        return bean;
    }
}
  • createBean方法:增幅方法执行
private Object createBean(String beanName, BeanDefinition beanDefinition) {

        Class clazz = beanDefinition.getType();
        Object instance = null;
        //这里会涉及到推断构造方法,这里演示就拿取无参构造方法。
        try {
            instance = clazz.getConstructor().newInstance();

            for (Field field : clazz.getDeclaredFields()) {
                if (field.isAnnotationPresent(CustomerAutowired.class)) {

                    field.setAccessible(true);
                    /**
                     * 这里我直接从单例池中获取值,这样是存在问题,有可能在做这一步操作的时候,单例池
                     * 中并没有还加载此bean
                     */
                    field.set(instance, getBean(field.getName()));
                }
            }

            if (instance instanceof CustomerInitializingBean) {
                ((CustomerInitializingBean) instance).afterPropertiesSet();
            }

            for (CustomerBeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                instance = beanPostProcessor.postProcessAfterInitialization(instance,beanName);
            }


        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return instance;
    }
  • scanner方法:增加对process的判断
 private void scanner(Class configClass) {
        if (configClass.isAnnotationPresent(CustomerComponentScan.class)) {
            //如果存在CustomerComponentScan注解,则获取注解信息得到value
            CustomerComponentScan customerComponentScan = (CustomerComponentScan) configClass.getAnnotation(CustomerComponentScan.class);

            //得到需要扫描的包路径
            String path = customerComponentScan.value();
            //将com.xxx.xx替换成 com/xxx/xx文件格式
            path = path.replace(".", "/");

            //获取CustomerAnnotationConfigApplicationContext类资源
            ClassLoader classLoader = CustomerAnnotationConfigApplicationContext.class.getClassLoader();
            URL resource = classLoader.getResource(path);
            //因为我本地目录存在中文,因此这里进行了解码。
            File file = new File(URLDecoder.decode(resource.getFile()));


            if (file.isDirectory()) {
                //循环目录下文件
                for (File f : file.listFiles()) {
                    //获取绝对路径
                    String absolutePath = f.getAbsolutePath();
                    //将文件路径转换为 com.xxx.xx
                    absolutePath = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class")).replace("\\", ".");


                    try {
                        //加载文件资源
                        final Class<?> clazz = classLoader.loadClass(absolutePath);
                        //判断类上是否存在CustomerComponent注解
                        if (clazz.isAnnotationPresent(CustomerComponent.class)) {

                            if (CustomerBeanPostProcessor.class.isAssignableFrom(clazz)) {
                                beanPostProcessorList.add((CustomerBeanPostProcessor) clazz.getConstructor().newInstance());
                            }
                            //如果存在,则表示为一个bean,则封装BeanDefinition,这是重点。
                            BeanDefinition beanDefinition = new BeanDefinition();
                            beanDefinition.setType(clazz);


                            //该类上是 单例模型 还是 原型模式
                            if (clazz.isAnnotationPresent(CustomerScope.class)) {
                                String value = clazz.getAnnotation(CustomerScope.class).value();
                                beanDefinition.setScope(value);
                            } else {
                                beanDefinition.setScope("singleton");
                            }

                            //将封装的beanDefinition放入map

                            //获取map的key
                            String beanName = clazz.getAnnotation(CustomerComponent.class).value();

                            if ("".equals(beanName)) {
                                //首字母小写处理
                                beanName = Introspector.decapitalize(clazz.getSimpleName());
                            }
                            beanDefinitionMap.put(beanName, beanDefinition);

                        }
                    } catch (ClassNotFoundException e) {
                        e.printStackTrace();
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    }

                }

            }
        }
    }

本篇文章简单模拟一下spring加载bean的流程,来大致深入了解一下上一篇文章所提到的知识点,还希望各位有问题请指正。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

陈橙橙丶

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

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

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

打赏作者

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

抵扣说明:

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

余额充值