Spring IOC - ConfigurationClassPostProcessor源码解析

上文提到Spring在Bean扫描过程中,会手动将5个Processor类注册到beanDefinitionMap中,其中ConfigurationClassPostProcessor就是本文将要讲解的内容,该类会在refresh()方法中通过调用invokeBeanFactoryPosstProcessors(beanFactory)被调用。
5个Processor类列表如下:
类名
是否BeanDefinitionRegistryPostProcessor
是否BeanFactoryPostProcessor
是否BeanPostProcessor
ConfigurationClassPostProcessor
AutowiredAnnotationBeanPostProcessor
CommonAnnotationBeanPostProcessor
EventListenerMethodProcessor
DefaultEventListenerFactory
ConfigurationClassPostProcessor中两个核心方法将被调用,其主要作用如下:

方法名

作用

postProcessBeanDefinitionRegistry()

(1)@Conditional注解条件解析

(2)被以下注解标注的类及内部类

@Component、@PropertySources、@PropertySource、@ComponentScans、@ComponentScan、@Import、@ImportResource、@Bean、接口中的@Bean

postProcessBeanFactory()

对full模式的配置类BeanDefinition进行CGLib增强:把CGLib生成的子类型设置到beanDefinition中

postProcessBeanDefinitionRegistry()源码及注释如下:
@Overridepublic void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {int registryId = System.identityHashCode(registry);if (this.registriesPostProcessed.contains(registryId)) {throw new IllegalStateException("postProcessBeanDefinitionRegistry already called on this post-processor against " + registry);}if (this.factoriesPostProcessed.contains(registryId)) {throw new IllegalStateException("postProcessBeanFactory already called on this post-processor against " + registry);}this.registriesPostProcessed.add(registryId);// 执行解析和扫描processConfigBeanDefinitions(registry);}
进入processConfigBeanDefinitions(registry)方法,其主体逻辑流程图如下:
其中标红步骤为两个关键步骤:

        1. 通过beanDefinition判断是否为配置类

        Spring专门提供了一个工具类:ConfigurationClassUtils来检查是否为配置类,并标记full、lite模式,逻辑如下:

        2. 解析配置类
doProcessConfigurationClass即为真正的对个注解的解析方法,其逻辑流程图如下:

        具体的源码及详细注释如下:

        1. 主流程processConfigBeanDefinitions方法:

public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {// 候选配置类定义列表List<BeanDefinitionHolder> configCandidates = new ArrayList<>();// 获取容器中所有bean定义的名字String[] candidateNames = registry.getBeanDefinitionNames();for (String beanName : candidateNames) {// 获取bean定义BeanDefinition beanDef = registry.getBeanDefinition(beanName);// org.springframework.context.annotation.ConfigurationClassPostProcessor.configurationClass// Bean Definition是否已经被标记为配置类(full、lite模式)if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {if (logger.isDebugEnabled()) {logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);}}// 查看bean是否是ConfigurationClass//被@Configuration、@Component、@ComponentScan、@Import、@ImportResource、@Bean标记else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {// 是配置类,则加入到候选列表configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// Return immediately if no @Configuration classes were found// 如果为空,即找不到被@Configuration、@Component、@ComponentScan、@Import、@ImportResource、@Bean标记的类,则立即返回if (configCandidates.isEmpty()) {return;}// Sort by previously determined @Order value, if applicable// 对需要处理的BeanDefinition排序,值越小越靠前configCandidates.sort((bd1, bd2) -> {int i1 = ConfigurationClassUtils.getOrder(bd1.getBeanDefinition());int i2 = ConfigurationClassUtils.getOrder(bd2.getBeanDefinition());return Integer.compare(i1, i2);});// Detect any custom bean name generation strategy supplied through the enclosing application context// 检查是否有自定义的beanName生成器SingletonBeanRegistry sbr = null;if (registry instanceof SingletonBeanRegistry) {sbr = (SingletonBeanRegistry) registry;if (!this.localBeanNameGeneratorSet) {//获取自定义的beanName生成器BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);if (generator != null) {// 如果spring有自定义的beanName生成器,则重新赋值this.componentScanBeanNameGenerator = generator;this.importBeanNameGenerator = generator;}}}//如果环境对象为空,则创建新的环境对象if (this.environment == null) {this.environment = new StandardEnvironment();}// 构造一个配置类解析器,用来把bean定义的重要信息提取转化为ConfigurationClass// Parse each @Configuration classConfigurationClassParser parser = new ConfigurationClassParser(this.metadataReaderFactory, this.problemReporter, this.environment,this.resourceLoader, this.componentScanBeanNameGenerator, registry);// 候选配置类定义列表(查重)Set<BeanDefinitionHolder> candidates = new LinkedHashSet<>(configCandidates);// 存储已解析的配置类列表Set<ConfigurationClass> alreadyParsed = new HashSet<>(configCandidates.size());do {// 解析配置类parser.parse(candidates);// 配置类不能被申明为final(因为CGLIB的限制),除非它声明proxyBeanMethods=false// 在@Configuration类中,@Bean方法的实例必须是可重写的,以适应CGLIBparser.validate();// 获取解析器中解析出的配置类ConfigurationClass: parser.getConfigurationClasses()Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());// 过滤掉已解析的配置类configClasses.removeAll(alreadyParsed);// 构造一个bean定义读取器// Read the model and create bean definitions based on its contentif (this.reader == null) {this.reader = new ConfigurationClassBeanDefinitionReader(registry, this.sourceExtractor, this.resourceLoader, this.environment,this.importBeanNameGenerator, parser.getImportRegistry());}// 读取ConfigurationClass获取衍生bean定义并注册到容器// 核心方法,将完全填充好的ConfigurationClass实例转化为BeanDefinition注册入IOC容器this.reader.loadBeanDefinitions(configClasses);// 加入已解析配置类alreadyParsed.addAll(configClasses);// 清空候选配置类定义列表candidates.clear();// 如果容器中bean定义有新增if (registry.getBeanDefinitionCount() > candidateNames.length) {// 查找出新增的配置类bean定义 startString[] newCandidateNames = registry.getBeanDefinitionNames();Set<String> oldCandidateNames = new HashSet<>(Arrays.asList(candidateNames));Set<String> alreadyParsedClasses = new HashSet<>();for (ConfigurationClass configurationClass : alreadyParsed) {alreadyParsedClasses.add(configurationClass.getMetadata().getClassName());}for (String candidateName : newCandidateNames) {if (!oldCandidateNames.contains(candidateName)) {BeanDefinition bd = registry.getBeanDefinition(candidateName);if (ConfigurationClassUtils.checkConfigurationClassCandidate(bd, this.metadataReaderFactory) &&!alreadyParsedClasses.contains(bd.getBeanClassName())) {candidates.add(new BeanDefinitionHolder(bd, candidateName));}}}candidateNames = newCandidateNames;}}while (!candidates.isEmpty());// Register the ImportRegistry as a bean in order to support ImportAware @Configuration classes// 如果想要在@Configuration类中使用ImportAware接口,需要将ImportRegistry注册为bean。这样,@Configuration类就可以// 使用ImportRegistry来获取导入的类信息if (sbr != null && !sbr.containsSingleton(IMPORT_REGISTRY_BEAN_NAME)) {sbr.registerSingleton(IMPORT_REGISTRY_BEAN_NAME, parser.getImportRegistry());}if (this.metadataReaderFactory instanceof CachingMetadataReaderFactory) {// Clear cache in externally provided MetadataReaderFactory; this is a no-op// for a shared cache since it'll be cleared by the ApplicationContext.// 如果MetadataReaderFactory是由外部提供的,则需要清楚其缓存。但是,如果MetadataReaderFactory是由ApplicationContext共享的,则不需要进行清除,// 因为ApplicationContext会自动清楚共享缓存((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();}}

        2. 其中检查是否为配置类方法ConfigurationClassUtils.checkConfigurationClassCandidate

//检查给定的BeanDefinition是否是一个配置类的候选者(或一个在配置/组件类中声明的嵌套组件类)//并对其进行相应的标记处理//被@Configuration、@Component、@ComponentScan、@Import、@ImportResource、@Bean标记public static boolean checkConfigurationClassCandidate(BeanDefinition beanDef, MetadataReaderFactory metadataReaderFactory) {//获取bean定义信息中的class类名String className = beanDef.getBeanClassName();//如果className为空,或者bean定义信息中的factoryMethod不等于空,那么直接返回if (className == null || beanDef.getFactoryMethodName() != null) {return false;}AnnotationMetadata metadata;//通过注解注入的BeanDefinition都是AnnotatedGenericBeanDefinition,实现了AnnotatedBeanDefinition//Spring内部的BeanDefinition都是RootBeanDefiniton,实现了AbstractBeanDefinition//此处主要用于判断是否是归属于AnnotatedBeanDefinitionif (beanDef instanceof AnnotatedBeanDefinition &&className.equals(((AnnotatedBeanDefinition) beanDef).getMetadata().getClassName())) {// Can reuse the pre-parsed metadata from the given BeanDefinition...// 从当前bean的定义信息中获取元素metadata = ((AnnotatedBeanDefinition) beanDef).getMetadata();}//判断是否是Spring中默认的BeanDefinitionelse if (beanDef instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) beanDef).hasBeanClass()) {// Check already loaded Class if present...// since we possibly can't even load the class file for this Class.//获取当前bean对象的Class对象Class<?> beanClass = ((AbstractBeanDefinition) beanDef).getBeanClass();//如果class实例是下面4种类或者接口的子类,父接口等任何一种情况,直接返回if (BeanFactoryPostProcessor.class.isAssignableFrom(beanClass) ||BeanPostProcessor.class.isAssignableFrom(beanClass) ||AopInfrastructureBean.class.isAssignableFrom(beanClass) ||EventListenerFactory.class.isAssignableFrom(beanClass)) {return false;}//为给定类创建新的AnnotationMetadatametadata = AnnotationMetadata.introspect(beanClass);}//如果上述两种情况都不符合else {try {//获取className的MetadataReader实例MetadataReader metadataReader = metadataReaderFactory.getMetadataReader(className);//读取底层类的完整注解元数据,包括带注解方法的元数据metadata = metadataReader.getAnnotationMetadata();}catch (IOException ex) {if (logger.isDebugEnabled()) {logger.debug("Could not find class file for introspecting configuration annotations: " +className, ex);}return false;}}// 获取Bean Definition的元数据被@Configuration注解标注的属性字典值Map<String, Object> config = metadata.getAnnotationAttributes(Configuration.class.getName());// 如果bean被@Configuration注解标注,并且proxyBeanMethods属性的值为true,则标注当前配置类为full,即需要代理增强,为一个代理类if (config != null && !Boolean.FALSE.equals(config.get("proxyBeanMethods"))) {beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_FULL);}// 如果存在,或者isConfigurationCandidate返回true,则标注当前配置类为lite,即不需要代理增强,为一个普通类// 标注了Configuration注解且proxyBeanMethods属性的值为false,则为lite模式// 没有标注Configuration注解,但是标注了Component、ComponentScan、Import、ImportResource、@Bean注解、则为lite模式else if (config != null || isConfigurationCandidate(metadata)) {beanDef.setAttribute(CONFIGURATION_CLASS_ATTRIBUTE, CONFIGURATION_CLASS_LITE);}// 组件类不存在@Configuration注解,直接返回falseelse {return false;}// 获取配置类的排序顺序,设置到组件定义属性中// It's a full or lite configuration candidate... Let's determine the order value, if any.//Bean Definition是一个标记为full/lite的候选项,如果有order属性就设置order属性Integer order = getOrder(metadata);if (order != null) {//设置bean的order值beanDef.setAttribute(ORDER_ATTRIBUTE, order);}return true;}

        3. 真正干活的方法doProessConfigurationClass

// 通过读取源类的注释、成员和方法来构建完整的ConfigurationClass// 1.处理@Component// 2.处理每个@PropertySource// 3.处理每个@ComponentScan// 4.处理每个Impoit// 5.处理每个@ImportResource// 6.找配置类中@Bean注解的方法(找出Class中存在@Bean标注的方法,加入到ConfigurationClass的beanMethods属性)// 7.找接口中@Bean注解的方法// 8.存在父类则递归处理@Nullableprotected final SourceClass doProcessConfigurationClass(ConfigurationClass configClass, SourceClass sourceClass, Predicate<String> filter)throws IOException {if (configClass.getMetadata().isAnnotated(Component.class.getName())) {// Recursively process any member (nested) classes first// 递归处理嵌套的成员类processMemberClasses(configClass, sourceClass, filter);}// Process any @PropertySource annotations// 处理@PropertySources和@PropertySource注解for (AnnotationAttributes propertySource : AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), PropertySources.class,org.springframework.context.annotation.PropertySource.class)) {if (this.environment instanceof ConfigurableEnvironment) {processPropertySource(propertySource);}else {logger.info("Ignoring @PropertySource annotation on [" + sourceClass.getMetadata().getClassName() +"]. Reason: Environment must implement ConfigurableEnvironment");}}// Process any @ComponentScan annotations// 处理@ComponentScans和@ComponentScan注解Set<AnnotationAttributes> componentScans = AnnotationConfigUtils.attributesForRepeatable(sourceClass.getMetadata(), ComponentScans.class, ComponentScan.class);if (!componentScans.isEmpty() &&!this.conditionEvaluator.shouldSkip(sourceClass.getMetadata(), ConfigurationPhase.REGISTER_BEAN)) {for (AnnotationAttributes componentScan : componentScans) {// The config class is annotated with @ComponentScan -> perform the scan immediately// 配置类使用了注解@ComponentScan -> 立即执行扫描。Set<BeanDefinitionHolder> scannedBeanDefinitions =this.componentScanParser.parse(componentScan, sourceClass.getMetadata().getClassName());// Check the set of scanned definitions for any further config classes and parse recursively if neededfor (BeanDefinitionHolder holder : scannedBeanDefinitions) {BeanDefinition bdCand = holder.getBeanDefinition().getOriginatingBeanDefinition();if (bdCand == null) {bdCand = holder.getBeanDefinition();}if (ConfigurationClassUtils.checkConfigurationClassCandidate(bdCand, this.metadataReaderFactory)) {parse(bdCand.getBeanClassName(), holder.getBeanName());}}}}// Process any @Import annotations// 处理@Import注解// getImports:递归获取@Import导入的类processImports(configClass, sourceClass, getImports(sourceClass), filter, true);// Process any @ImportResource annotations// 处理@ImportResource注解// 读取locations, reader属性,封装存放在importResources map集合中AnnotationAttributes importResource =AnnotationConfigUtils.attributesFor(sourceClass.getMetadata(), ImportResource.class);if (importResource != null) {String[] resources = importResource.getStringArray("locations");Class<? extends BeanDefinitionReader> readerClass = importResource.getClass("reader");for (String resource : resources) {String resolvedResource = this.environment.resolveRequiredPlaceholders(resource);configClass.addImportedResource(resolvedResource, readerClass);}}// Process individual @Bean methods// 处理@Bean方法Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));}// Process default methods on interfaces// 处理接口的默认方法实现,从jdk8开始,接口中的方法可以有自己的默认实现,因此如果这个接口的方法加了@Bean注解,也需要被解析processInterfaces(configClass, sourceClass);// Process superclass, if anyif (sourceClass.getMetadata().hasSuperClass()) {String superclass = sourceClass.getMetadata().getSuperClassName();if (superclass != null && !superclass.startsWith("java") &&!this.knownSuperclasses.containsKey(superclass)) {this.knownSuperclasses.put(superclass, configClass);// Superclass found, return its annotation metadata and recursereturn sourceClass.getSuperClass();}}// No superclass -> processing is completereturn null;}

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

68 内网安全-域横向PTHPTKPTT哈希票据传递

目录 演示案例:域横向移动PTH传递-Mimikatz域横向移动PTK传递-Mimikatz域横向移动PTT传递-MS14068&kekeo&local国产Ladon内网杀器测试验收-信息收集,连接等 涉及资源: PTH(pass the hash) #利用lm或ntlm的值进行的渗透测试 PTT(pass the ticket) #利用的票据凭证TGT进行…

C++ Qt 学习(二):常用控件使用与界面布局

1. Qt 布局详解 ui 设计器设计界面很方便&#xff0c;为什么还要手写代码&#xff1f; 更好的控制布局更好的设置 qss代码复用 完全不会写 Qt 布局&#xff0c;很麻烦&#xff0c;怎么学会手写布局&#xff1f; 看 Qt 自己怎么写改良 Qt 的布局写法 1.1 水平布局 #include …

LVGL库入门 02 - 布局

1、简单布局 可以使用 lv_obj_set_pos(obj, x, y) 调整一个控件的位置&#xff08;或者使用类似的函数单独调整一个方向的坐标&#xff09;&#xff0c;将它放在相对父容器左上角的合适位置。不过这种布局方式非常死板&#xff0c;因为绝对坐标一旦设定就不能自动调整&#xf…

fastapi-参数

路径参数 你可以使用与 Python 格式化字符串相同的语法来声明路径"参数"或"变量"&#xff1a; from fastapi import FastAPIapp FastAPI()app.get("/items/{item_id}") async def item_details(item_id: int):return {"item_id": i…

竖版视频怎么做二维码?扫码播放竖版视频的方法

当我们在将视频转二维码图片展示的时候&#xff0c;一般横版视频在手机展示不会有影响&#xff0c;但是竖版视频会默认用横版的方式播放就会导致无法清晰的看到画面的内容&#xff0c;那么如何将竖版视频生成二维码是很多小伙伴头疼的一个问题。那么下面教大家使用二维码生成器…

Android 如何在Android studio中快速创建raw和assets文件夹

一 方案 1. 创建raw文件夹 切成project浏览模式——>找到res文件粘贴要放入raw文件夹下的文件。 当然此时raw文件还没有&#xff0c;直接在右侧输入框中出现的路径~\res后面加上\raw即可。 2. 创建assets文件夹 同理在main文件夹下粘贴要放入assets文件夹的文件&#xff0…

Java精品项目62基于Springboot+Vue实现的大学生在线答疑平台(编号V62)

Java精品项目62基于SpringbootVue实现的大学生在线答疑平台(编号V62) 大家好&#xff0c;小辰今天给大家介绍一个基于SpringbootVue实现的大学生在线答疑平台(编号V62)&#xff0c;演示视频公众号&#xff08;小辰哥的Java&#xff09;对号查询观看即可 文章目录 Java精品项目…

云尘-Node1 js代码

继续做题 拿到就是基本扫一下 nmap -sP 172.25.0.0/24 nmap -sV -sS -p- -v 172.25.0.13 然后顺便fscan扫一下咯 nmap: fscan: 还以为直接getshell了 老演员了 其实只是302跳转 所以我们无视 只有一个站 直接看就行了 扫出来了两个目录 但是没办法 都是要跳转 说明还是需要…

tomcat必要的配置

tomcat要配置两个&#xff0c;不然访问不了localhost:8080 名&#xff1a;CATALINA_HOME 值&#xff1a;D:\software\computer_software\Tomcat\tomcat8.5.66

taro全局配置页面路由和tabBar页面跳转

有能力可以看官方文档&#xff1a;Taro 文档 页面路由配置&#xff0c;配置在app.config.ts里面的pages里&#xff1a; window用于设置小程序的状态栏、导航条、标题、窗口背景色&#xff0c;其配置项如下&#xff1a; tabBar配置&#xff1a;如果小程序是一个多 tab 应用&…

你一般会什么时候使用CHATGPT?

在当今数字时代&#xff0c;人们对于人工智能&#xff08;AI&#xff09;的依赖程度日益增加&#xff0c;而ChatGPT作为一种强大的自然语言处理工具&#xff0c;吸引了人们的广泛关注和应用。那么&#xff0c;人一般在什么时候会想要使用ChatGPT呢&#xff1f;这个问题涵盖了多…

【原创】java+swing+mysql志愿者管理系统设计与实现

摘要&#xff1a; 志愿者管理系统是一个用于管理志愿者以及活动报名的系统&#xff0c;提高志愿者管理的效率&#xff0c;同时为志愿者提供更好的服务和体验。本文主要介绍如何使用javaswingmysql去实现一个志愿者管理系统。 功能分析&#xff1a; 系统主要提供给管理员和志…

什么是块存储、文件存储、对象存储?

我们都知道&#xff0c;存储设备就是为数据提供空间。 U盘、硬盘和固态硬盘都是存储最终的存储设备。而块存储、文件存储和对象存储也可以简单地理解是不同类型的存储设备&#xff0c;它们是根据使用介质存储数据的手段或方法不同来划分的。 首先我们来看下块存储&#xff1a;…

160. 相交链表、Leetcode的Python实现

博客主页&#xff1a;&#x1f3c6;看看是李XX还是李歘歘 &#x1f3c6; &#x1f33a;每天分享一些包括但不限于计算机基础、算法等相关的知识点&#x1f33a; &#x1f497;点关注不迷路&#xff0c;总有一些&#x1f4d6;知识点&#x1f4d6;是你想要的&#x1f497; ⛽️今…

Unity Perception合成数据生成、标注与ML模型训练

在线工具推荐&#xff1a; Three.js AI纹理开发包 - YOLO合成数据生成器 - GLTF/GLB在线编辑 - 3D模型格式在线转换 - 3D场景编辑器 任何训练过机器学习模型的人都会告诉你&#xff0c;模型是从数据得到的&#xff0c;一般来说&#xff0c;更多的数据和标签会带来更好的性能。 …

垃圾分类箱通过工业4G路由器实现无人值守远程管理

据今年发布的相关数据统计&#xff0c;人们日常生活中每人每天至少能制造1.2kg垃圾&#xff0c;在环保事业中日常垃圾处理已经成为一项紧迫且不可忽视的任务。为了实现城市清洁和环境保护&#xff0c;越来越多的地区开始引入垃圾分类箱。传统的垃圾分类箱管理方式存在着一些不便…

【APP】go-musicfox - 一款网易云音乐命令行客户端, 文件很小Mac版本只有16.5M

go-musicfox 是用 Go 写的又一款网易云音乐命令行客户端&#xff0c;支持各种音质级别、UnblockNeteaseMusic、Last.fm、MPRIS 和 macOS 交互响应&#xff08;睡眠暂停、蓝牙耳机连接断开响应和菜单栏控制等&#xff09;等功能特性。 预览 启动 启动界面 主界面 主界面 通…

网络工程师-入门基础课:华为HCIA认证课程介绍

【微/信/公/众/号&#xff1a;厦门微思网络】 华为HCIA试听课程&#xff1a;超级实用&#xff0c;华为VRP系统文件详解 华为HCIA试听课程&#xff1a;不会传输层协议&#xff0c;HCIA都考不过 华为HCIA试听课程&#xff1a;网络工程师的基本功&#xff1a;网络地址转换NAT 一…

16. 机器学习 - 决策树

Hi&#xff0c;你好。我是茶桁。 在上一节课讲SVM之后&#xff0c;再给大家将一个新的分类模型「决策树」。我们直接开始正题。 决策树 我们从一个例子开始&#xff0c;来看下面这张图&#xff1a; 假设我们的x1 ~ x4是特征&#xff0c;y是最终的决定&#xff0c;打比方说是…

linux下mysql-8.2.0集群部署(python版本要在2.7以上)

目录 一、三台主机准备工作 1、mysql官方下载地址&#xff1a;https://dev.mysql.com/downloads/ 2、修改/etc/hosts 3、关闭防火墙 二、三台主机安装mysql-8.2.0 1、解压 2、下载相应配置 3、初始化mysql&#xff0c;启动myslq&#xff0c;设置开机自启 4、查看初始密…