手撕spring bean加载过程

这里我们采用手撕源码的方式,开始探索spring boot源码中最有意思的部分-bean的生命周期,也可以通过其中的原理理解很多面试以及工作中偶发遇到的问题。

springboot基于约定大于配置的思想对spring进行优化,使得这个框架变得更加轻量化,集成各种starter组件时使其能够更加全面。

1、SpringApplication启动类的配置与软件包的反射加载

通常我们在建立一个新的spring boot项目时,利用idea脚手架生成模板内部会自带一个标注有SpringApplication注解的启动类,如下所示:

java 复制代码
/**
 * @author : spring
 * {@code @description:}
 * {@code @date} : 2024/2/4
 * {@code @modified} By: spring
 * {@code @project:} spring-plus
 */
@SpringApplication(scanBeanPackagePath = "com/hlc/springplus/test")
public class ApplicationStarter {
    public static void main(String[] args) {
        ApplicationContext applicationContext = new DefaultApplicationContext(ApplicationStarter.class);
        TestBean contextBean = (TestBean) applicationContext.getBean("test");
        contextBean.test();
    }
}

由于本文的代码全部都是手撕,所以会与spring boot的源码有所不同,个人主义完美凸显。

言回正传,用过spring boot的人都知道它主要的特色基础是它的"容器"的概念,我们可以通过配置文件、注解、导入以及反射实例化后调用通用应用上下文注入的方式将我们的bean交给spring容器管理,那么这里启动类启动后"容器"是怎么识别出我们标准或配置的bean信息同时将其实例化、配置属性、配置名称.....的呢?

那么下面就是一个过程

获取启动类上标注注解中的包路径值

ini 复制代码
SpringApplication annotation = appconfig.getAnnotation(SpringApplication.class);

String beanPackagePath = annotation.scanBeanPackagePath();
String path = beanPackagePath.replace('.', '/');

这里的path就是我们实际的包路径,为什么需要将.替换城/呢?实际上我们配置的包路径是软件包中的相对路径,并不是Resource获取时规定的路径格式。

获取当前类的类加载器并根据路径加载URL获取文件

ini 复制代码
 ClassLoader classLoader = DefaultApplicationContext.class.getClassLoader();
 URL resource = classLoader.getResource(path);

 File file = new File(resource.getFile());

DefaultApplicationContext就是我当前类的名称,后续串完全部的流程会将全部的代码挂出的,这里的类加载器获取资源的方式是比较常用的。

需要注意的是这里拿到的file有可能是文件夹,也可能是文件。

通过文件夹或文件夹杂字节码

scss 复制代码
 for (File item : files) {
      int begin = item.getAbsolutePath().indexOf("com");
        int end = item.getAbsolutePath().indexOf('.');

        String className = item.getAbsolutePath().substring(begin, end).replace('\', '.');

        try {
            Class<?> clazz = Class.forName(className);
            if (clazz.isAnnotationPresent(Component.class)) {
                beanClazzList.add(clazz);
                //收集后置处理器(意图是收集后置处理器而不是收集bean对象)
                if (BeanPostprocessor.class.isAssignableFrom(clazz)) {
                    beanPostprocessorList.add((BeanPostprocessor) clazz.getDeclaredConstructor().newInstance());
                }
            }
        } catch (Exception e) {

            throw new RuntimeException(e.getMessage());
        }                            
 }

上面的类路径获取方式以及判断字节码是否实现了接口BeanPostprocessor的判断、字节码是否标注了注解Component的判断都是比较常用的方法。

那么通过此三步就将全部需要加载的字节码文件都获取到我们的成员变量beanClazzList列表中去了。

2、ApplicationContext接口的定义以及相关注解的配置

虽然我们解决了待加载bean的字节码列表的收集问题,但是spring boot的容器我们还没有加载出来,也没有实现相关注解的配置,注解标注了bean的身份、名称、类型、加载方式、加载条件、加载顺序、依赖关系等。

ApplicationContext接口的定义

typescript 复制代码
public interface ApplicationContext extends BeanFactory {
    Object getBean(String beanName);

    <T> void registerBean(T bean, Class<T> clazz, String name);
}

ApplicationContext接口的释义是"应用上下文",在计算机科学中,上下文表示进程在执行过程中系统内部的资源情况与中断向量表的记录情况,总之代表的是进程所处的逻辑环境。这里顾名思义ApplicationContext代表的也自然就是bean所处的环境。也就是我们口中的spring boot的容器。

对于基本的ApplicationContext能力而言,它应当具备获取bean对象与注册bean对象的能力。所以这里定义的两个基础能力接口。在考虑如何实现它之前,我们还需要配置以下bean相关的其他注解:

组件注解(标注类型为bean组件)

less 复制代码
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface Component {
    String name() default "";
}

注入注解(标注为某类型注入某bean的属性值)

less 复制代码
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface AutoWired {
    String value() default "";
}

作用域注解(标注单例、原型等生存周期的bean类型)

less 复制代码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Scope {
    String value();
}

懒加载注解(标注即用即加载还是立刻加载)

less 复制代码
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Lazy {
}

初始化方法注解(标注bean的初始化方法)

less 复制代码
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface InitBeanMethod {
}

3、实现ApplicationContext接口与bean加载原理

上述我们定义了相关接口与注解,接下来我们实现容器上下文接口以及描述bean是如何加载到容器内部管理的。

那么这里我们就先将实现ApplicationContext接口的DefaultApplicationContext.java代码放在下方:

scss 复制代码
public class DefaultApplicationContext implements ApplicationContext {
    private final Class<?> appconfig;

    private List<Class<?>> beanClazzList = new LinkedList<>();
    private Map<String, Object> singletonBeanMap = new ConcurrentHashMap<>();

    private Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>();
    private List<BeanPostprocessor> beanPostprocessorList = new LinkedList<>();

    public DefaultApplicationContext(Class<?> appconfig) {
        this.appconfig = appconfig;
        //1、扫描启动类注解的字节码列表
        scanBeansByPackage(beanClazzList);
        //2、注册bean的BeanDefinition初始配置信息
        initBeanDefinition(beanClazzList, beanDefinitionMap);
        //3、实例化单例bean并存入map中
        instanceSingletonBeans(beanDefinitionMap, singletonBeanMap);
    }

    @Override
    public Object getBean(String beanName) {
        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
        if (null != beanDefinition && "prototype".equals(beanDefinition.getScope())) {
            try {
                return beanDefinition.getBeanClazz().getDeclaredConstructor().newInstance();
            } catch (InstantiationException | IllegalAccessException | InvocationTargetException |
                     NoSuchMethodException e) {
                throw new RuntimeException(e.getMessage());
            }
        }
        return singletonBeanMap.get(beanName);
    }

    @Override
    public <T> void registerBean(T bean, Class<T> clazz, String name) {
        singletonBeanMap.put(name, bean);
    }


    /**
     * 扫描bean的字节码列表
     *
     * @param beanClazzList bean的字节码列表(待填充)
     */
    protected void scanBeansByPackage(List<Class<?>> beanClazzList) {

        if (null != appconfig && appconfig.isAnnotationPresent(SpringApplication.class)) {

            SpringApplication annotation = appconfig.getAnnotation(SpringApplication.class);
            if (null != annotation) {

                String beanPackagePath = annotation.scanBeanPackagePath();
                String path = beanPackagePath.replace('.', '/');

                ClassLoader classLoader = DefaultApplicationContext.class.getClassLoader();
                URL resource = classLoader.getResource(path);

                if (resource != null) {
                    File file = new File(resource.getFile());
                    if (file.isDirectory()) {
                        File[] files = file.listFiles();
                        if (files != null) {
                            for (File item : files) {

                                loadAndFilterBeanClazzes(beanClazzList, item);
                            }
                        }
                    } else {

                        loadAndFilterBeanClazzes(beanClazzList, file);
                    }
                }
            } else {
                throw new RuntimeException("Annotation SpringApplication is not exist");
            }
        } else {
            throw new RuntimeException("Annotation SpringApplication is not exist and appconfig is null");
        }
    }

    /**
     * 加载bean的字节码列表并过滤
     *
     * @param beanClazzList bean的字节码列表(待填充)
     * @param item          文件或文件夹
     */
    private void loadAndFilterBeanClazzes(List<Class<?>> beanClazzList, File item) {
        int begin = item.getAbsolutePath().indexOf("com");
        int end = item.getAbsolutePath().indexOf('.');

        String className = item.getAbsolutePath().substring(begin, end).replace('\', '.');

        try {
            Class<?> clazz = Class.forName(className);
            if (clazz.isAnnotationPresent(Component.class)) {
                beanClazzList.add(clazz);
                //收集后置处理器(意图是收集后置处理器而不是收集bean对象)
                if (BeanPostprocessor.class.isAssignableFrom(clazz)) {
                    beanPostprocessorList.add((BeanPostprocessor) clazz.getDeclaredConstructor().newInstance());
                }
            }
        } catch (Exception e) {

            throw new RuntimeException(e.getMessage());
        }
    }

    /**
     * 注册bean的BeanDefinition初始配置信息
     *
     * @param beanClazzList     bean的字节类型列表
     * @param beanDefinitionMap bean的BeanDefinition初始配置信息池子
     */
    private void initBeanDefinition(List<Class<?>> beanClazzList, Map<String, BeanDefinition> beanDefinitionMap) {
        if (null != beanClazzList && !beanClazzList.isEmpty()) {
            for (Class<?> clazz : beanClazzList) {
                BeanDefinition beanDefinition = new BeanDefinition();
                Component component = clazz.getAnnotation(Component.class);
                Scope scope = clazz.getAnnotation(Scope.class);
                Lazy lazy = clazz.getAnnotation(Lazy.class);

                beanDefinition.setBeanClazz(clazz);
                beanDefinition.setLazy(null != lazy);
                beanDefinition.setScope(null != scope ? scope.value() : "prototype");
                String beanName = component.name();
                if (beanName.isEmpty()) {
                    beanName = clazz.getSimpleName();
                }
                beanDefinitionMap.put(beanName, beanDefinition);
            }
        }
    }

    /**
     * 实例化单例bean
     *
     * @param beanDefinitionMap bean定义信息
     * @param singletonBeanMap  单例bean池子
     */
    private void instanceSingletonBeans(Map<String, BeanDefinition> beanDefinitionMap, Map<String, Object> singletonBeanMap) {

        if (null != beanDefinitionMap && !beanDefinitionMap.isEmpty()) {
            for (Class<?> clazz : beanDefinitionMap.values().stream().map(BeanDefinition::getBeanClazz).toList()) {
                if (clazz.isAnnotationPresent(Scope.class) && "prototype".equals(clazz.getAnnotation(Scope.class).value())) {
                    continue;
                }
                if (!clazz.isAnnotationPresent(Lazy.class)) {
                    //实例化bean
                    try {
                        Component component = clazz.getAnnotation(Component.class);
                        String beanName = component.name();
                        if (null == beanName || beanName.isEmpty()) {
                            beanName = clazz.getSimpleName();
                        }
                        //1、实例化bean
                        Object newInstance = clazz.getDeclaredConstructor().newInstance();
                        //2、属性填充
                        attributeAutoWiredPadding(clazz, newInstance);
                        //3、aware能力透传
                        awareBeanInstancePadding(newInstance);
                        //4、初始化
                        //4.1、后置处理器 初始化前执行
                        for (BeanPostprocessor beanPostprocessor : beanPostprocessorList) {
                            newInstance = beanPostprocessor.beforeInitialization(newInstance, beanName);
                        }
                        //4.2、初始化bean执行
                        //检查是否实现初始化Bean的接口
                        initializeBeanInstancePadding(newInstance);
                        //检查是否配置过init方法
                        initBeanMethodInstancePadding(newInstance);
                        //4.3、后置处理器能力 初始化后执行
                        for (BeanPostprocessor beanPostprocessor : beanPostprocessorList) {
                            newInstance = beanPostprocessor.afterInitialization(newInstance, beanName);
                        }

                        singletonBeanMap.put(beanName, newInstance);
                    } catch (InvocationTargetException | NoSuchMethodException | InstantiationException |
                             IllegalAccessException e) {
                        throw new RuntimeException(e.getMessage());
                    }
                }
            }
        }
    }



    /**
     * bean的属性填充
     *
     * @param beanClazz   bean的字节类型
     * @param newInstance 实例化的bean
     */
    private void attributeAutoWiredPadding(Class<?> beanClazz, Object newInstance) {
        if (null != beanClazz) {
            Field[] fields = beanClazz.getDeclaredFields();

            for (Field field : fields) {

                if (field.isAnnotationPresent(AutoWired.class)) {

                    field.setAccessible(true);
                    Class<?> declaringClass = field.getType();

                    AutoWired autoWired = field.getAnnotation(AutoWired.class);
                    String name = autoWired.value();

                    if (null == name || name.isEmpty()) {
                        name = declaringClass.getSimpleName();
                    }

                    Object fieldBean = singletonBeanMap.get(name);

                    if (null == fieldBean) {

                        List<Class<?>> beanClazzList = new LinkedList<>();
                        beanClazzList.add(declaringClass);
                        initBeanDefinition(beanClazzList, beanDefinitionMap);

                        Map<String, BeanDefinition> definitionMap = new HashMap<>();
                        definitionMap.put(name, beanDefinitionMap.get(name));
                        instanceSingletonBeans(definitionMap, singletonBeanMap);

                        try {
                            field.set(newInstance, singletonBeanMap.get(name));
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e.getMessage());
                        }
                    } else {

                        try {
                            field.set(newInstance, fieldBean);
                        } catch (IllegalAccessException e) {
                            throw new RuntimeException(e.getMessage());
                        }
                    }
                }
            }
        }
    }

    /**
     * bean的Aware接口的实现类填充
     *
     * @param bean bean实例对象
     */
    private void awareBeanInstancePadding(Object bean) {
        if (null != bean) {
            if (bean instanceof Aware) {
                if (bean instanceof ApplicationContextAware) {
                    ((ApplicationContextAware) bean).setApplicationContext(this);
                }
                if (bean instanceof BeanNameAware) {
                    ((BeanNameAware) bean).setBeanName();
                }
            }
        }
    }

    /**
     * bean的初始化方法填充
     *
     * @param bean 实例化的bean
     */
    private void initializeBeanInstancePadding(Object bean) {
        if (null != bean) {
            if (bean instanceof InitializingBean) {
                ((InitializingBean) bean).afterPropertiesSet();
            }
        }
    }

    /**
     * bean的初始化方法填充
     *
     * @param newInstance 实例化的bean
     */
    private void initBeanMethodInstancePadding(Object newInstance) {
        if (null != newInstance) {
            Method[] methods = newInstance.getClass().getDeclaredMethods();
            for (Method method : methods) {
                if (method.isAnnotationPresent(InitBeanMethod.class)) {
                    method.setAccessible(true);
                    try {
                        method.invoke(newInstance);
                    } catch (IllegalAccessException | InvocationTargetException e) {
                        throw new RuntimeException(e.getMessage());
                    }
                }
            }
        }
    }
    private void sortBeanInstanceClazzList() {

    }
}

这里我们着重描述一下关于bean加载原理的这一块代码:

scss 复制代码
private void instanceSingletonBeans(Map<String, BeanDefinition> beanDefinitionMap, Map<String, Object> singletonBeanMap) {

        if (null != beanDefinitionMap && !beanDefinitionMap.isEmpty()) {
            for (Class<?> clazz : beanDefinitionMap.values().stream().map(BeanDefinition::getBeanClazz).toList()) {
                if (clazz.isAnnotationPresent(Scope.class) && "prototype".equals(clazz.getAnnotation(Scope.class).value())) {
                    continue;
                }
                if (!clazz.isAnnotationPresent(Lazy.class)) {
                    //实例化bean
                    try {
                        Component component = clazz.getAnnotation(Component.class);
                        String beanName = component.name();
                        if (null == beanName || beanName.isEmpty()) {
                            beanName = clazz.getSimpleName();
                        }
                        //1、实例化bean
                        Object newInstance = clazz.getDeclaredConstructor().newInstance();
                        //2、属性填充
                        attributeAutoWiredPadding(clazz, newInstance);
                        //3、aware能力透传
                        awareBeanInstancePadding(newInstance);
                        //4、初始化
                        //4.1、后置处理器 初始化前执行
                        for (BeanPostprocessor beanPostprocessor : beanPostprocessorList) {
                            newInstance = beanPostprocessor.beforeInitialization(newInstance, beanName);
                        }
                        //4.2、初始化bean执行
                        //检查是否实现初始化Bean的接口
                        initializeBeanInstancePadding(newInstance);
                        //检查是否配置过init方法
                        initBeanMethodInstancePadding(newInstance);
                        //4.3、后置处理器能力 初始化后执行
                        for (BeanPostprocessor beanPostprocessor : beanPostprocessorList) {
                            newInstance = beanPostprocessor.afterInitialization(newInstance, beanName);
                        }

                        singletonBeanMap.put(beanName, newInstance);
                    } catch (InvocationTargetException | NoSuchMethodException | InstantiationException |
                             IllegalAccessException e) {
                        throw new RuntimeException(e.getMessage());
                    }
                }
            }
        }
    }

到这里我们已经了解在获取待加载bean的字节码列表之后,我们需要将bean的配置信息存储到我们的beanDefinitionMap中,再根据beanDefinitionMap将其中的单例bean信息加载成一个个bean放入单例bean map中,这里的存储key统一都是beanName。

看以上代码我们不难分析出通过bean配置信息加载bean的过程中,一个bean需要经过6步周期性工作才会被放入容器中给我们使用。以下是图示:

相关推荐
以后不吃煲仔饭10 分钟前
Java基础夯实——2.7 线程上下文切换
java·开发语言
进阶的架构师10 分钟前
2024年Java面试题及答案整理(1000+面试题附答案解析)
java·开发语言
The_Ticker16 分钟前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
大数据编程之光38 分钟前
Flink Standalone集群模式安装部署全攻略
java·大数据·开发语言·面试·flink
爪哇学长1 小时前
双指针算法详解:原理、应用场景及代码示例
java·数据结构·算法
ExiFengs1 小时前
实际项目Java1.8流处理, Optional常见用法
java·开发语言·spring
paj1234567891 小时前
JDK1.8新增特性
java·开发语言
捂月1 小时前
Spring Boot 深度解析:快速构建高效、现代化的 Web 应用程序
前端·spring boot·后端
繁依Fanyi1 小时前
简易安卓句分器实现
java·服务器·开发语言·算法·eclipse
慧都小妮子1 小时前
Spire.PDF for .NET【页面设置】演示:打开 PDF 时自动显示书签或缩略图
java·pdf·.net