• 手写模拟Spring的底层原理2.1


    先来引入两个问题

    第一个懒加载还是立即加载的问题,这个问题还是在于就是说,当我们xml配置了一个对象bean的时候,它在spring容器里面是什么时候开始会给我们创建这个对象

    那如果我们想对某个对象启动懒加载,可以添加@lazy这个注解

     这个注解一加上,它就只会在得到对象的时候给我们在容器中创建对象

    也就是在使用下面的方法的时候才会去创建一个对象

    那么默认这个对象得到的是单例还是多例,我们先来说一个小知识点

    那么我们测试一下多例还是单例

     

    很明显上面是单例设计模式

    那么我们如何把它变成多例,就得靠下面这个注解来做

     

     java的三个类加载器分别是什么

     

     下面说关于注解的一些反射方法的用法

    1.要判断在一个类上面有没有某个注解接口

    configClass.isAnnotationPresent(ComponentScan.class)

    利用这个类的字节码对象去调用上面的方法,内部传入我们需要去判断注解接口的字节码对象

    2.如果有这个注解,我们需要去拿到这个注解对象

    ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);

    还是利用当前类字节码对象去调用getAnnotation()方法,内部传入一个我们需要得到的这个注解对象的字节码对象

    3.当拿到这个注解对象之后,我们现在想要去拿到值,也就是调用内部的方法就可以了,比如value的值,直接调用value()方法

    String path = componentScanAnnotation.value();

    这样就可以得到配置在这个注解上面的值,重点还是value属性的值

    下面讲解一下关于class文件资源路径获取的问题

     我们一般在程序的开发当中,生成的class文件会直接放到下面这个目录里面,然后我一般通过内存操作判定就是这个文件的信息

    上面两者包都是对应的,只是说在target里面,他还有前面两个固定的包

     那么我们如果想获取target里面的User怎么获取

    上面就是拿到类加载器,然后去调用getResource(),里面传入一个你需要查找类的相对包的路径,然后会给我们返回一个URL ,这个URL就是一个在target上面的绝对路径,他包括了整个硬盘的位置

    也就是说类加载器会给我们自动凭借好target/classses这两个目录

    然后通过URL的resource对象调用getFile(),就可以把这个路径变成一个字符串,然后可以传入到一个FIle对象里面,然后利用File对象我们就可以做一些事儿

     下面说一下文件对象里面的listFiles()的用法

     

     比如我刚噶上面的操作,贴过来

     

    他就可以找到这个class文件的完整路径

    我们还可以做一些截取操作直接拿到com\pxx\domain\User这部分

    循环遍历就可以拿到domain下面的所有class文件

    下面写一个简单一点的spring的源码

     这个就是先跑通最基础的程序吧,就是我们可以通过getBean去得到一个单例或者多例的对象

    先把项目结构简单的弄出来

    下面做一个简单的分析,怎么着手

    首先我们在spring容器中我们需要一个容器对象,就类似于下面这个容器对象

    所以我们把这个容器对象在我们的spring里面给创建出来,我们这里叫PxxApplicationContext对象

     下面我们要干嘛,是不是要去扫描包,为什么说我们要去扫描包,因为我们主要是扫描上面的注解

    比如我们要去判断这个类上面有没有component这个注解啊,有的话,我们就要去创建这个对象

    还有比如说这个对象上面有没有scope这个注解,这个注解里面给我们配置的值是单例还是多例

    这些注解都要被扫描到

    因为现在我们要去扫描到这些注解嘛,所以,我们先去写几个常用的注解在上面

    这些注解先都是放到spring下面的annotation的包下面

    第一个注解:Component注册对象用的

    第二个注解: ComponentScan配置要扫描哪一个包用的

     第三个注解:Scope注解这个对象的生成是单例还是多例

    好了,现就写上面三个注解

    下面我们去扫描包,扫描类,扫描注解,还是去完成scan方法

    大概步骤如下

    写到下面我发现需要一个BeanDefinition对象来保存一些对象的信息

    比如它是什么类型的Class,它的Scope是什么值,他是否采用延迟加载

    于是我们去构建一个这样的对象,放到了spring下面的domain包里面

    1. package com.spring.domian;
    2. public class BeanDefinition {
    3. private Class type;
    4. private String scope;
    5. private boolean isLazy;
    6. public Class getType() {
    7. return type;
    8. }
    9. public void setType(Class type) {
    10. this.type = type;
    11. }
    12. public String getScope() {
    13. return scope;
    14. }
    15. public void setScope(String scope) {
    16. this.scope = scope;
    17. }
    18. public boolean isLazy() {
    19. return isLazy;
    20. }
    21. public void setLazy(boolean lazy) {
    22. isLazy = lazy;
    23. }
    24. }

     既然有了这个我们就可以继续去改写我们的代码

    写到下面这个位置,我又开始迷茫了

     于是我做了集合对象保存他们

    做好了这个映射关系的集合之后,scan这个方法方法就做完了,下面贴一下代码,这个代码有详细的注释,可以结合着前面来看

    1. package com.spring;
    2. import com.spring.annotation.Component;
    3. import com.spring.annotation.ComponentScan;
    4. import com.spring.annotation.Scope;
    5. import com.spring.domian.BeanDefinition;
    6. import java.io.File;
    7. import java.net.URL;
    8. import java.util.HashMap;
    9. import java.util.Map;
    10. /**
    11. * @author pxx
    12. */
    13. public class PxxApplicationContext {
    14. //内部需要传入一个配置类的class字节码文件
    15. private Class configClass;
    16. //这个集合用来存放对象与它的BeanDefinitionMap的映射信息
    17. private Map beanDefinitionMap = new HashMap<>();
    18. //写一个构造函数
    19. //这个构造函数不止是帮我们传入这个配置类的calss对象
    20. //还要帮我们做一些初始化操作,比如如果一个对象是单例
    21. //这个ApplicationContext new出来之后,就会创建这个对象
    22. public PxxApplicationContext(Class configClass) {
    23. this.configClass = configClass;
    24. //写一个函数做一些扫描的动作
    25. scan(configClass);//从这个配置类上面的包开始进行解析
    26. }
    27. //下面我们定义scan这个扫描的方法
    28. private void scan(Class configClass) {
    29. //先来判断有没有ComponentScan这个注解
    30. if(configClass.isAnnotationPresent(ComponentScan.class)) {
    31. //如果上面这个配置类有需要扫描的包的注解
    32. //那么我们就要取出里面的值=》然后去扫描相应的包
    33. //既然有了,那么我们就拿到这个ComponentScan这个对象
    34. ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
    35. String path = componentScanAnnotation.value();//就能得到我们需要扫描的包
    36. path = path.replace(".","/");//因为得到的是com.pxx.service
    37. //但是我们下面要去利用文件扫描,我们需要一个真实的路径,所以上面需要替换一下
    38. //上面就得到了我们需要扫描的包
    39. //那么我们真正要扫描的是什么?是源代码文件吗?并不是,我们需要的是一个class字节码对象文件
    40. //我们真正要去扫的是这个class文件,这个文件很多对象的信息嘛
    41. //怎么得到这个文件位置,我们用类加载器器来做
    42. //类加载器一般就用来寻找资源文件
    43. ClassLoader classLoader = PxxApplicationContext.class.getClassLoader();
    44. //这里你可以这样,他会自动去开始从target/classes下面开始拼接你这个额path路径
    45. URL resource = classLoader.getResource(path);//这个就会绝对的路径URl对象,是在target里面
    46. //把这个路径变成字符串然后封装到File对象,我们用Filed对象再去遍历这个文件夹下面的每一个类
    47. File file = new File(resource.getFile());
    48. //下面开始遍历这个绝对路径下面的文件
    49. if (file.isDirectory()) {
    50. for (File f : file.listFiles()) {
    51. //我们要去拿到的是什么
    52. //其实就是com.pxx.servcie下面的每一个类的全限定类名
    53. //比如com.pxx.service.User com.pxx.service.Order ..
    54. String absolutePath = f.getAbsolutePath();
    55. //主要在于这里我们需要按照字符串位置截取
    56. //这里会截出一个com\pxx\service\User 比如com\pxx\service\Ortder
    57. absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
    58. //我们把上面的\全部变成.,因为后面我们需要加载进内存
    59. absolutePath = absolutePath.replace("\\",".");
    60. //上面基本的找类的字节码对象就已经找到了
    61. //下面我们就需要把这些类加载到内存里面,然后变成一个Class字节码对象
    62. //因为只有变成Class字节码对象,我们才能去查找这个类上面有什么注解
    63. try {
    64. Class clazz = classLoader.loadClass(absolutePath);
    65. //判断这个字节码对象有没有Component注解
    66. if(clazz.isAnnotationPresent(Component.class)) {
    67. //如果有这个注解,说明这个对象需要被getBean创建
    68. //我们拿到一个注解的名字,在Component里面,可能配了名字,可能没有
    69. Component componentAnno = clazz.getAnnotation(Component.class);
    70. String beanName = componentAnno.value();
    71. //结合下面来看每一个对象好像都有一些信息配置
    72. //那么我们就考虑到为了灵活性
    73. //我们就把这些信息封装到一个BeanDefinition对象里面
    74. BeanDefinition beanDefinition = new BeanDefinition();
    75. //设置一下它的class类型
    76. beanDefinition.setType(clazz);//什么类的字节码对象
    77. //然后我们又要去判断有没有Scope这个注解
    78. if(clazz.isAnnotationPresent(Scope.class)) {
    79. //如果有这个注解,我们就要去得到这个注解的对象
    80. Scope scopeAnn = clazz.getAnnotation(Scope.class);//从内存中就得到了这个注解对象
    81. String value = scopeAnn.value();//用来判断这里是单例还是多例
    82. //这里还是把信息封装到BeanDefinition里面
    83. beanDefinition.setScope(value);
    84. } else {
    85. //如果没有这个注解说明它就是一个单例
    86. beanDefinition.setScope("singleton");//这是这个Scope注解的可用取值
    87. }
    88. //写到这,我们又得来考虑一个问题
    89. //首先就是说在com.pxx.service下面他不止一个class,有很多
    90. //那么也就是说产生的BeanDefinition就会有很多,就是会有很多对象的信息的嘛
    91. //那么我们这里为了区分,我们指定用beanName->BeanDefinition对象
    92. //上面有一个映射关系,那么我们就做一个集合存进去
    93. beanDefinitionMap.put(beanName,beanDefinition);
    94. }
    95. } catch (ClassNotFoundException e) {
    96. e.printStackTrace();
    97. }
    98. }
    99. }
    100. }
    101. }
    102. }

    下面我们测一下,写几个类,看看这个map里面有没有把对象收录进去

    下面在service包里面做了两个service对象

     

    下面这个service是一个什么都没有加的service

     

    然后我们要去写一个配置类用于开启包扫描

     

    然后写我们主方法,写之前我在scan里面打印了一下保存对象信息的map集合

     然后主方法直接new,上面的构造方法开始运行

    看一下运行结果

     

    打印了两个对象,完美,因为有一个对象没有加任何注解,所以下面 他根本进不去

    找到Class字节码对象之后,验证了有Component注解才会进去

    还有一个小问题是,我刚刚没在@Component注解里面写名字,所有对象进来都没有名字,去测试一下发现确实为空

     

    我们先去给注解加上名字,方便后面进行测试

     

     下面再去测名字就进去了

     下面我们就要去写getBean方法来给我们生产对象

    写到这个位置,我又卡住了单例多例这里怎么来设计

    单例必须保证每次取出来的对象都是同一个对象,我考虑用一个集合来做,如果同一个对象过来,都从集合里面取出唯一的键,然后获取固定的对象不就好了

    因此我在做一个下面的映射集合存放名字与对象的映射集合

     

     上面除了createBean这个方法没有之外,还是存在一个问题的,也就是,单例从集合中取,第一次过来数据从哪里来的?

    这个时候,我们考虑一个问题,那就是spirng的加载模式,是在new 一个容器之后就创建出来对象,还是getBean时创建出来对象吗,这里我们默认是立即加载

    既然这样,那么我们创建对象的工作,就必须放到容器的构造函数里面执行,也就是说,san包扫描完成之后,把相应的对象信息都放到集合里面去了之后,就要卡死给我们生产对象

    于是我们去修改代码

    先来看一个方法

    这个是HashMap里面的饿方法,把集合里面元素放到Set集合里面,Set集合内部保存的是一个Entry对象,有 下面两个方法

    好了继续我们的代码,说这个只是因为等会我要用增强for循环来遍历beanDefinitionMap里面的数据

    再来说一点

     这个位置加载不同类的class文件,会返回不同类Class类的信息。

    所以这里放进来的也不是不同类的Class字节码对象

    下面直接上完整代码

    1. package com.spring;
    2. import com.spring.annotation.Component;
    3. import com.spring.annotation.ComponentScan;
    4. import com.spring.annotation.Scope;
    5. import com.spring.domian.BeanDefinition;
    6. import java.io.File;
    7. import java.lang.reflect.InvocationTargetException;
    8. import java.net.URL;
    9. import java.util.HashMap;
    10. import java.util.Map;
    11. /**
    12. * @author pxx
    13. */
    14. public class PxxApplicationContext {
    15. //内部需要传入一个配置类的class字节码文件
    16. private Class configClass;
    17. //这个集合用来存放对象与它的BeanDefinitionMap的映射信息
    18. private Map beanDefinitionMap = new HashMap<>();
    19. //这个用来存放bean名字与之对应的对象
    20. private Map singletonObjects = new HashMap<>();
    21. //写一个构造函数
    22. //这个构造函数不止是帮我们传入这个配置类的calss对象
    23. //还要帮我们做一些初始化操作,比如如果一个对象是单例
    24. //这个ApplicationContext new出来之后,就会创建这个对象
    25. public PxxApplicationContext(Class configClass) {
    26. this.configClass = configClass;
    27. //写一个函数做一些扫描的动作
    28. scan(configClass);//从这个配置类上面的包开始进行解析
    29. //立即加载,new一个对象之后马上创建Bean对象
    30. //采用循环,遍历出beanDefinitionMap里面能的beanName
    31. for (Map.Entry entry : beanDefinitionMap.entrySet()) {
    32. String beanName = entry.getKey();//得到bean的名字
    33. BeanDefinition beanDefinition = entry.getValue();//得到对应的对象信息
    34. //这里不管是单例都应该马上创建一个对象出来
    35. Object bean = createBean(beanName, beanDefinition);
    36. //再来判断一把,如果这个对象是单例,放到集合里面去
    37. if (beanDefinition.getScope().equals("singleton")) {
    38. singletonObjects.put(beanName,bean);
    39. }
    40. }
    41. }
    42. //下面我们定义scan这个扫描的方法
    43. private void scan(Class configClass) {
    44. //先来判断有没有ComponentScan这个注解
    45. if(configClass.isAnnotationPresent(ComponentScan.class)) {
    46. //如果上面这个配置类有需要扫描的包的注解
    47. //那么我们就要取出里面的值=》然后去扫描相应的包
    48. //既然有了,那么我们就拿到这个ComponentScan这个对象
    49. ComponentScan componentScanAnnotation = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
    50. String path = componentScanAnnotation.value();//就能得到我们需要扫描的包
    51. path = path.replace(".","/");//因为得到的是com.pxx.service
    52. //但是我们下面要去利用文件扫描,我们需要一个真实的路径,所以上面需要替换一下
    53. //上面就得到了我们需要扫描的包
    54. //那么我们真正要扫描的是什么?是源代码文件吗?并不是,我们需要的是一个class字节码对象文件
    55. //我们真正要去扫的是这个class文件,这个文件很多对象的信息嘛
    56. //怎么得到这个文件位置,我们用类加载器器来做
    57. //类加载器一般就用来寻找资源文件
    58. ClassLoader classLoader = PxxApplicationContext.class.getClassLoader();
    59. //这里你可以这样,他会自动去开始从target/classes下面开始拼接你这个额path路径
    60. URL resource = classLoader.getResource(path);//这个就会绝对的路径URl对象,是在target里面
    61. //把这个路径变成字符串然后封装到File对象,我们用Filed对象再去遍历这个文件夹下面的每一个类
    62. File file = new File(resource.getFile());
    63. //下面开始遍历这个绝对路径下面的文件
    64. if (file.isDirectory()) {
    65. for (File f : file.listFiles()) {
    66. //我们要去拿到的是什么
    67. //其实就是com.pxx.servcie下面的每一个类的全限定类名
    68. //比如com.pxx.service.User com.pxx.service.Order ..
    69. String absolutePath = f.getAbsolutePath();
    70. //主要在于这里我们需要按照字符串位置截取
    71. //这里会截出一个com\pxx\service\User 比如com\pxx\service\Ortder
    72. absolutePath = absolutePath.substring(absolutePath.indexOf("com"),absolutePath.indexOf(".class"));
    73. //我们把上面的\全部变成.,因为后面我们需要加载进内存
    74. absolutePath = absolutePath.replace("\\",".");
    75. //上面基本的找类的字节码对象就已经找到了
    76. //下面我们就需要把这些类加载到内存里面,然后变成一个Class字节码对象
    77. //因为只有变成Class字节码对象,我们才能去查找这个类上面有什么注解
    78. try {
    79. Class clazz = classLoader.loadClass(absolutePath);
    80. //判断这个字节码对象有没有Component注解
    81. if(clazz.isAnnotationPresent(Component.class)) {
    82. //如果有这个注解,说明这个对象需要被getBean创建
    83. //我们拿到一个注解的名字,在Component里面,可能配了名字,可能没有
    84. Component componentAnno = clazz.getAnnotation(Component.class);
    85. String beanName = componentAnno.value();
    86. //结合下面来看每一个对象好像都有一些信息配置
    87. //那么我们就考虑到为了灵活性
    88. //我们就把这些信息封装到一个BeanDefinition对象里面
    89. BeanDefinition beanDefinition = new BeanDefinition();
    90. //设置一下它的class类型
    91. beanDefinition.setType(clazz);//什么类的字节码对象
    92. //然后我们又要去判断有没有Scope这个注解
    93. if(clazz.isAnnotationPresent(Scope.class)) {
    94. //如果有这个注解,我们就要去得到这个注解的对象
    95. Scope scopeAnn = clazz.getAnnotation(Scope.class);//从内存中就得到了这个注解对象
    96. String value = scopeAnn.value();//用来判断这里是单例还是多例
    97. //这里还是把信息封装到BeanDefinition里面
    98. beanDefinition.setScope(value);
    99. } else {
    100. //如果没有这个注解说明它就是一个单例
    101. beanDefinition.setScope("singleton");//这是这个Scope注解的可用取值
    102. }
    103. //写到这,我们又得来考虑一个问题
    104. //首先就是说在com.pxx.service下面他不止一个class,有很多
    105. //那么也就是说产生的BeanDefinition就会有很多,就是会有很多对象的信息的嘛
    106. //那么我们这里为了区分,我们指定用beanName->BeanDefinition对象
    107. //上面有一个映射关系,那么我们就做一个集合存进去
    108. beanDefinitionMap.put(beanName,beanDefinition);
    109. }
    110. } catch (ClassNotFoundException e) {
    111. e.printStackTrace();
    112. }
    113. }
    114. }
    115. }
    116. }
    117. //下面我们去实现一个创建对象的方法
    118. private Object createBean(String beanName, BeanDefinition beanDefinition) {
    119. //这里先简单写一些,等会方便测试
    120. Class clazz = beanDefinition.getType();//它是哪一个类的对象实例,之前scan就已经扫进去了
    121. Object instance = null;
    122. try {
    123. instance = clazz.getConstructor().newInstance();
    124. } catch (InstantiationException e) {
    125. e.printStackTrace();
    126. } catch (IllegalAccessException e) {
    127. e.printStackTrace();
    128. } catch (InvocationTargetException e) {
    129. e.printStackTrace();
    130. } catch (NoSuchMethodException e) {
    131. e.printStackTrace();
    132. }
    133. return instance;
    134. }
    135. public Object getBean(String beanName) {
    136. //先从map集合中去看有没有名字与之对应的BeanDefinition信息
    137. if (!beanDefinitionMap.containsKey(beanName)) {
    138. throw new NullPointerException();
    139. }
    140. //如果有,我们从beanDefinitionMap中取出这个相应对象的信息
    141. BeanDefinition beanDefinition = beanDefinitionMap.get(beanName);
    142. //然后根据内部属性判断这个对象是单例还是多例
    143. if (beanDefinition.getScope().equals("singleton")) {
    144. //单例设计
    145. Object singleBean = singletonObjects.get(beanName);
    146. return singleBean;
    147. } else {
    148. //多例
    149. //多例,其实就是每次都要重新创建一个对象
    150. Object prototypeBean = createBean(beanName, beanDefinition);
    151. return prototypeBean;
    152. }
    153. }
    154. }

    测试

    下面改成多例测试一下

     

     

    明显一测试就不一样了

    下面说一下@Scope这里设置值的问题 

     

     

     

  • 相关阅读:
    UE5中APlayerController属性与方法列表(翻译中......)
    低噪声 256 细分微步进电机驱动MS35774/MS35774A(汽车应用级别)
    高精度随流检测技术助力金融行业实现智能运维
    2022爱分析・采购数字化厂商全景报告 | 爱分析报告
    C&C!无法检测的远程命令执行工具
    LeetCode 1710. 卡车上的最大单元数
    阿里巴巴商品详情API接口(item_get-获得商品详情接口),阿里巴巴API接口
    VUE element-ui之el-form表单点击按钮自动增加表单(输入框),可新增删除
    公网远程访问macOS本地web服务器
    LeetCode每日一题(1862. Sum of Floored Pairs)
  • 原文地址:https://blog.csdn.net/Pxx520Tangtian/article/details/132880130