Spring源码解析-构造函数

news2024/11/27 23:50:12

1、构造函数概述

构造函数中,主要创建两个对象分别用来读取注解参数和classpath下的文件

  • AnnotatedBeanDefinitionReader 专门读取注解参数的Reader

  • ClassPathBeanDefinitionScanner 专门读取classpath下的文件,例如yml、properties等。

AnnotationConfigApplicationContext 可以通过扫描指定的包或类来自动检测和注册带有特定注解的组件,例如@Service、@Repository和@Controller等。使用AnnotationConfigApplicationContext,我们可以使用注解来配置和管理Spring应用程序的各种组件,而不需要显式地在XML配置文件中进行配置。这样可以简化配置过程并提高开发效率。

1.1 构造器源码

public AnnotationConfigApplicationContext() {
    StartupStep createAnnotatedBeanDefReader = this.getApplicationStartup().start("spring.context.annotated-bean-reader.create");
    this.reader = new AnnotatedBeanDefinitionReader(this);
    createAnnotatedBeanDefReader.end();
    this.scanner = new ClassPathBeanDefinitionScanner(this);
}

主要的代码在this.readerthis.sanner

1.2 继承关系

总览继承关系:

从继承关系来看,AnnotationConfigApplicationContext 继承 GenericApplicationContext 类,GenericApplicationContext 又实现了BeanDefinitionRegistry 接口,所以AnnotationConfigApplicationContext 本身就具备了BeanDefinitionRegistry 的功能。其他的关系也可以从上图中看出.

总结一些主要的特性:

  • AnnotationConfigApplicationContext 是一个 BeanDefinitionRegistry 注册器。

  • AnnotationConfigApplicationContext 是一个ConfigurableApplicationContext上下文配置的容器

  • AnnotationConfigApplicationContext 继承GenericApplicationContextDefaultListableBeanFactoryGenericApplicationContext的一个final属性。

2、AnnotatedBeanDefinitionReader注解读取器

AnnotatedBeanDefinitionReader 构造函数:

public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {
    this(registry, getOrCreateEnvironment(registry));
}


public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {
    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    Assert.notNull(environment, "Environment must not be null");
    this.registry = registry;
    this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);
    AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}

从构造函数可以看出是在需要传递一个Bean定义对象的注解器BeanDefinitionRegistry,这个register就是AnnotationConfigApplicationContext

构造函数中主要做了两件事情,创建条件解析器以及注解注解配置处理器

  • this.conditionEvaluator 条件解析器,主要针对Conditional.class

  • AnnotationConfigUtils.registerAnnotationConfigProcessors 注册注解配置处理器

2.1 条件解析器this.conditionEvaluator

ConditionEvaluator用于在运行时评估条件表达式。它的主要作用是根据条件表达式的结果来决定是否应该创建或注册某个Bean。

在Spring中,我们可以使用条件注解(如@Conditional)来根据条件来决定是否应该创建或注册某个Bean。ConditionEvaluator就是用于解析和评估这些条件表达式的。

ConditionEvaluator 的构造器仅仅是new了一个ConditionContextImpl, 且ConditionContextImplConditionEvaluator的内部类。

这个不是本章节的重点,有兴趣的笔友可以深入研究。

2.2 注册注解配置处理器

    再了解`AnnotationConfigUtils.registerAnnotationConfigProcessors`方法之前,我们先看`AnnotationConfigUtils`都给我们提供了那些方法。

注解9.png

AnnotationConfigUtils提供了公用定义的注解的处理、注册注解后置处理器、注解后置处理器以及一些常量。这些常量也是公用内置的公用类。常量如下:

  • org.springframework.context.annotation.internalConfigurationAnnotationProcessor

  • org.springframework.context.annotation.internalConfigurationBeanNameGenerator

  • org.springframework.context.annotation.internalAutowiredAnnotationProcessor

  • org.springframework.context.annotation.internalRequiredAnnotationProcessor(已过时)

  • org.springframework.context.annotation.internalCommonAnnotationProcessor

  • org.springframework.context.annotation.internalPersistenceAnnotationProcessor

  • org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor

  • org.springframework.context.event.internalEventListenerProcessor

  • org.springframework.context.event.internalEventListenerFactory

从命名可以看出,大部分的类都是以internal 开头的。这个都是解析注解所必要的内部使用的类,不对外暴露的类。

2.3 来研究具体的注册注解处理器的方法

源码

public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {
    registerAnnotationConfigProcessors(registry, null);
}

public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
            BeanDefinitionRegistry registry, @Nullable Object source) { 
    DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
    // ......
    Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);
    // ......
    return beanDefs;
}

从源码来看,注册注解处理器调用重载方法registerAnnotationConfigProcessors( BeanDefinitionRegistry registry, @Nullable Object source)

2.3.1 源码(1)
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
if (beanFactory != null) {
    if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
        beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
    }
    if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
        beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
    }
}

拆解registry是不是DefaultListableBeanFactory Bean工厂,如果是就会设置属性,如果不是,则会忽略。 由于上面1.2继承关系的分析,我们知道当前的registry继承自GenericApplicationContext

private static DefaultListableBeanFactory unwrapDefaultListableBeanFactory(BeanDefinitionRegistry registry) {
    if (registry instanceof DefaultListableBeanFactory) {
        return (DefaultListableBeanFactory) registry;
    }
    else if (registry instanceof GenericApplicationContext) {
        return ((GenericApplicationContext) registry).getDefaultListableBeanFactory();
    }
    else {
        return null;
    }
}

所以我们必然可以得到一个DefaultListableBeanFactory对象。

接下来,就是给beanFactory 设置属性,根据是否满足条件设置对应的属性。

beanFactory.setDependencyComparator() 给BeanFactory设置依赖的比较器,主要用来排序。设置的是AnnotationAwareOrderComparator的单例。

beanFactory.setAutowireCandidateResolver() 给BeanFactory设置候选的解析器,也就new了一个ContextAnnotationAutowireCandidateResolver对象,设置了进去。

ContextAnnotationAutowireCandidateResolver对象的父类QualifierAnnotationAutowireCandidateResolver的构造器中添加了对Qualifier.class的支持,私有方法中又包含Value.class,所以注解注入的解析器可以处理两个注解:

  • @Qualifier 与@Autowired配合使用,按照名称注入

  • @Value 源码中通过 findValue() 解析

    总结一下,beanFactory设置以上两个属性,让beanFactory具备了使用比较器、以及解析@Qualifier@Value 的能力。

2.3.2 源码(2)
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);

if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));
}

if (!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)) {
    RootBeanDefinition def = new RootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class);
    def.setSource(source);
    beanDefs.add(registerPostProcessor(registry, def, AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));
}
// ......
    因为我们之前调用的方法是`registerAnnotationConfigProcessors`,没有返回值。所以我们没有必要关心`beanDefs.add()`的结果。只需要关注`registerPostProcessor()` 这个方法即可。

    接下来的源码大致的含义就是判断`register` 中是否包含某些`BeanDefinition`,如果不存在就会注册到`register`中。也就是`register`中一定会有这些`BeanDefinition`。
  • org.springframework.context.annotation.internalConfigurationAnnotationProcessor

    • 对应ConfigurationClassPostProcessor.class,主要用来处理@Configuration类
  • org.springframework.context.annotation.internalAutowiredAnnotationProcessor

    • 对应AutowiredAnnotationBeanPostProcessor.class 主要用来处理@Autowired和@Value,还支持JSR-330de @Inject注解,可以替代@Autowired。
  • org.springframework.context.annotation.internalCommonAnnotationProcessor

    • 对应CommonAnnotationBeanPostProcessor.class,主要用来处理JSR-250的@Resource,以及@PostConstruct、@PreDestroy初始化和销毁注解
  • org.springframework.context.annotation.internalPersistenceAnnotationProcessor

    • 其实加载的是org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor类,注解检查对JPA的支持。
  • org.springframework.context.event.internalEventListenerProcessor

    • 对应EventListenerMethodProcessor.class类,主要处理 ApplicationListener 的实例,也就是Spring的事件驱动的模型。
  • org.springframework.context.event.internalEventListenerFactory

    • 对应DefaultEventListenerFactory.class类,支持@EventListener纾解的一个默认实现

总结一下,通过一系列的设置可以处理的注解包括:

  • @Configuration

  • @Resource

  • @Autowired

  • @Inject

  • @PostConstruct

  • @PreDestroy

  • @Value

  • @EventListener

3、ClassPathBeanDefinitionScanner 扫描器

ClassPathBeanDefinitionScanner 构造函数

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
    this(registry, true);
}

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters) {
        this(registry, useDefaultFilters, getOrCreateEnvironment(registry));
}

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
            Environment environment) {

    this(registry, useDefaultFilters, environment,
            (registry instanceof ResourceLoader ? (ResourceLoader) registry : null));
}

public ClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry, boolean useDefaultFilters,
            Environment environment, @Nullable ResourceLoader resourceLoader) {

    Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
    this.registry = registry;

    if (useDefaultFilters) {
        registerDefaultFilters();
    }
    setEnvironment(environment);
    setResourceLoader(resourceLoader);
}

该构造函数有不同参数的重载。最终主要的是三个方法:

  • registerDefaultFilters() 注册默认的过滤器

  • setEnvironment(environment) 设置运行环境

  • setResourceLoader(resourceLoader) 设置resource资源的加载器

3.1 注册默认的过滤器

    从上面的构造函数看,默认传递的`useDefaultFilters` 参数为true,也就是`registerDefaultFilters()` 一定会执行。

    注册默认的注解过滤器,处理`@Component`,以及具有`@Component`元注解的扩展注解,如`@Service`、`@Controller`、`@respository`.

    如果支持Java EE 6的`@ManagedBean`以及JSR-330的`@Named` 注解。

源码如下:

protected void registerDefaultFilters() {
    this.includeFilters.add(new AnnotationTypeFilter(Component.class));
    ClassLoader cl = ClassPathScanningCandidateComponentProvider.class.getClassLoader();
    try {
        this.includeFilters.add(new AnnotationTypeFilter(
                ((Class<? extends Annotation>) ClassUtils.forName("javax.annotation.ManagedBean", cl)), false));
        logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
        // JSR-250 1.1 API (as included in Java EE 6) not available - simply skip.
    }
    try {
        this.includeFilters.add(new AnnotationTypeFilter(
                ((Class<? extends Annotation>) ClassUtils.forName("javax.inject.Named", cl)), false));
        logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");
    }
    catch (ClassNotFoundException ex) {
        // JSR-330 API not available - simply skip.
    }
}

3.2 设置运行的环境

ClassPathBeanDefinitionScanner 构造函数可知,获取环境的方法是:getOrCreateEnvironment(registry)

源码如下:

private static Environment getOrCreateEnvironment(BeanDefinitionRegistry registry) {
	Assert.notNull(registry, "BeanDefinitionRegistry must not be null");
	if (registry instanceof EnvironmentCapable) {
		return ((EnvironmentCapable) registry).getEnvironment();
	}
	return new StandardEnvironment();
}

如果registry 具备环境的能力,也就是实现EnvironmentCapable,就直接返回环境,如果没有,就直接创建标椎的环境。

设置环境很简单,就是简单的赋值。

public void setEnvironment(Environment environment) {
	Assert.notNull(environment, "Environment must not be null");
	this.environment = environment;
	this.conditionEvaluator = null;
}

3.3 设置资源加载器

public void setResourceLoader(@Nullable ResourceLoader resourceLoader) {
	this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);
	this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);
	this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(this.resourcePatternResolver.getClassLoader());
}

从传递参数来看,resourceLoader是通过register强制转化过来的。

this(registry, useDefaultFilters, environment,
				(registry instanceof ResourceLoader ? (ResourceLoader) registry : null));

我们的registry是间接实现ResourceLoader接口的,所以resourceLoader 也是registry本身。

主要设置是三个属性:

  • this.resourcePatternResolver

  • this.metadataReaderFactory

  • this.componentsIndex

3.3.1 设置资源正则解析器

this.resourcePatternResolver = ResourcePatternUtils.getResourcePatternResolver(resourceLoader);

源码:

public static ResourcePatternResolver getResourcePatternResolver(@Nullable ResourceLoader resourceLoader) {
	if (resourceLoader instanceof ResourcePatternResolver) {
		return (ResourcePatternResolver) resourceLoader;
	}
	else if (resourceLoader != null) {
		return new PathMatchingResourcePatternResolver(resourceLoader);
	}
	else {
		return new PathMatchingResourcePatternResolver();
	}
}

根据逻辑,返回的是new的PathMatchingResourcePatternResolver对象。解析项目中的资源文件,如xml等

3.3.2 设置原数据读取的工厂

this.metadataReaderFactory = new CachingMetadataReaderFactory(resourceLoader);

源码:

public CachingMetadataReaderFactory(@Nullable ResourceLoader resourceLoader) {
	super(resourceLoader);
	if (resourceLoader instanceof DefaultResourceLoader) {
		this.metadataReaderCache =
				((DefaultResourceLoader) resourceLoader).getResourceCache(MetadataReader.class);
	}
	else {
		setCacheLimit(DEFAULT_CACHE_LIMIT);
	}
}

CachingMetadataReaderFactory的父类是SimpleMetadataReaderFactory,为元数据的读取设置缓存。

3.3.3 设置候选的Component的索引

this.componentsIndex = CandidateComponentsIndexLoader.loadIndex(this.resourcePatternResolver.getClassLoader());

源码:

public static CandidateComponentsIndex loadIndex(@Nullable ClassLoader classLoader) {
	ClassLoader classLoaderToUse = classLoader;
	if (classLoaderToUse == null) {
		classLoaderToUse = CandidateComponentsIndexLoader.class.getClassLoader();
	}
	return cache.computeIfAbsent(classLoaderToUse, CandidateComponentsIndexLoader::doLoadIndex);
}

其中CandidateComponentsIndexLoader::doLoadIndex 会加载META-INF/spring.components 下的URL。

4、小结

构造函数中主要设置了两个东西,一个是处理注解的解析器以及扫描Bean定义的解析器。

注解解析处理的注解包括:

  • @Configuration

  • @Resource

  • @Autowired

  • @Inject

  • @PostConstruct

  • @PreDestroy

  • @Value

  • @EventListener

  • @Qualifier

    Classpath的扫描器可以处理的注解

  • @Service

  • @Controller

  • @Respository

  • @ManagedBean

  • @Named

  • @Component

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/976242.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

hive指定字段插入数据,包含了分区表和非分区表

1、建表 语句如下&#xff1a; CREATE EXTERNAL TABLE ods_lineitem_full (l_shipdate date,l_orderkey bigint,l_linenumber int,l_partkey int,l_suppkey int,l_quantity decimal(15, 2),l_extendedprice decimal(15, 2),l_discount de…

专题:跨域数据管理

点击上方蓝字关注我们 2023年2月&#xff0c;中共中央、国务院印发《数字中国建设整体布局规划》&#xff0c;指出建设数字中国是数字时代推进中国式现代化的重要引擎&#xff0c;加快数字中国建设&#xff0c;对全面建设社会主义现代化国家、全面推进中华民族伟大复兴具有重要…

JVM详细教程

JVM 前言 还在完善中先发布 JVM虚拟机厂家多钟多样&#xff0c;具体实现细节可能不一样&#xff0c;这里主要讲的是虚拟机的规范&#xff0c;以下内容融合了各个平台发布的内容和周志明老师的《深入理解java虚拟机》 JVM概述 如何理解jvm跨平台&#xff1f; 编译成汇编代码…

供应商多样性:减少电子元器件供应链风险

供应商多样性是为了减少电子元器件供应链风险而采取的一项重要战略。以下是一些关于如何通过供应商多样性来降低风险的方法&#xff1a; 多供应商采购策略&#xff1a; 不要依赖于单一供应商&#xff0c;而是寻找多个可靠的供应商。这有助于分散风险&#xff0c;当一个供应商出…

大数据的关键技术之——大数据采集

大数据的关键技术之——大数据采集 本文目录&#xff1a; 一、写在前面的话 二、大数据采集概念 三、大数据采集步骤 3.1、大数据采集步骤&#xff08;总体角度&#xff09; 3.2、大数据采集步骤&#xff08;数据集角度&#xff09; 3.3、大数据采集步骤&#xff08;数据…

PyQt5报错Process finished with exit code -1073740791 (0xC0000409)

点击按钮之后&#xff0c;就直接退出程序&#xff0c;控制台出现一个提示&#xff1a;解决办法&#xff1a; 在PyCharm中打开Run菜单&#xff0c;找到Edit Configurations进入&#xff0c;勾选Emulate terminal in output console即可。 然后再运行一下程序&#xff0c;就可以…

贴片排阻的基本原理和应用

贴片排阻&#xff08;Surface Mount Resistor&#xff0c;简称SMD Resistor&#xff09;是一种用于控制电流、电压、分压信号、限制电流等的电子元件&#xff0c;它的基本原理是根据欧姆定律&#xff0c;电阻与电流和电压之间的关系&#xff0c;通过电阻值来控制电路中的电流和…

【腾讯云 Cloud Studio 实战训练营】使用python爬虫和数据可视化对比“泸州老窖和五粮液4年内股票变化”

Cloud Studio 简介 Cloud Studio是腾讯云发布的云端开发者工具&#xff0c;支持开发者利用Web IDE&#xff08;集成开发环境&#xff09;&#xff0c;实现远程协作开发和应用部署。 现在的Cloud Studio已经全面支持Java Spring Boot、Python、Node.js等多种开发模板示例库&am…

正中优配:证券账户除了炒股还可以干啥?

大部分出资者开一个证券账户是用来买卖股票的&#xff0c;殊不知&#xff0c;证券账户除了买卖股票之外&#xff0c;还能够干许多其他的出资&#xff0c;下面正中优配为大家准备了相关内容&#xff0c;以供参阅。 证券账户除了炒股之外还能够干以下出资&#xff1a; 1、购买基…

智能电力监控平台

智能电力监控平台依托电易云-智慧电力物联网&#xff0c;综合采用物联网、云计算、边缘计算&#xff0c;人工智能等”现代化信息技术将配电室进行远程集中监控&#xff0c;实时监测配电室设备运行状态、电参量、配电室环境等&#xff0c;可以有效降低隐患风险。 智能电力监控平…

这么近,那么美,到天津看大爷跳水

点击文末“阅读原文”即可参与节目互动 剪辑、音频 / 小黑 运营 / SandLiu 卷圈 监制 / 姝琦 现场采访 / 朱峰、姝琦 产品统筹 / bobo 场地支持 / 声湃轩天津录音间 日前&#xff0c;天津局部持续下“大爷”。连续多日的下“大爷”让天津这座城市接棒淄博烧烤&#xff0c…

算法 数据结构 什么是递归 递归解决阶乘 阶乘递归代码 递归解决问题 递归反向打印字符串 数据结构(七)

递 归&#xff1a; 计算机科学中&#xff0c;递归是一种解决计算问题的方法&#xff0c;其中解决方案取决于同一类问题的更小子集 In computer science, recursion is a method of solving a computational problem where the solution depends on solutions to smaller instan…

代码随想录算法训练营之JAVA|第四十三天|139. 单词拆分

今天是第 天刷leetcode&#xff0c;立个flag&#xff0c;打卡60天。 算法挑战链接 139. 单词拆分https://leetcode.cn/problems/word-break/ 第一想法 看完之后完全没有想法。 看完代码随想录之后的想法 这是一个完全背包的问题&#xff0c;使用完全背包的解法。 单词就…

pinia和vuex的使用以及区别

还是要记笔记多看才行&#xff0c;要不然老是会忘记 它没有mutation,他只有state&#xff0c;getters&#xff0c;action【同步、异步】使用他来修改state数据pinia没有modules配置&#xff0c;每一个独立的仓库都是definStore生成出来的state是一个对象返回一个对象和组件的da…

面试被问到:测试计划和测试方案有什么区别?你会回答吗~

面试的时候&#xff0c;很多小伙伴都被面试官问过这个问题 “测试计划和测试方案有什么区别”&#xff1f; 到底有什么区别呢&#xff1f;我们先好好了解下这两个文档。 一、测试计划 1、测试计划是什么 测试计划是组织管理层面的文件&#xff0c;从组织管理的角度对一次测…

接口自动化测试如何处理 Header cookie

Cookie&#xff08;复数形态&#xff1a;Cookies&#xff09;是某些网站为了辨别用户身份而储存在用户本地终端上的数据。在接口测试过程中&#xff0c;如果网站采取了 Cookie 认证的方式&#xff0c;那么发送的请求需要附带 Cookie&#xff0c;才会得到正常的响应的结果。接口…

牛客周赛 Round 10

A.游游的最长稳定子数组 原题链接 : 登录—专业IT笔试面试备考平台_牛客网 思路 : 一个线性dp,dp[i]表示以a[i]结尾的最长稳定子数组的长度; dp方程 : if(abs(a[i]-a[i-1]) < 1) dp[i]dp[i-1]1; else dp[i] 1; 代码 : #include<bits/stdc.h> #def…

Java低代码开发:jvs-list(列表引擎)功能(一)配置说明

在低代码开发平台中&#xff0c;列表页是一个用于显示数据列表的页面。它通常用于展示数据库中的多条记录&#xff0c;并提供搜索、排序和筛选等功能&#xff0c;以方便用户对数据进行查找和浏览。 jvs-list是jvs快速开发平台的列表页的配置引擎&#xff0c;它和普通的crud 具…

【招标投标API】有人靠它赚数百万,绝大多数企业却熟视无睹!

引言 在当前优胜劣汰的市场&#xff0c;中小企业的竞争压力日益增大&#xff0c;一些与时代发展逆向而行的企业也将被淘汰。众所周知&#xff0c;传统的招投标行业信息存在各自分裂、数据局限的问题&#xff0c;所以招投标行业发展滞后&#xff0c;给各招投标企业造成了巨大的…

利用MQ实现mysql与elasticsearch数据同步

流程 1.声明exchange、queue、RoutingKey 2. 在hotel-admin中进行增删改&#xff08;SQL&#xff09;&#xff0c;完成消息发送 3. 在hotel-demo中完成消息监听&#xff0c;并更新elasticsearch数据 4. 测试同步 1.引入依赖 <!--amqp--> <dependency><groupId&…