阅读Spring源码:IOC控制反转前的处理

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 阅读Spring源码:IOC控制反转前的处理

技术经验交流:点击入群


ClassPathXmlApplicationContext的注册方式


源码分析基于Spring4.3


ClassPathXmlApplicationContext入口,最终都会调用到


/*
     * 使用给定父级创建新的ClassPathXmlApplicationContext,从给定的XML文件加载定义信息。
     * 加载所有的bean 定义信息并且创建所有的单例
     * 或者,在进一步配置上下文之后手动调用刷新。
*/
public ClassPathXmlApplicationContext(String[] configLocations, boolean refresh, ApplicationContext parent)
  throws BeansException {
  super(parent);
  setConfigLocations(configLocations);
  if (refresh) {
    refresh();
  }
}


上述注释的解释如是说:在容器的启动过程中,初始化过程中所有的bean都是单例存在的


自动刷新


ApplicationContext context = new ClassPathXmlApplicationContext("xxx.xml");


就等同于手动刷新


ApplicationContext context = new ClassPathXmlApplicationContext();
context.register("xxx.xml");
context.refresh();


上述一共有三条链路,下面来一一分析


加载父子容器


首先是加载并初始化父容器的方法


image.png



1、第一个出场的是ClassPathXmlApplicationContext,它是一个独立的应用程序上下文,从类路径获取上下文定义文件,能够将普通路径解析为包含包路径的类路径资源名称。它可以支持Ant-Style(路径匹配原则),它是一站式应用程序的上下文,考虑使用GenericApplicationContext类结合XmlBeanDefinitionReader来设置更灵活的上下文配置。


Ant-Style 路径匹配原则,例如 "mypackages/application-context.xml" 可以用"mypackages/*-context.xml" 来替换。


⚠️注意: 如果有多个上下文配置,那么之后的bean定义将覆盖之前加载的文件。这可以用来通过额外的XML文件故意覆盖某些bean定义


2、随后不紧不慢走过来的不是一个完整的somebody,AbstractXmlApplicationContext, 它是为了方便ApplicationContext的实现而出现的(抽象类一个很重要的思想就是适配)。


AbstractXmlApplicationContext 的最主要作用就是通过创建一个XML阅读器解析ClassPathXmlApplicationContext 注册的配置文件。它有两个最主要的方法 loadBeanDefinitions(DefaultListableBeanFactory beanFactory)loadBeanDefinitions(XmlBeanDefinitionReader reader)


3、下一个缓缓出场的是 AbstractRefreshableConfigApplicationContext ,它就像是中间人的角色,并不作多少工作,很像古代丞相的奏折要呈递给皇上,它的作用就相当于是拿奏折的角色。它用作XML应用程序上下文实现的基类,例如ClassPathXmlApplicationContext、FileSystemXmlApplicationContext和XmlWebApplicationContext


4、当老板的一般都比较听小秘的,那么AbstractRefreshableApplicationContext就扮演了小秘的角色,它是ApplicationContext的基类,支持多次调用refresh()方法,每次都会创建一个新的内部bean factory实例。继承 AbstractRefreshableApplicationContext 需要唯一实现的方法就是loadBeanDefinitions,在每一次调用刷新方法的时候。一个具体的实现是加载bean定义信息的DefaultListableBeanFactory


5、但是只有小秘给老板递交请辞不行,中间还要有技术leader 来纵览大局,向上与老板探讨公司发展计划,在下领导新人做项目打硬仗(这种男人真的很有魅力哈哈哈),但是技术leader也不能干完所有的工作,他还需要交给手下的程序员去帮他完成具体的工作,程序员接到一项工作,看看有没有可复用的项目和开源类库,发现有可用的,直接把"引用"链接过去就可以了。这就是容器的初始化工作,但是这一步的流程还没有结束,你还得时刻记住你是给boss干活的。


public AbstractApplicationContext(@Nullable ApplicationContext parent) {
  // 交给其他程序员去完成的工作
  this();
  // 明确自己的老板是谁
  setParent(parent);
}
public AbstractApplicationContext() {
  this.resourcePatternResolver = getResourcePatternResolver();
}
// 返回 ResourcePatternResolver 去解析资源实例中的匹配模式,默认的是 PathMatchingResourcePatternResolver 支持 Ant-Style 模式。
protected ResourcePatternResolver getResourcePatternResolver() {
  return new PathMatchingResourcePatternResolver(this);
}
// 此时的resourceLoader 就是ClassPathXmlApplicationContext 对象。
public PathMatchingResourcePatternResolver(ResourceLoader resourceLoader) {
  Assert.notNull(resourceLoader, "ResourceLoader must not be null");
  this.resourceLoader = resourceLoader;
}


你需要一些程序员帮你做具体的编码工作,也需要明确你是公司的员工,需要听从老板的,所以你需要明确老板是谁


@Override
public void setParent(@Nullable ApplicationContext parent) {
  this.parent = parent;
  if (parent != null) {
    Environment parentEnvironment = parent.getEnvironment();
    if (parentEnvironment instanceof ConfigurableEnvironment) {
      getEnvironment().merge((ConfigurableEnvironment) parentEnvironment);
    }
  }
}

但是这个时候老板出差了,不在了(因为传过来的parent 是 null),所以你需要自己做一些decision。至此,第一条线路就分析完成了。


配置路径解析


第二条线路,ApplicationContext中的 setConfigLocations(configLocations)

// 参数传过来的是可变参数,可变参数是一个数组,也就是说,你可以传递多个配置文件,用","分隔起来。
public void setConfigLocations(@Nullable String... locations) {
  if (locations != null) {
    Assert.noNullElements(locations, "Config locations must not be null");
    // configlocations 是一个可为空的String数组,可以为null,为null可以进行手动注册。
    this.configLocations = new String[locations.length];
    // 解析数组中的每一个配置文件的路径。
    for (int i = 0; i < locations.length; i++) {
      this.configLocations[i] = resolvePath(locations[i]).trim();
    }
  }
  // 默认是直接创建了一个 ClassPathXmlApplicationContext 的无参数的构造函数,采用手动注册的方式。
  else {
    this.configLocations = null;
  }
}


关键点:路径解析方法 : AbstractRefreshableConfigApplicationContext 中的 resolvePath(locations[i]).trim(); 来看看是如何进行路径解析的


// 解析给定的路径,必要时用相应的环境属性值替换占位符。应用于路径配置。
protected String resolvePath(String path) {
  return getEnvironment().resolveRequiredPlaceholders(path);
}


涉及两个方法,AbstractRefreshableConfigApplicationContext 中的


getEnvironment() 和 validateRequiredProperties(),先来看第一个

getEnvironment()


// 以配置的形式返回此应用程序上下文的Environment,来进一步自定义
// 如果没有指定,则通过初始化默认的环境。
@Override
public ConfigurableEnvironment getEnvironment() {
if (this.environment == null) {
  // 使用默认的环境配置
  this.environment = createEnvironment();
}
return this.environment;
}


下面来看一下createEnvironment()如何初始化默认的环境:


// 创建并返回一个 StandardEnvironment,子类重写这个方法为了提供
// 一个自定义的 ConfigurableEnvironment 实现。
protected ConfigurableEnvironment createEnvironment() {
        // StandardEnvironment 继承AbstractEnvironment,而AbstractEnvironment
        // 实现了ConfigurableEnvironment
        return new StandardEnvironment();
    }


其实很简单,也只是new 了一个StandardEnvironment() 的构造器而已。StandardEnvironment是什么?非web应用程序的Environment 的标准实现。他实现了AbstractEnvironment 抽象类,下面是具体的继承树:


image.png


StandardEnvironment是AbstractEnvironment的具体实现,而AbstractEnvironment又是继承了ConfigurableEnvironment接口,提供了某些方法的具体实现,ConnfigurableEnvironment 继承了Environment,而Environment 和 ConfigurablePropertyResolver 同时继承了PropertyResolver

下面来看一下StandardEnvironment() 的源码:


public class StandardEnvironment extends AbstractEnvironment {
  // 系统属性资源名称
    public static final String SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME = "systemEnvironment";
  // JVM系统属性资源名:
    public static final String SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME = "systemProperties";
  //为标准的Java 环境 自定义合适的属性文件
    @Override
    protected void customizePropertySources(MutablePropertySources propertySources) {
        propertySources.addLast(new MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
        propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME, getSystemEnvironment()));
    }
}


现在读者就会产生疑问,不是说new出来一个标准的StandardEnvironment 实现吗,但是StandardEnvironment并没有默认的构造方法啊?这是什么回事呢?


其实StandardEnvironment 的构造方法是 AbstractEnvironment


public AbstractEnvironment() {
  // 实现自定义属性资源的方法,也就是StandardEnvironment中customizePropertySources()
  customizePropertySources(this.propertySources);
  if (logger.isDebugEnabled()) {
    logger.debug("Initialized " + getClass().getSimpleName() + " with PropertySources " + this.propertySources);
  }
}


上述的customizePropertySourcesStandardEnvironment 来实现,具体如下


@Override
protected void customizePropertySources(MutablePropertySources propertySources) {
  propertySources.addLast(new       MapPropertySource(SYSTEM_PROPERTIES_PROPERTY_SOURCE_NAME, getSystemProperties()));
  propertySources.addLast(new SystemEnvironmentPropertySource(SYSTEM_ENVIRONMENT_PROPERTY_SOURCE_NAME,     getSystemEnvironment()));
}


由于容器在刚起步的时候 propertySources 是null,所以添加完系统环境

(systemEnvironment)和系统属性(systemProperties) 之后,会变成下图所示


image.png


如何获取系统属性和如何获取系统环境没有往下跟,有兴趣的读者可以继续沿用。


大致截一个图,里面大概的属性是这样


systemProperties


image.png


systemEnvironment


image.png


另外一个是 resolveRequiredPlaceholders,它是由 PropertyResolver 超顶级接口定义的方法


// 在给定的text 参数中解析${} 占位符,将其替换为getProperty 解析的相应属性值。
// 没有默认值的无法解析的占位符将导致抛出IllegalArgumentException。
String resolveRequiredPlaceholders(String text) throws IllegalArgumentException;


AbstractPropertyResolver 子类来实现,且看AbstractPropertyResolver 的继承树


image.png


具体实现的方法如下:

// 传递进来的文本就是解析过的 配置文件 SimpleName
@Override
public String resolveRequiredPlaceholders(String text) throws IllegalArgumentException {
  if (this.strictHelper == null) {
    this.strictHelper = createPlaceholderHelper(false);
  }
  return doResolvePlaceholders(text, this.strictHelper);
}
// 调用createPlaceholderHelper
private PropertyPlaceholderHelper createPlaceholderHelper(boolean ignoreUnresolvablePlaceholders) {
        return new PropertyPlaceholderHelper(this.placeholderPrefix, this.placeholderSuffix,
                this.valueSeparator, ignoreUnresolvablePlaceholders);
}
----------------------------PropertyPlaceholderHelper-------------------------------
  // PropertyPlaceholderHelper加载的时候会把下面的特殊字符放进去
  static {
        wellKnownSimplePrefixes.put("}", "{");
        wellKnownSimplePrefixes.put("]", "[");
        wellKnownSimplePrefixes.put(")", "(");
    }
/*
    创建一个新的 PropertyPlaceholderHelper 使用提供的前缀 和 后缀
     * 参数解释:placeholderPrefix 占位符开头的前缀
     *         placeholderSuffix 占位符结尾的后缀
     *         valueSeparator 占位符变量和关联的默认值 之间的分隔符
     *         ignoreUnresolvablePlaceholders 指示是否应忽略不可解析的占位符。
*/
public PropertyPlaceholderHelper(String placeholderPrefix, String placeholderSuffix,
                                 @Nullable String valueSeparator, boolean ignoreUnresolvablePlaceholders) {
  Assert.notNull(placeholderPrefix, "'placeholderPrefix' must not be null");
  Assert.notNull(placeholderSuffix, "'placeholderSuffix' must not be null");
  this.placeholderPrefix = placeholderPrefix;
  this.placeholderSuffix = placeholderSuffix;
  String simplePrefixForSuffix = wellKnownSimplePrefixes.get(this.placeholderSuffix);
  if (simplePrefixForSuffix != null && this.placeholderPrefix.endsWith(simplePrefixForSuffix)) {
    this.simplePrefix = simplePrefixForSuffix;
  }
  else {
    this.simplePrefix = this.placeholderPrefix;
  }
  this.valueSeparator = valueSeparator;
  this.ignoreUnresolvablePlaceholders = ignoreUnresolvablePlaceholders;
}

解析完成占位符之后,需要做真正的解析,调用AbstractPropertyResolver中的doResolvePlaceholders 方法。

private String doResolvePlaceholders(String text, PropertyPlaceholderHelper helper) {
  return helper.replacePlaceholders(text, new PropertyPlaceholderHelper.PlaceholderResolver() {
    @Override
    public String resolvePlaceholder(String placeholderName) {
      return getPropertyAsRawString(placeholderName);
    }
  });
}

PlaceholderResolver是 PropertyPlaceholderHelper类的内部类,这是一种匿名内部类的写法,它真正调用的就是PropertyPlaceholderHelper`中的 replacePlaceholders 方法,具体如下:

// 将格式为 ${name} 的占位符替换为从提供 PlaceholderResolver 返回的值。
public String replacePlaceholders(String value, PlaceholderResolver placeholderResolver) {
  Assert.notNull(value, "'value' must not be null");
  return parseStringValue(value, placeholderResolver, new HashSet<String>());
}
protected String parseStringValue(
            String value, PlaceholderResolver placeholderResolver, Set<String> visitedPlaceholders) {
        StringBuilder result = new StringBuilder(value);
        int startIndex = value.indexOf(this.placeholderPrefix);
      // 判断指定的占位符有无 ${ 存在,没有的话直接返回
        while (startIndex != -1) {
            int endIndex = findPlaceholderEndIndex(result, startIndex);
            if (endIndex != -1) {
                String placeholder = result.substring(startIndex + this.placeholderPrefix.length(), endIndex);
                String originalPlaceholder = placeholder;
                if (!visitedPlaceholders.add(originalPlaceholder)) {
                    throw new IllegalArgumentException(
                            "Circular placeholder reference '" + originalPlaceholder + "' in property definitions");
                }
                // Recursive invocation, parsing placeholders contained in the placeholder key.
                placeholder = parseStringValue(placeholder, placeholderResolver, visitedPlaceholders);
                // Now obtain the value for the fully resolved key...
                String propVal = placeholderResolver.resolvePlaceholder(placeholder);
                if (propVal == null && this.valueSeparator != null) {
                    int separatorIndex = placeholder.indexOf(this.valueSeparator);
                    if (separatorIndex != -1) {
                        String actualPlaceholder = placeholder.substring(0, separatorIndex);
                        String defaultValue = placeholder.substring(separatorIndex + this.valueSeparator.length());
                        propVal = placeholderResolver.resolvePlaceholder(actualPlaceholder);
                        if (propVal == null) {
                            propVal = defaultValue;
                        }
                    }
                }
                if (propVal != null) {
                    // Recursive invocation, parsing placeholders contained in the
                    // previously resolved placeholder value.
                    propVal = parseStringValue(propVal, placeholderResolver, visitedPlaceholders);
                    result.replace(startIndex, endIndex + this.placeholderSuffix.length(), propVal);
                    if (logger.isTraceEnabled()) {
                        logger.trace("Resolved placeholder '" + placeholder + "'");
                    }
                    startIndex = result.indexOf(this.placeholderPrefix, startIndex + propVal.length());
                }
                else if (this.ignoreUnresolvablePlaceholders) {
                    // Proceed with unprocessed value.
                    startIndex = result.indexOf(this.placeholderPrefix, endIndex + this.placeholderSuffix.length());
                }
                else {
                    throw new IllegalArgumentException("Could not resolve placeholder '" +
                            placeholder + "'" + " in value \"" + value + "\"");
                }
                visitedPlaceholders.remove(originalPlaceholder);
            }
            else {
                startIndex = -1;
            }
        }
        return result.toString();
    }


直白一点,上述过程就是用来判断有没有 ${} 这个占位符,如果有的话就进入下面的判断逻辑,把${}中的值替换为 PlaceholderResolver 返回的值,如果没有的话,就直接返回。


容器刷新


在经过上述的准备工作完成后,接下来就是整个IOC,DI和AOP的核心步骤了,也是Spring框架的灵魂。由于源码太多,设计范围太广,本篇只分析刷新预处理应该做的事:我们都知道,无论你加载的是哪一种上下文环境,最终都会调用 AbstractApplicationContext 的refresh()方法,此方法是一切加载、解析、注册、销毁的核心方法,采用了工厂的设计思想。


// 完成IoC容器的创建及初始化工作
    @Override
    public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) {
      // 1: 刷新前的准备工作。
            prepareRefresh();
            // 告诉子类刷新内部bean 工厂。
      //  2:创建IoC容器(DefaultListableBeanFactory),加载解析XML文件(最终存储到Document对象中)
      // 读取Document对象,并完成BeanDefinition的加载和注册工作
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
      //  3: 对IoC容器进行一些预处理(设置一些公共属性)
            prepareBeanFactory(beanFactory);
            try {
                //  4:  允许在上下文子类中对bean工厂进行后处理。
                                postProcessBeanFactory(beanFactory);
                //  5: 调用BeanFactoryPostProcessor后置处理器对BeanDefinition处理
                invokeBeanFactoryPostProcessors(beanFactory);
                //  6: 注册BeanPostProcessor后置处理器
                registerBeanPostProcessors(beanFactory);
                //  7: 初始化一些消息源(比如处理国际化的i18n等消息源)
                initMessageSource();
                //  8: 初始化应用事件多播器
                initApplicationEventMulticaster();
                //  9: 初始化一些特殊的bean
                onRefresh();
                //  10: 注册一些监听器
                registerListeners();
                //  11: 实例化剩余的单例bean(非懒加载方式)
        //      注意事项:Bean的IoC、DI和AOP都是发生在此步骤
                finishBeanFactoryInitialization(beanFactory);
                //  12: 完成刷新时,需要发布对应的事件
                finishRefresh();
            }
            catch (BeansException ex) {
                if (logger.isWarnEnabled()) {
                    logger.warn("Exception encountered during context initialization - " +
                            "cancelling refresh attempt: " + ex);
                }
                // 销毁已经创建的单例避免占用资源
                destroyBeans();
                // 重置'active' 标签。
                cancelRefresh(ex);
                // 传播异常给调用者
                throw ex;
            }
            finally {
                // 重置Spring核心中的常见内省缓存,因为我们可能不再需要单例bean的元数据了...
                resetCommonCaches();
            }
        }
    }


刷新容器之刷新预处理


此步骤的主要作用在于:准备刷新的上下文,设置启动的时间和active的标志作为扮演属性资源初始化的角色。


protected void prepareRefresh() {
        this.startupDate = System.currentTimeMillis();
        this.closed.set(false);
        this.active.set(true);
        if (logger.isInfoEnabled()) {
            logger.info("Refreshing " + this);
        }
        // 初始化environment 上下文中的占位符属性资源
        initPropertySources();
        // 验证标记为必需的所有属性是否可解析
        getEnvironment().validateRequiredProperties();
        // 允许收集早期的ApplicationEvents
        this.earlyApplicationEvents = new LinkedHashSet<>();
    }


这里面有两处代码需要说明:initPropertySources这个方法是需要子类进行实现的,默认是不会做任何事情的;getEnvironment() 这个方法由于上述的源码分析过程中,已经默认创建了 createEnvironment,所以这段代码是直接返回的


@Override
public ConfigurableEnvironment getEnvironment() {
  if (this.environment == null) {
    this.environment = createEnvironment();
  }
  return this.environment;
}


下面只剩下了validateRequiredProperties()的分析,不着急,看源码不能着急,要怀着这个世界很美好的心情去看。


首先在 ConfigurablePropertyResolver 接口中定义了


validateRequiredProperties 方法


// 验证每一个被setRequiredProperties 设置的属性存在并且解析非空值,会抛出
// MissingRequiredPropertiesException 异常如果任何一个需要的属性没有被解析。
void validateRequiredProperties() throws MissingRequiredPropertiesException;


在抽象子类AbstractPropertyResolver 中被重写


@Override
public void validateRequiredProperties() {
  // 属性找不到抛出异常的对象
  MissingRequiredPropertiesException ex = new MissingRequiredPropertiesException();
  for (String key : this.requiredProperties) {
    if (this.getProperty(key) == null) {
      ex.addMissingRequiredProperty(key);
    }
  }
  if (!ex.getMissingRequiredProperties().isEmpty()) {
    throw ex;
  }
}


因为在我们的源码分析中,没有看到任何操作是在对 requiredProperties 进行添加操作,也就是如下:


@Override
public void setRequiredProperties(String... requiredProperties) {
  if (requiredProperties != null) {
    for (String key : requiredProperties) {
      this.requiredProperties.add(key);
    }
  }
}


所以,此时的 requiredProperties 这个set集合是null, 也就不存在没有解析的元素了。

本篇到此就结束了,下一篇文章会进行源码分析的下一个步骤: 创建IOC容器以及Bean的解析


(点击文字可跳转)

1.深究Spring中Bean的生命周期

2. 深入SpringBoot核心注解原理

3.线上环境部署概览

4.Springboot Vue shiro 实现前后端分离、权限控制



目录
相关文章
|
3天前
|
监控 Java 应用服务中间件
高级java面试---spring.factories文件的解析源码API机制
【11月更文挑战第20天】Spring Boot是一个用于快速构建基于Spring框架的应用程序的开源框架。它通过自动配置、起步依赖和内嵌服务器等特性,极大地简化了Spring应用的开发和部署过程。本文将深入探讨Spring Boot的背景历史、业务场景、功能点以及底层原理,并通过Java代码手写模拟Spring Boot的启动过程,特别是spring.factories文件的解析源码API机制。
16 2
|
19天前
|
数据采集 监控 前端开发
二级公立医院绩效考核系统源码,B/S架构,前后端分别基于Spring Boot和Avue框架
医院绩效管理系统通过与HIS系统的无缝对接,实现数据网络化采集、评价结果透明化管理及奖金分配自动化生成。系统涵盖科室和个人绩效考核、医疗质量考核、数据采集、绩效工资核算、收支核算、工作量统计、单项奖惩等功能,提升绩效评估的全面性、准确性和公正性。技术栈采用B/S架构,前后端分别基于Spring Boot和Avue框架。
|
9天前
|
前端开发 Java 开发者
Spring生态学习路径与源码深度探讨
【11月更文挑战第13天】Spring框架作为Java企业级开发中的核心框架,其丰富的生态系统和强大的功能吸引了无数开发者的关注。学习Spring生态不仅仅是掌握Spring Framework本身,更需要深入理解其周边组件和工具,以及源码的底层实现逻辑。本文将从Spring生态的学习路径入手,详细探讨如何系统地学习Spring,并深入解析各个重点的底层实现逻辑。
35 9
|
18天前
|
XML 缓存 Java
搞透 IOC、Spring IOC ,看这篇就够了!
本文详细解析了Spring框架的核心内容——IOC(控制反转)及其依赖注入(DI)的实现原理,帮助读者理解如何通过IOC实现组件解耦,提高程序的灵活性和可维护性。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
|
10天前
|
安全 Java 测试技术
Java开发必读,谈谈对Spring IOC与AOP的理解
Spring的IOC和AOP机制通过依赖注入和横切关注点的分离,大大提高了代码的模块化和可维护性。IOC使得对象的创建和管理变得灵活可控,降低了对象之间的耦合度;AOP则通过动态代理机制实现了横切关注点的集中管理,减少了重复代码。理解和掌握这两个核心概念,是高效使用Spring框架的关键。希望本文对你深入理解Spring的IOC和AOP有所帮助。
22 0
|
1月前
|
Java Spring
Spring底层架构源码解析(三)
Spring底层架构源码解析(三)
109 5
|
1月前
|
XML Java 数据格式
Spring底层架构源码解析(二)
Spring底层架构源码解析(二)
|
1月前
|
设计模式 JavaScript Java
Spring 事件监听机制源码
Spring 提供了事件发布订阅机制,广泛应用于项目中。本文介绍了如何通过自定义事件类、订阅类和发布类实现这一机制,并展示了如何监听 SpringBoot 启动过程中的多个事件(如 `ApplicationStartingEvent`、`ApplicationEnvironmentPreparedEvent` 等)。通过掌握这些事件,可以更好地理解 SpringBoot 的启动流程。示例代码展示了从事件发布到接收的完整过程。
|
30天前
|
XML Java 数据格式
Spring IOC容器的深度解析及实战应用
【10月更文挑战第14天】在软件工程中,随着系统规模的扩大,对象间的依赖关系变得越来越复杂,这导致了系统的高耦合度,增加了开发和维护的难度。为解决这一问题,Michael Mattson在1996年提出了IOC(Inversion of Control,控制反转)理论,旨在降低对象间的耦合度,提高系统的灵活性和可维护性。Spring框架正是基于这一理论,通过IOC容器实现了对象间的依赖注入和生命周期管理。
65 0
|
1月前
|
XML Java 数据格式
手动开发-简单的Spring基于注解配置的程序--源码解析
手动开发-简单的Spring基于注解配置的程序--源码解析
46 0