[Spring框架] 手写Spring

目录

一、背景

二、简易版Spring代码

三、使用自定义Spring代码

四、总结


一、背景

作为一个后端程序员,Spring框架是在开发中必不可少的,相信很多人都学过Spring的底层原理并且看过很多源码。但是对我来说,实操是一个会加深对代码的理解和掌握程度的事情,所以在学习spring原理的时候,跟着视频写了下面的代码。

二、简易版Spring代码

注解里面都会涉及到两个注解,@Rentention 和 @Target,这两个注解的作用分别是:

  • @Retention(RetentionPolicy.RUNTIME): 这个注解指定了注解的保留策略为RUNTIME,意味着这个注解不仅会被编译到class文件中,而且在运行时通过反射还能够被程序读取。这是使得运行时动态处理注解成为可能的关键;

  • @Target(ElementType.TYPE): 这个注解指定了注解的应用目标为TYPE,意味着这个注解只能用于类、接口(包括注解类型)或枚举的声明上。

1、Autowired注解:用于依赖注入

package com.xxx.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

    boolean required() default true;
}

2、Component注解:声明当前类是一个bean对象,由Spring来管理生命周期

这里面value()方法的作用:在使用Component注解的时候可以指定当前bean的name,这里面value的作用是获取beanName。

package com.xxx.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

3、ComponentScan注解:用于指定bean的扫描路径

package com.xxx.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

4、Scope注解:指定当前bean是多例还是单例,多例是prototype,默认是单例

package com.xxx.spring;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

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

    String value() default "";
}

5、BeanDefinition类:保存一个bean的定义

package com.xxx.spring;

public class BeanDefinition {

    private Class clazz;
    private String scope;

    public Class getClazz() {
        return clazz;
    }

    public void setClazz(Class clazz) {
        this.clazz = clazz;
    }

    public String getScope() {
        return scope;
    }

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

6、BeanPostProcessor接口:接口中包含的是简易版的方法,可用于AOP,在生成bean对象以后,根据业务需求对bean对象进行进一步的处理

package com.xxx.spring;

public interface BeanPostProcessor {

    Object postProcessorBeforeInitialization(Object bean, String beanName);

    Object postProcessorAfterInitialization(Object bean, String beanName);
}

7、XXXApplicationContext类

package com.xxx.spring;

import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class XXXApplicationContext {

    // 配置类
    private Class configClass;

    // 保存bean定义的map
    private Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

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

    // 保存bean后处理器,在初始化前后对指定bean进行特定的处理
    private List<BeanPostProcessor> beanPostProcessorList = new ArrayList<>();

    public XXXApplicationContext(Class configClass) {
        this.configClass = configClass;

        scan(configClass); // beanDefinition

        preInstantiateSingletons(); // 实例化单例--->单例池
    }

    /**
     * 遍历保存bean定义的map:
     * 如果当前bean对象是单例,调用createBean方法,并且放入单例池;
     */
    private void preInstantiateSingletons() {
        for(Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            BeanDefinition beanDefinition = entry.getValue();
            String beanName = entry.getKey();
            if (beanDefinition.getScope().equals("singleton")) {
                Object bean = createBean(beanName, beanDefinition);
                singletonObjects.put(beanName, bean);
            }
        }
    }

    /**
     * 创建bean对象
     * 1、根据bean定义创建一个bean的对象;
     * 2、遍历当前对象里面的所有属性,如果当前属性使用了@Autowired注解,调用getBean()方法获取对应的bean对象,把bean对象
     *    赋值给当前属性,其中一定要有filed.setAccessible(true);这行代码,作用是:设置当前字段可访问,这样就可以访问和修改
     *    这个字段的值;
     * 3、初始化前的代码,spring框架里面当前方法返回的是null。如果返回的是对象,就不会继续执行后面的代码,所以要返回null;
     * 4、初始化代码;
     * 5、初始化后的代码:其中代理可以在这里实现;
     * 6、返回创建好的bean对象。
     * @param beanName
     * @param beanDefinition
     * @return
     */
    private Object createBean(String beanName, BeanDefinition beanDefinition) {
        try {

            Class clazz = beanDefinition.getClazz();
            Object instance = clazz.newInstance();

            // 依赖注入,这里Autowired注解如果required = true,如果getBean为null的话,就会报错,需要其他处理逻辑
            for (Field filed : clazz.getDeclaredFields()) {
                if (filed.isAnnotationPresent(Autowired.class)) {
                    String name = filed.getName();
                    Object bean = getBean(name);
                    filed.setAccessible(true);
                    filed.set(instance, bean);
                }
            }

            // 初始化前
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                beanPostProcessor.postProcessorBeforeInitialization(instance, beanName);
            }

            // 初始化
            if (instance instanceof InitializingBean) {
                // 这里的后处理方法可以给defaultUser赋值
                ((InitializingBean)instance).afterPropertiesSet();
            }

            // 初始化后
            for (BeanPostProcessor beanPostProcessor : beanPostProcessorList) {
                beanPostProcessor.postProcessorAfterInitialization(instance, beanName);
            }

            return instance;
        } catch (InstantiationException e) {
            throw new RuntimeException(e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException(e);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 扫描指定路径下面的bean对象
     * 1、从指定的配置类里面获取ComponentScan注解,得到要扫描的路径;
     * 2、使用类加载器获取对应路径下面的所有文件;
     * 3、遍历所有文件:
     *              如果当前文件以.class结尾,那么获取以com开头,以类名结尾的字符串,比如:com/xxx/test/userService
     *              用'.'替换字符串中的'/'
     *              如果当前类里面包含Component注解,
     *                  如果是BeanPostProcessor的实现类,就放到对应的list里面;
     *                  获取Component里面的value,也就是beanName;
     *                  把当前clazz和beanName放到bean定义的map中,同时根据scope注解设置是singleton还是prototype
     * @param configClass
     */
    private void scan(Class configClass) {
        // 解析配置类,获取扫描路径
        ComponentScan annotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
        String path = annotation.value();
        path = path.replace(".", "/");

        // 扫描加了Component注解的类--->生成Bean对象(多例Bean还是单例Bean)
        ClassLoader classLoader = BhlApplicationContext.class.getClassLoader();
        // 这里是相对路径,对的是classPath
        URL resource = classLoader.getResource(path);
        File file = new File(resource.getFile());

        File[] files = file.listFiles();
        for(File f : files) {
            String fileName = f.getAbsolutePath();

            if (fileName.endsWith(".class")) {
                String className = fileName.substring(fileName.indexOf("com"), fileName.indexOf(".class"));
                className = className.replace("/", ".");

                try {
                    Class clazz = classLoader.loadClass(className);

                    if (clazz.isAnnotationPresent(Component.class)) {

                        if (BeanPostProcessor.class.isAssignableFrom(clazz)) {
                            BeanPostProcessor beanPostProcessor = (BeanPostProcessor) clazz.newInstance();
                            beanPostProcessorList.add(beanPostProcessor);
                        }

                        Component component = (Component) clazz.getAnnotation(Component.class);
                        String beanName = component.value();

                        BeanDefinition beanDefinition = new BeanDefinition();
                        beanDefinition.setClazz(clazz);

                        if (clazz.isAnnotationPresent(Scope.class)) {
                            Scope scope = (Scope) clazz.getAnnotation(Scope.class);
                            beanDefinition.setScope(scope.value());
                        } else {
                            beanDefinition.setScope("singleton");
                        }

                        beanDefinitionMap.put(beanName, beanDefinition);
                    }
                } catch (ClassNotFoundException e) {
                    throw new RuntimeException(e);
                } catch (InstantiationException e) {
                    throw new RuntimeException(e);
                } catch (IllegalAccessException e) {
                    throw new RuntimeException(e);
                }

            }
        }
    }

    /**
     * 根据name获取bean对象
     * 判断bean定义map中是否包含当前beanName;
     * 如果当前scope是singleton,从单例池里面获取直接返回;
     * 如果scope是prototype,调用createBean创建对象并返回。
     * @param beanName
     * @return
     */
    public Object getBean(String beanName) { // xxx--->Map--->BeanDefinition--->scope

        if (beanDefinitionMap.containsKey(beanName)) {
            BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);

            if (beanDefinition.getScope().equals("singleton")) {
                Object singleObject = singletonObjects.get(beanName);
                return singleObject;
            } else {
                Object prototypeObject = createBean(beanName, beanDefinition);
                return prototypeObject;
            }
        } else {
            throw new NullPointerException();
        }
    }
}

8、InitializingBean接口

package com.xxx.spring;

public interface InitializingBean {

    void afterPropertiesSet() throws Exception;
}

三、使用自定义Spring代码

1、AppConfig类

package com.xxx.test;

import com.xxx.spring.ComponentScan;

/**
 * ComponentScan里面的路径和JVM类加载器有关
 * JVM类加载器:BootstrapCLassLoader:最顶层的加载类,由 C++实现,通常表示为 null,并且没有父级,主要用来加载 JDK 内部的核心类库
 *                                ( %JAVA_HOME%/lib目录下的 rt.jar、resources.jar、charsets.jar等 jar 包和类)
 *                                 以及被 -Xbootclasspath参数指定的路径下的所有类
 *            ExtensionCLassLoader:主要负责加载 %JRE_HOME%/lib/ext 目录下的 jar 包和类以及被 java.ext.dirs
 *                                 系统变量所指定的路径下的所有类
 *            AppClassLoader:面向我们用户的加载器,负责加载当前应用 classpath 下的所有 jar 包和类。
 */
@ComponentScan("com.xxx.test")
public class AppConfig {
}

2、XXXBeanPostProcessor类

package com.xxx.test;

import com.xxx.spring.BeanPostProcessor;
import com.xxx.spring.Component;

/**
 * 根据业务中的需要对bean对象进行处理,
 * 如果只针对某一个特定的bean进行处理,使用 if 进行条件判断。
 *
 * 这里面方法的返回值是Object,如果需要的话,可以替换之前生成的bean对象。
 */
@Component
public class BhlBeanPostProcessor implements BeanPostProcessor {

    @Override
    public Object postProcessorBeforeInitialization(Object bean, String beanName) {
        System.out.println("before -->" + bean);
        return bean;
    }

    // 代理是在 after 方法里面实现的。
    @Override
    public Object postProcessorAfterInitialization(Object bean, String beanName) {
        System.out.println("after -->" + bean);
        return bean;
    }
}

3、Main类

package com.xxx.test;

import com.xxx.spring.BhlApplicationContext;

public class Main {
    public static void main(String[] args) {
        // 使用自定义Spring
        XXXApplicationContext applicationContext = new XXXApplicationContext(AppConfig.class);

        UserService userService = (UserService) applicationContext.getBean("userService");

        userService.test();
    }
}

4、OrderService类

package com.xxx.test;

import com.xxx.spring.Component;
import com.xxx.spring.Scope;

@Component("orderService")
@Scope("prototype") // prototype指定这是一个多例bean
public class OrderService {
}

5、UserService类

package com.xxx.test;

import com.xxx.spring.Autowired;
import com.xxx.spring.Component;
import com.xxx.spring.InitializingBean;

@Component("userService")
public class UserService implements InitializingBean { // BeanDefinition--->Map<beanName, BeanDefinition对象>--->scope属性

    @Autowired
    private OrderService orderService;

    private User defaultUser; // MySQL ---> User ----> defaultUser

    public void test() {
        System.out.println(orderService);
        System.out.println(defaultUser);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
        // MySQL ---> User ----> defaultUser
        defaultUser = new User();
    }
}

6、User类

  UserService中实现了InitializingBean接口,实现了afterPropertiesSet方法,在该方法里面对bean对象中的其他字段进行了赋值。

package com.xxx.test;

public class User {
}

四、总结

这里只是简单写了一下spring框架里面的几个方法,其中很多逻辑都有欠缺的地方,没有涉及到的地方有很多,比如循环依赖的问题,在springboot中循环依赖涉及到了三个map对象,这里只有一个单例池,但是代码里面把大概的逻辑都讲了一下。

比如在启动Spring的时候,spring如何扫描的bean对象,如何把bean对象放到了容器里面,也稍微涉及了AOP的事情,可以在后处理器中进行实现。

AppConfig是启动类,通常在开发的时候main方法是写在AppConfig里面的,这里类里面指明了bean对象的扫描路径。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Golang——Strconv包

func ParseBool(str string) (value bool, err error) strconv包实现了基本数据类型与其字符串表示的转换&#xff0c;主要有以下常用函数&#xff1a;Atoi()&#xff0c;Itoa()&#xff0c;parse系列函数&#xff0c;format系列函数&#xff0c;append系列函数。 1.1 string与…

6份不用辞职就能赚钱的副业,上班族必看!

在这个经济浪潮中&#xff0c;生活成本的上升与工资增长的缓慢形成了鲜明对比。对于许多上班族来说&#xff0c;寻找额外收入的途径显得尤为迫切。 今天&#xff0c;就让我们一起探索那些适合在业余时间开展的副业&#xff0c;为你的财务自由之路添砖加瓦。 1. 闲鱼二手手机售卖…

SEO之高级搜索指令(三)

初创企业需要建站的朋友看这篇文章&#xff0c;谢谢支持&#xff1a; 我给不会敲代码又想搭建网站的人建议 新手上云 &#xff08;接上一篇。。。。&#xff09; 11、link: link:也是SEO 常用的指令&#xff0c;用来搜索某个url的反向链接&#xff0c;既包括内部链接&#xf…

python编程“常识”【pip安装路径、计算、pycharm中的terminal运行前面的PS修改成自己环境】

一、默认的pip install包路径&#xff1a; pip show pip 二、计算 打开cmd&#xff0c;输入&#xff1a; ipython 例如你要计算2的13次方&#xff1a; ok. 三、pycharm中的terminal运行前面的PS修改成自己环境 未修改前&#xff1a; 修改过程&#xff1a; 打开设置找到too…

渗透之sql注入---宽字节注入

宽字节注入原理&#xff1a; 宽字节注入就是在对用户输入进行处理时&#xff0c;将编码方式改变&#xff0c;当某些关键字符被过滤&#xff08;转义&#xff09;时&#xff0c;我们可以使用其他的编码在被转义的字符前面&#xff0c;这样就可以组成一个新的字符从而来实现绕过…

【备战软考(嵌入式系统设计师)】10 - 软件工程基础

这一部分的内容是概念比较多&#xff0c;不要理解&#xff0c;去感受。 涉及的知识点是嵌入式系统开发和维护的部分&#xff0c;也就是和管理相关的&#xff0c;而不是具体如何进行嵌入式系统开发的细节。 系统开发生命周期 按照顺序有下面几个阶段&#xff0c;我们主要要记…

WPF容器控件之WrapPanel、布局控件

WrapPanel: 换行panel 子元素进行换行&#xff0c;当子元素的宽度或者高度超出了父元素&#xff0c;才进行换行。高度超出父元素的高度 也会另起一列 属性 Orientation布局方式 实例 <WrapPanel Orientation"Horizontal"><Label>C# 是从 C/C 衍生出来的…

zabbix动作执行命令失效不起作用?

1. zabbix在web界面设置完主机组&#xff0c;主机&#xff0c;监控项&#xff0c;触发器&#xff0c;动作之后 监控项监控到了&#xff0c;触发器触发动作&#xff0c;但是执行的指令不起作用 流程 在zabbix-agent端将nginx服务down掉&#xff0c;zabbix会自动监控并执行重启的…

一文了解Memcache内存分配机制及stats参数

一、Memcache内存分配机制 了解memcached必须了解的三个单位&#xff1a;page、slabs、chunk。 1.1、Page Page为内存分配的最小单位,Memcached的内存分配以page为单位&#xff0c;默认情况下一个page是1M&#xff0c;可以通过-I参数在启动时指定。如果需要申请内存 时&#…

NeRF算法

目录 算法介绍 基本原理 1. 体渲染 2. 多层感知机&#xff08;MLP&#xff09; 3. 位置编码 4. 两阶段层次化体采样 实验展示 代码解析 算法介绍 NeRF&#xff08;Neural Radiance Fields&#xff09;是一种用于从2D图像中重建3D场景的神经网络模型。它通过训练一个深度…

sourceTree push失败

新电脑选择commit and push&#xff0c;报错了&#xff0c;不过commit成功&#xff0c;只不过push失败了。 原因是这个&#xff0c;PuTTYs cache and carry on connecting. 这里的ssh选择的是 PuTTY/Plink&#xff0c;本地没有这个ssh密钥&#xff0c;改换成openSSH&#xff…

SRM系统供应链库存协同提升企业服务水平

SRM系统供应链库存协同是一种以提高供应链整体效率和竞争力为目标的管理方法。它涉及到企业与供应商之间的紧密合作&#xff0c;以实现库存优化、成本降低、风险分担和灵活响应市场变化等目标。 一、SRM供应链库存协同的概念和特点 SRM供应链库存协同是指企业与供应商之间通过…

音转文工具,9.8k star! 【送源码】

我们经常会遇到将音频转为文字的情况&#xff0c;比如在开会时录音的会议纪要、上课时录下的老师讲课内容。虽然网上也有一些在线的工具可以将音频转为文字&#xff0c;但是考虑到数据安全和费用问题&#xff0c;使用起来也不是很方便。 今天了不起给大家介绍一款开源工具——…

毕业论文应该怎么写?推荐几款ai写论文工具

时间过的好快&#xff0c;马上又到了一年一度的毕业季了&#xff0c;对于即将毕业的学生来说毕业论文是一道难过的坎&#xff0c;想到自己为了毕业论文熬的夜&#xff0c;掉的头发&#xff0c;真的深有感触。 不过虽然翟博士给大家的毕业论文设了高门槛&#xff0c;但是随着时…

python之装饰器,模块和文件操作和面向对象

1.装饰器详解(重点) 1_1 装饰器 程序运行的时候的记录 -- 日志 在实际工作中&#xff0c;python这样的东西&#xff0c;是放在服务器上运行的 日志其实就是记录下来当前程序的运行&#xff0c;协助我们定位问题 确定问题的方式&#xff08;通过日志、报错信…

宏的优缺点?C++有哪些技术替代宏?(const)权限的平移、缩小

宏的优缺点&#xff1f; 优点&#xff1a; 1.增强代码的复用性。【减少冗余代码】 2.提高性能&#xff0c;提升代码运行效率。 缺点&#xff1a; 1.不方便调试宏。&#xff08;因为预编译阶段进行了替换&#xff09; 2.导致代码可读性差&#xff0c;可维护性差&#xff0…

Java线程池(更新中)

1.线程池介绍 顾名思义&#xff0c;线程池就是管理一系列线程的资源池&#xff0c;其提供了一种限制和管理线程资源的方式。每个线程池还维护一些基本统计信息&#xff0c;例如已完成任务的数量。 总结一下使用线程池的好处&#xff1a; 降低资源消耗。通过重复利用已创建的…

猎头告诉你正确的“离职流程”

往期热门文章&#xff1a; 1&#xff0c;史上最全猎头技能资料&#xff0c;独家最新放送 2&#xff0c;互联网大厂java面试题知识库&#xff08;100万字&#xff09; 3&#xff0c;一线互联网大数据面试题知识库&#xff08;100万字&#xff09; 4&#xff0c;中国猎头公司排行…

SQL优化详解

目录 插入数据 insert的优化&#xff08;少量数据&#xff09; 批量插入 手动事务提交 主键顺序插入 插入大量数据 主键优化 数据组织方式&#xff1a; 页分裂&#xff1a; 主键顺序插入的方式&#xff1a; 主键乱序插入&#xff1a; 页合并&#xff1a; 主键设计…

HTML5/CSS3粒子效果进度条 超炫酷进度条动画源码

特效介绍 之前我已经分享了几款效果很不错的CSS3进度条插件&#xff0c;比如CSS3 Loading进度条加载动画特效、CSS3 3D进度条按钮 18款精美样式。今天我再来分享一款很有特色的HTML5/CSS3进度条应用。这款进度条插件在播放进度过程中出现粒子效果&#xff0c;就像一些小颗粒从…
最新文章