jjzjj

Spring源码系列:初探底层,手写Spring

努力的小雨 2023-04-27 原文

前言

在学习Spring框架源码时,记住一句话:源码并不难,只需要给你各种业务场景或者项目经理,你也能实现自己的Spring。虽然你的实现可能无法与开源团队相媲美,但是你肯定可以实现一个0.0.1版本。因此,初次阅读源码时,不要陷入太深的细节中。先了解大体逻辑,再仔细研读。

实现功能

本文将带领大家实现一个简易版的Spring框架,并介绍以下功能点:

  1. 了解Spring的底层源码启动过程
  2. 了解BeanDefinition的概念
  3. 了解Spring解析配置类等底层源码工作流程
  4. 了解依赖注入,Aware回调等底层源码工作流程
  5. 了解Spring AOP的底层源码工作流程

以上功能点将使我们对Spring框架的实现有所了解,但我们并不会一下子实现整个Spring框架的业务。我们将从上述功能点入手,通过手写模拟Spring框架来实现这些功能。

首先,我们像使用Spring一样,传入配置类获取applicationContext,再通过getBean方法获取具体对象。最后,我们调用方法并打印日志。如果你对这些基本流程不熟悉,可以查看我的入门系列文章:Spring入门系列:浅析知识点

详细流程如下:

  1. 解析配置类上的ComponentScan注解,获取扫描的基本路径
  2. 开始解析各个被扫描到的文件,是否是需要被Spring管理,如果是则暂存到list集合中
  3. 开始遍历被Spring管理list集合,解析各个类上的注解,比如是否是懒加载,然后将这些属性都封装到applicationContext中的以beanName为key的BeanDefineMap中
  4. 针对已经解析好的bean定义进行创建对象并实例化,并将其放入以beanName为key的singletonMap实例化缓存池中。
  5. 在实例化时,如果发现有依赖注入的对象,则将实例化缓存池中的对象存入。如果缓存池中没有该对象,则进行创建后再注入。
  6. 判断对象是否实现了各个Aware接口,如果实现,则进行回调。
  7. 判断对象是否属于增强类。在这里,我们模拟了事务注解。如果有事务注解,则创建一个代理对象,并为所有方法增加拦截器。然后将该代理对象存入单例缓存池中。

详细解析

项目结构

基本路径:com.user目录

各个注解及上下文类:config目录

需要被管理的Bean:service目录

启动类:com.user根目录

源码分析

@Component
public class UserService implements ApplicationContextAware, BeanNameAware {

    @AutoWired
    ServiceDemo serviceDemo;

    private XiaoyuApplicationContext applicationContext;
    private String beanName;

    public void test() {
        serviceDemo.say();
//        System.out.println(serviceDemo);
        System.out.println("userService:"+applicationContext.getBean("userService"));
        System.out.println("beanName:"+beanName);
    }

    @Override
    public void setApplicationContext(XiaoyuApplicationContext applicationContext) {
        this.applicationContext = applicationContext;
    }

    @Override
    public void setBeanName(String beanName) {
        this.beanName = beanName;
    }
}

UserService类主要用于测试是否Spring已经管理了相关对象并生成了代理对象,是我们的日常业务类,我们仔细看下XiaoyuApplicationContext类,主要的流程在这边:

public class XiaoyuApplicationContext {


    //配置类
    private Class config;
    //初始的bean定义
    private Map<String,BeanDefinition> beanDefineMap = new HashMap<>();
    //单例缓存池
    private Map<String,Object> singleBean = new HashMap<>();

    public XiaoyuApplicationContext(Class myDemoConfigClass) {
        config = myDemoConfigClass;
        //解析配置类
        scan();

    }

    public void scan(){
        ComponentScan declaredAnnotation = (ComponentScan) config.getDeclaredAnnotation(ComponentScan.class);
        String value = declaredAnnotation.basePackages();
        doScan(value);
        //将bean定义Map生成具体的Bean对象
        beanDefineMap.entrySet().stream().forEach(item->{
            String beanName = item.getKey();
            BeanDefinition beanDefinition = item.getValue();
            if (!beanDefinition.isLazy() && "singleton".equals(beanDefinition.getScope())) {
                Object bean = createBean(beanName);
                singleBean.put(beanName,bean);
            }
        });
    }

    /**
     *     解析配置类
     */
    private void doScan(String value) {
        String path = value.replace(".","/");
        //正常走文件解析
        ClassLoader classLoader = this.getClass().getClassLoader();
        URL resource = classLoader.getResource(path);
        File file = new File(resource.getFile());

        List<File> classFile = new ArrayList<>();
        //简单点直接双层解析即可
        if (file.isDirectory()) {
            for (File f : file.listFiles()) {
                if (f.isDirectory()) {
                    for (File f1 : f.listFiles()) {
                        if (!f1.isDirectory()) {
                            classFile.add(f1);
                        }
                    }
                } else {
                    classFile.add(f);
                }
            }
        }
        //遍历所有解析文件
        for (File cFile : classFile) {
            String absolutePath = cFile.getAbsolutePath();
            String className = absolutePath.substring(absolutePath.indexOf("com"), absolutePath.indexOf(".class"))
                    .replace("\\", ".");

            try {
                Class<?> clazz = classLoader.loadClass(className);
                //是否需要被Spring管理
                if (clazz.isAnnotationPresent(Component.class)) {
                    //将bean上的注解封装到bean定义中
                    BeanDefinition beanDefinition = new BeanDefinition();
                    beanDefinition.setType(clazz);
                    beanDefinition.setLazy(clazz.isAnnotationPresent(Lazy.class));
                    if (clazz.isAnnotationPresent(Scope.class)) {
                        beanDefinition.setScope(clazz.getAnnotation(Scope.class).value());
                    } else {
                        beanDefinition.setScope("singleton");
                    }

                    String beanName = clazz.getAnnotation(Component.class).value();
                    if (beanName.isEmpty()) {
                        //如果不设置beanName会默认生产唯一一个name,Spring底层也是这样做的
                        beanName = Introspector.decapitalize(clazz.getSimpleName());
                    }

                    beanDefineMap.put(beanName, beanDefinition);

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

        }
    }

    public Object createBean(String beanName){

        BeanDefinition beanDefinition = beanDefineMap.get(beanName);
        Class type = beanDefinition.getType();

        try {
            Object instance = type.newInstance();
            //属性填充,依赖注入
            populateBean(instance);

            if (instance instanceof ApplicationContextAware){
                ((ApplicationContextAware) instance).setApplicationContext(this);
            }
            if (instance instanceof BeanNameAware) {
                ((BeanNameAware) instance).setBeanName(beanName);
            }
            //是否需要AOP增强
            if (type.isAnnotationPresent(Transaction.class)) {
                Enhancer enhancer = new Enhancer();
                enhancer.setSuperclass(type);
                //简单的方法切面
                enhancer.setCallback(new MethodInterceptor() {
                    @Override
                    public Object intercept(Object proxy, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                        //开启事务,关闭自动提交
                        System.out.println("事务已开启");
                        Object res = method.invoke(instance, objects);
                        //提交事务
                        System.out.println("事务已提交");
                        return res;
                    }
                });
                return enhancer.create();
            }
            return instance;
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

        return null;
    }

    private void populateBean(Object instance) {
        Field[] declaredFields = instance.getClass().getDeclaredFields();

        Arrays.stream(declaredFields).forEach(item->{
            //寻找注入点
            if (item.isAnnotationPresent(AutoWired.class)) {
                Object bean = getBean(item.getName());
                item.setAccessible(true);
                try {
                    item.set(instance,bean);
                } catch (IllegalAccessException e) {
                    e.printStackTrace();
                }
            }
        });

    }


    public Object getBean(String beanName){
        if (!beanDefineMap.containsKey(beanName)) {
           throw new NullPointerException();
        }
        if ("singleton".equals(beanDefineMap.get(beanName).getScope())) {
            if (singleBean.containsKey(beanName)) {
                return singleBean.get(beanName);
            } else {
                Object bean = createBean(beanName);
                singleBean.put(beanName,bean);
                return bean;
            }
        }
        return createBean(beanName);
    }
}

以上即为整个流程的基本梳理。我们在实现过程中没有涉及Bean循环依赖以及其他各种创建缓存,但Spring在实现Bean的创建过程中确实用到了各种本地缓存和同步锁(synchronized)。在学习源码时,不要太关注这些细节。首先要理解整个流程,再深入研究。

结语

最后,我们在gitee上提供了项目源码。如果需要,可以查看spring-xiaoyu。虽然我认为写一遍自己的代码更好,因为这是最简单的流程,有助于理解Spring源码。

有关Spring源码系列:初探底层,手写Spring的更多相关文章

  1. python - 如何使用 Ruby 或 Python 创建一系列高音调和低音调的蜂鸣声? - 2

    关闭。这个问题是opinion-based.它目前不接受答案。想要改进这个问题?更新问题,以便editingthispost可以用事实和引用来回答它.关闭4年前。Improvethisquestion我想在固定时间创建一系列低音和高音调的哔哔声。例如:在150毫秒时发出高音调的蜂鸣声在151毫秒时发出低音调的蜂鸣声200毫秒时发出低音调的蜂鸣声250毫秒的高音调蜂鸣声有没有办法在Ruby或Python中做到这一点?我真的不在乎输出编码是什么(.wav、.mp3、.ogg等等),但我确实想创建一个输出文件。

  2. ruby-on-rails - 使用一系列等级计算字母等级 - 2

    这里是Ruby新手。完成一些练习后碰壁了。练习:计算一系列成绩的字母等级创建一个方法get_grade来接受测试分数数组。数组中的每个分数应介于0和100之间,其中100是最大分数。计算平均分并将字母等级作为字符串返回,即“A”、“B”、“C”、“D”、“E”或“F”。我一直返回错误:avg.rb:1:syntaxerror,unexpectedtLBRACK,expecting')'defget_grade([100,90,80])^avg.rb:1:syntaxerror,unexpected')',expecting$end这是我目前所拥有的。我想坚持使用下面的方法或.join,

  3. ruby-on-rails - 带 Spring 锁的 Rails 4 控制台 - 2

    我正在使用Ruby2.1.1和Rails4.1.0.rc1。当执行railsc时,它被锁定了。使用Ctrl-C停止,我得到以下错误日志:~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`gets':Interruptfrom~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.2/lib/spring/client/run.rb:47:in`verify_server_version'from~/.rvm/gems/ruby-2.1.1/gems/spring-1.1.

  4. 【鸿蒙应用开发系列】- 获取系统设备信息以及版本API兼容调用方式 - 2

    在应用开发中,有时候我们需要获取系统的设备信息,用于数据上报和行为分析。那在鸿蒙系统中,我们应该怎么去获取设备的系统信息呢,比如说获取手机的系统版本号、手机的制造商、手机型号等数据。1、获取方式这里分为两种情况,一种是设备信息的获取,一种是系统信息的获取。1.1、获取设备信息获取设备信息,鸿蒙的SDK包为我们提供了DeviceInfo类,通过该类的一些静态方法,可以获取设备信息,DeviceInfo类的包路径为:ohos.system.DeviceInfo.具体的方法如下:ModifierandTypeMethodDescriptionstatic StringgetAbiList​()Obt

  5. UE4 源码阅读:从引擎启动到Receive Begin Play - 2

    一、引擎主循环UE版本:4.27一、引擎主循环的位置:Launch.cpp:GuardedMain函数二、、GuardedMain函数执行逻辑:1、EnginePreInit:加载大多数模块int32ErrorLevel=EnginePreInit(CmdLine);PreInit模块加载顺序:模块加载过程:(1)注册模块中定义的UObject,同时为每个类构造一个类默认对象(CDO,记录类的默认状态,作为模板用于子类实例创建)(2)调用模块的StartUpModule方法2、FEngineLoop::Init()1、检查Engine的配置文件找出使用了哪一个GameEngine类(UGame

  6. 阿里云RDS——产品系列概述 - 2

    基础版云数据库RDS的产品系列包括基础版、高可用版、集群版、三节点企业版,本文介绍基础版实例的相关信息。RDS基础版实例也称为单机版实例,只有单个数据库节点,计算与存储分离,性价比超高。说明RDS基础版实例只有一个数据库节点,没有备节点作为热备份,因此当该节点意外宕机或者执行重启实例、变更配置、版本升级等任务时,会出现较长时间的不可用。如果业务对数据库的可用性要求较高,不建议使用基础版实例,可选择其他系列(如高可用版),部分基础版实例也支持升级为高可用版。基础版与高可用版的对比拓扑图如下所示。优势 性能由于不提供备节点,主节点不会因为实时的数据库复制而产生额外的性能开销,因此基础版的性能相对于

  7. spring.profiles.active和spring.profiles.include的使用及区别说明 - 2

    转自:spring.profiles.active和spring.profiles.include的使用及区别说明下文笔者讲述spring.profiles.active和spring.profiles.include的区别简介说明,如下所示我们都知道,在日常开发中,开发|测试|生产环境都拥有不同的配置信息如:jdbc地址、ip、端口等此时为了避免每次都修改全部信息,我们则可以采用以上的属性处理此类异常spring.profiles.active属性例:配置文件,可使用以下方式定义application-${profile}.properties开发环境配置文件:application-dev

  8. ruby-on-rails - Spring 不起作用。 [未初始化常量 Spring::SID::DL] - 2

    我无法运行Spring。这是错误日志。myid-no-MacBook-Pro:myid$spring/Users/myid/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/spring-0.0.10/lib/spring/sid.rb:17:in`fiddle_func':uninitializedconstantSpring::SID::DL(NameError)from/Users/myid/.rbenv/versions/1.9.3-p484/lib/ruby/gems/1.9.1/gems/spring-0.0.10/li

  9. ruby - 从结束值创建一系列字符串 - 2

    我使用irb。下面是我写的代码。“斧头”..“bc”我期待"ax""ay""az""ba"bb""bc"但结果只是“斧头”..“bc”我该如何纠正?谢谢。 最佳答案 >puts("ax".."bc").to_aaxayazbabbbc 关于ruby-从结束值创建一系列字符串,我们在StackOverflow上找到一个类似的问题: https://stackoverflow.com/questions/7617092/

  10. elasticsearch源码关于TransportSearchAction【阶段三】 - 2

    1.回顾.TransportServicepublicclassTransportServiceextendsAbstractLifecycleComponentTransportService:方法:1publicfinalTextendsTransportResponse>voidsendRequest(finalTransport.Connectionconnection,finalStringaction,finalTransportRequestrequest,finalTransportRequestOptionsoptions,TransportResponseHandlerT>

随机推荐