spring源码篇——BeanDefinition的注册
spring-framework 版本:v5.3.19
文章目录
- 注解方式(AnnotationConfigApplicationContext)
- AnnotationConfigApplicationContext#register
- AnnotatedBeanDefinitionReader#doRegisterBean
- BeanDefinitionRegistry#registerBeanDefinition
- AnnotationConfigApplicationContext#scan
- ClassPathBeanDefinitionScanner#doScan
- findCandidateComponents
- BeanDefinitionRegistry#registerBeanDefinition
- XML方式(AbstractXmlApplicationContext)
- AbstractXmlApplicationContext#loadBeanDefinitions
- XmlBeanDefinitionReader#doLoadBeanDefinitions
- BeanDefinitionDocumentReader#doRegisterBeanDefinitions
- BeanDefinitionRegistry#registerBeanDefinition
- 总结
注解方式(AnnotationConfigApplicationContext)
AnnotationConfigRegistry接口有两个方法,分别对应了注解生成beanDefinition的两种方式
来到其实现类AnnotationConfigApplicationContext可以发现类里有两个成员变量reader和scanner
reader对应register方法(根据传入class生成beanDefinition)
scanner对应scan方法(根据传入包名扫描特定注解表示的class生成beanDefinition)
先看register方法的实现
AnnotationConfigApplicationContext#register
AnnotatedBeanDefinitionReader#doRegisterBean
private <T> void doRegisterBean(Class<T> beanClass, @Nullable String name,@Nullable Class<? extends Annotation>[] qualifiers, @Nullable Supplier<T> supplier,@Nullable BeanDefinitionCustomizer[] customizers) {//根据bean类生成beanDefinition(实际上就是解析出 AnnotationMetadata 元数据)AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(beanClass);//根据condition注解条件判断是否跳过if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {return;}//创建bean之后的回调方法abd.setInstanceSupplier(supplier);//设置作用域ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);abd.setScope(scopeMetadata.getScopeName());//得到beanNameString beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry));//通用注解解析:Lazy、Primary、DependsOn、Role、DescriptionAnnotationConfigUtils.processCommonDefinitionAnnotations(abd);if (qualifiers != null) {for (Class<? extends Annotation> qualifier : qualifiers) {if (Primary.class == qualifier) {abd.setPrimary(true);}else if (Lazy.class == qualifier) {abd.setLazyInit(true);}else {abd.addQualifier(new AutowireCandidateQualifier(qualifier));}}}//自定义处理if (customizers != null) {for (BeanDefinitionCustomizer customizer : customizers) {customizer.customize(abd);}}//封装成BeanDefinitionHolderBeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);//是否需要代理definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);//注册beanDefinition到registryBeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);}
BeanDefinitionRegistry#registerBeanDefinition
进入BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry)方法
可以发现最终会调用BeanDefinitionRegistry.registerBeanDefinition方法。但这是一个接口,其对应的实现类是哪个呢?
其实就是AnnotationConfigApplicationContext本身。
在赋值reader的时候会把自身传进去赋值给register,而AnnotationConfigApplicationContext继承自GenericApplicationContext,GenericApplicationContext又继承自BeanDefinitionRegistry,所以AnnotationConfigApplicationContext也是一个BeanDefinitionRegistry。
GenericApplicationContext.registerBeanDefinition实际上只是调用了另一个实现类DefaultListableBeanFactory的registerBeanDefinition方法
reader赋值
GenericApplicationContext.registerBeanDefinition
综上最终会来到DefaultListableBeanFactory#registerBeanDefinition,也即BeanDefinitionRegistry#registerBeanDefinition
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)throws BeanDefinitionStoreException {Assert.hasText(beanName, "Bean name must not be empty");Assert.notNull(beanDefinition, "BeanDefinition must not be null");//抽象beanDefinition的一些处理(beanDefinition也有继承关系)if (beanDefinition instanceof AbstractBeanDefinition) {try {((AbstractBeanDefinition) beanDefinition).validate();}catch (BeanDefinitionValidationException ex) {throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,"Validation of bean definition failed", ex);}}//beanName是否已经存在beanDefinitionBeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);if (existingDefinition != null) {//不允许覆盖则报错if (!isAllowBeanDefinitionOverriding()) {throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);}//打印一些日志else if (existingDefinition.getRole() < beanDefinition.getRole()) {// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTUREif (logger.isInfoEnabled()) {logger.info("Overriding user-defined bean definition for bean '" + beanName +"' with a framework-generated bean definition: replacing [" +existingDefinition + "] with [" + beanDefinition + "]");}}else if (!beanDefinition.equals(existingDefinition)) {if (logger.isDebugEnabled()) {logger.debug("Overriding bean definition for bean '" + beanName +"' with a different definition: replacing [" + existingDefinition +"] with [" + beanDefinition + "]");}}else {if (logger.isTraceEnabled()) {logger.trace("Overriding bean definition for bean '" + beanName +"' with an equivalent definition: replacing [" + existingDefinition +"] with [" + beanDefinition + "]");}}//覆盖原beanDefinitionthis.beanDefinitionMap.put(beanName, beanDefinition);}else {//判断是否有bean开始注册了,有就加锁保证线程安全if (hasBeanCreationStarted()) {synchronized (this.beanDefinitionMap) {this.beanDefinitionMap.put(beanName, beanDefinition);List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);updatedDefinitions.addAll(this.beanDefinitionNames);updatedDefinitions.add(beanName);this.beanDefinitionNames = updatedDefinitions;removeManualSingletonName(beanName);}}//没有就直接注册beanDefinitionelse {this.beanDefinitionMap.put(beanName, beanDefinition);this.beanDefinitionNames.add(beanName);removeManualSingletonName(beanName);}this.frozenBeanDefinitionNames = null;}//如果存在过beanName对应的beanDefinition或者单例bean则重置beanDefinition//移除合并的beanDefinition、销毁单例bean、执行MergedBeanDefinitionPostProcessor.resetBeanDefinitionif (existingDefinition != null || containsSingleton(beanName)) {resetBeanDefinition(beanName);}//如果beanDefinition被冻结的一些处理else if (isConfigurationFrozen()) {clearByTypeCache();}}
至此beanDefinition就注册到容器上了。
接下来扫描包的方式即scan方法
AnnotationConfigApplicationContext#scan
ClassPathBeanDefinitionScanner#doScan
![ protected Set<BeanDefinitionHolder> doScan(String... basePackages) {Assert.notEmpty(basePackages, "At least one base package must be specified");Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<>();for (String basePackage : basePackages) {//扫描生成BeanDefinitionSet<BeanDefinition> candidates = findCandidateComponents(basePackage);for (BeanDefinition candidate : candidates) {//得到作用域元数据ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);//设置作用域candidate.setScope(scopeMetadata.getScopeName());//得到beanNameString beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);if (candidate instanceof AbstractBeanDefinition) {postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}//通用注解解析:Lazy、Primary、DependsOn、Role、Descriptionif (candidate instanceof AnnotatedBeanDefinition) {AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}//检测BeanDefinition的beanName是否与其他BeanDefinition存在冲突//虽然是一个判断,当时会有三种情况:true:beanName没有冲突,false:有冲突但兼容,抛异常:有冲突且不兼容if (checkCandidate(beanName, candidate)) {//没有冲突则注册BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);beanDefinitions.add(definitionHolder);//注册beanDefinitionregisterBeanDefinition(definitionHolder, this.registry);}}}return beanDefinitions;}]
findCandidateComponents
findCandidateComponents方法返回Set < BeanDefinition > 。可以发现当有指定spring.components文件时,会直接用该文件加快加载,否则扫描包下所有资源。
scanCandidateComponents
这里会先得到包下所有的Resource,再依次获取元数据封装到MetadataReader,当满足excludeFilter/includeFilter/condition条件时,创建ScannedGenericBeanDefinition。
private Set<BeanDefinition> scanCandidateComponents(String basePackage) {Set<BeanDefinition> candidates = new LinkedHashSet<>();try {String packageSearchPath = ResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX +resolveBasePackage(basePackage) + '/' + this.resourcePattern;Resource[] resources = getResourcePatternResolver().getResources(packageSearchPath);boolean traceEnabled = logger.isTraceEnabled();boolean debugEnabled = logger.isDebugEnabled();for (Resource resource : resources) {if (traceEnabled) {logger.trace("Scanning " + resource);}try {//得到元数据MetadataReader metadataReader = getMetadataReaderFactory().getMetadataReader(resource);//判断excludeFilter/includeFilter/condition条件if (isCandidateComponent(metadataReader)) {ScannedGenericBeanDefinition sbd = new ScannedGenericBeanDefinition(metadataReader);sbd.setSource(resource);//判断excludeFilter/includeFilter/condition条件if (isCandidateComponent(sbd)) {if (debugEnabled) {logger.debug("Identified candidate component class: " + resource);}//添加扫描beanDefinitioncandidates.add(sbd);}//以下都是日志异常处理......
isCandidateComponent
BeanDefinitionRegistry#registerBeanDefinition
进入registerBeanDefinition(definitionHolder, this.registry);
可以发现最后跟register一样调用BeanDefinitionReaderUtils#registerBeanDefinition进而调用BeanDefinitionRegistry#registerBeanDefinition,这里就不再说一遍了。
XML方式(AbstractXmlApplicationContext)
AbstractXmlApplicationContext#loadBeanDefinitions
AbstractXmlApplicationContext会调用BeanDefinitionReader#loadBeanDefinitions加载xml文件,而xml方式具体的实现类就是XmlBeanDefinitionReader。
虽然XmlBeanDefinitionReader和上面提到的AnnotatedBeanDefinitionReader都是以BeanDefinitionReader结尾,但是实际上这两个从类继承来看没有什么关系,并不来自于同一基类也不互相继承。XmlBeanDefinitionReader实现了BeanDefinitionReader接口,而AnnotatedBeanDefinitionReader不实现任何接口也不继承任何类。
跟踪XmlBeanDefinitionReader代码来到真正做事的地方:doLoadBeanDefinitions方法
XmlBeanDefinitionReader#doLoadBeanDefinitions
最终来到BeanDefinitionDocumentReader.doRegisterBeanDefinitions()方法
BeanDefinitionDocumentReader#doRegisterBeanDefinitions
这一步主要就是对各种标签的解析
BeanDefinitionRegistry#registerBeanDefinition
进入bean标签的解析
可以发现最后同样调用BeanDefinitionReaderUtils#registerBeanDefinition进而调用BeanDefinitionRegistry#registerBeanDefinition,剩下的步骤就跟注解方式的一样了。
总结
注解方式可register直接注册,也可scan扫描注册。register方式通过AnnotatedBeanDefinitionReader创建beanDefinition(同时会得到元数据),再根据元数据等设置完beanDefinition属性后,用BeanDefinitionRegistry注册到容器。scan方式通过ClassPathBeanDefinitionScanner先扫描生成所有的BeanDefinition(同时会得到元数据),依次遍历BeanDefinition,根据元数据等设置BeanDefinition的属性,最后检查是否冲突,当无冲突时也用BeanDefinitionRegistry注册到容器。
xml方式通过XmlBeanDefinitionReader先加载xml文件并封装成document,再将document委托给BeanDefinitionDocumentReader对各种标签进行解析生成beanDefinition,最后用BeanDefinitionRegistry注册到容器中。
可以发现无论是注解register还是注解scan还是xml最终在注册到容器时都会用到BeanDefinitionRegistry,而其具体的实现就是DefaultListableBeanFactory#registerBeanDefinition。
相关文章:
spring源码篇——BeanDefinition的注册
spring-framework 版本:v5.3.19 文章目录注解方式(AnnotationConfigApplicationContext)AnnotationConfigApplicationContext#registerAnnotatedBeanDefinitionReader#doRegisterBeanBeanDefinitionRegistry#registerBeanDefinitionAnnotatio…...
virtualbox7虚拟机中安装苹果macOS big sur系统详细教程
第1步,在 Windows 10/11 PC 上启用虚拟化。 现在的电脑一般都默认开启虚拟化技术了。 如果你遇到一些报错,比如收到错误消息“无法在虚拟机上打开会话”,可以查看 如果没有遇到问题,可以直接进入到第二步。 第2步,在…...
用spectralayers 简单去一下人声做个伴奏
最近有个同事说有个工作要一个歌的伴奏不会下载问我能不能给下一个。问题是我五音不全,也不咋关注伴奏这方面的事儿,然后巧了,当天晚上就有个网上的大哥在群里聊天的时候说有个去人声比较给力的软件,我马上给要来了。 软件叫啥sp…...
高峰对话|深度探讨「多云与边缘」
2022 年 12 月,分析师 Zeus Kerravala 与 VMware 通信运营商和边缘事业部高级副总裁兼总经理 Sanjay Uppal 进行非常有启发性的谈话,分享了科技行业领导者的见解。 二位主要围绕以下主题进行探讨: 📍 如何定义多云,以…...
开发手册——一、编程规约_2.常量定义
这篇文章主要梳理了在java的实际开发过程中的编程规范问题。本篇文章主要借鉴于《阿里巴巴java开发手册终极版》 下面我们一起来看一下吧。 1. 【强制】不允许任何魔法值(即未经定义的常量)直接出现在代码中。 反例:String key "Id#…...
Sandstorm 建设者亮点——2023 年 2 月
隆重推出 Sandstorm 建设者亮点——2023 年 2 月版,这是由最厉害的 Sandstorm 社区制作的独一无二的 NFT 系列。 从突破性的兔子机器人到神奇的蒸汽朋克海盗船,Sandstorm 建设者亮点 NFT 系列展示了一系列独一无二的创作。 19 项新资产将添加至 Sandstor…...
MyBatis快速入门
创建表(自行完成)创建模块,引入坐标(1).进入mybatis官网:MyBatis中文网按步骤进行添加坐标先添加mybatis依赖然后手动添加mysql驱动junit单元测试坐标:logback坐标:用的时候直接复制…...
Mysql的一些提权方式(mysql提权、UDF)
目录 bash命令提权 必要条件 实验 UDF提权 什么是UDF 必要条件 实验 手动测试...
【2023】DevOps、SRE、运维开发面试宝典之Docker相关面试题
文章目录 1、docker的工作原理是什么2、docker的组成包含哪几大部分3、讲一下镜像的分层结构以及为什么要使用镜像的分层结构?4、简单描述一下Dockerfile的整个构建镜像过程?5、Docker的四种网络类型?6、Docker跨宿主机通讯的方式1、docker的工作原理是什么 docker是一个Cl…...
圣杯布局的实现方式
1.什么是圣杯布局? 左右盒子固定,中间盒子自适应 2.实现方式 (1)flex布局 思路:左右盒子给固定的宽高,中间盒子flex:1 <!DOCTYPE html> <html lang"en"> <head> <met…...
RecastDemo用法
这里写自定义目录标题recastnavigation介绍recastnavigation的内容RecastDemo安装RecastDemo介绍可配置参数合理的创建标题,有助于目录的生成如何改变文本的样式生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个…...
IIC总线式驱动开发(mpu6050)(二)
目录 六、I2C总线二级外设驱动开发方法 七、I2C总线二级外设驱动开发之名称匹配 1. i2c_register_board_info 2. i2c_new_device:明确二级外设地址的情况下可用 3. i2c_new_probed_device 八、I2C总线二级外设驱动开发之设备树匹配 六、I2C总线二级外设驱动开…...
盘点一下那些远程办公的神仙公司
其实远程办公已经有50多年的历史了,这几年,这种工作方式越来越受到大家的喜欢,对于员工来说,工作效率可以大幅提高,节省下来的通勤时间和成本,有更多的时间花在工作上。可以更好的平衡工作与生活。对于公司…...
Spring Cloud Alibaba全家桶(四)——微服务调用组件Feign
前言 本文小新为大家带来 微服务调用组件Feign 的相关知识,具体内容包含什么是Feign,Spring Cloud Alibaba快速整合OpenFeign,Spring Cloud Feign的自定义配置及使用(包括:日志配置、契约配置、自定义拦截器实现认证逻…...
安装pytorch
一、在anaconda中创建虚拟环境 打开Anaconda Prompt创建一个虚拟环境。比如要创建一个名字为pytorch的虚拟环境,可以如下输入。其中python3.7指定该虚拟环境的python版本号。 conda create -n pytorch python3.7 二、进入新创建的虚拟环境。 创建好虚拟环境后&a…...
自动化测试 Appium之Python运行环境搭建 Part2
环境部署 1、安装Android SDK 安装好后,配置ANDROID_HOME环境变量,设置为Android SDK安装路径(例中:D:\Program Files (x86)\Android\android-sdk) 2、安装其它SDK相关软件包 安装好Android SDK后,选择打开Android SDK Manager…...
LeetCode 2 - 两数相加
给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。 请你将两个数相加,并以相同形式返回一个表示和的链表。 你可以假设除了数字 0 之外,这两个数都不会以 0 开…...
用Python实现九九乘法表的几种方式,最简单只需一行代码
前言 嗨喽~大家好呀,这里是魔王呐 ❤ ~! 我们在学习Python的过程中需要不断的积累和练习,这样才能够走的更远, 今天一起来学习怎么用Python写九九乘法表~ 更多教程源码资料电子书: 点击此处跳转文末名片获取 第一种方法、for-for 代码&…...
入职外包三个月,我提桶跑路了
有一种打工人的羡慕,叫做“大厂”。 真是年少不知大厂香,错把青春插稻秧。 但是,在深圳有一群比大厂员工更庞大的群体,他们顶着大厂的“名”,做着大厂的工作,还可以享受大厂的伙食,却没有大厂…...
企业邮箱对企业有哪些好处以及便捷性
企业邮箱拥有更专业的办公功能,更适合职场使用。同时,使用企业邮箱还可以帮助企业“公私分明”。一方面保护了公司信息,另一方面也提高了工作效率。加上公司统一邮箱也有助于提升公司形象。使用企业邮箱除了收发邮件方便外,还可以…...
ConvNeXt V2实战:使用ConvNeXt V2实现图像分类任务(一)
文章目录摘要安装包安装timm安装 grad-cam数据增强Cutout和MixupEMA项目结构计算mean和std生成数据集关于不上分的问题摘要 论文:https://arxiv.org/pdf/2301.00808.pdf 论文翻译:https://wanghao.blog.csdn.net/article/details/128541957 官方源码&am…...
3.2 报错整理
报错1: 报错:RuntimeError: DataLoader worker (pid 93789) is killed by signal: Killed.原因:显存不够报错2: 报错:TqdmWarning: IProgress not found. Please update jupyter and ipywidgets.解决:pip i…...
从0开始学python -46
Python CGI编程 什么是CGI CGI 目前由NCSA维护,NCSA定义CGI如下: CGI(Common Gateway Interface),通用网关接口,它是一段程序,运行在服务器上如:HTTP服务器,提供同客户端HTML页面的接口。 网页浏览 为了更好的了解CGI是如何工作…...
JavaScript事件委托机制详解
一、什么是事件委托机制 事件委托机制就是:我们给元素添加click事件时不在该元素上添加,而是委托给某个公共的祖辈元素,告诉祖辈元素如果接收到了click事件,并且这个click事件是由该元素触发的,就执行祖辈元素上委托绑…...
【项目实战】MySQL中union和union all的相同点与不同点
一、union和union all的相同点 在MySQL中,Union和Union All都是用来合并两个或者多个查询结果集的关键字 二、union和union all的不同点 union复杂,union all简单 2.1 自动压缩,自动求并集、去重、排序操作 (1)unio…...
ChatGPT最牛应用,让它帮你更新网站新闻吧!
谁能想到,ChatGPT火了!既能对话入流,又能写诗歌论文、出面试题、编代码,甚至还通过了谷歌面试拿到L3工程师offer,放在一年之前,没人相信这是当前AI能够达到的水平。ChatGPT自面世以来,凭借其极为…...
乌班图安装kvm并配置网络
乌班图22安装KVM 1.安装KVM sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virt-manager virtinstsudo adduser id -un libvirt sudo adduser id -un kvm sudo apt install virtinst qemu-efi sudo systemctl enable --now libvirtd sudo s…...
蓝库云|ERP系统在企业数字化转型中最常用的八大功能
ERP系统和与企业数字化转型 随着数字化发展的兴起,规划和管理已成为企业产生富有成效的成果的关键。许多企业采用了企业资源规划 (ERP) 等先进工具,使企业所有者能够以高效的方式规划和管理其资源和运营。 ERP系统负责整合业务的不同流程并向决策者提供…...
Pytorch学习笔记#1:拟合函数/梯度下降
学习自https://pytorch.org/tutorials/beginner/pytorch_with_examples.html 概念 Pytorch Tensor在概念上和Numpy的array一样是一个nnn维向量的。不过Tensor可以在GPU中进行计算,且可以跟踪计算图(computational graph)和梯度(…...
挑战图像处理100问(24)——伽玛校正
伽马校正(Gamma Correction)是一种图像处理技术,用于校正显示设备的非线性响应。通过对图像进行伽马变换,可以将图像的亮度范围映射到显示设备的亮度范围内,从而提高图像的对比度和细节,改善图像的视觉效果…...
淘宝网的网站设计方案/河北百度seo关键词
《中国古代文学(六)》作业 一、填空题 1、在晚明文学领域中,公安派提出了一系列体现晚明文学新价值观的理论主张,( )为其理论内核。 2、( “ ” )是明代创作成就最高的白话短篇小说…...
手机网站底部导航代码/长沙网站seo服务
题目:http://www.lydsy.com:808/JudgeOnline/problem.php?id3211 分析: 区间开根是没法区间合并的。 但是注意到10^9开根开个5次就变成1了…… 于是只要在每个区间额外维护个值b,b1表示这段全部都是1了,不用修改了,b2…...
宁波电器网站制作/社交网络推广方法有哪些
mybatis3中增加了使用注解来配置Mapper的新特性,本篇文章主要介绍其中几个Provider的使用方式,他们是:SelectProvider、UpdateProvider、InsertProvider和DeleteProvider。MyBatis 3 User Guide中的最后一章描述了注解的简单用法,…...
怎样提高网站首页权重/百度联盟广告
在将php5.3的程序向php7迁移过程中 出现这样的问题:1、我将mysql全换成mysqli2、在判断mysql结果集时正确,能够正常得到结果,但是换成mysqli后也能够得到结果现在问题原因是换成mysqli后,is_resource()方法判断mysqli结果集的结果…...
辛集哪做网站/中国万网域名查询
1. 卸载 1.1 删除ubuntu系统的分区 首先【win】【R】,然后输入【diskpart】,如下图。 输入【list disk】查看分区,有多个磁盘分区可分别使用【select disk 0】或者【select disk 1】,然后再list partition查看分区,其…...
wordpress文章修改大小/什么是网络整合营销
.Net 2.0中,ListView中虽然增加了AutoResizeColumns函数,允许自动对列宽进行调整,但是对于其中的 AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent) AutoResizeColumns(ColumnHeaderAutoResizeStyle.ColumnContent) 也就是根…...