深入剖析Spring(五):IOC核心思想(代码篇)

我会带着你远行 2023-07-19 12:19 73阅读 0赞

在上一篇文章中,我们粗略的对Spring源码IOC这块过了一遍,那么这篇文章来简单写一个IOC的过程。由于理论性的东西都在上一篇解释过了,这篇咱就直接在代码中理解。

一、准备工作

1.1本文所用到的依赖包:
  1. <dependency>
  2. <groupId>org.projectlombok</groupId>
  3. <artifactId>lombok</artifactId>
  4. <version>1.18.12</version>
  5. </dependency>
  6. <dependency>
  7. <groupId>org.springframework</groupId>
  8. <artifactId>spring-webmvc</artifactId>
  9. <version>4.3.9.RELEASE</version>
  10. </dependency>
  11. <dependency>
  12. <groupId>javax.servlet</groupId>
  13. <artifactId>servlet-api</artifactId>
  14. <version>2.5</version>
  15. </dependency>
1.2 web.xml文件
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd" version="4.0">
  3. <display-name>MVC Web Application</display-name>
  4. <servlet>
  5. <servlet-name>CcMvc</servlet-name>
  6. <!-- 指定我们自定义的DispatcherServlet的路径-->
  7. <servlet-class>com.ccc.spring2.webmvc.XHDispatcherServlet</servlet-class>
  8. <init-param>
  9. <!--指定spring的xml的配置文件,我在这里用properties文件代替。-->
  10. <param-name>contextConfigLocation</param-name>
  11. <param-value>classpath:application.properties</param-value>
  12. </init-param>
  13. <!-- 让tomcat启动时即加载时便初始化servlet -->
  14. <load-on-startup>1</load-on-startup>
  15. </servlet>
  16. <servlet-mapping>
  17. <!-- url-->
  18. <servlet-name>CcMvc</servlet-name>
  19. <url-pattern>/*</url-pattern>
  20. </servlet-mapping>
  21. </web-app>
1.3 建立项目结构

我们模仿spring的项目结构

1.3.1 创建BeanFactory接口

在包beans下创建:

  1. /** * @author chenxh * @date 2020/3/23 20:28 * @Description: 单例工厂顶层 * @modify: * @modifyDate: * @Description: */
  2. public interface XHBeanFactory {
  3. /** * 根据beanName从IOC容器中获取一个实例Bean * spring中使用单例有利于管理和维护 * */
  4. Object getBean(String beanName) throws Exception;
  5. Object getBean(Class<?> beanClass) throws Exception;
  6. }
1.3.2 创建BeanDefinition

在之前说道过在Spring容器启动的过程中,会将Bean解析成BeanDefinition结构。因此我们也创建一个,模仿spring的包名创建一个config包:

  1. @Data
  2. public class XHBeanDefinition {
  3. private String beanClassName; //全包名
  4. private boolean isLazyInit = false; //是否懒加载
  5. private String factoryBeanName; //类名首字母小写
  6. private boolean isSingleton = true; //是否单例
  7. }
1.3.3 BeanDefinitionReader

这个方法主要作用是解析配置文件并将其封装成BeanDefinition对象供IOC操作,此方法在上几篇文章MVC中写过,这里就不在解释了。

  1. package com.ccc.spring2.beans.support;
  2. import com.ccc.spring2.beans.config.XHBeanDefinition;
  3. import java.io.File;
  4. import java.io.IOException;
  5. import java.io.InputStream;
  6. import java.net.URL;
  7. import java.util.ArrayList;
  8. import java.util.List;
  9. import java.util.Properties;
  10. /** * 加载配置文件的类 */
  11. public class XHBeanDefinitionReader {
  12. private Properties config = new Properties();
  13. //定义配置文件,暂时写死
  14. private final String SCAN_PACKAGE = "scanPackage";
  15. //存储所有的类名
  16. private List<String> registyBeanClasses = new ArrayList<String>(); //全包名
  17. public XHBeanDefinitionReader(String... locations) {
  18. //通过URL定位找到对应的文件,转为文件流读取
  19. try (InputStream is = this.getClass().getClassLoader().getResourceAsStream(locations[0].replace("classpath:", ""))) {
  20. config.load(is);
  21. } catch (IOException e) {
  22. e.printStackTrace();
  23. }
  24. //进行扫描
  25. doScanner(config.getProperty(SCAN_PACKAGE));
  26. }
  27. private void doScanner(String scanPackage) {
  28. //转换为文件路径,实际上就是把.替换为/就OK了
  29. URL url = this.getClass().getResource("/" + scanPackage.replaceAll("\\.","/"));
  30. File classPath = new File(url.getFile());
  31. for (File file : classPath.listFiles()) {
  32. if (file.isDirectory()) {
  33. doScanner(scanPackage + "." + file.getName());
  34. } else {
  35. if (!file.getName().endsWith(".class")) {
  36. continue;
  37. }
  38. String className = (scanPackage + "." + file.getName().replace(".class", ""));
  39. registyBeanClasses.add(className);
  40. }
  41. }
  42. }
  43. public Properties getConfig() {
  44. return this.config;
  45. }
  46. //扫描配置信息并内部封装成XHBeanDefinition对象,便于IOC操作
  47. public List<XHBeanDefinition> loadBeanDefinitions(String... locations) {
  48. List<XHBeanDefinition> result = new ArrayList<XHBeanDefinition>();
  49. try {
  50. System.out.println("registyBeanClasses:"+registyBeanClasses);
  51. for (String className : registyBeanClasses) {
  52. Class<?> beanClass = Class.forName(className);
  53. //如果是一个接口,是不能实例化的
  54. //用它实现类来实例化
  55. if(beanClass.isInterface()) { continue; }
  56. //beanName有三种情况:
  57. //1、默认是类名首字母小写
  58. //2、自定义名字
  59. //3、接口注入
  60. result.add(doCreateBeanDefinition(toLowerFirstCase(beanClass.getSimpleName()),beanClass.getName()));
  61. result.add(doCreateBeanDefinition(beanClass.getName(),beanClass.getName()));
  62. Class<?> [] interfaces = beanClass.getInterfaces();
  63. for (Class<?> i : interfaces) {
  64. //如果是多个实现类,只能覆盖
  65. //为什么?因为Spring没那么智能,就是这么傻
  66. //这个时候,可以自定义名字
  67. result.add(doCreateBeanDefinition(i.getName(),beanClass.getName()));
  68. }
  69. }
  70. }catch (Exception e){
  71. e.printStackTrace();
  72. }
  73. return result;
  74. }
  75. //把配置信息解析成BeanDefinition
  76. //把每一个配信息解析成一个BeanDefinition
  77. private XHBeanDefinition doCreateBeanDefinition(String factoryBeanName,String beanClassName){
  78. XHBeanDefinition beanDefinition = new XHBeanDefinition();
  79. beanDefinition.setBeanClassName(beanClassName);
  80. beanDefinition.setFactoryBeanName(factoryBeanName);
  81. return beanDefinition;
  82. }
  83. /** * 首字母小写 */
  84. private String toLowerFirstCase(String simpleName) {
  85. char [] chars = simpleName.toCharArray();
  86. chars[0] += (1<<5);
  87. return String.valueOf(chars);
  88. }
  89. }
1.3.4 BeanWrapper

BeanWrapper是对Bean的包装,其接口中所定义的功能很简单包括设置获取被包装的对象,获取被包装bean的属性描述器.在包beans下:

  1. public class XHBeanWrapper {
  2. private Object wrappedInstance; //类名,首字母小写
  3. private Class<?> wrappedClass; //对象
  4. public XHBeanWrapper(Object wrappedInstance){
  5. this.wrappedInstance = wrappedInstance;
  6. }
  7. public Object getWrappedInstance(){
  8. return this.wrappedInstance;
  9. }
  10. // 返回代理以后的Class
  11. // 可能会是这个 $Proxy0
  12. public Class<?> getWrappedClass(){
  13. return this.wrappedInstance.getClass();
  14. }
  15. }
1.3.5 IOC容器AbstractApplicationContext

创建包名context在子包support下创建(support包名的意思在java中大多数是扩展的意思。)
在上篇文章中提到过IOC初始化真正的逻辑是在refresh() 中,而该又是实际上又是一个模板方法,只是规定了IOC启动的流程,具体的逻辑还是由其子类来实现的。

  1. public abstract class XHAbstractApplicationContext {
  2. //规范方法,提供重写
  3. public void refresh() throws Exception { };
  4. }
1.3.6 DefaultListableBeanFactory

是否还记得这个类,在这稍微解释一下spring中BeanFactory定义了一系列规范都由子类去实现,例如表示可序列化的Bean,有继承关系的bean等等,这些Bean在spring中都是分开管理的而DefaultListableBeanFactory这个类就是这些子类最终的实现类。也就是这些类最终都由它来实现了。
在这里我们简单的定义一下。理解一下它的思想:在beans包下创建support

  1. //我们简单就实现一个IOC父类
  2. public class XHDefaultListableBeanFactory extends XHAbstractApplicationContext {
  3. //存储注册信息的BeanDefinition,伪IOC容器 //key是类名小写,value是beanDefinition对象
  4. protected final Map<String, XHBeanDefinition> beanDefinitionMap = new ConcurrentHashMap<String, XHBeanDefinition>();
  5. }
1.3.7 ApplicationContext

这个应该不陌生了。

  1. public class XHApplicationContext extends XHDefaultListableBeanFactory implements XHBeanFactory {
  2. //配置文件
  3. private String[] configLocations;
  4. //加载配置文件的类
  5. private XHBeanDefinitionReader reader;
  6. //单例IOC容器
  7. private Map<String, Object> singletonObjects = new ConcurrentHashMap<>();
  8. //通用IOC容器
  9. private Map<String, XHBeanWrapper> factoryBeanInstanceCache = new ConcurrentHashMap<>();
  10. public XHApplicationContext(String... configLocations) throws Exception {
  11. this.configLocations = configLocations;
  12. refresh();
  13. }
  14. /** * 重写AbstractApplicationContext的refresh方法 * 此方法是一个模板方法,规定了IOC容器的运行流程, * 调用的是父类AbstractApplicationContext 的方法. * 真正的载入也是从这个时候开始的。 */
  15. @Override
  16. public void refresh() throws Exception {
  17. //IOC
  18. //1.定位,找到配置文件
  19. reader = new XHBeanDefinitionReader(this.configLocations);
  20. //2.加载配置文件,扫描相关的类,把它们封装成BeanDefinition
  21. List<XHBeanDefinition> beanDefinitions = reader.loadBeanDefinitions();
  22. //3.注册,把配置信息放到容器里(伪IOC容器)
  23. doRegisterBeanDefinition(beanDefinitions);
  24. //4.把非懒加载的类提前初始化
  25. doAutowired();
  26. }
  27. }

手动创建以上方法。到此准备工作结束了。

2. 填坑

既然我们主路拉通了,那么现在就开始来填坑。
关注refresh()这个方法是IOC容器初始化的入口。其实在之前的文章中就讲过这个过程。在这里1、2两步就不说啦,代码也贴出来了也注释啦,有兴趣的朋友可以去看看小编这个专栏下的MVC篇。里面提到了这些。

2.1 doRegisterBeanDefinition(beanDefinitions)

此方法将BeanDefinitionReader对象解析出来对象进行迭代并存放到Map中。

  1. private void doRegisterBeanDefinition(List<XHBeanDefinition> beanDefinitions) {
  2. //IOC加载
  3. for (XHBeanDefinition beanDefinition : beanDefinitions) {
  4. //父类XHDefaultListableBeanFactory中定义的map。这个Map不是真正意义上的IOC容器
  5. super.beanDefinitionMap.put(beanDefinition.getFactoryBeanName(), beanDefinition);
  6. }
  7. }
2.2 doAutowired()

遍历上一步中存入Map中的集合,并判断对象中isLazyInit属性是否为懒加载,如果不是懒加载的就进行getBean()DI注入操作。

  1. //处理非延时加载
  2. private void doAutowired() throws Exception {
  3. //遍历定位
  4. for (Map.Entry<String, XHBeanDefinition> beanDefinitionEntry : super.beanDefinitionMap.entrySet()) {
  5. String beanName = beanDefinitionEntry.getKey();
  6. if (!beanDefinitionEntry.getValue().isLazyInit()) {
  7. getBean(beanName);
  8. }
  9. }
  10. }

IOC的简单步骤到此就介绍的差不多了,我们发现不管是之前MVC中介绍的,还是本文章介绍的内容,我们都能发现Spring在处理过程中,都是一步步去解析的,例如之前在解析配置文件的时候,会先加载->在找到所有class类->在判断是否@Service、@Controller这些类注解->在判断是否@Autowried等注解。

对于IOC 整理就以上这些就介绍这么多,如果发现有补充的地方,我会继续添加,同时希望各位看过的伙伴们如果发现了问题能够及时批评指正,在此感谢。下一篇我会记录DI的过程。

上一篇:深入剖析Spring(四):IOC核心思想(源码分析篇)

发表评论

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

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

相关阅读