大家好,我是Leo。Spring核心中依赖注入和IOC容器是非常常见的,用起来也是非常的顺手,只能说是真香,那如何实现一个丐版的SpringIOC呢?那今天我们就来讲如何实现一个乞丐版的IOC和通过注解进行依赖注入。

IOC控制反转

在开始之前,首先得先理解什么是IOC的控制反转,Spring 通过 IoC 容器来管理所有 Java 对象的实例化和初始化,控制对象与对象之间的依赖关系 。我们将由 IoC 容器管理的 Java 对象称为 Spring Bean,它与使用关键字 new 创建的 Java 对象没有任何区别。

说白了就是,IOC负责我们的饮食,我们要饭和菜,只需要从IOC容器获取就行。

那这样的作用也就非常明显了,解耦、方便

依赖注入

依赖注入肯定是IOC的精髓所在,我们平常使用@Autowired注解等自动注入方式,用起来确实舒服。

那依赖注入常用方式有哪些呢?

  • 构造函数的参数实现注入

  • 基于set注入(静态工厂和动态工厂)

  • 基于属性的注入

Bean的自动装配方式

可以在文件中设置自动装配(autowire)方式,支持的装配方式有

方式 描述
no 手动装配
byName 通过id的名字自动注入对象
byType 通过类型自动注入对象
constructor 通过构造方法自动注入对象
autodectect 完全交给Spring管理,按先Constructor后byType的顺序进行匹配

丐版IOC实现

BeanDefinition.java

Bean的定义类,主要是包括配置Bean定义的对应的实体,对应的构造方法和getset方法就省略了

  1. public class BeanDefinition {
  2. private String beanName;
  3. private Class beanClass;

ResourceLoader.java

资源加载器,用来完成包的扫描和Bean的加载

  1. public class ResourceLoader {
  2. private static String rootPath;
  3. private Map<String, BeanDefinition> beanDefinitionMap = new HashMap(16);
  4. public Map<String, BeanDefinition> getResource(String basePackage) {
  5. // com.zly
  6. try {
  7. // 1. 把.换成/
  8. String packagePath = basePackage.replaceAll("\\.", "\\\\");
  9. // 2.获取包的绝对路径
  10. Enumeration<URL> urls =
  11. Thread.currentThread()
  12. .getContextClassLoader()
  13. .getResources(packagePath);
  14. while (urls.hasMoreElements()) {
  15. URL url = urls.nextElement();
  16. String filePath = URLDecoder.decode(url.getFile(), "utf-8");
  17. rootPath = filePath.substring(0, filePath.length() - packagePath.length());
  18. // 包扫描
  19. try {
  20. loadBean(new File(filePath));
  21. } catch (Exception e) {
  22. e.printStackTrace();
  23. }
  24. }
  25. } catch (IOException e) {
  26. e.printStackTrace();
  27. }
  28. return this.beanDefinitionMap;
  29. }
  30. private void loadBean(File file) throws Exception {
  31. // 1. 判断是否是文件夹
  32. if (file.isDirectory()) {
  33. // 2. 获取文件夹的所有内容
  34. File[] childFiles = file.listFiles();
  35. // 3. 判断文件夹内为空,直接返回
  36. if (childFiles == null || childFiles.length == 0) {
  37. return;
  38. }
  39. // 4. 如果文件夹里面不为空,遍历文件夹所有的内容
  40. for (File childFile : childFiles) {
  41. // 4.1 遍历得到每个File对象,继续是文件夹,递归
  42. if (childFile.isDirectory()) {
  43. loadBean(childFile);
  44. } else {
  45. // 4.2 得到不是文件夹,是文件
  46. // 4.3 得到包路径 和类名称
  47. String pathWithClass = childFile.getAbsolutePath().substring(rootPath.length() - 1);
  48. // 4.4 判断当前文件类型是否为class
  49. if (pathWithClass.contains(".class")) {
  50. // 4.5 是class类型,把\替换成. 把.class去掉
  51. String allName = pathWithClass.replaceAll("\\\\", ".").replace(".class", "");
  52. // 4.6 判断类上面是否有注解@Bean,放到map集合beanFactory
  53. // 4.6.1 获取类的Class对象
  54. Class<?> clazz = Class.forName(allName);
  55. // 4.6.2 判断不是接口
  56. if (!clazz.isInterface()) {
  57. // 4.6.3 判断上面是否有@Bean注解
  58. Bean annotation = clazz.getAnnotation(Bean.class);
  59. if (annotation != null) {
  60. // 4.6.4 实例化
  61. String beanName = annotation.value();
  62. if ("".equals(beanName)) {
  63. beanName = allName;
  64. }
  65. if (clazz.getInterfaces().length > 0) {
  66. System.out.println("正在加载【"+ clazz.getInterfaces()[0] +"】");
  67. BeanDefinition beanDefinition = new BeanDefinition(beanName, clazz);
  68. beanDefinitionMap.put(beanName, beanDefinition);
  69. } else {
  70. System.out.println("正在加载【"+ clazz.getInterfaces()[0]);
  71. BeanDefinition beanDefinition = new BeanDefinition(beanName, clazz);
  72. beanDefinitionMap.put(beanName, beanDefinition);
  73. }
  74. }
  75. }
  76. }
  77. }
  78. }
  79. }
  80. }
  81. }

BeanRegister.java

用于向容器中注册一个Bean,在扫描后,我们的Bean主要是放在了beanDefinitionMap中,还没有进行加载和初始化,在获取中再进行加载到这个缓存Map中

  1. public class BeanRegister {
  2. private Map<String, Object> singletonMap = new HashMap<>(32);
  3. public Object getSingletonBean(String beanName) {
  4. return singletonMap.get(beanName);
  5. }
  6. public void registerSingletonBean(String beanName, Object bean) {
  7. if (singletonMap.containsKey(beanName)) {
  8. return;
  9. }
  10. singletonMap.put(beanName, bean);
  11. }
  12. }

Bean和DI的注解

这里的两个注解主要是用来标志这是一个Bean和进行依赖注入的

  1. @Target(ElementType.TYPE)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface Bean {
  4. String value() default "";
  5. }
  1. @Target(ElementType.FIELD)
  2. @Retention(RetentionPolicy.RUNTIME)
  3. public @interface Di {
  4. }

BeanFactory.java

这里的对象工厂主要是我们最重要的一个类,在创建时,需要加载注册器和资源加载,在获取Bean中,需要进行依赖注入,并创建一个Bean

  1. public class BeanFactory {
  2. /**
  3. * 创建一个map集合,放bean对象
  4. */
  5. private Map<String , BeanDefinition> beanDefinitionMap = new HashMap<>();
  6. private BeanRegister beanRegister;
  7. public BeanFactory(String basePackage) {
  8. beanRegister = new BeanRegister();
  9. this.beanDefinitionMap = new ResourceLoader().getResource(basePackage);
  10. }
  11. public Object getBean(String beanName) {
  12. // 从缓存中获取
  13. Object bean = beanRegister.getSingletonBean(beanName);
  14. if (bean != null) {
  15. return bean;
  16. }
  17. // 创建bean
  18. return createBean(beanDefinitionMap.get(beanName));
  19. }
  20. public Object getBean(Class<?> clazz) {
  21. BeanDefinition beanDefinition = getBeanNameByType(clazz);
  22. if (Objects.isNull(beanDefinition)) {
  23. log.error("can not find {}", clazz);
  24. return null;
  25. } else {
  26. return getBean(beanDefinition.getBeanName());
  27. }
  28. }
  29. private Object createBean(BeanDefinition beanDefinition) {
  30. try {
  31. Object bean = beanDefinition.getBeanClass().getConstructor().newInstance();
  32. beanRegister.registerSingletonBean(beanDefinition.getBeanName(), bean);
  33. loadDi(beanDefinition.getBeanName());
  34. return bean;
  35. } catch (Exception e) {
  36. e.printStackTrace();
  37. }
  38. return null;
  39. }
  40. /**
  41. * 属性注入
  42. */
  43. private void loadDi(String beanName) {
  44. // 1. 实例化对象都在beanFactory的map集合中,遍历
  45. Object bean = this.beanRegister.getSingletonBean(beanName);
  46. // 2. 获取map集合每个对象和属性
  47. Class<?> objectClass = bean.getClass();
  48. // 3. 遍历得到每个对象属性数组,得到每个属性
  49. Field[] declaredFields = objectClass.getDeclaredFields();
  50. for (Field declaredField : declaredFields) {
  51. // 4. 判断属性上面是否有@Di注解
  52. Di annotation = declaredField.getAnnotation(Di.class);
  53. if (annotation != null) {
  54. declaredField.setAccessible(true);
  55. // 如果私有属性,可以设置值
  56. // 5. 如果有@Di注解,把对象进行注入
  57. try {
  58. BeanDefinition beanDefinition = getBeanNameByType(declaredField.getType());
  59. if (Objects.isNull(beanDefinition)) {
  60. declaredField.set(bean, null);
  61. } else {
  62. declaredField.set(bean, getBean(beanDefinition.getBeanName()));
  63. }
  64. } catch (IllegalAccessException e) {
  65. throw new RuntimeException(e);
  66. }
  67. }
  68. }
  69. }
  70. private BeanDefinition getBeanNameByType(Class clazz) {
  71. Set<Map.Entry<String, BeanDefinition>> entries = this.beanDefinitionMap.entrySet();
  72. for (Map.Entry<String, BeanDefinition> entry : entries) {
  73. if (entry.getValue().getBeanClass().equals(clazz)) {
  74. return entry.getValue();
  75. }
  76. if (entry.getValue().getBeanClass().getInterfaces()[0].equals(clazz)) {
  77. return entry.getValue();
  78. }
  79. }
  80. return null;
  81. }
  82. }

ApplicationContext

我也模仿了Spring一样定义了一个ApplicationContext容器,获取Bean从这个容器获取,context容器再从BeanFactory中获取

  1. public class AnnotationApplicationContext implements ApplicationContext {
  2. /**
  3. * 创建一个map集合,放bean对象
  4. */
  5. private BeanFactory beanFactory;
  6. @Override
  7. public Object getBean(Class clazz) {
  8. return beanFactory.getBean(clazz);
  9. }
  10. @Override
  11. public Object getBean(String beanName) {
  12. return beanFactory.getBean(beanName);
  13. }
  14. /**
  15. * 设置包扫描规则
  16. * 当前包及其子包,哪个类有@Bean注解,把这个类通过反射化
  17. */
  18. public AnnotationApplicationContext(String basePackage) throws Exception {
  19. this.beanFactory = getBeanFactory(basePackage);
  20. }
  21. private BeanFactory getBeanFactory(String baskPackage) {
  22. return new BeanFactory(baskPackage);
  23. }
  24. }

测试,实现

  1. @Bean
  2. public class UserServiceImpl implements UserService {
  3. @Di
  4. private UserDao userDao;
  5. @Override
  6. public void add() {
  7. System.out.println("service.........");
  8. userDao.add();
  9. }
  10. }
  1. public interface UserService {
  2. void add();
  3. }
  1. @Bean
  2. public class UserDaoImpl implements UserDao {
  3. @Override
  4. public void add() {
  5. System.out.println("dao add...");
  6. }
  7. }
  1. public interface UserDao {
  2. void add();
  3. }
  1. public class UserTest {
  2. public static void main(String[] args){
  3. try {
  4. ApplicationContext context = new AnnotationApplicationContext("com.zly");
  5. UserService userService = (UserService)context.getBean(UserService.class);
  6. userService.add();
  7. } catch (Exception e) {
  8. e.printStackTrace();
  9. }
  10. }
  11. }

相信你看到这里,就可以很容易的理解了这个丐版的Spring就算完成了,希望可以对大家有帮助啦

Spring之丐版IOC实现的更多相关文章

  1. 简单实现Spring框架--注解版

    自己写的Spring框架——简单实现IoC容器功能 前几天在网上看了篇帖子,是用xml的方式实现spring的ioc容器,觉得挺有意思的,这边自己试着用注解的形式造了一套轮子. 工程结构 codein ...

  2. spring.net中的IOC和DI-初使用

    前面准备:下载spring.net并解压 下载地址:spring.net下载地址 Ioc:控制反转         DI:依赖注入 一.IOC(控制反转) 1.新建一个控制台程序springTest, ...

  3. spring 学习 AOP和IOC

    自11开始接触三大框架,至今已俞5载, 当时风光无限的ssh,现在还在被广泛使用,并有扩大之势的只有spring了 spring主要特性,是广为使用的AOP(面向切面)和IOC(控制反转) 1.其中, ...

  4. spring源码浅析——IOC

    =========================================== 原文链接: spring源码浅析--IOC   转载请注明出处! ======================= ...

  5. spring mvc注解版01

    spring mvc是基于servlet实现的在spring mvc xml版中已经说过了,注解版相较于xml版更加简洁灵活. web项目的jar包: commons-logging-1.1.3.ja ...

  6. spring学习(01)之IOC

    spring学习(01)之IOC IOC:控制反转——Spring通过一种称作控制反转(IOC)的技术促进了低耦合.当应用了IOC,一个对象依赖的其它对象会通过被动的方式传递进来,而不是这个对象自己创 ...

  7. spring源码分析---IOC(1)

    我们都知道spring有2个最重要的概念,IOC(控制反转)和AOP(依赖注入).今天我就分享一下spring源码的IOC. IOC的定义:直观的来说,就是由spring来负责控制对象的生命周期和对象 ...

  8. Spring框架学习之IOC(二)

    Spring框架学习之IOC(二) 接着上一篇的内容,下面开始IOC基于注解装配相关的内容 在 classpath 中扫描组件 <context:component-scan> 特定组件包 ...

  9. Spring框架学习之IOC(一)

    Spring框架学习之IOC(一) 先前粗浅地学过Spring框架,但当时忙于考试及后期实习未将其记录,于是趁着最近还有几天的空闲时间,将其稍微整理一下,以备后期查看. Spring相关知识 spri ...

  10. spring之:XmlWebApplicationContext作为Spring Web应用的IoC容器,实例化和加载Bean的过程

    它既是 DispatcherServlet 的 (WebApplicationContext)默认策略,又是 ContextLoaderListener 创建 root WebApplicationC ...

随机推荐

  1. 安装mysql8.0

    安装repo源 参考mysql官方文档 参考文章 redhat7通过yum安装mysql5.7.17教程:https://www.jb51.net/article/103676.htm mysql r ...

  2. mymath.so共享库

    共享库的使用(.so)文件   1.共享库的概念 2.创建共享库命令 # 1.将.c生成.o文件,(生成与位置无关的代码-fPIC)gcc -c add.c -o add.o -fPIC # 2.使用 ...

  3. 西电oj135题 拼数字并排序

    类别综合 时间限制 1S 内存限制 1000Kb 问题描述 对于输入的字符串(只包含字母和数字),将其中的连续数字拼接成整数,然后将这些整数按从大到小顺序输出.例如字符串"abc123d5e ...

  4. 一分钟教你分清各种光纤跳线接头(SC、ST、FC、LC、MPO)

    一分钟教你分清各种光纤跳线接头(SC.ST.FC.LC.MPO)  市场上常见的光纤跳线有以下几种接头:SC.ST.FC.LC.MPO,相信很多入门者和小编一样,面对各种英文缩写也是我只认识他们,却不 ...

  5. Unity泛型单例模式

    using System.Collections; using System.Collections.Generic; using UnityEngine; public class Singleto ...

  6. OpenJudge2811:熄灯问题(枚举)

    熄灯问题 有一个由按钮组成的矩阵,其中每行有6个按钮,共5行.每个按钮的位置上有一盏灯.当按下一个按钮后,该按钮以及周围位置(上边.下边.左边.右边)的灯都会改变一次.即,如果灯原来是点亮的,就会被熄 ...

  7. MySQL学习(十)索引

    1.索引的种类 聚簇索引,非聚簇索引 主键索引,唯一索引,普通索引(前缀索引),全文索引 单值索引,复合索引 二级索引 覆盖索引 1.1 聚簇索引,非聚簇索引 参考文档: https://www.cn ...

  8. P4555 [国家集训队]最长双回文串 回文树(回文自动机)简单题

    贴个题目链接:https://www.luogu.org/problem/P4555 题目:输入长度为n的串S,求S的最长双回文子串T,即可将T分为两部分X,Y,(∣X∣,∣Y∣≥1)且X和Y都是回文 ...

  9. AC自动机模板题 HDU - 2222

    Keywords Search  HDU - 2222 贴个vj的链接https://vjudge.net/problem/HDU-2222 题意:T组数据,n个单词,再给你一个串,看有几个单词在这个 ...

  10. 后疫情时代,RTE“沉浸式”体验还能这么玩?丨RTE 2022 编程挑战赛赛后专访

    前言 9 月 17 日,由声网.环信与 RTE 开发者社区联合主办的"RTE 2022 编程挑战赛"圆满落幕.从 300+ 支参赛队伍中冲出重围的 27 支决赛队伍,在元宇宙中用精 ...