spring(四)——————从spring源码角度去解释前面的疑问

news2024/9/21 0:32:24

前面两篇文章,我们从mybatis-spring的插件包出发,探究如何将第三方框架集成到spring中,也知道了mybatis中使用了FactoryBean+ImportBeanDefifinitionRegistrar+@Import对mapper进行注入。

不过我们在前两篇文章中仍然遗留很多疑点,例如

1、ImportBeanDefifinitionRegistrar是在什么时机被调用的,怎么被回调的,为什么不能搭配@Compnent一起使用?

2、mybatis是如何扩展spring的扫描器的?(扫描自定义注解或某个包下的类),而spring本身又是如何扫描的?

3、FeactoryBean的工作原理是什么?

4、BeanFactoryPostProcess的工作原理?怎么回调的?回调时机?本身是如何注入到spring中的?其子类的原理和作用?可以获取修改beandefinition,为什么不能注册?为什么有List<BeanFactoryPostProcess>这个集合?

5、什么是但理财、BeanDefinitionMap、@import是如何工作的?

下面我们才算开始学习spring源码,我们可以带着这些疑问,从源码的角度去解释这些疑问。

-----------------------------------------------------------------------------------------

一、什么是spring容器?

 上面是spring官方对容器的一个解释:

BeanFactory接口提供了一种高级配置机制,能够管理任何类型的对象。ApplicationContext是BeanFactory的子接口,该子接口补充了下面的功能:

1、更容易与Spring的AOP特性集成

2、消息资源处理(用于国际化)

3、事件发布

4、应用程序层特定的上下文,如web应用程序中使用的WebApplicationContext。

    简而言之,BeanFactory提供了配置框架和基本功能,ApplicationContext添加了更多企业特定的功能。ApplicationContext是BeanFactory的一个完整超集。

 我们可以简单的总结spring容器:容器由spring多个组件组成(如各种后置处理器、扩展接口),用于管理任何类型对象,并提供多种应用功能。其主要存在两个分支:BeanFactory、ApplicationContext(两者都可以叫做spring的容器,只是提供的功能不同)。BeanFactory相当于一个标配版容器(提供了基本的对对象的管理),而ApplicationContext是选配豪华版容器(不仅提供了父接口的功能,还提供了依赖注入、各种注解扫描、更简单的aop集成、事件发布、国际化等等)。

Integrated lifecycle management----------------

Automatic BeanPostProcessor registration---------@Autowried等依赖注入功能

Automatic BeanFactoryPostProcessor registration-----------------

Convenient MessageSource access (for internationalization)------国际化功能

Built-in ApplicationEvent publication mechanism---------事件发布功能

我们从三个方面来举例两者的不同:(以ApplicationContext比BeanFactory多出了依赖注入、各种注解扫描、事件发布这三个功能举例)

1、依赖注入功能

2、注解扫描功能

3、事件发布功能(springboot中大量使用事件发布功能进行扩展)

代码实例:

1)、SpringContainerBean 

package com.spring.demo.container;


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;


/**
 * 测试依赖注入、注解扫描、事件发布等功能
 * */
@Slf4j
@Component
public class SpringContainerBean implements InitializingBean {


	//1、测试能否进行依赖注入
    @Autowired
    C c;

	@Autowired
	ApplicationContext context;


    public SpringContainerBean(){
		System.out.println("create SpringContainerBean  Object");
    }

	//2、测试能否扫描到这个注解
	//3、context.publishEvent()----测试事件发布功能
    @PostConstruct
    public void  init(){
		System.out.println("SpringContainerBean init PostConstruct");
		context.publishEvent(new ABeanInitEvent(context));

	}

    @Override
    public void afterPropertiesSet() throws Exception {
		System.out.println("SpringContainerBean init afterPropertiesSet");
    }


}

2)、依赖注入的类C

@Component
@Slf4j
public class C {

    public C(){
		System.out.println("create c Object");
    }

}

3)、事件类

/**
 * 发布事件类不需要进行注入操作(监听的类需要进行注入操作)
 */
public class ABeanInitEvent extends ApplicationContextEvent {

	public ABeanInitEvent(ApplicationContext source) {
		super(source);
	}
}

4)、监听事件类

/**
 * 监听ABeanInitEvent事件(需要进行注入)
 * */
@Component
public class ABeanListener implements ApplicationListener<ABeanInitEvent> {
	@Override
	public void onApplicationEvent(ABeanInitEvent event) {
		System.out.println("event----" + event.getClass().getSimpleName());
	}
}

5)、ApplicationContext容器的启动配置类ContextConfig 

@Component
@ComponentScan("com.spring.demo.container")
public class ContextConfig {
}

现在我们可以使用两个不同的容器,看看他们的功能区分:

启动ApplicationContext容器

public class ApplicationContextTest {


    public static void main(String[] args) {

        //获取ApplicationContext容器(传入ContextConfig配置类是为了让其自动调用register、refresh方法,也可以不传手动调用这两个方法)
		//这个ContextConfig就是我们平常写的Application类
        AnnotationConfigApplicationContext conext = new AnnotationConfigApplicationContext(ContextConfig.class);
	}
}

查看打印结果:

启动BeanFactory容器

public class BeanFactoryTest {


    public static void main(String[] args) {

         DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SpringContainerBean.class);
		AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
		beanFactory.registerBeanDefinition("springContainerBean", beanDefinition);
		//BeanFactory注册的bean默认都是懒加载,需要手动getBean才会进行初始化
		beanFactory.getBean(SpringContainerBean.class);

	}
}

查看打印结果:

 从上面的结果看到,BeanFatory容器确实不支持依赖注入(c构造方法没被打印),注解扫描(@

@PostConstruct注解方法没被执行),事件发布。但两者都支持对对象的基本管理(对对象进行注入,对实现了InitializingBean的方法调用)

那么BeanFatory既然本身没支持这些功能,我们可以自己给他扩展吗?

答案是可以的,例如我们以依赖注入功能为例,看看怎么实现:

我们先注释掉SpringContainerBean 的context代码

package com.spring.demo.container;


import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;


/**
 * 测试依赖注入、注解扫描、事件发布等功能
 * */
@Slf4j
@Component
public class SpringContainerBean implements InitializingBean {


	//1、测试能否进行依赖注入
    @Autowired
    C c;

	/*@Autowired
	ApplicationContext context;*/


    public SpringContainerBean(){
		System.out.println("create SpringContainerBean  Object");
    }

	//2、测试能否扫描到这个注解
	//3、context.publishEvent()----测试事件发布功能
   /* @PostConstruct
    public void  init(){
		System.out.println("SpringContainerBean init PostConstruct");
		context.publishEvent(new ABeanInitEvent(context));

	}*/

    @Override
    public void afterPropertiesSet() throws Exception {
		System.out.println("SpringContainerBean init afterPropertiesSet");
    }





}

修改BeanFactory代码:

public class BeanFactoryTest {


    public static void main(String[] args) {

         DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
		BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(SpringContainerBean.class);
		//builder.getBeanDefinition().setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
		AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
		beanFactory.registerBeanDefinition("springContainerBean", beanDefinition);


		BeanDefinitionBuilder builderC = BeanDefinitionBuilder.genericBeanDefinition(C.class);
		AbstractBeanDefinition beanDefinitionC = builderC.getBeanDefinition();
		beanFactory.registerBeanDefinition("c", beanDefinitionC);
		//@Autowired的依赖注入原理就是依靠这个AutowiredAnnotationBeanPostProcessor,所以我们把他交给容器即可
		AutowiredAnnotationBeanPostProcessor auto = new AutowiredAnnotationBeanPostProcessor();
		auto.setBeanFactory(beanFactory);
		beanFactory.addBeanPostProcessor(auto);
		//BeanFactory注册的bean默认都是懒加载,需要手动getBean才会进行初始化
		//beanFactory.getBean(C.class);
		beanFactory.getBean(SpringContainerBean.class);

	}
}

如果知道@Autowrited注解的源码,应该就能知道@Autowired的依赖注入原理就是依靠这个AutowiredAnnotationBeanPostProcessor,所以我们需要把他交给容器。

这里还要注意一点:我们是没有去手动getBean(C.class)的,并且所以BeanFactory注入的bean都是懒加载的,如果我们依赖注入成功,则会打印C类的构造方法。

此时我们看下打印结果,发现依赖注入的功能实现了:

 2、BeandefinitionMap?

前面说到一个对象被转化为一个BeanDefinition,然后基本会被put到BeandefinitionMap。而BeandefinitionMap存在两种BeanDefinition,一种spring自己内部的Beandefinition,一种我们通过spring的扩展注入的。

现在我们以ApplicationContex容器为例

1)、ApplicationContex容器中实例化了一个DefaultListableBeanFactory
从AnnotationConfigApplicationContext为入口:
new AnnotationConfigApplicationContext
------------------》this()构造方法(有父类会先调用父类构造方法)
------------------》父类GenericApplicationContext的构造方法
-----------------》父类中存在一个属性DefaultListableBeanFactory beanFactory;并且构造方法会赋值this.beanFactory = new DefaultListableBeanFactory();

实际上就是以DefaultListableBeanFactory(BeanFactory容器为壳进行扩展自己的功能)为壳。

2)、进入DefaultListableBeanFactory

进入DefaultListableBeanFactory后我们可以发现一个属性------>

/** Map of singleton and non-singleton bean names, keyed by dependency type. */
Map<String, BeanDefinition> beanDefinitionMap = new ConcurrentHashMap<>(256);
其中key----bean的名字,values----某个类的Beandefinition对象

3)、开始断点查找扫描后BeanDefinition put进beanDefinitionMap的代码

经过不断debug,当我们执行完AnnotationConfigApplicationContext--->refresh();------>

invokeBeanFactoryPostProcessors()方法后beanDefinitionMap出现了我们自己注入的BeanDefinition对象

-----------------------invokeBeanFactoryPostProcessors()方法执行前

---------------------invokeBeanFactoryPostProcessors()方法执行后

 所以我们可以推断 invokeBeanFactoryPostProcessors()方法完成了对我们自己注入的对象的扫描+put beanDefinitionMap的过程(执行这个方法之前Map没有值)

------------------------------invokeBeanFactoryPostProcessors方法执行前各个BeanDefinition的含义

(即此时未扫描我们自己注入的对象/类)

我们先不进去invokeBeanFactoryPostProcessors方法看其扫描的原理,我们先来看看这几个BeanDefinition的用处:

contextConfig -> 这是我们自己注册的BeanDefinition,因为实例化容器时需要传入这个类,所以就一同被注入了。

下面则是spring内部注册的Beandefinition:

1)、internalConfigurationAnnotationProcessor -> 对应的ConfigurationClassPostProcessor

该类是spring中非常重要的类,完成了spring的大部分工作(这里不细讲)

2)、internalEventListenerFactory" -> 对应的类DefaultEventListenerFactory

3)、internalEventListenerProcessor" -> 对应的类EventListenerMethodProcessor

4)、internalAutowiredAnnotationProcessor-> 对应类AutowiredAnnotationBeanPostProcessor

解析1@Autowrited注解依赖注入

5)、internalCommonAnnotationProcessor-> 对应的类CommonAnnotationBeanPostProcessor

解析@Resource注解依赖注入等功能,还有其他功能

这几个BeanDefinition是什么时候实例化出来的?

AnnotationConfigApplicationContext---》this()----->AnnotatedBeanDefinitionReader类的构造器

--->AnnotationConfigUtils类的registerAnnotationConfigProcessors()方法

	/**
	 * Register all relevant annotation post processors in the given registry.
	 * @param registry the registry to operate on
	 * @param source the configuration source element (already extracted)
	 * that this registration was triggered from. May be {@code null}.
	 * @return a Set of BeanDefinitionHolders, containing all bean definitions
	 * that have actually been registered by this call
	 */
	public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
			BeanDefinitionRegistry registry, @Nullable Object source) {

		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());
			}
		}

		Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);


//--------------------实例化BenDefinition
		if (!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)) {

//-------1、实例化出对应的BeanDefinition
			RootBeanDefinition def = new RootBeanDefinition(ConfigurationClassPostProcessor.class);
			def.setSource(source);

//--------2、registerPostProcessor方法将其put到BeanDefinitionMap中
			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));
		}

		// Check for JSR-250 support, and if present add the CommonAnnotationBeanPostProcessor.
		if (jsr250Present && !registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition(CommonAnnotationBeanPostProcessor.class);
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

		// Check for JPA support, and if present add the PersistenceAnnotationBeanPostProcessor.
		if (jpaPresent && !registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)) {
			RootBeanDefinition def = new RootBeanDefinition();
			try {
				def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,
						AnnotationConfigUtils.class.getClassLoader()));
			}
			catch (ClassNotFoundException ex) {
				throw new IllegalStateException(
						"Cannot load optional framework class: " + PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);
			}
			def.setSource(source);
			beanDefs.add(registerPostProcessor(registry, def, PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));
		}

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

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

		return beanDefs;
	}

put到BeanDefinitionMap中是在registerPostProcessor方法中执行的,调用链:

registerPostProcessor()
--->BeanDefinitionRegistry接口的registerBeanDefinition()方法
----->其实现类DefaultListableBeanFactory中将BeanDefinition put到BeanDefinitionMap中

下篇我们开始讲invokeBeanFactoryPostProcessors()方法怎么扫描我们自己注入的对象/类,然后put到BeanDefinitionMap中的。

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

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

相关文章

Revit导出PDF格式图纸流程及“批量导出图纸”

一、Revit导出PDF格式图纸流程 1、点击左上方“应用程序菜单”即“R”图标&#xff0c;进择“打印”选项。 2、在弹出的对话框中&#xff0c;需要设置图纸“打印范围”&#xff0c;选择“所选的视图/图纸选项”&#xff0c;点击“选择”&#xff0c;按钮&#xff0c;选择我们需…

LESS模型与随机森林

模型学习 1 随机森林 https://blog.csdn.net/weixin_35770067/article/details/107346591? 森林就是建立了很多决策树&#xff0c;把很多决策树组合到一起就是森林。 这些决策树都是为了解决同一任务建立的&#xff0c;最终的目标也都是一致的&#xff0c;最后将其结果来平均…

Xline v0.2.0: 一个用于元数据管理的分布式KV存储

Xline是什么&#xff1f;我们为什么要做Xline&#xff1f; Xline是一个基于Curp协议的&#xff0c;用于管理元数据的分布式KV存储。现有的分布式KV存储大多采用Raft共识协议&#xff0c;需要两次RTT才能完成一次请求。当部署在单个数据中心时&#xff0c;节点之间的延迟较低&a…

08 SpringCloud 微服务网关Gateway组件

网关简介 大家都都知道在微服务架构中&#xff0c;一个系统会被拆分为很多个微服务。那么作为客户端要如何去调用这么多的微服务呢&#xff1f; 如果没有网关的存在&#xff0c;我们只能在客户端记录每个微服务的地址&#xff0c;然后分别去用。 这样的架构&#xff0c;会存…

Linux软件管理:源代码安装

前言 Linux上面的软件几乎都是经过GPL的授权&#xff0c;所以每个软件都提供源码 我们的系统中有一个http软件包&#xff08;上一篇文章中安装的&#xff09; 通过man手册我们知道了该软件包属于Apache&#xff0c;所以我们就可以进入它的官网中获取此源码 官网除了能获取源码…

IB学校获得IBO授权究竟有多难?

IB 学校认证之路&#xff0c;道阻且长 The road to IB school accreditation is long and difficult一所学校能获得IB授权必须经过IBO非常严格的审核&#xff0c;在办学使命&教育理念、组织架构、师资力量&授课技能、学校硬件设施和课程体系上完全符合标准才可获得授权…

英特尔 31.0.101.4125显卡驱动更新!

周更驱动的英特尔又来啦&#xff01;时隔一周英特尔为大家带来了31.0.101.4125版本的显卡驱动&#xff0c;支持《英雄连3》、《工人物语&#xff1a;新兴同盟》、《原子之心》、《狂野之心》、《如龙维新&#xff01;极》等游戏。 《如龙维新&#xff01;极》将在2月22日正式登…

功率放大器在lamb波方向算法的损伤定位中的应用

实验名称&#xff1a;基于PZT结Lamb波方向算法的损伤定位方法研究方向&#xff1a;损伤定位测试目的&#xff1a;Lamb波是在具有自由边界的固体板或层状结构中传输的一种弹性导波&#xff0c;由于其本身的传播特性&#xff0c;如沿传播路径衰减小&#xff0c;能量损失小&#x…

makefile简易教程

makefile简易教程 一、学习目标 达到多文件快速编译的需求&#xff0c;相关符号的意思&#xff0c;以及其它注意事项。 二、快速入门 2.1 基本概念 Makefile 是一个在Unix和Linux操作系统上使用的构建工具&#xff0c;用于自动化编译和构建源代码。 2.2 用处 通过Makefi…

Linux环境下(CentOS 7)安装MySQL

Linux环境下(CentOS 7)安装MySQL数据库 文章目录Linux环境下(CentOS 7)安装MySQL数据库一、安装MySQL数据库二、安装过程的中相关问题三、如何卸载已安装的MySQL四、参考链接一、安装MySQL数据库 1、下载mysql源安装包(version: 5.7.41 MySQL Community Server) wget http://…

ACM MM 相关内容的整理+汇总

目录一、网址二、重要时间点三、论文篇幅要求四、征稿主题五、论文格式相关要求六、论文模板修改成投稿模式上述参考七、模板使用相关八、关于图片方面的问题九、Review and Rebuttal十、ACM MM2022相关论文参考arxiv上 ACM MM2022 论文汇总一、网址 ACM MM2023 主页&#xff1…

阿里工作7年,一个30岁女软件测试工程师的心路历程

简单的先说一下&#xff0c;坐标杭州&#xff0c;14届本科毕业&#xff0c;算上年前在阿里巴巴的面试&#xff0c;一共有面试了有6家公司&#xff08;因为不想请假&#xff0c;因此只是每个晚上去其他公司面试&#xff0c;所以面试的公司比较少&#xff09; 其中成功的有4家&am…

嵌入式原理与应用期末复习汇总(附某高校期末真题试卷)

文章目录一、选择题二、填空题&#xff1a;三、判读题&#xff1a;四、简答题&#xff1a;五、程序设计题高校真题试卷第一套第二套第三套重修试卷一、选择题 1、为保证在启动服务器时自动启动DHCP进程&#xff0c;应对&#xff08; B &#xff09;文件进行编辑。 A、 /etc/rc…

少儿户外拓展北斗定位解决方案

一、项目背景户外拓展训练是指通过专业的机构&#xff0c;对久居城市的人进行的一种野外生存训练。拓展训练通常利用崇山峻岭、翰海大川等自然环境&#xff0c;通过精心设计的活动达到“磨练意志、陶冶情操、完善人格、熔炼团队”的培训目的。针对户外拓展人员安全管理存在的实…

【LeetCode】剑指 Offer(2)

目录 写在前面&#xff1a; 题目&#xff1a; 题目的接口&#xff1a; 解题思路&#xff1a; 代码&#xff1a; 过啦&#xff01;&#xff01;&#xff01; 写在最后&#xff1a; 写在前面&#xff1a; 今天的每日一题好难&#xff0c;我不会dp啊啊啊啊啊啊。 所以&am…

Cache-Control 常见字段

Cache-Control 常见字段 参考&#xff1a;https://blog.csdn.net/qq_41996454/article/details/108644436 Cache-Control 可以在请求头或者响应头中设置&#xff0c;并且可以组合使用多种指令 no-cache 和 no-store 用作控制缓存&#xff0c;被服务器通过响应头 Cache-Contro…

wireshark抓包后通过工具分包

分包说明&#xff1a;关于现场问题分析&#xff0c;一般都是通过日志&#xff0c;这个属于程序中加的打印&#xff0c;或存数据库&#xff0c;或者存文本形式&#xff0c;这种一般比较符合程序逻辑&#xff1b;还有一种就是涉及到网络通信方面的&#xff0c;需要通过抓包来分析…

C++ inline内联函数详解

函数是一个可以重复使用的代码块&#xff0c;CPU 会一条一条地挨着执行其中的代码。CPU 在执行主调函数代码时如果遇到了被调函数&#xff0c;主调函数就会暂停&#xff0c;CPU 转而执行被调函数的代码&#xff1b;被调函数执行完毕后再返回到主调函数&#xff0c;主调函数根据…

AntD-tree组件使用详析

目录 一、selectedKeys与onSelect 官方文档 代码演示 onSelect 注意事项 二、expandedKeys与onExpand 官方文档 代码演示 onExpand 注意事项 三、loadedKeys与onLoad和onExpand 官方文档 代码演示 onExpand与onLoad&#xff1a;​ 注意事项 四、loadData …

从“服务”,到“赋能”,日日顺再次定义供应链生态建设

在众多不确定因素的交织下&#xff0c;当下的供应链企业变革呈现出前所未有的紧迫感。一体化、全链路的趋势&#xff0c;为企业的发展指明方向&#xff0c;与此同时数字化与科技化开始承托供应链管理能力的升级与变革。 2月15日&#xff0c;由日日顺供应链、运联智库联合举办的…