Spring boot实现一个简单的ioc(2)
发布时间 - 2026-01-11 00:53:09 点击率:次前言

跳过废话,直接看正文
仿照spring-boot的项目结构以及部分注解,写一个简单的ioc容器。
测试代码完成后,便正式开始这个ioc容器的开发工作。
正文
项目结构
实际上三四个类完全能搞定这个简单的ioc容器,但是出于可扩展性的考虑,还是写了不少的类。
因篇幅限制,接下来只将几个最重要的类的代码贴出来并加以说明,完整的代码请直接参考https://github.com/clayandgithub/simple-ioc。
SimpleAutowired
代码
import java.lang.annotation.*;
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleAutowired {
boolean required() default true;
String value() default ""; // this field is moved from @Qualifier to here for simplicity
}
说明
@SimpleAutowired的作用是用于注解需要自动装配的字段。
此类和spring的@Autowired的作用类似。但又有以下两个区别:
- @SimpleAutowired只能作用于类字段,而不能作用于方法(这样实现起来相对简单些,不会用到aop)
- @SimpleAutowired中包括了required(是否一定需要装配)和value(要装配的bean的名字)两个字段,实际上是将spring中的@Autowired以及Qualifier的功能简单地融合到了一起
SimpleBean
代码
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleBean {
String value() default "";
}
说明
@SimpleBean作用于方法,根据方法返回值来生成一个bean,对应spring中的@Bean
用value来设置要生成的bean的名字
SimpleComponent
代码
import java.lang.annotation.*;
@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleBean {
String value() default "";
}
说明
@SimpleComponent作用于类,ioc容器会为每一个拥有@SimpleComponent的类生成一个bean,对应spring中的@Component。特殊说明,为了简单起见,@SimpleComponent注解的类必须拥有一个无参构造函数,否则无法生成该类的实例,这个在之后的SimpleAppliationContext中的processSingleClass方法中会有说明。
SimpleIocBootApplication
代码
import java.lang.annotation.*;
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SimpleIocBootApplication {
String[] basePackages() default {};
}
说明
@SimpleIocBootApplication作用于应用的入口类。
这个启动模式是照搬了spring-boot的启动模式,将启动任务委托给SimpleIocApplication来完成。ioc容器将根据注解@SimpleIocBootApplication的相关配置自动扫描相应的package,生成beans并完成自动装配。(如果没有配置,默认扫描入口类(测试程序中的SampleApplication)所在的package及其子package)
以上就是这个ioc容器所提供的所有注解,接下来讲解ioc容器的扫描和装配过程的实现。
SimpleIocApplication
代码
import com.clayoverwind.simpleioc.context.*;
import com.clayoverwind.simpleioc.util.LogUtil;
import java.util.Arrays;
import java.util.Map;
import java.util.logging.Logger;
public class SimpleIocApplication {
private Class<?> applicationEntryClass;
private ApplicationContext applicationContext;
private final Logger LOGGER = LogUtil.getLogger(this.getClass());
public SimpleIocApplication(Class<?> applicationEntryClass) {
this.applicationEntryClass = applicationEntryClass;
}
public static void run(Class<?> applicationEntryClass, String[] args) {
new SimpleIocApplication(applicationEntryClass).run(args);
}
public void run(String[] args) {
LOGGER.info("start running......");
// create application context and application initializer
applicationContext = createSimpleApplicationContext();
ApplicationContextInitializer initializer = createSimpleApplicationContextInitializer(applicationEntryClass);
// initialize the application context (this is where we create beans)
initializer.initialize(applicationContext); // here maybe exist a hidden cast
// process those special beans
processSpecialBeans(args);
LOGGER.info("over!");
}
private SimpleApplicationContextInitializer createSimpleApplicationContextInitializer(Class<?> entryClass) {
// get base packages
SimpleIocBootApplication annotation = entryClass.getDeclaredAnnotation(SimpleIocBootApplication.class);
String[] basePackages = annotation.basePackages();
if (basePackages.length == 0) {
basePackages = new String[]{entryClass.getPackage().getName()};
}
// create context initializer with base packages
return new SimpleApplicationContextInitializer(Arrays.asList(basePackages));
}
private SimpleApplicationContext createSimpleApplicationContext() {
return new SimpleApplicationContext();
}
private void processSpecialBeans(String[] args) {
callRegisteredRunners(args);
}
private void callRegisteredRunners(String[] args) {
Map<String, SimpleIocApplicationRunner> applicationRunners = applicationContext.getBeansOfType(SimpleIocApplicationRunner.class);
try {
for (SimpleIocApplicationRunner applicationRunner : applicationRunners.values()) {
applicationRunner.run(args);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
说明
前面说到应用的启动会委托SimpleIocApplication来完成,通过将应用入口类(测试程序中的SampleApplication)传入SimpleIocApplication的构造函数,构造出SimpleIocApplication的一个实例并运行run方法。在run方法中,会首先生成一个applicationContext,并调用SimpleApplicationContextInitializer来完成applicationContext的初始化(bean的扫描、装配)。然后调用processSpecialBeans来处理一些特殊的bean,如实现了SimpleIocApplicationRunner接口的bean会调用run方法来完成一些应用程序的启动任务。
这就是这个ioc容器的整个流程。
SimpleApplicationContextInitializer
代码
import java.io.IOException;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
public class SimpleApplicationContextInitializer implements ApplicationContextInitializer<SimpleApplicationContext> {
private Set<String> basePackages = new LinkedHashSet<>();
public SimpleApplicationContextInitializer(List<String> basePackages) {
this.basePackages.addAll(basePackages);
}
@Override
public void initialize(SimpleApplicationContext applicationContext) {
try {
applicationContext.scan(basePackages, true);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
} catch (IOException e) {
throw new RuntimeException(e);
}
applicationContext.setStartupDate(System.currentTimeMillis());
}
}
说明
在SimpleIocApplication的run中,会根据basePackages来构造一个SimpleApplicationContextInitializer 的实例,进而通过这个ApplicationContextInitializer来完成SimpleApplicationContext 的初始化。
在SimpleApplicationContextInitializer中, 简单地调用SimpleApplicationContext 中的scan即可完成SimpleApplicationContext的初始化任务
SimpleApplicationContext
说明:
终于到了最重要的部分了,在SimpleApplicationContext中将真正完成扫描、生成bean以及自动装配的任务。这里scan即为SimpleApplicationContext的程序入口,由SimpleApplicationContextInitializer在初始化时调用。
代码的调用逻辑简单易懂,就不多加说明了。
这里只简单列一下各个字段的含义以及几个比较关键的方法的作用。
字段
- startupDate:启动时间记录字段
- scannedPackages:已经扫描的包的集合,保证不重复扫描
- registeredBeans:已经完全装配好并注册好了的bean
- earlyBeans : 只是生成好了,还未装配完成的bean,用于处理循环依赖的问题
- totalBeanCount : 所有bean的计数器,在生成bean的名字时会用到其唯一性
方法
- processEarlyBeans:用于最终装配earlyBeans 中的bean,若装配成功,则将bean移至registeredBeans,否则报错
- scan : 扫描并处理传入的package集合
- processSingleClass:处理单个类,尝试生成该类的bean并进行装配(前提是此类有@SimpleComponent注解)
- createBeansByMethodsOfClass : 顾名思义,根据那些被@Bean注解的方法来生成bean
- autowireFields:尝试装配某个bean,lastChance代表是否在装配失败是报错(在第一次装配时,此值为false,在装配失败后会将bean移至earlyBeans,在第二次装配时,此值为true,实际上就是在装配earlyBeans中的bean,因此若仍然装配失败,就会报错)。在这个方法中,装配相应的bean时会从registeredBeans以及earlyBeans中去寻找符合条件的bean,只要找到,不管是来自哪里,都算装配成功。
代码
import com.clayoverwind.simpleioc.context.annotation.SimpleAutowired;
import com.clayoverwind.simpleioc.context.annotation.SimpleBean;
import com.clayoverwind.simpleioc.context.annotation.SimpleComponent;
import com.clayoverwind.simpleioc.context.factory.Bean;
import com.clayoverwind.simpleioc.util.ClassUtil;
import com.clayoverwind.simpleioc.util.ConcurrentHashSet;
import com.clayoverwind.simpleioc.util.LogUtil;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
import java.util.logging.Logger;
/**
* @author clayoverwind
* @E-mail clayanddev@163.com
* @version 2017/4/5
*/
public class SimpleApplicationContext implements ApplicationContext {
private long startupDate;
private Set<String> scannedPackages = new ConcurrentHashSet<>();
private Map<String, Bean> registeredBeans = new ConcurrentHashMap<>();
private Map<String, Bean> earlyBeans = new ConcurrentHashMap<>();
private final Logger LOGGER = LogUtil.getLogger(this.getClass());
AtomicLong totalBeanCount = new AtomicLong(0L);
AtomicLong nameConflictCount = new AtomicLong(0L);
@Override
public Object getBean(String name) {
return registeredBeans.get(name);
}
@Override
public <T> T getBean(String name, Class<T> type) {
Bean bean = (Bean)getBean(name);
return bean == null ? null : (type.isAssignableFrom(bean.getClazz()) ? type.cast(bean.getObject()) : null);
}
@Override
public <T> T getBean(Class<T> type) {
Map<String, T> map = getBeansOfType(type);
return map.isEmpty() ? null : type.cast(map.values().toArray()[0]);
}
@Override
public boolean containsBean(String name) {
return getBean(name) != null;
}
@Override
public <T> Map<String, T> getBeansOfType(Class<T> type) {
Map<String, T> res = new HashMap<>();
registeredBeans.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getValue().getClazz())).forEach(entry -> res.put(entry.getKey(), type.cast(entry.getValue().getObject())));
return res;
}
@Override
public void setStartupDate(long startupDate) {
this.startupDate = startupDate;
}
@Override
public long getStartupDate() {
return startupDate;
}
/**
* try to autowire those beans in earlyBeans
* if succeed, remove it from earlyBeans and put it into registeredBeans
* otherwise ,throw a RuntimeException(in autowireFields)
*/
private synchronized void processEarlyBeans() {
for (Map.Entry<String, Bean> entry : earlyBeans.entrySet()) {
Bean myBean = entry.getValue();
try {
if (autowireFields(myBean.getObject(), myBean.getClazz(), true)) {
registeredBeans.put(entry.getKey(), myBean);
earlyBeans.remove(entry.getKey());
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
/**
* scan base packages and create beans
* @param basePackages
* @param recursively
* @throws ClassNotFoundException
*/
public void scan(Set<String> basePackages, boolean recursively) throws ClassNotFoundException, IOException {
LOGGER.info("start scanning......");
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// get all classes who haven't been registered
Set<Class<?>> classes = new LinkedHashSet<>();
for (String packageName : basePackages) {
if (scannedPackages.add(packageName)) {
classes.addAll(ClassUtil.getClassesByPackageName(classLoader, packageName, recursively));
}
}
// autowire or create bean for each class
classes.forEach(this::processSingleClass);
processEarlyBeans();
LOGGER.info("scan over!");
}
/**
* try to create a bean for certain class, put it into registeredBeans if success, otherwise put it into earlyBeans
* @param clazz
*/
private void processSingleClass(Class<?> clazz) {
LOGGER.info(String.format("processSingleClass [%s] ...", clazz.getName()));
Annotation[] annotations = clazz.getDeclaredAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof SimpleComponent) {
Object instance;
try {
instance = clazz.newInstance();
} catch (InstantiationException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
long beanId = totalBeanCount.getAndIncrement();
SimpleComponent component = (SimpleComponent) annotation;
String beanName = component.value();
if (beanName.isEmpty()) {
beanName = getUniqueBeanNameByClassAndBeanId(clazz, beanId);
}
try {
if (autowireFields(instance, clazz, false)) {
registeredBeans.put(beanName, new Bean(instance, clazz));
} else {
earlyBeans.put(beanName, new Bean(instance, clazz));
}
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
try {
createBeansByMethodsOfClass(instance, clazz);
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
}
private void createBeansByMethodsOfClass(Object instance, Class<?> clazz) throws InvocationTargetException, IllegalAccessException {
List<Method> methods = getMethodsWithAnnotation(clazz, SimpleBean.class);
for (Method method : methods) {
method.setAccessible(true);
Object methodBean = method.invoke(instance);
long beanId = totalBeanCount.getAndIncrement();
Class<?> methodBeanClass = methodBean.getClass();
//bean name
SimpleBean simpleBean = method.getAnnotation(SimpleBean.class);
String beanName = simpleBean.value();
if (beanName.isEmpty()) {
beanName = getUniqueBeanNameByClassAndBeanId(clazz, beanId);
}
// register bean
registeredBeans.put(beanName, new Bean(methodBean, methodBeanClass));
}
}
private List<Method> getMethodsWithAnnotation(Class<?> clazz, Class<?> annotationClass) {
List<Method> res = new LinkedList<>();
Method[] methods = clazz.getDeclaredMethods();
for (Method method : methods) {
Annotation[] annotations = method.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation.annotationType() == annotationClass) {
res.add(method);
break;
}
}
}
return res;
}
/**
* try autowire all fields of a certain instance
* @param instance
* @param clazz
* @param lastChance
* @return true if success, otherwise return false or throw a exception if this is the lastChance
* @throws IllegalAccessException
*/
private boolean autowireFields(Object instance, Class<?> clazz, boolean lastChance) throws IllegalAccessException {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
Annotation[] annotations = field.getAnnotations();
for (Annotation annotation : annotations) {
if (annotation instanceof SimpleAutowired) {
SimpleAutowired autowired = (SimpleAutowired) annotation;
String beanName = autowired.value();
Bean bean = getSimpleBeanByNameOrType(beanName, field.getType(), true);
if (bean == null) {
if (lastChance) {
if (!autowired.required()) {
break;
}
throw new RuntimeException(String.format("Failed in autowireFields : [%s].[%s]", clazz.getName(), field.getName()));
} else {
return false;
}
}
field.setAccessible(true);
field.set(instance, bean.getObject());
}
}
}
return true;
}
/**
* only used in autowireFields
* @param beanName
* @param type
* @param allowEarlyBean
* @return
*/
private Bean getSimpleBeanByNameOrType(String beanName, Class<?> type, boolean allowEarlyBean) {
// 1. by name
Bean res = registeredBeans.get(beanName);
if (res == null && allowEarlyBean) {
res = earlyBeans.get(beanName);
}
// 2. by type
if (type != null) {
if (res == null) {
res = getSimpleBeanByType(type, registeredBeans);
}
if (res == null && allowEarlyBean) {
res = getSimpleBeanByType(type, earlyBeans);
}
}
return res;
}
/**
* search bean by type in certain beans map
* @param type
* @param beansMap
* @return
*/
private Bean getSimpleBeanByType(Class<?> type, Map<String, Bean> beansMap) {
List<Bean> beans = new LinkedList<>();
beansMap.entrySet().stream().filter(entry -> type.isAssignableFrom(entry.getValue().getClazz())).forEach(entry -> beans.add(entry.getValue()));
if (beans.size() > 1) {
throw new RuntimeException(String.format("Autowire by type, but more than one instance of type [%s] is founded!", beans.get(0).getClazz().getName()));
}
return beans.isEmpty() ? null : beans.get(0);
}
private String getUniqueBeanNameByClassAndBeanId(Class<?> clazz, long beanId) {
String beanName = clazz.getName() + "_" + beanId;
while (registeredBeans.containsKey(beanName) || earlyBeans.containsKey(beanName)) {
beanName = clazz.getName() + "_" + beanId + "_" + nameConflictCount.getAndIncrement();
}
return beanName;
}
}
后记
至此,一个简单的ioc容器就完成了,总结一下优缺点。
优点:
小而简单。
可以使用@SimpleBean、@SimpleComponent以及@SimpleAutowired 来完成一些简单但常用的依赖注入任务.
缺点:
很明显,实现过于简单,提供的功能太少。
如果你想了解ioc的实现原理,或者你想要开发一个小型个人项目但又嫌spring过于庞大,这个简单的ioc容器或许可以帮到你。
如果你想做的不仅如此,那么你应该将目光转向spring-boot。
完整代码参考:https://github.com/clayandgithub/simple-ioc。
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。
# Spring
# boot
# ioc
# 深入理解Java的Spring框架中的IOC容器
# 关于SpringBoot获取IOC容器中注入的Bean(推荐)
# Spring学习笔记1之IOC详解尽量使用注解以及java代码
# Spring中IoC优点与缺点解析
# 浅析Java的Spring框架中IOC容器容器的应用
# 简单实现Spring的IOC原理详解
# 利用Spring IOC技术实现用户登录验证机制
# Spring核心IoC和AOP的理解
# 用java的spring实现一个简单的IOC容器示例代码
# 简单谈谈Spring Ioc原理解析
# 来完成
# 作用于
# 报错
# 几个
# 好了
# 最重要
# 此类
# 值为
# 移至
# 就会
# 如果你
# 会有
# 在这个
# 就不
# 这就是
# 又有
# 你想
# 说到
# 如果没有
# 到你
相关栏目:
【
网站优化151355 】
【
网络推广146373 】
【
网络技术251813 】
【
AI营销90571 】
相关推荐:
Laravel Seeder怎么填充数据_Laravel数据库填充器的使用方法与技巧
如何获取PHP WAP自助建站系统源码?
昵图网官方站入口 昵图网素材图库官网入口
Laravel Eloquent关联是什么_Laravel模型一对一与一对多关系精讲
如何在阿里云购买域名并搭建网站?
如何有效防御Web建站篡改攻击?
微信小程序 wx.uploadFile无法上传解决办法
如何快速生成ASP一键建站模板并优化安全性?
logo在线制作免费网站在线制作好吗,DW网页制作时,如何在网页标题前加上logo?
如何快速重置建站主机并恢复默认配置?
如何用IIS7快速搭建并优化网站站点?
网站制作企业,网站的banner和导航栏是指什么?
Laravel如何优化应用性能?(缓存和优化命令)
ChatGPT常用指令模板大全 新手快速上手的万能Prompt合集
百度输入法ai面板怎么关 百度输入法ai面板隐藏技巧
js实现获取鼠标当前的位置
如何在七牛云存储上搭建网站并设置自定义域名?
Laravel如何实现数据导出到CSV文件_Laravel原生流式输出大数据量CSV【方案】
Laravel怎么进行浏览器测试_Laravel Dusk自动化浏览器测试入门
C#如何调用原生C++ COM对象详解
JS碰撞运动实现方法详解
长沙做网站要多少钱,长沙国安网络怎么样?
免费的流程图制作网站有哪些,2025年教师初级职称申报网上流程?
如何确保FTP站点访问权限与数据传输安全?
如何在橙子建站中快速调整背景颜色?
Laravel怎么实现验证码功能_Laravel集成验证码库防止机器人注册
Laravel怎么使用artisan命令缓存配置和视图
如何在 Telegram Web View(iOS)中防止键盘遮挡底部输入框
什么是JavaScript解构赋值_解构赋值有哪些实用技巧
Laravel如何生成PDF或Excel文件_Laravel文档导出工具与使用教程
Win11怎么设置虚拟桌面 Win11新建多桌面切换操作【技巧】
,交易猫的商品怎么发布到网站上去?
郑州企业网站制作公司,郑州招聘网站有哪些?
Laravel如何发送邮件_Laravel Mailables构建与发送邮件的简明教程
Python并发异常传播_错误处理解析【教程】
Mybatis 中的insertOrUpdate操作
Laravel PHP版本要求一览_Laravel各版本环境要求对照
如何在IIS中新建站点并配置端口与IP地址?
Laravel如何实现多级无限分类_Laravel递归模型关联与树状数据输出【方法】
Laravel如何实现邮件验证激活账户_Laravel内置MustVerifyEmail接口配置【步骤】
香港服务器网站测试全流程:性能评估、SEO加载与移动适配优化
佛山网站制作系统,佛山企业变更地址网上办理步骤?
免费网站制作appp,免费制作app哪个平台好?
如何用虚拟主机快速搭建网站?详细步骤解析
Laravel路由Route怎么设置_Laravel基础路由定义与参数传递规则【详解】
Win11怎么关闭专注助手 Win11关闭免打扰模式设置【操作】
百度输入法全感官ai怎么关 百度输入法全感官皮肤关闭
🚀拖拽式CMS建站能否实现高效与个性化并存?
Python文本处理实践_日志清洗解析【指导】
如何用AI一键生成爆款短视频文案?小红书AI文案写作指令【教程】

