[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定义的mapprivate 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); // beanDefinitionpreInstantiateSingletons(); // 实例化单例--->单例池}/*** 遍历保存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();// 这里是相对路径,对的是classPathURL 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--->scopeif (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 {@Overridepublic Object postProcessorBeforeInitialization(Object bean, String beanName) {System.out.println("before -->" + bean);return bean;}// 代理是在 after 方法里面实现的。@Overridepublic 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) {// 使用自定义SpringXXXApplicationContext 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属性@Autowiredprivate OrderService orderService;private User defaultUser; // MySQL ---> User ----> defaultUserpublic void test() {System.out.println(orderService);System.out.println(defaultUser);}@Overridepublic void afterPropertiesSet() throws Exception {// MySQL ---> User ----> defaultUserdefaultUser = 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.mzph.cn/pingmian/9331.shtml

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

相关文章

07-面向对象编程(基础部分)

学习java最核心最重要的就是要理解面向对象。 1. 类与对象 1.1 介绍 类是抽象的&#xff0c;概念的&#xff0c;代表一类事物&#xff0c;比如人类&#xff0c;猫类&#xff0c;狗类.., 即它是数据类型。 对象是具体的&#xff0c;实际的&#xff0c;代表一个具体事物,&…

Leetcode227. 基本计算器 II

Every day a Leetcode 题目来源&#xff1a;227. 基本计算器 II 解法1&#xff1a;单栈模拟 因为没有括号&#xff0c;所以可以简化成单栈。 代码&#xff1a; class Solution { public:int calculate(string s){vector<int> nums;char preSign ;int num 0;int n …

可以修改linux 文件的所有者吗

是的&#xff0c;你可以修改Linux文件的所有者。这通常是通过chown&#xff08;change owner&#xff09;命令来完成的。以下是使用chown命令的一些示例&#xff1a; 更改文件的所有者&#xff1a; chown new_owner file.txt这条命令会将file.txt的所有者更改为new_owner。 同…

如果数据给的是树形 转好的树形结构并且是有两个二级children的话 该如何写?

第一我们要自己写一个children 并且张数据里面的所要渲染的二级进行赋值 赋给我们新建的children 以下是代码转树形赋值 organ().then(function (res) {console.log(res); // 成功回调// setLists(res.data.data)res.data.data res.data.data.map((obj) > ({...obj, // …

Kafka 面试题(三)

1. 简述什么是 Kafka 的 Broker &#xff1f; Kafka的Broker是Kafka集群中的一个核心组件&#xff0c;扮演着消息代理的角色。它是生产者和消费者之间的中间件&#xff0c;负责接收、存储和分发消息。具体来说&#xff0c;Broker接收来自生产者的消息&#xff0c;并将其持久化…

(十二)C语言的结构体

一.结构体 数组&#xff1a;允许定义可存储相同类型数据项的变量&#xff0c; 结构&#xff1a;另一种用户自定义的可用的数据类型&#xff0c;它允许您存储不同类型的数据项。 特点&#xff1a;结构体中的数据成员可以是基本数据类型&#xff08;如 int、float、char 等&…

Android 系统版本与SDK API对应关系-2024.5

官网地址&#xff1a;https://developer.android.google.cn/tools/releases/platforms?hlth

Vue 监控变化watch

1、watch 监控某个定义的属性的值,如果想要深层次的监听则需要定义deep:true 2、监控多层次单个元素使用"b.aa" 3、监控多层次所有的值定义deep:true ******data:{a:true,b:{aa:1,bb:2 }}*****catch:{a:{handler(newavlue,oldvalue){}},//监控b里面的aab.aa:{h…

经典分类网络LeNet5和VGG16项目:实现CIFAR10分类

CIFAR10分类 v1&#xff1a;LeNet5&#xff1a;2cnn3fc 可视化结果 精确率 损失 最佳 v2&#xff1a;LeNet5&#xff1a;3cnn2fc 可视化结果 精确率 损失 最佳 v3&#xff1a;LeNet5&#xff1a;2cnnbnres3fc 可视化结果 精确率 损失 最佳 v4&#xff1a;VG…

Sarcasm detection论文解析 |基于语义知识和辅助信息增强讽刺检测方法

论文地址 论文地址&#xff1a;https://www.sciencedirect.com/science/article/abs/pii/S0306457322000139?via%3Dihub 论文首页 笔记框架 基于语义知识和辅助信息增强讽刺检测方法 &#x1f4c5;出版年份:2022 &#x1f4d6;出版期刊:Information Processing & Managem…

webpack进阶 -- 自定义Plugin,Loader封装打包优化

介绍 Webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。在 Webpack 处理应用程序时&#xff0c;它会在内部构建一个依赖图(dependency graph)&#xff0c;这个依赖图对应映射到项目所需的每个模块&#xff0c;并生成一个或多个 bundle。在这个过程中…

Docker-compsoe部署prysm-beacon-chain + geth服务(geth版本v1.14.0)

1、创建目录结构 ~ # mkdir -p /data/docker-compose/eth ~ # cd /data/docker-compose/eth /data/docker-compose/eth# mkdir beacondata eth ethdata prysm2、编写prysm-beacon-chain Dockerfile和启动脚本文件 /data/docker-compose/eth# vim Dockerfile /data/docker-…

Git的系统级设置

一、如何查看当前系统登录的GIt用户信息 1.看全局Git配置文件 cat ~/.gitconfig 2.查看系统级Git配置文件 cat /etc/gitconfig 3.使用Git命令查看配置的用户名和电子邮件地址 git config --global user.name git config --global user.email 4.如果你想查看所有Git配置&…

(delphi11最新学习资料) Object Pascal 学习笔记---第11章第2节 ( 多个接口与方法别名)

11.2.3 多个接口与方法别名 ​ 接口的另一个非常重要的特点是一个类可以实现多个接口。下面的 TAthlete 类就演示了这一点&#xff0c;该类同时实现了 IWalker 和 IJumper 接口&#xff1a; TAthlete class(TInterfacedObject, IWalker, IJumper) privateFJumpImpl: TJumpe…

旅行卡使用秘籍:告别出游烦恼

在现代社会&#xff0c;随着人们生活水平的不断提高&#xff0c;旅游已经成为越来越多人的休闲方式。而为了让旅行更加方便、实惠&#xff0c;各种旅游卡应运而生。然而&#xff0c;很多人在使用旅游卡时&#xff0c;对于其使用规则并不是非常清楚。接下来&#xff0c;我们就来…

如何把公章盖在电子档文件上?

将公章盖在电子档文件上&#xff0c;尤其是确保其法律效力和安全性&#xff0c;通常涉及以下步骤&#xff1a; 准备工作 获取合法的电子公章&#xff1a;确保你拥有公司或机构正式授权的电子公章图像&#xff0c;且该图像经过了必要的加密或数字签名处理&#xff0c;以确保其…

使用海外云手机为亚马逊店铺引流

在全球经济一体化的背景下&#xff0c;出海企业与B2B外贸企业愈发重视海外市场的深耕&#xff0c;以扩大市场份额。本文旨在探讨海外云手机在助力亚马逊店铺提升引流效果方面的独特作用与优势。 海外云手机&#xff0c;一种基于云端技术的虚拟手机&#xff0c;能够在单一硬件上…

Centos系统实用运维命令记录(持续更新)

本文记录Centos服务器常用的运维命令&#xff0c;备忘 查询当前内存占用最高(前10)的进程列表和占用比例&#xff0c;进程ID ps -eo pid,comm,%mem,cmd --sort-%mem | head -n 11注: 内存警报时定位问题时非常有用 查询占用某个端口号的进程id lsof -i :9000注: 后面的9000…

Ai绘画工具Stable Diffusion提示词如何使用

Stable Diffusion是一种基于深度学习的AI绘画工具&#xff0c;它可以根据用户提供的提示词&#xff08;prompt&#xff09;生成相应的图像。提示词是引导AI生成图像的关键&#xff0c;正确的使用和调试提示词对于获得满意的结果至关重要。本文将介绍如何使用和调试Stable Diffu…

let命令

let 命令 let 与 var 二者区别&#xff1a; 作用域不同&#xff1a;变量提升&#xff08;Hoisting&#xff09;&#xff1a;临时性死区重复声明&#xff1a; 联系&#xff1a;举例说明&#xff1a; 块级作用域 块级作用域的关键字使用 var&#xff08;无块级作用域&#xff09;…