【Spring专题】手写简易Spring容器过程分析——引导篇

news2024/12/23 11:46:32

目录

  • 前言
    • 说在前面
    • 阅读准备
  • 思路整理
  • 手写源码示例
    • 一、手写前的准备
      • 1.1 注解
      • 1.2 测试Bean
      • 1.3 调用实例
    • 二、构造方法(构建基本流程)
    • 三、实现scan()方法
      • 3.1 doGetScanPackage():获取扫描路径
      • 3.2 doLoadClassFromDiskAndScan():从电脑磁盘中加载文件,并且扫描
      • 3.3 transferToFullyQualifiedClassName() :获取全限定名
      • *3.4 doRecordBeanInfo() :记录Bean信息
    • 四、实现ioc()方法
      • *4.1 createBean() :创建Bean
      • 4.2 doDependecyInjection() :依赖注入
      • 4.3 doAwareCallBack() :各种Aware回调
      • 4.4 doInitBefore() :初始化前
      • 4.5 doInit() :初始化
      • 4.6 doInitAfter() :初始化后
    • 五、实现aop()方法
    • 六、MyApplicationContext完整源码

前言

说在前面

首先必须得声明的是,下面的流程并不代表Spring源码中真正的流程。而是,我们通过现在的Spring提供给我们的某些基础功能,反推过来的流程。所以,并不全面,但是会具有一点参考性。

阅读准备

由于Spring源码分析是一个前后联系比较强的过程,而且这边分析,也是按照代码顺序讲解的,所以不了解前置知识的情况下,大概率没办法看懂当前的内容。所以,特别推荐看看我前面的文章(自上而下次序):

  • 【Spring专题】Spring底层核心原理解析【学习难度:★★☆☆☆

思路整理

我们在上一节《【Spring专题】Spring底层核心原理解析》课里面有简单分析过一个Spring容器的一般流程,所以,本节课我们这里尝试写一下简易的Spring容器。

手写源码示例

一、手写前的准备

1.1 注解

既然是需要手写Spring容器,那我们肯定需要自定义一个MyApplicationContext类,以及自定义注解@ComponentScan@Component@Autowired@Scope代码如下:

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

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

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

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface Autowired {
}

其中@Scope是为了验证多例Bean跟单例Bean的。当然,需要准备的接口不止于此,后面我会随着方法的完善,逐渐引出其余需要的接口或者注解。

1.2 测试Bean

另外就是测试用的Bean,代码如下:

/**
 * @author zhangshen
 * @tag 【手写】
 * @Date 2023/8/7 19:57
 * @slogan 编码即学习
 **/
@Component("userService")
public class MyUserService implements InitializingBean, BeanPostProcessor {

    private int afterPropertiesSet = 0;
    private int postProcessBeforeInitialization = 0;
    private int postProcessAfterInitialization = 0;
    private static int count = 0;

    public void showYourFace() {
        System.out.println("手写的spring。。。。。 test一下");
        System.out.println("初始化是第几步,答案是:" + afterPropertiesSet);
        System.out.println("初始化前是第几步,答案是:" + postProcessBeforeInitialization);
        System.out.println("初始化后是第几步,答案是:" + postProcessAfterInitialization);
    }


    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println("这里是初始化,是在aware回调之后");
        count++;
        afterPropertiesSet = count;
    }

    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) {
        System.out.println("这里是初始化前");
        count++;
        postProcessBeforeInitialization = count;
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        System.out.println("这里是初始化后");
        count++;
        postProcessAfterInitialization = count;
        return bean;
    }
}

1.3 调用实例

调用实例:

/**
 * 手写Spring容器测试
 *
 * @author zhangshen
 * @tag 【手写】
 * @Date 2023/8/7 19:55
 * @slogan 编码即学习
 **/
public class MyApplicationContextTest {

    public static void main(String[] args) {
        MyApplicationContext context = new MyApplicationContext(MyAppConfig.class);
        MyUserService userService = (MyUserService) context.getBean("userService");
        userService.showYourFace();
    }
}

二、构造方法(构建基本流程)

在上节课中,我们说过,在容器启动的构造方法里面,大致的流程可以分为三步:
在这里插入图片描述
所以,我们代码起手式,是新建一个MyApplicationContext类,然后一个默认的无参构造方法,代码如下:

public class MyApplicationContext {
    public MyApplicationContext(Class clazz) {
        
        // 步骤一:扫描
        scan(clazz);
        
        // 步骤二:IOC
        ioc();

        // 步骤三:AOP
        aop();
    }
}

但事实上,我们前面说过,虽然看似大过程是3步,不过AOP是需要发生在IOC内部的(Bean放入单例池之前),所以,后面,我们会把这个AOP的过程写在IOC里面,如下:

public class MyApplicationContext {
    public MyApplicationContext(Class clazz) {
        
        // 步骤一:扫描
        scan(clazz);
        
        // 步骤二:IOC
        ioc();
    }

    private void doIOC() {

        // 步骤三:AOP
        aop();
    }
}

三、实现scan()方法

先上扫描的简易流程图:
在这里插入图片描述
接下来,我们只需要在doScan()方法里面实现这些步骤就好了。

    /**
     * 扫描方法
     *
     * @param clazz 配置类
     */
    private void scan(Class clazz) {

        // 步骤一:获取扫描路径
        String basePackage = doGetScanPackage(clazz);
        if (StrUtil.isEmpty(basePackage)) {
            return;
        }

        // 步骤二、三:从电脑磁盘中加载文件,并且扫描
        doLoadClassFromDiskAndScan(basePackage, clazz);
    }

3.1 doGetScanPackage():获取扫描路径

    /**
     * 获取包扫描路径
     *
     * @param clazz 配置类
     * @return 扫描路径
     */
    private String doGetScanPackage(Class clazz) {
        if (clazz.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScanAnnotation = (ComponentScan) clazz.getAnnotation(ComponentScan.class);
            return componentScanAnnotation.value();
        }

        return null;
    }

3.2 doLoadClassFromDiskAndScan():从电脑磁盘中加载文件,并且扫描

    /**
     * 从磁盘中加载class,并且扫描
     *
     * @param basePackage 扫描路径
     */
    private void doLoadClassFromDiskAndScan(String basePackage) {

        // 将Java包名转换为电脑路径名
        basePackage = basePackage.replace(".", "/");

        // 加载
        ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(basePackage);
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            for (File listFile : file.listFiles()) {

				// 获取全限定名
                String fullyQualifiedClassName = transferToFullyQualifiedClassName(listFile);

                try {
                    Class<?> beanClass = classLoader.loadClass(fullyQualifiedClassName);
                    if (!beanClass.isAnnotationPresent(Component.class)) {
                        continue;
                    }

                    // 记录Bean信息
                    doRecordBeanInfo(beanClass);

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

方法解读:

  1. 在上一步,我们已经获取到了扫描路径,但是扫描路径是我们Java包名,现在需要从磁盘重读取的话,定然是需要转换成电脑磁盘重的路径的。
  2. 大家可能对classLoader这一块比较难理解,这是属于JVM那一块的。不过这么说,我们自己写的业务代码,除非特别指定,不然都是使用的同一个类加载器
  3. 这里还有2个关键方法调用,一个是transferToFullyQualifiedClassName(),是将文件名转换为类加载器能识别的类全限定名;另一个是doRecordBeanInfo()方法,记录Bean定义信息的。代码会在后面

3.3 transferToFullyQualifiedClassName() :获取全限定名

    /**
     * 将电脑磁盘上的文件,转换为AppClassLoader能识别的类全限定名(包 + 类名)
     *
     * <p>
     * 由于JVM的类加载器有三种,默认加载用户自定义class文件的,其实是ApplicationClassLoader
     * 该类加载器所能识别的,其实是包名
     * </p>
     *
     * @param listFile 电脑磁盘上的文件
     */
    private String transferToFullyQualifiedClassName(File listFile) {
        String absolutePath = listFile.getAbsolutePath();
        absolutePath = absolutePath.substring(absolutePath.indexOf("org"), absolutePath.indexOf(".class"));
        absolutePath = absolutePath.replace("\\", ".");
        return absolutePath;
    }

*3.4 doRecordBeanInfo() :记录Bean信息

    /**
     * 过滤,并且记录Bean信息
     */
    private void doRecordBeanInfo(Class<?> beanClass) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        // 获取bean的名字
        String beanName = doGetBeanName(beanClass);

		// 记录BeanPostProcessor
        if (BeanPostProcessor.class.isAssignableFrom(beanClass)) {
            BeanPostProcessor instance = (BeanPostProcessor) beanClass.getConstructor().newInstance();
            beanPostProcessorList.add(instance);
        }

        BeanDefinition beanDefinition = new BeanDefinition();
        beanDefinition.setType(beanClass);
        if (beanClass.isAnnotationPresent(Scope.class)) {
            Scope scopeAnnotation = beanClass.getAnnotation(Scope.class);
            String value = scopeAnnotation.value();
            beanDefinition.setScope(value);
        }

        // 记录BeanDefinition
        beanDefinitionMap.put(beanName, beanDefinition);
    }

这里,引入了一个新的类BeanDefinition类,源码如下:

/**
 * 手写Bean定义
 *
 * @author zhanghuitong
 * @tag 【手写】 【Bean定义】 【图纸】
 * @Date 2023/8/8 10:54
 * @slogan 编码即学习
 **/
@Getter
@Setter
public class BeanDefinition {

    private static final String SINGLETON = "singleton";

    /**
     * Bean类型
     */
    private Class type;

    /**
     * 作用域
     * 是原型,还是单例
     */
    private String scope = SINGLETON;
}

也许大家会很奇怪,为什么需要引入这么一个东西呢?我都已经扫描好了,我直接根据类信息生成不就行了吗?如果你也有这个想法,那么我反问你一个问题:如果我后面想修改怎么办?还别说,真的有可能修改!Spring提供了那么多拓展机制,其中就有对类信息修改的拓展点(至于具体应用场景,我还没彻底学清楚,我后面学到了我会回来更新的)。但是为了方便大家理解这个玩意的存在,我举个通俗的例子,如下:

BeanDefinition的存在更像是一份家具定制的图纸,Bean是具体的某个家具。而Spring里面,在后面我们会学到的ApplicationContext,则是生产家具的厂家。这样类比的话,你应该能想明白,为什么需要BeanDefinition了吧。
总结一句话:ApplicationContext厂家根据BeanDefinition图纸生成具体的某个家具Bean。(PS:ApplicationContext 包含 BeanFactory,都是Bean工厂)

四、实现ioc()方法

先看看ioc简易流程图:
在这里插入图片描述
(PS:下面的手写源码,没有实现【推断构造方法】的逻辑)

    /**
     * 进行IOC过程
     */
    private void ioc() {

        // 循环遍历beanDefinitionMap
        Set<Map.Entry<String, BeanDefinition>> entries = beanDefinitionMap.entrySet();
        for (Map.Entry<String, BeanDefinition> entry : entries) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            if (beanDefinition.getScope().equals(BeanDefinition.SINGLETON)) {

                // 创建Bean
                Object bean = createBean(beanName, beanDefinition);

                // AOP
                aop();
                
                singletonPool.put(beanName, bean);
            }
        }
    }

方法解读:

  1. 在这里,就是遍历我们上一步获得的beanDefinitionMap图纸Map。
  2. 接着就开始根据图纸生成Bean了(createBean()方法)
  3. 接着看看是否需要AOP了(在放入单例池之前)
  4. 最后就是放入单例池里面了

*4.1 createBean() :创建Bean

    /**
     * 创建bean
     *
     * @param beanName       bean名称
     * @param beanDefinition 对应的bean定义
     */
    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        Class clazz = beanDefinition.getType();
        Object instance = null;
        try {

            // 实例化。本应该实现推断构造方法,但是这里从简使用默认的
            instance = clazz.getConstructor().newInstance();

            // 依赖注入
            doDependecyInjection(clazz, instance);

            // 各种Aware回调
            doAwareCallBack(beanName, instance);

            // 初始化前
            instance = doInitBefore(beanName, instance);

            // 初始化
            doInit(instance);

            // 初始化后
            instance = doInitAfter(beanName, instance);

        } 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;
    }

方法解读:
虽然上面的代码是简单的实现,但也稍微能窥见一点IOC的生命周期了,大家稍微理解下就好。(PS:这里只是简单推理,事实上Spring源码中Bean的声明周期不止于此,还有:实例化前、实例化后呢)

实例化 -> 依赖注入 -> 各种Aware回调 -> 初始化前 -> 初始化 -> 初始化后

因为是简单实现,所以上面这个流程不全,应该是缺了点其他拓展点的实现的。但是局部上的顺序是没啥问题,而且这不是我吹的,而是在Spirng接口上注释的。

4.2 doDependecyInjection() :依赖注入

    /**
     * 依赖注入
     *
     * @param clazz    需要被注入的对象的类信息
     * @param instance 需要被注入的对象
     */
    private void doDependecyInjection(Class clazz, Object instance) throws IllegalAccessException {
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            if (declaredField.isAnnotationPresent(Autowired.class)) {
                declaredField.setAccessible(true);
                declaredField.set(instance, getBean(declaredField.getName()));
            }
        }
    }

4.3 doAwareCallBack() :各种Aware回调

    /**
     * 实现各种Aware的回调处理
     *
     * @param beanName bean名称
     * @param instance bean对象
     */
    private void doAwareCallBack(String beanName, Object instance) {
        if (instance instanceof BeanNameAware) {
            ((BeanNameAware) instance).setBeanName(beanName);
        }
    }

方法解读:
在这里,引入了一个新的接口BeanNameAware,它实现自Aware接口。其实除了这个我们用来展示的BeanNameAware还有很多其他的XxxAware,主要的目的是在实例化之后,让我们获得感知一些Spring组件等等能力。下面是BeanNameAware接口的代码示例(其中注释源于Spring源码):

/**
 * 手写Spring的BeanNameAware接口
 *
 * @author zhangshen
 * @tag 【手写】
 * @Date 2023/8/8 19:04
 * @slogan 编码即学习
 **/
public interface BeanNameAware {

    /**
     * 在创建该bean的bean工厂中设置该bean的名称。
     * 在填充普通bean属性之后,但在初始化回调(如InitializingBean.afterPropertiesSet())或自定义初始化方法之前调用。
     * 参数:
     * 名称-工厂中bean的名称。请注意,这个名称是工厂中使用的实际bean名称,它可能与最初指定的名称不同:特别是对于内部bean名称,实际bean名称可能通过附加“#…”后缀来使其唯一。如果需要,使用BeanFactoryUtils.originalBeanName(String)方法提取原始bean名称(不带后缀)。
     */
    void setBeanName(String name);
}

4.4 doInitBefore() :初始化前

    /**
     * 实施初始化之前
     *
     * @param beanName bean名称
     * @param instance bean对象
     */
    private Object doInitBefore(String beanName, Object instance) {
        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
        }
        return instance;
    }

方法解读:

  1. 初始化前这是属于SpringIOC提供的一个拓展点,需要一个非常非常非常重要的接口BeanPostProcessor ,它的定义如下:(其中注释源于Spring源码)
/**
 * 手写Spring的ABeanPostProcessor接口
 *
 * @author zhangshen
 * @tag 【手写】
 * @Date 2023/8/8 19:04
 * @slogan 编码即学习
 **/
public interface BeanPostProcessor {

    /**
     * 在任何bean初始化回调(如InitializingBean的afterPropertiesSet或自定义初始化方法)之前,将此BeanPostProcessor应用于给定的新bean实例。这个bean已经被属性值填充了。返回的bean实例可能是原始bean实例的包装器。
     * 默认实现按原样返回给定的bean。
     * 参数:
     * Bean——新的Bean实例
     * beanName—bean的名称
     * 返回:
     * 要使用的bean实例,无论是原始的还是包装的;如果为空,则不会调用后续的BeanPostProcessors
     * 抛出:
     * BeansException -在错误的情况下
     */
    default Object postProcessBeforeInitialization(Object bean, String beanName) {
        return bean;
    }

    /**
     * 在任何bean初始化回调(如InitializingBean的afterPropertiesSet或自定义init-method)之后,将此BeanPostProcessor应用于给定的新bean实例。这个bean已经被属性值填充了。返回的bean实例可能是原始bean实例的包装器。
     * 对于FactoryBean,将为FactoryBean实例和由FactoryBean创建的对象调用这个回调(从Spring 2.0开始)。后处理器可以通过相应的FactoryBean instanceof检查来决定是应用于FactoryBean还是已创建的对象,或者两者都应用。
     * 这个回调也将在由InstantiationAwareBeanPostProcessor触发的短路之后被调用。postProcessBeforeInstantiation方法,与所有其他BeanPostProcessor回调相反。
     * 默认实现按原样返回给定的bean。
     * 参数:
     * Bean——新的Bean实例
     * beanName—bean的名称
     * 返回:
     * 要使用的bean实例,无论是原始的还是包装的;如果为空,则不会调用后续的BeanPostProcessors
     * 抛出:
     * BeansException -在错误的情况下
     * 参见:
     * org.springframework.beans.factory.InitializingBean。afterPropertiesSet, org.springframework.beans.factory.FactoryBean
     * 以上翻译结果来自有道神经网络翻译(YNMT)· 通用场景
     */
    default Object postProcessAfterInitialization(Object bean, String beanName) {
        return bean;
    }
}

从上面的注释希望大家在后面真的阅读源码的时候能知道,BeanPostProcessor这个拓展点就是作用在初始化前后的

4.5 doInit() :初始化

    /**
     * 实施初始化方法
     *
     * @param instance bean对象
     */
    private void doInit(Object instance) throws Exception {
        if (instance instanceof InitializingBean) {
            ((InitializingBean) instance).afterPropertiesSet();
        }
    }

方法解读:

  1. 初始化方法,在这里又是需要一个新的接口,InitializingBean,在工作使用频率挺高的。当然也可以通过自定义初始化方法,不一定就是这个。接口代码如下:(其中注释源于Spring源码)
/**
 * 手写Spring的ABeanPostProcessor接口
 *
 * @author zhangshen
 * @tag 【手写】
 * @Date 2023/8/8 19:04
 * @slogan 编码即学习
 **/
public interface InitializingBean {

    /**
     * 在设置了所有bean属性并满足BeanFactoryAware、ApplicationContextAware等要求后,由包含bean的BeanFactory调用。
     * 此方法允许bean实例在设置了所有bean属性后对其总体配置和最终初始化执行验证。
     * 抛出:
     * 异常-在配置错误的情况下(例如设置基本属性失败),或者由于任何其他原因导致初始化失败
     */
    void afterPropertiesSet() throws Exception;
}

4.6 doInitAfter() :初始化后

    /**
     * 实施初始化之后
     *
     * @param beanName bean名称
     * @param instance bean对象
     */
    private Object doInitAfter(String beanName, Object instance) {
        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
        }
        return instance;
    }

方法解读:

  1. 初始化后,跟初始化前一样,是属于SpringIOC提供的一个拓展点。并且使用的也是BeanPostProcessor接口

五、实现aop()方法

这个就不实现了,大家知道有这个东西就好

六、MyApplicationContext完整源码

package org.tuling.spring.handwriten;


import cn.hutool.core.util.StrUtil;
import org.springframework.beans.factory.BeanNameAware;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;

import java.beans.Introspector;
import java.io.File;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.*;

/**
 * 手写一个简易的Spring容器
 *
 * @author zhangshen
 * @tag 【Spring】 【手写】
 * @Date 2023/8/7 19:37
 * @slogan 编码即学习
 **/
public class MyApplicationContext {
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();
    private Map<String, Object> singletonPool = new HashMap<>();
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    public MyApplicationContext(Class clazz) {

        // 扫描
        scan(clazz);

        // IOC
        ioc();
    }

    /**
     * 进行IOC过程
     */
    private void ioc() {

        // 循环遍历beanDefinitionMap
        Set<Map.Entry<String, BeanDefinition>> entries = beanDefinitionMap.entrySet();
        for (Map.Entry<String, BeanDefinition> entry : entries) {
            String beanName = entry.getKey();
            BeanDefinition beanDefinition = entry.getValue();
            if (beanDefinition.getScope().equals(BeanDefinition.SINGLETON)) {

                // 创建Bean
                Object bean = createBean(beanName, beanDefinition);

                // AOP
                aop();

                singletonPool.put(beanName, bean);
            }
        }
    }

    /**
     * 创建bean
     *
     * @param beanName       bean名称
     * @param beanDefinition 对应的bean定义
     */
    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        Class clazz = beanDefinition.getType();
        Object instance = null;
        try {

            // 实例化。本应该实现推断构造方法,但是这里从简使用默认的
            instance = clazz.getConstructor().newInstance();

            // 依赖注入
            doDependecyInjection(clazz, instance);

            // 各种Aware回调
            doAwareCallBack(beanName, instance);

            // 初始化前
            instance = doInitBefore(beanName, instance);

            // 初始化
            doInit(instance);

            // 初始化后
            instance = doInitAfter(beanName, instance);

        } 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;
    }

    /**
     * 实施初始化之后
     *
     * @param beanName bean名称
     * @param instance bean对象
     */
    private Object doInitAfter(String beanName, Object instance) {
        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            instance = beanPostProcessor.postProcessAfterInitialization(instance, beanName);
        }
        return instance;
    }

    /**
     * 实施初始化方法
     *
     * @param instance bean对象
     */
    private void doInit(Object instance) throws Exception {
        if (instance instanceof InitializingBean) {
            ((InitializingBean) instance).afterPropertiesSet();
        }
    }

    /**
     * 实施初始化之前
     *
     * @param beanName bean名称
     * @param instance bean对象
     */
    private Object doInitBefore(String beanName, Object instance) {
        for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
            instance = beanPostProcessor.postProcessBeforeInitialization(instance, beanName);
        }
        return instance;
    }

    /**
     * 实现各种Aware的回调处理
     *
     * @param beanName bean名称
     * @param instance bean对象
     */
    private void doAwareCallBack(String beanName, Object instance) {
        if (instance instanceof BeanNameAware) {
            ((BeanNameAware) instance).setBeanName(beanName);
        }
    }

    /**
     * 依赖注入
     *
     * @param clazz    需要被注入的对象的类信息
     * @param instance 需要被注入的对象
     */
    private void doDependecyInjection(Class clazz, Object instance) throws IllegalAccessException {
        Field[] declaredFields = clazz.getDeclaredFields();
        for (Field declaredField : declaredFields) {
            if (declaredField.isAnnotationPresent(Autowired.class)) {
                declaredField.setAccessible(true);
                declaredField.set(instance, getBean(declaredField.getName()));
            }
        }
    }


    /**
     * 进行AOP过程
     */
    private void aop() {

    }

    /**
     * 根据beanName获取Bean
     *
     * @param beanName bean名称
     * @return bean对象
     */
    public Object getBean(String beanName) {
        if (StrUtil.isEmpty(beanName)) {
            return null;
        }

        // 不存在beanDefinition,则肯定不是bean
        if (!beanDefinitionMap.containsKey(beanName)) {
            throw new NoSuchBeanDefinitionException("没有对应的bean定义");
        }

        BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

        // 创建对象
        if (beanDefinition.getScope().equals(BeanDefinition.SINGLETON)) {

            // 获取单例
            Object singletonBean = singletonPool.get(beanName);
            if (singletonBean == null) {
                singletonBean = createBean(beanName, beanDefinition);
                singletonPool.put(beanName, singletonBean);
            }
            return singletonBean;
        } else {

            // 获取多例
            Object prototype = createBean(beanName, beanDefinition);
            return prototype;
        }
    }

    /**
     * 扫描方法
     *
     * @param clazz 配置类
     */
    private void scan(Class clazz) {

        // 获取扫描路径
        String basePackage = doGetScanPackage(clazz);
        if (StrUtil.isEmpty(basePackage)) {
            return;
        }

        // 从电脑磁盘中加载文件,并且扫描
        doLoadClassFromDiskAndScan(basePackage);
    }

    /**
     * 从磁盘中加载class,并且扫描
     *
     * @param basePackage 扫描路径
     */
    private void doLoadClassFromDiskAndScan(String basePackage) {

        // 将Java包名转换为电脑路径名
        basePackage = basePackage.replace(".", "/");

        // 加载
        ClassLoader classLoader = MyApplicationContext.class.getClassLoader();
        URL resource = classLoader.getResource(basePackage);
        File file = new File(resource.getFile());
        if (file.isDirectory()) {
            for (File listFile : file.listFiles()) {

                // 获取全限定名
                String fullyQualifiedClassName = transferToFullyQualifiedClassName(listFile);

                try {
                    Class<?> beanClass = classLoader.loadClass(fullyQualifiedClassName);
                    if (!beanClass.isAnnotationPresent(Component.class)) {
                        continue;
                    }

                    // 记录Bean信息
                    try {
                        doRecordBeanInfo(beanClass);
                    } catch (NoSuchMethodException e) {
                        e.printStackTrace();
                    } catch (InvocationTargetException e) {
                        e.printStackTrace();
                    } catch (InstantiationException e) {
                        e.printStackTrace();
                    } catch (IllegalAccessException e) {
                        e.printStackTrace();
                    }

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

    /**
     * 过滤,并且记录Bean信息
     */
    private void doRecordBeanInfo(Class<?> beanClass) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {

        // 获取bean的名字
        String beanName = doGetBeanName(beanClass);

        // 记录BeanPostProcessor
        if (BeanPostProcessor.class.isAssignableFrom(beanClass)) {
            BeanPostProcessor instance = (BeanPostProcessor) beanClass.getConstructor().newInstance();
            beanPostProcessorList.add(instance);
        }

        BeanDefinition beanDefinition = new BeanDefinition();
        beanDefinition.setType(beanClass);
        if (beanClass.isAnnotationPresent(Scope.class)) {
            Scope scopeAnnotation = beanClass.getAnnotation(Scope.class);
            String value = scopeAnnotation.value();
            beanDefinition.setScope(value);
        }

        // 记录BeanDefinition
        beanDefinitionMap.put(beanName, beanDefinition);
    }

    /**
     * 获取当前Bean的名字
     *
     * @param aClass 要加载的类
     * @return bean的名字
     */
    private String doGetBeanName(Class<?> aClass) {
        Component componentAnnotaion = aClass.getAnnotation(Component.class);
        String beanName = componentAnnotaion.value();
        if (StrUtil.isEmpty(beanName)) {
            beanName = Introspector.decapitalize(aClass.getSimpleName());
        }

        return beanName;
    }

    /**
     * 将电脑磁盘上的文件,转换为AppClassLoader能识别的类全限定名(包 + 类名)
     *
     * <p>
     * 由于JVM的类加载器有三种,默认加载用户自定义class文件的,其实是ApplicationClassLoader
     * 该类加载器所能识别的,其实是包名
     * </p>
     *
     * @param listFile 电脑磁盘上的文件
     */
    private String transferToFullyQualifiedClassName(File listFile) {
        String absolutePath = listFile.getAbsolutePath();
        absolutePath = absolutePath.substring(absolutePath.indexOf("org"), absolutePath.indexOf(".class"));
        absolutePath = absolutePath.replace("\\", ".");
        return absolutePath;
    }

    /**
     * 获取包扫描路径
     *
     * @param clazz 配置类
     * @return 扫描路径
     */
    private String doGetScanPackage(Class clazz) {
        if (clazz.isAnnotationPresent(ComponentScan.class)) {
            ComponentScan componentScanAnnotation = (ComponentScan) clazz.getAnnotation(ComponentScan.class);
            return componentScanAnnotation.value();
        }

        return null;
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/875346.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【100天精通python】Day34:使用python操作数据库_ORM(SQLAlchemy)使用

目录 专栏导读 1 ORM 概述 2 SQLAlchemy 概述 3 ORM&#xff1a;SQLAlchemy使用 3.1 安装SQLAlchemy&#xff1a; 3.2 定义数据库模型类&#xff1a; 3.3 创建数据表&#xff1a; 3.4 插入数据&#xff1a; 3.5 查询数据&#xff1a; 3.6 更新数据&#xff1a; 3.7 删…

24届最新计算机毕业设计选题推荐 -计算机专业毕业设计题目参考大全

大家好&#xff0c;我们是竹林可以&#xff08;计算机毕ye设ji代做团队&#xff09;&#xff0c;大四的同学马上要开始毕业设计开题啦&#xff0c;大家要好好准备哦。 学长学姐们给大家详细整理了计算机毕设最新选题&#xff0c;我们专注毕ye设ji多年&#xff0c;积累了丰富的…

【设计模式——学习笔记】23种设计模式——状态模式State(原理讲解+应用场景介绍+案例介绍+Java代码实现)

文章目录 案例引入介绍基本介绍登场角色应用场景 案例实现案例一类图实现 案例二&#xff1a;借贷平台源码剖析传统方式实现分析状态修改流程类图实现 案例三&#xff1a;金库警报系统系统的运行逻辑伪代码传统实现方式使用状态模式 类图实现分析问题问题一问题二 总结文章说明…

element-ui的el-dialog,简单的封装。

el-dialog是使用率很高的组件 使用el-dialog很多都是按照文档的例子&#xff0c;用一个变量控制是否显示&#xff0c;再来一个变量控制标题。 如果我这个对话框多个地方使用的话还要创建多个变量&#xff0c;甚至关闭之后还要清空一些变量&#xff0c;应该可以简化一点。我写…

数据结构:力扣OJ题(每日一练)

目录 题一&#xff1a;环形链表 思路一&#xff1a; 题二&#xff1a;复制带随机指针的链表 思路一&#xff1a; 本人实力有限可能对一些地方解释的不够清晰&#xff0c;可以自己尝试读代码&#xff0c;望海涵&#xff01; 题一&#xff1a;环形链表 给定一个链表的头节点…

奥威BI数据可视化工具:报表就是平台,随时自助分析

别的数据可视化工具&#xff0c;报表就只是报表&#xff0c;而奥威BI数据可视化工具&#xff0c;一张报表就约等于一个平台&#xff0c;可随时展开多维动态自助分析&#xff0c;按需分析&#xff0c;立得数据信息。 奥威BI是一款多维立体分析数据的数据可视化工具。它可以帮助…

Camtasia2023最新专业的电脑屏幕录制和视频剪辑软件

Camtasia专业的屏幕录制和视频剪辑软件3000多万专业人士在全球范围内使用Camtasia展示产品&#xff0c;教授课程&#xff0c;培训他人&#xff0c;以更快的速度和更吸引人的方式进行沟通和屏幕分享。使您在Windows和&#xff01;Camtasia是一款功能强大的屏幕录制和视频编辑软件…

办理流量卡也是有条件的,这五种情况就不能办理流量卡!

流量卡资费虽然便宜&#xff0c;但也不是谁都可以办得&#xff0c;以下这几种情况是办不了的&#xff01; 看到网上的流量卡资费便宜&#xff0c;也想随手申请一张&#xff0c;别想得太简单了&#xff0c;流量卡也不是那么好办理的&#xff0c;换句话来讲&#xff0c;办理流量…

【Quarkus技术系列】「云原生架构体系」在云原生时代下的Java“拯救者”是Quarkus,那云原生是什么呢?

云原生时代下的Java"拯救者" 在云原生时代&#xff0c;其实Java程序是有很大的劣势的&#xff0c;以最流行的spring boot/spring cloud微服务框架为例&#xff0c;启动一个已经优化好&#xff0c;很多bean需要lazy load的application至少需要3-4秒时间&#xff0c;内…

Chrome小技巧---多用户登录同一网站不串信息

测试中经常需要用到浏览器需要登录多个账号 但是有一个问题就是会串号 通过添加不同的用户再用这用户登录&#xff0c;就不串号了&#xff1a; 还可以在浏览器的偏好设置中添加启动地址 这样每次打开&#xff0c;就进到设置的地址中了

如何将阿里云WiredTiger引擎的MongoDB物理备份文件恢复至自建数据库

数据库操作一直是一个比较敏感的话题&#xff0c;动不动“删库跑路”&#xff0c;可见数据库操作对于一个项目而言是非常重要的&#xff0c;我们有时候会因为一个游戏的严重bug或者运营故障要回档数据库&#xff0c;而你们刚好使用的是阿里云的Mongodb&#xff0c;那么这篇文章…

用ThreadLocal做链路追踪(演变升级版)

前言 1、ThreadLocal是线程变量&#xff0c;线程之间彼此隔离&#xff0c;天生线程安全。因为它是跟着线程走的&#xff0c;考虑到这点&#xff0c;它很适合做链路追踪&#xff08;TraceId&#xff09; 2、当我们写的接口接收到其它地方&#xff08;可能是前端、也可能是其它服…

python如何开发一个电商进销存管理系统?

让我们来看一下题主的需求&#xff1a; 管理公司的淘宝天猫平台&#xff0c;后端仓库&#xff0c;采购进行数据同步。其中最主要的还是要对接淘宝API &#xff0c;实现实时订单的通知&#xff0c;同步淘宝订单&#xff0c;管理买家信息&#xff0c;发货&#xff0c;财务统计等…

亚马逊举报差评有什么作用?有没有可以举报差评的软件?

在亚马逊上举报差评具有以下作用&#xff1a; 1、维护信誉和公平性&#xff1a; 举报差评有助于维护亚马逊市场的信誉和公平性。虚假或不当的差评可能会误导其他消费者&#xff0c;影响他们做出购买决策。通过举报这些问题&#xff0c;可以确保评价体现真实的用户体验&#xf…

【SpringCloud技术专题】「Resilience4j入门指南」(1)轻量级熔断框架的入门指南

基础介绍 Resilience4j是一款轻量级&#xff0c;易于使用的容错库&#xff0c;其灵感来自于Netflix Hystrix&#xff0c;但是专为Java 8和函数式编程而设计。轻量级&#xff0c;因为库只使用了Vavr&#xff0c;它没有任何其他外部依赖下。相比之下&#xff0c;Netflix Hystrix…

Beats:使用 Filebeat 将 golang 应用程序记录到 Elasticsearch - 8.x

毫无疑问&#xff0c;日志记录是任何应用程序最重要的方面之一。 当事情出错时&#xff08;而且确实会出错&#xff09;&#xff0c;我们需要知道发生了什么。 为了实现这一目标&#xff0c;我们可以设置 Filebeat 从我们的 golang 应用程序收集日志&#xff0c;然后将它们发送…

大数据培训前景怎么样?企业需求量大吗

大数据行业对大家来说并不陌生&#xff0c;大数据行业市场人才需求量大&#xff0c;越早入行越有优势&#xff0c;发展机会和上升空间等大。不少人通过大数据培训来提升自己的经验和自身技术能力&#xff0c;以此来获得更好的就业机会。 2023大数据培训就业前景怎么样呢?企业需…

落地大模型应知必会(3): 如何构建多任务的LLM应用

编者按&#xff1a;今年以来&#xff0c;大语言模型(LLM)已被广泛应用于各种自然语言处理任务&#xff0c;也越来越多地被用于构建复杂的语言应用。但是构建多任务的 LLM 应用仍面临一定的挑战&#xff0c;需要解决任务组合和调控等问题。 本文内容介绍了构建多任务 LLM 应用可…

win10中Docker安装、构建镜像、创建容器、Vscode连接实例

Docker方便一键构建项目所需的运行环境&#xff1a;首先构建镜像(Image)。然后镜像实例化成为容器(Container)&#xff0c;构成项目的运行环境。最后Vscode连接容器&#xff0c;方便我们在本地进行开发。下面以一个简单的例子介绍在win10中实现&#xff1a;Docker安装、构建镜像…

STM32--TIM定时器(2)

文章目录 输出比较PWM输出比较通道参数计算舵机简介直流电机简介TB6612 PWM基本结构PWM驱动呼吸灯PWM驱动舵机PWM控制电机 输出比较 输出比较&#xff0c;简称OC&#xff08;Output Compare&#xff09;。 输出比较的原理是&#xff0c;当定时器计数值与比较值相等或者满足某种…