【SpringBoot】SpringBoot核心启动流程源码解析

SpringBoot总体流程

当我们启动一个SpringBoot程序的时候,只需要一个main方法就可以启动,但是对于其中流程时如何执行的,以及如何调用spring的IOC和AOP机制,本篇带着这个问题来整体体系化的梳理下流程。

@SpringBootApplication
public class SpringBootApp {public static void main(String[] args) {//  testSpringApplication.run(SpringBootApp.class);}
}

实际调用的是如下,也就是run方法

	public static ConfigurableApplicationContext run(Class<?> primarySource, String... args) {// 调用重载的run方法,将传递的Class对象封装为了一个数组return run(new Class<?>[] { primarySource }, args);}public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {// 创建了一个SpringApplication对象,并调用其run方法// 1.先看下构造方法中的逻辑// 2.然后再看run方法的逻辑return new SpringApplication(primarySources).run(args);}

基本可以分为两块,一个是构造方法,一个是run。

SpringBootApplication 构造方法

构造方法中,其实主要完成的工作,设置主启动类 primarySources,判断当前应用类型 servlet类型。通过公共方法,getSpringFactoriesInstances 进行获取目标类。然后设置到对应的初始化器以及监听器对象中。
然后通过堆栈信息获取主启动类

	public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {// 传递的resourceLoader为nullthis.resourceLoader = resourceLoader;Assert.notNull(primarySources, "PrimarySources must not be null");// 记录主方法的配置类名称 用set进行去重复this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));// 记录当前项目的类型 servlet类型this.webApplicationType = WebApplicationType.deduceFromClasspath();// 加载配置在spring.factories文件中的ApplicationContextInitializer对应的类型并实例化// 并将加载的数据存储在了 initializers 成员变量中。setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));// 初始化监听器 并将加载的监听器实例对象存储在了listeners成员变量中setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));// 反推main方法所在的Class对象 并记录在了mainApplicationClass对象中this.mainApplicationClass = deduceMainApplicationClass();}

其他的比较简单,主要分析下getSpringFactoriesInstances()方法

getSpringFactoriesInstances

整体流程,其实就是根据入参,从spring.factorties中 根据key获取对应的权限定类集合。
在这里插入图片描述

getSpringFactoriesInstances(ApplicationContextInitializer.class));

    // 根据入参类型 返回一个集合对象private <T> Collection<T> getSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes, Object... args) {// 获取当前上下文类加载器 默认是app类加载ClassLoader classLoader = getClassLoader();// 获取到的扩展类名存入set集合中防止重复Set<String> names = new LinkedHashSet<>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));// 创建扩展点实例List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);// 进行排序AnnotationAwareOrderComparator.sort(instances);return instances;}

SpringFactoriesLoader.loadFactoryNames

通过调用loadSpringFactories 从META-INF/spring.factories处读取相应配置文件,key是对应的 factoryTypeName value是多个类名。

	public static List<String> loadFactoryNames(Class<?> factoryType, @Nullable ClassLoader classLoader) {// 根据Class获取名称String factoryTypeName = factoryType.getName();return loadSpringFactories(classLoader).getOrDefault(factoryTypeName, Collections.emptyList());}private static Map<String, List<String>> loadSpringFactories(@Nullable ClassLoader classLoader) {// 先查询缓存是否有 第一次进来是空MultiValueMap<String, String> result = cache.get(classLoader);if (result != null) {return result;}try {// 判断classLoader是否为空 Enumeration<URL> urls = (classLoader != null ?classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));result = new LinkedMultiValueMap<>();while (urls.hasMoreElements()) {URL url = urls.nextElement();UrlResource resource = new UrlResource(url);Properties properties = PropertiesLoaderUtils.loadProperties(resource);for (Map.Entry<?, ?> entry : properties.entrySet()) {String factoryTypeName = ((String) entry.getKey()).trim();for (String factoryImplementationName : StringUtils.commaDelimitedListToStringArray((String) entry.getValue())) {result.add(factoryTypeName, factoryImplementationName.trim());}}}cache.put(classLoader, result);return result;}catch (IOException ex) {throw new IllegalArgumentException("Unable to load factories from location [" +FACTORIES_RESOURCE_LOCATION + "]", ex);}}

在这里插入图片描述
在这里插入图片描述

# Application Context Initializers
org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.rsocket.context.RSocketPortInfoApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializerorg.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.autoconfigure.SharedMetadataReaderFactoryContextInitializer,\
org.springframework.boot.autoconfigure.logging.ConditionEvaluationReportLoggingListener

前面获取到所有类,然后进行反射进行实例化,添加到instances 集合中 返回。

	private <T> List<T> createSpringFactoriesInstances(Class<T> type, Class<?>[] parameterTypes,ClassLoader classLoader, Object[] args, Set<String> names) {// 创建实例的集合容器List<T> instances = new ArrayList<>(names.size());for (String name : names) {try {// 通过反射将扩展点实例实例化Class<?> instanceClass = ClassUtils.forName(name, classLoader);Assert.isAssignable(type, instanceClass);Constructor<?> constructor = instanceClass.getDeclaredConstructor(parameterTypes);T instance = (T) BeanUtils.instantiateClass(constructor, args);instances.add(instance);}catch (Throwable ex) {throw new IllegalArgumentException("Cannot instantiate " + type + " : " + name, ex);}}return instances;}

所以这个过程有设置了两个 ApplicationContextInitializer、ApplicationListener 会对配置中的类进行初始化。

构造方法就执行如下方法。
在这里插入图片描述

run方法

run方法比较核心,其中主要包含初始化环境变量、事件发布等,应用参数设置、打印banner、创建上下文对象,异常处理器、刷新前操作、刷新操作、刷新后操作、监听器运行等。好了,我们分析下主要的流程。

	public ConfigurableApplicationContext run(String... args) {// 创建一个任务执行观察器StopWatch stopWatch = new StopWatch();// 开始执行记录执行时间stopWatch.start();// 声明 ConfigurableApplicationContext 对象ConfigurableApplicationContext context = null;// 声明集合容器用来存储 SpringBootExceptionReporter 启动错误的回调接口Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();// 设置了一个名为java.awt.headless的系统属性// 其实是想设置该应用程序,即使没有检测到显示器,也允许其启动.//对于服务器来说,是不需要显示器的,所以要这样设置.configureHeadlessProperty();// 获取 SpringApplicationRunListener 加载的是 EventPublishingRunListener// 获取启动时的监听器---》 事件发布器  发布相关事件的  11个监听器 谁去发布事件?SpringApplicationRunListeners listeners = getRunListeners(args);// 触发启动事件  发布 starting 事件 --》 那么监听starting事件的监听器就会触发listeners.starting();try {// 构造一个应用程序的参数持有类 java -jar xx.jar aApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 创建并配置环境ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);// 配置需要忽略的BeanInfo信息configureIgnoreBeanInfo(environment);// 输出的Banner信息Banner printedBanner = printBanner(environment);// 创建应用上下文对象 AnnotationConfigServletWebServerApplicationContextcontext = createApplicationContext();// 加载配置的启动异常处理器exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,new Class[] { ConfigurableApplicationContext.class }, context);// 刷新前操作prepareContext(context, environment, listeners, applicationArguments, printedBanner);// 刷新应用上下文 完成Spring容器的初始化refreshContext(context);// 刷新后操作 功能拓展进行使用afterRefresh(context, applicationArguments);// 结束记录启动时间stopWatch.stop();if (this.logStartupInfo) {new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);}// 事件广播 启动完成了listeners.started(context);callRunners(context, applicationArguments);}catch (Throwable ex) {// 事件广播启动出错了handleRunFailure(context, ex, exceptionReporters, listeners);throw new IllegalStateException(ex);}try {// 监听器运行中listeners.running(context);}catch (Throwable ex) {handleRunFailure(context, ex, exceptionReporters, null);throw new IllegalStateException(ex);}// 返回上下文对象--> Spring容器对象return context;}

getRunListeners 获取监听器

这里会获取spring.factories 的中的SpringApplicationRunListener 类,然后实例化。并且调用start()进行启动执行。这里以 LoggingApplicationListener 为例子,执行了 日志的操作。

	private SpringApplicationRunListeners getRunListeners(String[] args) {Class<?>[] types = new Class<?>[] { SpringApplication.class, String[].class };return new SpringApplicationRunListeners(logger,// getSpringFactoriesInstances 读取spring.factories 文件中key 为 SpringApplicationRunListener 类型的getSpringFactoriesInstances(SpringApplicationRunListener.class, types, this, args));}void starting() {// 发布器 EventPulishingRunListenerfor (SpringApplicationRunListener listener : this.listeners) {listener.starting();}}public void starting() {// System.out.println("EventPublishingRunListener ----》starting ");this.initialMulticaster.multicastEvent(new ApplicationStartingEvent(this.application, this.args));}@Overridepublic void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));Executor executor = getTaskExecutor();for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {if (executor != null) {executor.execute(() -> invokeListener(listener, event));}else {invokeListener(listener, event);}}}doInvokeListener(listener, event);	private void onApplicationStartingEvent(ApplicationStartingEvent event) {this.loggingSystem = LoggingSystem.get(event.getSpringApplication().getClassLoader());this.loggingSystem.beforeInitialize();}

prepareEnvironment 环境准备

	private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners,ApplicationArguments applicationArguments) {// Create and configure the environment// 创建并且配置 Environment servlet context config \ systemt config等信息ConfigurableEnvironment environment = getOrCreateEnvironment();// 配置PropertySources和activeProfilesconfigureEnvironment(environment, applicationArguments.getSourceArgs());// 加载 configurationProperties 配置信息ConfigurationPropertySources.attach(environment);// 在配置环境信息之前发布事件 配置相关 这里会在执行监听器的 environmentPreparedlisteners.environmentPrepared(environment);// 把相关的配置信息绑定到Spring容器中bindToSpringApplication(environment);if (!this.isCustomEnvironment) {environment = new EnvironmentConverter(getClassLoader()).convertEnvironmentIfNecessary(environment,deduceEnvironmentClass());}// 配置PropertySources对它自己的递归依赖ConfigurationPropertySources.attach(environment);return environment;}

会再次执行

	private void onApplicationEnvironmentPreparedEvent(ApplicationEnvironmentPreparedEvent event) {// 加载系统提供的环境配置的后置处理器List<EnvironmentPostProcessor> postProcessors = loadPostProcessors();// 添加自身即 ConfigFileApplicationListener 为后置处理器postProcessors.add(this);// 原来有4个 现在加了一个需要重新排序AnnotationAwareOrderComparator.sort(postProcessors);for (EnvironmentPostProcessor postProcessor : postProcessors) {// 系统提供那4个不是重点,重点是 ConfigFileApplicationListener 中的这个方法处理postProcessor.postProcessEnvironment(event.getEnvironment(), event.getSpringApplication());}}

在这里插入图片描述

createApplicationContext 创建容器

这里比较简单根据webApplicationType的类型,选择创建对应的容器对象,这里是 AnnotationConfigServletWebServerApplicationContext 并且进行实例化。调用默认构造器进行初始化了两个对象,AnnotatedBeanDefinitionReader、ClassPathBeanDefinitionScanner 后续会使用到

	protected ConfigurableApplicationContext createApplicationContext() {Class<?> contextClass = this.applicationContextClass;if (contextClass == null) {try {switch (this.webApplicationType) {case SERVLET: // AnnotationConfigServletWebServerApplicationContextcontextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);break;case REACTIVE:contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);break;default:contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);}}catch (ClassNotFoundException ex) {throw new IllegalStateException("Unable create a default ApplicationContext, please specify an ApplicationContextClass", ex);}}return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);}// 在实例化的时候 调用构造方法 创建两个对象 reader和scannerpublic AnnotationConfigServletWebServerApplicationContext() {//this.reader = new AnnotatedBeanDefinitionReader(this);//this.scanner = new ClassPathBeanDefinitionScanner(this);}

Spring容器前置处理

将启动类注入容器,为后续开启自动化配置奠定基础。
这里其实主要做的两件事,一个是执行吃书画方法,将容器中的Initializer执行初始化操作,以及将主类,通过注解的方式添加到spring的beanDefinitionMap中。

	// 1.设置环境属性// 2.设置postprocess// 3.启动类config信息 准备上下文环境工作private void prepareContext(ConfigurableApplicationContext context, ConfigurableEnvironment environment,SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments, Banner printedBanner) {// 设置环境变量属性context.setEnvironment(environment);// 转换方法postProcessApplicationContext(context);// 应用初始化器 核心⭐️ 执行容器中的ApplicationContextInitializer(包括 spring.factories和自定义的实例)applyInitializers(context);listeners.contextPrepared(context);if (this.logStartupInfo) {logStartupInfo(context.getParent() == null);logStartupProfileInfo(context);}// Add boot specific singleton beans// 获取bean工厂ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();// 注册 springApplicationArguments 参数对象beanFactory.registerSingleton("springApplicationArguments", applicationArguments);if (printedBanner != null) {// 注册bannerbeanFactory.registerSingleton("springBootBanner", printedBanner);}// spring底层使用就是这个if (beanFactory instanceof DefaultListableBeanFactory) {((DefaultListableBeanFactory) beanFactory).setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);}if (this.lazyInitialization) {context.addBeanFactoryPostProcessor(new LazyInitializationBeanFactoryPostProcessor());}// Load the sourcesSet<Object> sources = getAllSources();Assert.notEmpty(sources, "Sources must not be empty");// 核心⭐️load(context, sources.toArray(new Object[0]));listeners.contextLoaded(context);}
	protected void applyInitializers(ConfigurableApplicationContext context) {// 遍历for (ApplicationContextInitializer initializer : getInitializers()) {Class<?> requiredType = GenericTypeResolver.resolveTypeArgument(initializer.getClass(),ApplicationContextInitializer.class);Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");// 执行初始化方法initializer.initialize(context);}}// ConfigurationWarningsApplicationContextInitializer 执行了将ConfigurationWarningsPostProcessor 添加到容器中 BFPP 等待容器进行刷新的BFPP的时候执行。public void initialize(ConfigurableApplicationContext context) {// 加入对象context.addBeanFactoryPostProcessor(new ConfigurationWarningsPostProcessor(getChecks()));}loader.load();int load() {int count = 0;for (Object source : this.sources) {count += load(source);}return count;}// 加载private int load(Object source) {Assert.notNull(source, "Source must not be null");//类if (source instanceof Class<?>) {return load((Class<?>) source);}//资源if (source instanceof Resource) {return load((Resource) source);}//包if (source instanceof Package) {return load((Package) source);}//if (source instanceof CharSequence) {return load((CharSequence) source);}throw new IllegalArgumentException("Invalid source type " + source.getClass());}if (isComponent(source)) { // 是否包含compoent注解// 注册SpringBootApp类到ioc 容器中this.annotatedReader.register(source);return 1;}this.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);

在这里插入图片描述

在这里插入图片描述

refreshContext(context);

这里其实就是调用spring的refresh() 由于整体过于庞大,先不讲解。后续在补坑。

小总结

在这里插入图片描述

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

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

相关文章

OFDM技术简介——背景

l 1966 年&#xff0c; R. W. Chang 提出在带限信道中用 正交信号 同时传输 多路数据 的原理&#xff0c;同时这种传输方式保证系统中不存在符号间串扰和子信道间干扰&#xff0c;该技术可以有效提高频谱利用率&#xff0c;可以有效对抗信道多径衰落。 l 1971 年&#xff0c; …

vue 自定义组件 实现跟使用

新建文件组件 选择器作用 ~ 波浪线这个是选择 li 后面的所有 a标签 调用 到使用的文件下引入 使用 效果

每日算法-插值查找

1.概念 插值查找是一种改良版的二分查找,其优势在于,对于较为均匀分布的有序数列,能够更快地使得mid中间游标快速接近目标值. 2.计算公式 中间游标计算公式. 公式说明: 公式的主要思路是,以第一次定位mid中间游标为例, 在接近平均分配的情况下,左右游标之间的差值表示总计供…

Animate软件基础:从单个图层复制帧

在使用Animate软件制作内容时&#xff0c;有时会需要复制制作好的部分动画&#xff0c;到新的场景中&#xff0c;或者从一个制作文件中复制内容到另一个制作文件&#xff0c;这就需要复制帧的操作&#xff0c;这里讲一下从单个图层复制帧的方法。 在图层中选择一组帧。要选择整…

React@16.x(44)路由v5.x(9)源码(1)- path-to-regexp

目录 1&#xff0c;作用2&#xff0c;实现获取 match 对象2.1&#xff0c;match 对象的内容2.2&#xff0c;注意点2.3&#xff0c;实现 1&#xff0c;作用 之前在介绍 2.3 match 对象 时&#xff0c;提到了 react-router 使用第3方库 path-to-regexp 来匹配路径正则。 我们也…

新能源汽车 LabCar 测试系统方案(二)

什么是LabCar测试 LabCar测试目标是进行整车黄板台架功能测试&#xff0c;用于整车开发和测试阶段&#xff0c;满足设计人员和测试人员的试验需求&#xff0c;以验证整车性能&#xff0c;减少开发工作量。系统主要用于测试静态及动态工况下的纯电动汽车的各项功能实现情况。 …

git 用户名密码Clone代码

#密码中包含&#xff0c;则使用%40代表 cd /disk03/wwwroot/GitDemo/BuildTemp && git clone -b dev --single-branch http://root:test%40123192.168.31.104/root/SaaS.Auto.Api.git git pull origin dev 今天使用LibGit2Sharp在Linux上Clone代码时报错&#xff0c;因…

【计算机网络】HTTP——基于HTTP的功能追加协议(个人笔记)

学习日期&#xff1a;2024.6.29 内容摘要&#xff1a;基于HTTP的功能追加协议和HTTP/2.0 HTTP的瓶颈与各功能追加协议 需求的产生 在Facebook、推特、微博等平台&#xff0c;每分每秒都会有人更新内容&#xff0c;我们作为用户当然希望时刻都能收到最新的消息&#xff0c;为…

Python | Leetcode Python题解之第188题买卖股票的最佳时机IV

题目&#xff1a; 题解&#xff1a; class Solution:def maxProfit(self, k: int, prices: List[int]) -> int:if not prices:return 0n len(prices)k min(k, n // 2)buy [0] * (k 1)sell [0] * (k 1)buy[0], sell[0] -prices[0], 0for i in range(1, k 1):buy[i] …

尚硅谷vue2的todolist案例解析,基本概括了vue2所有知识点,结尾有具体代码,复制粘贴学习即可

脚手架搭建 1-初始化脚手架&#xff08;全局安装&#xff09; npm install -g vue/cli2-切换到创建项目的空目录下 vue create xxxx整体结构 整体思路 App定义所有回调方法 增删改查 还有统一存放最终数据&#xff0c;所有子组件不拿数据&#xff0c;由App下发数据&#xf…

App托管服务分发平台 index-uplog.php 文件上传致RCE漏洞复现

0x01 产品简介 App托管服务分发平台是一个为开发者提供全面、高效、安全的应用程序托管、分发和推广服务的平台。开发者可以将自己开发的应用程序上传到平台上,平台会对上传的应用程序进行审核,确保应用的质量和安全性。平台会根据开发者的要求,将应用分发到不同的应用市场…

5G RAN

两个entity&#xff1a;NodeB、UE entity之间传输数据的东东 entity内部的流水线岗位&#xff1a;L3/L2/L1 岗位之间是消息交互/信令交互

利用labelme制作自己的coco数据集(labelme转coco数据集)

最近刚接触学习mmdetection&#xff0c;需要用到coco格式的数据集。 1.安装labelme 建议在conda(base)环境下安装&#xff08;前提是需要下载anaconda&#xff09;,下面是我已经装过的情况。 2.进入labelme环境下 中间可能会提示安装其它库&#xff0c;自行装上就行。 这里的…

智能社区服务小程序的设计

管理员账户功能包括&#xff1a;系统首页&#xff0c;个人中心&#xff0c;用户管理&#xff0c;房屋信息管理&#xff0c;住户信息管理&#xff0c;家政服务管理&#xff0c;家政预约管理&#xff0c;报修信息管理 微信端账号功能包括&#xff1a;系统首页&#xff0c;房屋信…

Flood Fill——AcWing 1097. 池塘计数

Flood Fill 定义 Flood Fill算法&#xff0c;又称为洪水填充或种子填充算法&#xff0c;是一种在图或网格数据结构中探索连通区域的搜索算法。它从一个初始节点&#xff08;种子点&#xff09;开始&#xff0c;将具有相同属性&#xff08;如颜色、值等&#xff09;的相邻节点…

P4. 微服务: 匹配系统(下)

P4. 微服务: 匹配系统 下 0 概述1 游戏同步系统1.1 游戏同步的设计1.2 游戏同步的实现 2 匹配系统微服务的实现2.1 微服务概述2.2 匹配系统接口url的实现2.3 微服务之间的通信2.4 匹配逻辑的实现2.5 匹配系统的权限控制 3 bug的解决3.1 自己匹配自己3.2 断开连接问题 0 概述 本…

大数据之Linux部署常用命令脚本封装

文章目录 编写集群命令执行脚本xcall集群命令执行1. 脚本需求2. 需求分析3. 脚本实现3.1 创建脚本存放目录3.2 编写xcall脚本3.3 修改脚本执行权限3.4 测试脚本 编写集群分发脚本xsync集群分发脚本1. 脚本需求2. 需求分析3. 脚本实现3.1 创建脚本存放目录3.2 编写xsync脚本3.3 …

计算机毕业设计PyFlink+Spark+Hive民宿推荐系统 酒店推荐系统 民宿酒店数据分析可视化大屏 民宿爬虫 民宿大数据 知识图谱 机器学习

本科毕业设计(论文) 开题报告 学院 &#xff1a; 计算机学院 课题名称 &#xff1a; 民宿数据可视化分析系统的设计与实现 姓名 &#xff1a; 庄贵远 学号 &#xff1a; 2020135232 专业 &#xff1a; 数据科学与大数据技术 班级 &#xff1a; 20大数据本科2…

重温react-06(初识函数组件和快速生成格式的插件使用方式)

开始 函数组件必然成为未来发展的趋势(个人见解),总之努力的去学习,才能赚更多的钱.加油呀! 函数组件的格式 import React from reactexport default function LearnFunction01() {return (<div>LearnFunction01</div>) }以上是函数式组件的组基本的方式 快捷生…

基于DSMM数据安全能力建设方案的落地性评估指标

写在前面&#xff1a; 随着信息技术的迅猛发展&#xff0c;数据已成为企业最宝贵的资产之一。然而&#xff0c;数据安全问题也随之而来&#xff0c;如何确保数据的安全性、完整性和可用性&#xff0c;已成为企业面临的重要挑战。DSMM&#xff08;数据安全能力成熟度模型&#x…