一步一步实现SpringIoc容器

男娘i 2022-11-18 05:49 334阅读 0赞

IoC**Inverse of Controll控制反转:指的是对象的创建方式进行了反转,传统的开发方式是程序员自己 new 对象,IoC就是将这一过程进行了反转,程序员不需要自己 new 对象,而是交给 IoC 容器来创建对象,程序员只需要使用这些对象即可。 控制反转的好处是**解耦合。将创建对象的控制权进行了反转,之前是直接 new 的,现在是接收工厂方法返回的对象,无法控制对象的创建,将对象创建的权力进行了反转,所以叫控制反转

IOC创建对象的两种方式:

一、**基于XML**

1、创建一个 XML 文件,在文件中将程序中需要用的对象进行配置。

20210412215905675.png

2、加载 IoC 容器,Spring 框架会自动读取 XML 文件,根据 XML 的配置,自动创建各种对象,放入到缓冲池中。

20210412215920403.png

3、开发者只需要根据需求从缓冲池中取出相应对象使用即可。

20210412215952467.png

二、**基于注解**

基于注解的方式和基于 XML 的方式原理是相通的,只不过换了一种形式来处理。

1、在需要注入的 IoC 的类处添加注解,标注一下,告诉Spring 框架,这个类是需要注入的 bean。默认是根据容器中的类型去加载。

方式一:20210412220057339.png20210412220206244.png

方式二:20210412220227291.png2021041222025365.png

2、加载 IoC 容器,Spring 框架会自动读取到所有添加了注解的类,通过反射机制创建 bean,注入到 IoC 容器中,指定要扫描的包。

20210412220310859.png

3、开发者从 IoC 容器中取出需要的 bean。

20210412220333843.png

基于注解的方式,类默认的 id 是类名的首字母小写,User —> user,如果要修改 id,只需要给@Component 注解添加 value 属性即可,属性值就是新的 id 值。


Spring IoC的核心源码原理:

一、基于 XML

1、IoC 容器加载的时候,会自动读取spring.xml。

2、通过 XML 解析,获取到目标数据。

3、通过反射机制,创建目标 bean。

4、将这些 bean 注入到 IoC 缓存中,以 key-value 的形式进行存储。

  1. 5、开发者只需要通过 key/Class 从缓存中取出 bean 使用即可。

二、基于注解

1、IoC 容器加载的时候,要遍历指定包名下的所有类。

2、判断这些类是否添加了@Component,把所有添加了@Component 注解的类进行数据收集(beanId、className),存入集合中。

3、将集合传递给下一个单元,遍历这个集合,获取目标数据。

4、通过反射机制,创建目标 bean。

5、将这些 bean 注入到 IoC 缓存中,以 key-value 的形式进行存储。

6、开发者只需要通过 key/Class 从缓存中取出 bean 使用即可。

watermark_type_ZmFuZ3poZW5naGVpdGk_shadow_10_text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3FxXzM2OTQyNzIw_size_16_color_FFFFFF_t_70

首先在指定给目标包中添加注解,然后启动ioc容器,找到添加的注解类,获取类上面的id和class信息然后封装成一个对象,每一个对象用BeanDefinition表示,把每一个BeanDefinition放入BeanDefinitions集合,解析这个集合,遍历每一个元素,拿到每一个id根据class创建对象,最后把对象放入cache中,把id作为key存放进去。


手写一个SpringIoc容器:

步骤:

  1. /**
  2. * 1、自定义一个AnnotationConfigApplicationContext,构造器中传入目标包。
  3. * 2、获取这个包下的所有类。
  4. * 3、遍历这些类,找出添加了 @Component 注解的类,获取对应的 beanName,beanClass,封装成一个BeanDefinition 对象,存入 Set 集合,这些就是 IoC 自动装载的原材料。
  5. * 4、遍历 Set 集合,通过反射机制创建对象,同时检测属性是否有 @Value 注解,有的话就赋值,没有就不赋值。
  6. * 5、将上面创建的 bean 以 k-v 的形式存入 cache Map
  7. * 6、提供 getBean 方法,通过 beanName 从缓存区取出对应的 bean
  8. */

使用Maven进行创建:

pom.xml文件,引入lombok和jdk版本号

  1. <dependencies>
  2. <dependency>
  3. <groupId>org.projectlombok</groupId>
  4. <artifactId>lombok</artifactId>
  5. <version>1.18.12</version>
  6. </dependency>
  7. </dependencies>
  8. <build>
  9. <plugins>
  10. <plugin>
  11. <groupId>org.apache.maven.plugins</groupId>
  12. <artifactId>maven-compiler-plugin</artifactId>
  13. <version>3.8.1</version>
  14. <configuration>
  15. <source>1.8</source>
  16. <target>1.8</target>
  17. </configuration>
  18. </plugin>
  19. </plugins>
  20. </build>

创建所需的标注类:

  1. Component.java
  2. // 标注在类上定义的注解
  3. @Target(ElementType.TYPE)
  4. @Retention(RetentionPolicy.RUNTIME)
  5. public @interface Component {
  6. String value() default "";
  7. }
  8. Autowired.java
  9. // 标注在属性上的注解,按照类型自动注入
  10. @Target(ElementType.FIELD)
  11. @Retention(RetentionPolicy.RUNTIME)
  12. public @interface Autowired {}
  13. Qualifier.java
  14. // 标注在属性上的注解,按照名称注入
  15. @Target(ElementType.FIELD)
  16. @Retention(RetentionPolicy.RUNTIME)
  17. public @interface Qualifier {
  18. String value();
  19. }
  20. Value.java
  21. // 标注在属性上的注解,用于给属性赋值
  22. @Target(ElementType.FIELD)
  23. @Retention(RetentionPolicy.RUNTIME)
  24. public @interface Value {
  25. String value();
  26. }

创建BeanDefinition包装类

  1. // 用于包装注解类的id和class
  2. @Data
  3. @AllArgsConstructor
  4. public class BeanDefinition {
  5. private String beanName;
  6. private Class beanClass;
  7. }

核心类:ioc容器入口

  1. AnnotationConfigApplicationContext.java
  2. public class AnnotationConfigApplicationContext {
  3. // 用来存储创建好的bean
  4. private Map<String,Object> cache = new HashMap<>();
  5. public AnnotationConfigApplicationContext(String basePackage){
  6. //遍历包,找到目标类
  7. Set<BeanDefinition> beanDefinitions = findBeanDefinition(basePackage);
  8. //根据原料创建bean
  9. createObject(beanDefinitions);
  10. //自动装载
  11. autowiredObject(beanDefinitions);
  12. }
  13. /**
  14. * 遍历包,找到目标类
  15. * @param basePackage 要扫描的包路径
  16. * @return 返回包装好的类
  17. */
  18. private Set<BeanDefinition> findBeanDefinition(String basePackage) {
  19. // 获取包下的所有Class
  20. Set<Class<?>> classes = MyUtil.getClasses(basePackage);
  21. // 遍历集合
  22. Iterator<Class<?>> iterator = classes.iterator();
  23. // 用来存放包装好的BeanDefinition
  24. Set<BeanDefinition> set = new HashSet<>();
  25. while (iterator.hasNext()){
  26. Class<?> clazz = iterator.next();
  27. // 判断该类是否添加了@Component注解
  28. Component component = clazz.getAnnotation(Component.class);
  29. if(component!=null){
  30. // 获取注解上的值
  31. String beanName = component.value();
  32. if ("".equals(beanName)){
  33. // 注解值为空,自动给这个bean取一个名字,默认类名首字母小写
  34. String packageName = clazz.getPackage().getName();
  35. String className = clazz.getName();
  36. beanName = className.replace(packageName+".","");
  37. beanName = beanName.substring(0,1).toLowerCase()+beanName.substring(1);
  38. }
  39. // 添加到集合
  40. set.add(new BeanDefinition(beanName,clazz));
  41. }
  42. }
  43. return set;
  44. }
  45. /**
  46. * 创建bean对象
  47. * @param beanDefinitions 包装好的beanDefinitions
  48. */
  49. private void createObject(Set<BeanDefinition> beanDefinitions) {
  50. // 遍历集合
  51. Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
  52. while (iterator.hasNext()) {
  53. BeanDefinition beanDefinition = iterator.next();
  54. try {
  55. // 获取beanClass信息
  56. Class beanClass = beanDefinition.getBeanClass();
  57. // 创建当前bean的实例
  58. Object instance = beanClass.getConstructor(null).newInstance(null);
  59. // 给属性赋值
  60. Field[] fields = beanClass.getDeclaredFields();
  61. for (Field field : fields) {
  62. // 判断当前属性是否添加了@Value注解
  63. Value fieldAnnotation = field.getAnnotation(Value.class);
  64. if(fieldAnnotation!=null){
  65. // 有@Value注解,给当前属性赋值
  66. // 获取注解上的值
  67. String value = fieldAnnotation.value();
  68. // 获得属性名字,并找到给当前属性赋值的方法setXxx()
  69. String fieldName = field.getName();
  70. String methodName = "set"+fieldName.substring(0,1).toUpperCase()+fieldName.substring(1);
  71. Method method = beanClass.getMethod(methodName, field.getType());
  72. // 得到属性的类型
  73. String name = field.getType().getName();
  74. Object val = null;
  75. switch (name){
  76. case "java.lang.Integer":
  77. val = Integer.parseInt(value);
  78. break;
  79. case "java.lang.String":
  80. val = value;
  81. break;
  82. case "java.lang.Double":
  83. val = Double.parseDouble(value);
  84. break;
  85. }
  86. // 通过反射给当前属性赋值
  87. method.invoke(instance,val);
  88. }
  89. }
  90. // 并将创建好的bean放在缓存中
  91. cache.put(beanDefinition.getBeanName(),instance);
  92. } catch (InstantiationException e) {
  93. e.printStackTrace();
  94. } catch (IllegalAccessException e) {
  95. e.printStackTrace();
  96. } catch (InvocationTargetException e) {
  97. e.printStackTrace();
  98. } catch (NoSuchMethodException e) {
  99. e.printStackTrace();
  100. }
  101. }
  102. }
  103. /**
  104. * 自动装载
  105. * @param beanDefinitions 包装好的beanDefinitions
  106. */
  107. private void autowiredObject(Set<BeanDefinition> beanDefinitions) {
  108. // 遍历集合
  109. Iterator<BeanDefinition> iterator = beanDefinitions.iterator();
  110. while (iterator.hasNext()) {
  111. BeanDefinition beanDefinition = iterator.next();
  112. // 获得当前的类信息
  113. Class beanClass = beanDefinition.getBeanClass();
  114. // 获得当前类的所有属性,并遍历它
  115. Field[] fields = beanClass.getDeclaredFields();
  116. for (Field field : fields) {
  117. // 判断该属性上是否添加了@Autowired注解
  118. Autowired autowired = field.getAnnotation(Autowired.class);
  119. if(autowired!=null){
  120. // 判断该属性上是否添加了@Qualifier注解
  121. Qualifier qualifier = field.getAnnotation(Qualifier.class);
  122. if(qualifier!=null){
  123. // 根据名字进行装载
  124. try {
  125. // 获取@Qualifier注解上的value值
  126. String value = qualifier.value();
  127. // 得到缓存中的bean对象
  128. Object obj = cache.get(value);
  129. Object bean = cache.get(beanDefinition.getBeanName());
  130. // 找到要注入的方法setXxx()
  131. String name = field.getName();
  132. String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
  133. Method method = beanClass.getMethod(methodName, field.getType());
  134. // 通过反射注入对象
  135. method.invoke(bean,obj);
  136. } catch (NoSuchMethodException e) {
  137. e.printStackTrace();
  138. } catch (IllegalAccessException e) {
  139. e.printStackTrace();
  140. } catch (InvocationTargetException e) {
  141. e.printStackTrace();
  142. }
  143. } else {
  144. // 根据类型进行装载
  145. try {
  146. // 找到当前对象的类名字,首字母小写
  147. Class<?> clazz = field.getType();
  148. String packageName = clazz.getPackage().getName();
  149. String className = clazz.getName();
  150. className = className.replace(packageName+".","");
  151. className = className.substring(0,1).toLowerCase()+className.substring(1);
  152. // 去缓存中获取bean对象
  153. Object obj = cache.get(className);
  154. Object bean = cache.get(beanDefinition.getBeanName());
  155. // 找到要注入的方法setXxx()
  156. String name = field.getName();
  157. String methodName = "set" + name.substring(0, 1).toUpperCase() + name.substring(1);
  158. Method method = beanClass.getMethod(methodName, field.getType());
  159. // 通过反射注入对象
  160. method.invoke(bean,obj);
  161. } catch (NoSuchMethodException e) {
  162. e.printStackTrace();
  163. } catch (IllegalAccessException e) {
  164. e.printStackTrace();
  165. } catch (InvocationTargetException e) {
  166. e.printStackTrace();
  167. }
  168. }
  169. }
  170. }
  171. }
  172. }
  173. /**
  174. * 通过name获取bean
  175. * @param beanName
  176. * @return
  177. */
  178. public Object getBean(String beanName){
  179. return cache.get(beanName);
  180. }
  181. }

工具类:

  1. MyUtil.java
  2. public class MyUtil {
  3. public static Set<Class<?>> getClasses(String pack) {
  4. // 第一个class类的集合
  5. Set<Class<?>> classes = new LinkedHashSet<Class<?>>();
  6. // 是否循环迭代
  7. boolean recursive = true;
  8. // 获取包的名字 并进行替换
  9. String packageName = pack;
  10. String packageDirName = packageName.replace('.', '/');
  11. // 定义一个枚举的集合 并进行循环来处理这个目录下的things
  12. Enumeration<URL> dirs;
  13. try {
  14. dirs = Thread.currentThread().getContextClassLoader().getResources(packageDirName);
  15. // 循环迭代下去
  16. while (dirs.hasMoreElements()) {
  17. // 获取下一个元素
  18. URL url = dirs.nextElement();
  19. // 得到协议的名称
  20. String protocol = url.getProtocol();
  21. // 如果是以文件的形式保存在服务器上
  22. if ("file".equals(protocol)) {
  23. // 获取包的物理路径
  24. String filePath = URLDecoder.decode(url.getFile(), "UTF-8");
  25. // 以文件的方式扫描整个包下的文件 并添加到集合中
  26. findClassesInPackageByFile(packageName, filePath, recursive, classes);
  27. } else if ("jar".equals(protocol)) {
  28. // 如果是jar包文件
  29. // 定义一个JarFile
  30. System.out.println("jar类型的扫描");
  31. JarFile jar;
  32. try {
  33. // 获取jar
  34. jar = ((JarURLConnection) url.openConnection()).getJarFile();
  35. // 从此jar包 得到一个枚举类
  36. Enumeration<JarEntry> entries = jar.entries();
  37. findClassesInPackageByJar(packageName, entries, packageDirName, recursive, classes);
  38. } catch (IOException e) {
  39. // log.error("在扫描用户定义视图时从jar包获取文件出错");
  40. e.printStackTrace();
  41. }
  42. }
  43. }
  44. } catch (IOException e) {
  45. e.printStackTrace();
  46. }
  47. return classes;
  48. }
  49. private static void findClassesInPackageByJar(String packageName, Enumeration<JarEntry> entries, String packageDirName, final boolean recursive, Set<Class<?>> classes) {
  50. // 同样的进行循环迭代
  51. while (entries.hasMoreElements()) {
  52. // 获取jar里的一个实体 可以是目录 和一些jar包里的其他文件 如META-INF等文件
  53. JarEntry entry = entries.nextElement();
  54. String name = entry.getName();
  55. // 如果是以/开头的
  56. if (name.charAt(0) == '/') {
  57. // 获取后面的字符串
  58. name = name.substring(1);
  59. }
  60. // 如果前半部分和定义的包名相同
  61. if (name.startsWith(packageDirName)) {
  62. int idx = name.lastIndexOf('/');
  63. // 如果以"/"结尾 是一个包
  64. if (idx != -1) {
  65. // 获取包名 把"/"替换成"."
  66. packageName = name.substring(0, idx).replace('/', '.');
  67. }
  68. // 如果可以迭代下去 并且是一个包
  69. if ((idx != -1) || recursive) {
  70. // 如果是一个.class文件 而且不是目录
  71. if (name.endsWith(".class") && !entry.isDirectory()) {
  72. // 去掉后面的".class" 获取真正的类名
  73. String className = name.substring(packageName.length() + 1, name.length() - 6);
  74. try {
  75. // 添加到classes
  76. classes.add(Class.forName(packageName + '.' + className));
  77. } catch (ClassNotFoundException e) {
  78. // .error("添加用户自定义视图类错误 找不到此类的.class文件");
  79. e.printStackTrace();
  80. }
  81. }
  82. }
  83. }
  84. }
  85. }
  86. private static void findClassesInPackageByFile(String packageName, String packagePath, final boolean recursive, Set<Class<?>> classes) {
  87. // 获取此包的目录 建立一个File
  88. File dir = new File(packagePath);
  89. // 如果不存在或者 也不是目录就直接返回
  90. if (!dir.exists() || !dir.isDirectory()) {
  91. // log.warn("用户定义包名 " + packageName + " 下没有任何文件");
  92. return;
  93. }
  94. // 如果存在 就获取包下的所有文件 包括目录
  95. File[] dirfiles = dir.listFiles(new FileFilter() {
  96. // 自定义过滤规则 如果可以循环(包含子目录) 或则是以.class结尾的文件(编译好的java类文件)
  97. @Override
  98. public boolean accept(File file) {
  99. return (recursive && file.isDirectory()) || (file.getName().endsWith(".class"));
  100. }
  101. });
  102. // 循环所有文件
  103. for (File file : dirfiles) {
  104. // 如果是目录 则继续扫描
  105. if (file.isDirectory()) {
  106. findClassesInPackageByFile(packageName + "." + file.getName(), file.getAbsolutePath(), recursive, classes);
  107. } else {
  108. // 如果是java类文件 去掉后面的.class 只留下类名
  109. String className = file.getName().substring(0, file.getName().length() - 6);
  110. try {
  111. // 添加到集合中去
  112. // classes.add(Class.forName(packageName + '.' +
  113. // className));
  114. classes.add(Thread.currentThread().getContextClassLoader().loadClass(packageName + '.' + className));
  115. } catch (ClassNotFoundException e) {
  116. // log.error("添加用户自定义视图类错误 找不到此类的.class文件");
  117. e.printStackTrace();
  118. }
  119. }
  120. }
  121. }
  122. }

测试用的实体类:Student.java和Class.java类

  1. @Data
  2. @Component("stu")
  3. public class Student {
  4. @Value("2021100101")
  5. private Integer sid;
  6. @Value("张三")
  7. private String name;
  8. @Value("男")
  9. private String sex;
  10. @Autowired
  11. @Qualifier("cls")
  12. private Class clazz;
  13. }
  14. @Data
  15. @Component("cls")
  16. public class Class {
  17. @Value("1001")
  18. private Integer cid;
  19. @Value("精英班")
  20. private String className;
  21. }

测试:

  1. public class Test {
  2. public static void main(String[] args) {
  3. AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext("com.raylei.entity");
  4. System.out.println(ioc.getBean("stu"));
  5. }
  6. }

结果:

20210412221748443.png

GitHub:https://github.com/Mr-LeiLei/MySpringIoc

CSDN:https://download.csdn.net/download/qq_36942720/16640502

发表评论

表情:
评论列表 (有 0 条评论,334人围观)

还没有评论,来说两句吧...

相关阅读

    相关 实现优雅重试

    重试的作用 对于重试是有场景限制的,不是什么场景都适合重试,比如参数校验不合法、写操作等(要考虑写是否幂等)都不适合重试。 远程调用超时、网络突然中断可以重试。在微服务

    相关 实现SpringIoc容器

    IoC(Inverse of Controll控制反转):指的是对象的创建方式进行了反转,传统的开发方式是程序员自己 new 对象,IoC就是将这一过程进行了反转,程序员不需要

    相关 地配置Spring

    本文旨在从一个空工程一步一步地配置Spring,空工程见上一篇文章[创建Maven父子工程][Maven]。 \\一、spring基本配置 \\\1. 添加spring依赖