news 2026/5/7 9:01:48

Spring源码的解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Spring源码的解析

大家在spring框架中都知道加注解,还有ioc容器,还有一开始就会加载配置类,然后自动创建对象放入ioc中,这些是怎么完成的呢?

下面我们来自己定义一下spring:

先来创建主启动类:

package com.code.liyunmiao.service; import com.code.liyunmiao.spring.liyunmiaocontext; public class Test { public static void main(String[] args) { liyunmiaocontext context = new liyunmiaocontext(configclass.class); Userservice userservice=(Userservice) context.getBean("Userservice"); } }

liyunmiaocontext就是容器类:

package com.code.liyunmiao.spring; import com.code.liyunmiao.service.Userservice; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class liyunmiaocontext { private Class configclass; public liyunmiaocontext(Class configclass) { this.configclass=configclass; if(configclass.isAnnotationPresent(ComponentScan.class)) { ComponentScan componentScan=(ComponentScan)configclass.getAnnotation(ComponentScan.class); String value=componentScan.value(); } } public Object getBean(String beanname) { return null; } }

当在主启动类里面run的时候也就是 liyunmiaocontext context = new liyunmiaocontext(configclass.class); 这一步,就是相当于new这个容器,然后也会正常的去执行他的构造函数也就是对应类的构造函数就会执行(以及父类构造函数链)。

再看配置类:

package com.code.liyunmiao.service; import com.code.liyunmiao.spring.ComponentScan; @ComponentScan("com.code.liyunmiao.service") public class configclass { }

配置类上面componentscan决定了要扫描哪些目录文件下。

自定义component注解:

package com.code.liyunmiao.spring; public @interface Component { String value() default ""; }

自定义componentscan注解:

package com.code.liyunmiao.spring; public @interface ComponentScan { String value() default ""; }

userService类:

package com.code.liyunmiao.service; import com.code.liyunmiao.spring.Component; @Component("Userservice") public class Userservice { }

下面直接来看完整版:

这是容器:

package com.code.liyunmiao.spring; import com.code.liyunmiao.service.Definationbean; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.net.URL; import java.security.KeyStore; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; /** * 手写版「应用上下文」:从配置类读取 {@link ComponentScan},在 classpath 上找到对应包目录, * 遍历其中的 .class,再用 {@link Component} 判断是否注册为 Bean,并把「Bean 定义」放进 map(思路接近 Spring 的组件扫描)。 */ public class liyunmiaocontext { /** 传入的配置类(如 configclass.class),作为读取 @ComponentScan 的入口。 */ private Class configclass; /** * Bean 定义表:key 为 {@link Component#value()}(Bean 名称),value 为封装了类型、作用域的 {@link Definationbean}。 * 扫描阶段只登记定义,真正创建实例在 {@link #getBean(String)} 中按作用域处理。 */ private final ConcurrentHashMap<String, Definationbean> map = new ConcurrentHashMap<>(); /** * 单例 Bean 的已创建实例缓存(仅当 {@link Definationbean#getScope()} 为 singleton 时使用)。 */ private final ConcurrentHashMap<String, Object> singletonObjects = new ConcurrentHashMap<>(); public liyunmiaocontext(Class configclass) { this.configclass = configclass; // 仅在配置类上声明了 @ComponentScan 时才做包扫描;注解需带 @Retention(RUNTIME) 才能在运行时被反射读到。 if (configclass.isAnnotationPresent(ComponentScan.class)) { // 取出 @ComponentScan("com.xxx.yyy") 的 value:要扫描的根包(点分包名)。 ComponentScan componentScan = (ComponentScan) configclass.getAnnotation(ComponentScan.class); String path = componentScan.value(); // ClassLoader#getResource 使用「目录式」路径:包名中的 . 换成 /,对应磁盘上 com/xxx/yyy 层级。 path = path.replace('.', '/'); // 用加载本类的 ClassLoader,在 classpath 上解析该包对应的 URL,再转成 File 以便列目录。 ClassLoader classLoader = liyunmiaocontext.class.getClassLoader(); // 部分环境下目录资源需以 / 结尾才能解析到 URL,故先尝试 path,再尝试 path/。 URL resource = classLoader.getResource(path); if (resource == null && !path.endsWith("/")) { resource = classLoader.getResource(path + "/"); } if (resource == null) { throw new IllegalStateException("classpath 上未找到包目录,请检查 @ComponentScan 的 value 是否与编译输出目录一致: " + path); } File file = new File(resource.getFile()); // 若是真实目录(开发时常见:target/classes 下),可列出该包内所有条目;若在 jar 内则需另行处理(JarURLConnection 等)。 if (file.isDirectory()) { File[] files = file.listFiles(); if (files == null) { return; } // 只遍历当前包目录下「一层」条目;子包(子目录)需递归或其它扫描方式才能覆盖。 for (File f : files) { String filename = f.getAbsolutePath(); if (filename.endsWith(".class")) { // filename 是绝对路径(如 ...\classes\com\code\...\Foo.class)。 // ClassLoader#loadClass 需要的是 JVM「二进制类名」(如 com.code.xxx.Foo),不是磁盘路径; // 此处从路径中截取从 "com" 开始到 .class 前的片段,再替换为点分全限定名。 // 注意:依赖路径中首次出现的 "com" 即包名起点;包名不以 com 开头或路径含多个 com 时可能截错。 String classname = filename.substring(filename.indexOf("com"), filename.lastIndexOf(".class")); classname = classname.replace('\\', '.'); try { // 变量名仅作占位;此处得到的是「类元数据」,后续用其读 @Component、@Scope。 Class<?> Class = classLoader.loadClass(classname); // 仅当类上标了 @Component 时,才登记为 Bean 定义。 if (Class.isAnnotationPresent(Component.class)) { Component component = Class.getAnnotation(Component.class); String beanname = component.value(); Definationbean definationbean = new Definationbean(); definationbean.setType(Class); // 若有 @Scope 则取注解值,否则与 Spring 类似默认为 singleton。 if (Class.isAnnotationPresent(Scope.class)) { Scope scope = Class.getAnnotation(Scope.class); definationbean.setScope(scope.value()); } else { definationbean.setScope("singleton"); } map.put(beanname, definationbean); } } catch (ClassNotFoundException e) { throw new RuntimeException(e); } } } // 扫描结束后:对作用域为 singleton 的 Bean 可在此「预创建」并放入 singletonObjects(与 Spring 预实例化单例思路类似)。 for (Map.Entry<String, Definationbean> entry : map.entrySet()) { String beanname = entry.getKey(); Definationbean definationbean = entry.getValue(); if (entry.getValue().getScope().equals("singleton")) { Object bean = creatbean(beanname, definationbean); singletonObjects.put(beanname, bean); } } } } } /** * 根据 Bean 名称与定义创建实例;具体创建方式由你自行实现(反射无参构造、工厂等)。 */ private Object creatbean(String beanname, Definationbean definationbean) { //通过反射也就是拿到calss类元信息然后进行创建对象这一过程称之为反射(对象的创建) Class clazz = definationbean.getType(); try { Object bean = clazz.getConstructor().newInstance(); return bean; } catch (InstantiationException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (InvocationTargetException e) { throw new RuntimeException(e); } catch (NoSuchMethodException e) { throw new RuntimeException(e); } } /** * 按名称从容器中获取 Bean 实例:先查 {@link #map} 得类型与作用域; * singleton 则复用 {@link #singletonObjects} 中已创建实例,否则每次反射新建(prototype 等)。 */ public Object getBean(String beanname) { // 未登记过该名称则无定义,直接返回 null。 Definationbean definationbean = map.get(beanname); if (definationbean == null) { return null; } // singleton:优先用缓存;缓存没有则创建后放入 map 并返回新建实例(注意应 return 新建对象,勿 return 旧的 null 引用)。 if (definationbean.getScope().equals("singleton")) { Object bean = singletonObjects.get(beanname); if (bean == null) { Object bean2 = creatbean(beanname, definationbean); singletonObjects.put(beanname, bean2); return bean2; } return bean; } // 非 singleton(如 prototype):每次 getBean 都新建,不放入 singletonObjects。 return creatbean(beanname, definationbean); } }

自定义scope注解:

package com.code.liyunmiao.spring; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; /** * 作用域(手写简化版):标在类上;未标注时容器内默认按 singleton 处理。 */ @Retention(RetentionPolicy.RUNTIME) public @interface Scope { String value() default ""; }
Definationbean实体类:
package com.code.liyunmiao.service; /** * Bean 定义:扫描阶段为每个带 {@code @Component} 的类生成一份,保存「类型 + 作用域」, * 供 {@link com.code.liyunmiao.spring.liyunmiaocontext} 在 {@code getBean} 时按作用域创建或复用实例。 */ public class Definationbean { /** 作用域字符串,如 singleton;与 {@link com.code.liyunmiao.spring.Scope} 或默认值对应。 */ private String scope; /** Bean 对应的类元数据,用于反射创建实例。 */ private Class type; public void setScope(String scope) { this.scope = scope; } public String getScope() { return scope; } public void setType(Class type) { this.type = type; } public Class getType() { return type; } }

当给userService加:

package com.code.liyunmiao.service; import com.code.liyunmiao.spring.Component; import com.code.liyunmiao.spring.Scope; /** * 示例组件:{@code @Component} 的 value 为容器中注册的 Bean 名称,{@code getBean("Userservice")} 与之对应。 */ @Component("Userservice") @Scope("singleton") public class Userservice { }

下面来看测试类:

package com.code.liyunmiao.service; import com.code.liyunmiao.spring.liyunmiaocontext; /** * 入口:显式传入配置类 {@link configclass},构造手写容器后按 Bean 名称取出 {@link Userservice}。 */ public class Test { public static void main(String[] args) { liyunmiaocontext context = null; // 构造过程中可能抛异常(如 classpath 上找不到扫描包目录等),此处统一捕获并包装。 try { context = new liyunmiaocontext(configclass.class); } catch (Exception e) { throw new RuntimeException(e); } // Bean 名需与 @Component("Userservice") 中 value 一致。 Userservice userservice=(Userservice) context.getBean("Userservice"); System.out.println(context.getBean("Userservice")); System.out.println(context.getBean("Userservice")); System.out.println(context.getBean("Userservice")); System.out.println(context.getBean("Userservice")); } }

这是输出:输出都是同一个对象也就是对应的单例bean。

下面看另一种:

package com.code.liyunmiao.service; import com.code.liyunmiao.spring.Component; import com.code.liyunmiao.spring.Scope; /** * 示例组件:{@code @Component} 的 value 为容器中注册的 Bean 名称,{@code getBean("Userservice")} 与之对应。 */ @Component("Userservice") @Scope("proporty") public class Userservice { }

当scope里面加这个的时候每次get的就是新的实例,也就是说是多例模式每次获取都是new一个新的而不是用那个旧的了,或者说而不是一直用那一个了。

这是输出。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/5/7 9:01:03

OpenClaw夜间值守方案:Kimi-VL-A3B-Thinking监控告警自动化

OpenClaw夜间值守方案&#xff1a;Kimi-VL-A3B-Thinking监控告警自动化 1. 为什么需要夜间自动化监控&#xff1f; 凌晨3点的服务器告警短信&#xff0c;可能是每个运维人员最不想收到的"礼物"。去年我负责的一个项目就曾因为半夜的磁盘爆满问题&#xff0c;导致次…

作者头像 李华
网站建设 2026/4/10 9:58:21

查老板查企业:合法避坑指南+高效工具推荐

查老板查企业&#xff0c;最核心的需求是合法、高效地获取关联信息和风险情况&#xff0c;避免踩坑。我之前合作项目时吃过亏&#xff0c;后来发现用风鸟企业查询平台能一站式解决这些问题——它不仅数据源合规&#xff0c;还能查多节点关联关系和AI智能解读风险。接下来我会分…

作者头像 李华
网站建设 2026/4/10 9:57:50

Dify大模型应用开发平台实战:从Prompt工程到生产级AI工作流蛹

一、什么是requests&#xff1f; requests 是一个用于发送HTTP请求的 Python 库。 它可以帮助你&#xff1a; 轻松发送GET、POST、PUT、DELETE等请求 处理Cookie、会话等复杂性 自动解压缩内容 处理国际化域名和URL 二、应用场景 requests 广泛应用于以下实际场景&#xff1a; …

作者头像 李华
网站建设 2026/4/10 9:56:27

Ubuntu系统实现自动登录的3种高效方法及安全考量

1. 为什么需要自动登录&#xff1f;这些场景你可能正在经历 每天早上打开电脑&#xff0c;输入密码登录系统这个动作&#xff0c;我已经重复了上千次。直到有一天&#xff0c;我突然意识到——为什么我的个人电脑还要这么麻烦&#xff1f;就像家里的智能门锁可以指纹解锁一样&a…

作者头像 李华