基于Spring注解的上下文初始化过程源码解析(一)
最近工作之余有时间和精力,加上平时对源码比较感兴趣,就开始啃起了Spring源码。为加深印象写了这篇博客,如有错误,望各位大佬不吝指正。
我看的是Spring5的源码,从同性社区download下来后编译,然后看源码、写注释、一步一步debug,理论指导实践,实践再反作用于理论。
因为基于注解的开发是现在比较主流的开发模式,所以就从 AnnotationConfigApplicationContext 开始啃了。
因为代码过多,执行流程过于复杂,就拆成了三篇来解析。
下面就从这个类开始探究Spring上下文的初始化。
这里需要留意 AnnotationConfigApplicationContext 继承了 GenericApplicationContext
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry { private final DefaultListableBeanFactory beanFactory;
/**
* 注意这个无参构造函数
*
* 这个初始化的DefaultListableBeanFactory就是Spring的Bean工厂
*/
public GenericApplicationContext() {
this.beanFactory = new DefaultListableBeanFactory();
}
}
再来看看 AnnotationConfigApplicationContext 这个类
public class AnnotationConfigApplicationContext extends GenericApplicationContext implements AnnotationConfigRegistry { /**
* 定义一个读取注解的 BeanDefinition 读取器
* 这个类在构造方法中被实例化
*/
private final AnnotatedBeanDefinitionReader reader; /**
* 定义一个扫描类路径下加了注解的 BeanDefinition 扫描器
* 这个类在构造方法中被实例化
*/
private final ClassPathBeanDefinitionScanner scanner; public AnnotationConfigApplicationContext() {
// 实例化 BeanDefinition 读取器
/**
* 注册所有注解相关的后置处理器
* 最重要的一个后置处理器 ConfigurationClassPostProcessor
* BeanName 是 internalConfigurationAnnotationProcessor
*/
this.reader = new AnnotatedBeanDefinitionReader(this);
// 实例化 BeanDefinition 扫描器
// 但是实际上扫描包的工作并不是 scanner 这个对象来完成的,是 Spring 自己创建的一个新的 ClassPathBeanDefinitionScanner
// 这里的 scanner 仅仅是为了程序员能够在外部调用 AnnotationConfigApplicationContext 对象的 scanner 方法
this.scanner = new ClassPathBeanDefinitionScanner(this);
} public AnnotationConfigApplicationContext(DefaultListableBeanFactory beanFactory) {
// 调用父类的构造方法,初始化 DefaultListableBeanFactory 的 Bean 工厂
super(beanFactory);
// 实例化该读取器
this.reader = new AnnotatedBeanDefinitionReader(this);
// 实例化该扫描器
this.scanner = new ClassPathBeanDefinitionScanner(this);
} /**
* 这个构造方法需要传入一个 @Configuration 注解配置类
* 通过注解读取器读取并解析
*/
public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
/**
* 由于继承了父类,这里会先去调用父类的构造方法,然后调用自身的构造方法
*
* this.beanFactory = new DefaultListableBeanFactory();
*
* 其实就是初始化一个 DefaultListableBeanFactory
*/
this();
/**
* 将传入的 @Configuration 配置类转换为 BeanDefinition
* 并添加到 DefaultListableBeanFactory 工厂的 BeanDefinitionMap 中
*/
register(annotatedClasses);
/**
* 1、准备刷新山下文
* 2、通知子类刷新内部的 bean 工厂,得到创建的 DefaultListableBeanFactory 工厂
* 3、配置工厂的标准上下文特征
* 4、允许在上下文子类中对 bean 工厂进行后置处理
* 5、在上下文中调用工厂处理器方法,注册为 bean
* 6、注册 BeanPostProcessor
* 7、初始化此上下文的消息源
* 8、初始化应用事件广播器【SpringBoot 的启动源码中与该方法有很大关系】
* 9、在特定的上下文子类中初始化其他特殊 bean
* 10、检查监听器 bean 并注册它们
* 11、实例化所有剩余(非延迟初始化)单例
* 12、发布相应的事件
*/
refresh();
} public AnnotationConfigApplicationContext(String... basePackages) {
/**
* 由于继承了父类,这里会先去调用父类的构造方法,然后调用自身的构造方法
*
* this.beanFactory = new DefaultListableBeanFactory();
*
* 其实就是初始化一个 DefaultListableBeanFactory
*/
this();
scan(basePackages);
refresh();
}
}
这里还要注意一下AnnotatedBeanDefinitionReader的实例化,代码跟进去发现调用了 AnnotationConfigUtils 的 registerAnnotationConfigProcessors 方法,Spring在初始化上下文时,在Bean工厂中添加了很多辅助其初始化的类
public static Set<BeanDefinitionHolder> registerAnnotationConfigProcessors(
BeanDefinitionRegistry registry, @Nullable Object source) {
// 获取 Bean 工厂
DefaultListableBeanFactory beanFactory = unwrapDefaultListableBeanFactory(registry);
if (beanFactory != null) {
if (!(beanFactory.getDependencyComparator() instanceof AnnotationAwareOrderComparator)) {
// AnnotationAwareOrderComparator 主要能解析 @Order 注解和 @Priority 注解
beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);
}
if (!(beanFactory.getAutowireCandidateResolver() instanceof ContextAnnotationAutowireCandidateResolver)) {
// ContextAnnotationAutowireCandidateResolver 提供处理延迟加载的功能
beanFactory.setAutowireCandidateResolver(new ContextAnnotationAutowireCandidateResolver());
}
} /**
* 存放所有辅助类的信息
*/
Set<BeanDefinitionHolder> beanDefs = new LinkedHashSet<>(8);
/**
* BeanDefinition 的注册,很重要,需要理解注册的每个 Bean 的类型和作用
*
* Spring 在初始化 ApplicationContext 和 Bean 工厂时,在 Bean 工厂中添加了很多辅助初始化 Bean 工厂的类
*
* 1.ConfigurationClassPostProcessor 类型是 BeanFactoryPostProcessor
* 2.AutowiredAnnotationBeanPostProcessor 类型是 BeanPostProcessor
* 3.CommonAnnotationBeanPostProcessor 类型是 BeanPostProcessor
* 4.PersistenceAnnotationBeanPostProcessor 类型是 BeanPostProcessor
* 5.EventListenerMethodProcessor 类型是 BeanFactoryPostProcessor
* 6.DefaultEventListenerFactory 类型是 EventListenerFactory
*/ /**
* BeanName 是否包含 org.springframework.context.annotation.internalConfigurationAnnotationProcessor
* BeanClass 是 ConfigurationClassPostProcessor,类型是 BeanFactoryPostProcessor
*/
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));
} /**
* BeanName 是否包含 org.springframework.context.annotation.internalAutowiredAnnotationProcessor
* BeanClass 是 AutowiredAnnotationBeanPostProcessor,类型是 BeanPostProcessor
*/
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.
/**
* BeanName 是否包含 org.springframework.context.annotation.internalCommonAnnotationProcessor
* BeanClass 是 CommonAnnotationBeanPostProcessor,类型是 BeanPostProcessor
*/
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.
/**
* 检查 JPA 支持,如果存在,则添加 PersistenceAnnotationBeanPostProcessor
*
* BeanName 是否包含 org.springframework.context.annotation.internalPersistenceAnnotationProcessor
* BeanClass 是 PersistenceAnnotationBeanPostProcessor,类型是 BeanPostProcessor
*/
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));
} /**
* BeanName 是否包含 org.springframework.context.event.internalEventListenerProcessor
* BeanClass 是 EventListenerMethodProcessor,类型是 BeanFactoryPostProcessor
*/
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));
} /**
* BeanName 是否包含 org.springframework.context.event.internalEventListenerFactory
* BeanClass 是 DefaultEventListenerFactory,类型是 EventListenerFactory
*/
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;
}
这个方法执行完后,可以看到 DefaultListableBeanFactory 中的 beanDefinitionMap 中已经有数据了,如果支持 JPA 则有6个元素,没有则有5个。分别是 ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、EventListenerMethodProcessor、DefaultEventListenerFactory,支持 JPA 的是 PersistenceAnnotationBeanPostProcessor。
这里实例化的 ClassPathBeanDefinitionScanner 仅仅是为了让程序员能够在外部调用 AnnotationConfigApplicationContext 对象的 scanner 方法。Spring 在后面又重新初始化了一个 ClassPathBeanDefinitionScanner,用新的 ClassPathBeanDefinitionScanner 进行扫描。
回到一开始,我这里是传了一个@Configuration的配置类,所以从register方法往下跟,最后调用的是 AnnotatedBeanDefinitionReader 的 doRegisterBean 方法
/**
* 将给定的 bean 类注册为 bean,从类声明的注释中派生其元数据
*/
private <T> void doRegisterBean(Class<T> annotatedClass, @Nullable String name,
@Nullable Class<? extends Annotation>[] qualifiers,
@Nullable Supplier<T> supplier, @Nullable BeanDefinitionCustomizer[] customizers) { // 根据指定的 bean 创建一个 AnnotatedGenericBeanDefinition
AnnotatedGenericBeanDefinition abd = new AnnotatedGenericBeanDefinition(annotatedClass);
// 若这个类是需要跳过解析的类,则返回
if (this.conditionEvaluator.shouldSkip(abd.getMetadata())) {
return;
} // 指定创建 bean 实例的回调方法,此时为 null
abd.setInstanceSupplier(supplier);
// 解析类的作用域元数据
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(abd);
// 添加类的作用域元数据
abd.setScope(scopeMetadata.getScopeName());
/**
* 生成 BeanName
* 调用 AnnotationBeanNameGenerator.generateBeanName()
*/
String beanName = (name != null ? name : this.beanNameGenerator.generateBeanName(abd, this.registry)); /**
* 处理类当中的通用注解
* 主要处理 @Lazy @DependsOn @Primary @Role @Description 等注解
* 处理完成之后将值赋给到 AnnotatedGenericBeanDefinition 对应的属性中
*/
AnnotationConfigUtils.processCommonDefinitionAnnotations(abd); /**
* 如果在向容器注册注解 BeanDefinition 时,使用了额外的限定符注解则解析
* byName 和 qualifiers 变量是 Annotation 类型的数组,里面不仅存了 @Qualifier 注解
* 所以 Spring 遍历这个数组判断是否加了指定注解
*
* 此时 qualifiers 为 null,if 语句不执行
*/
if (qualifiers != null) {
for (Class<? extends Annotation> qualifier : qualifiers) {
if (Primary.class == qualifier) {
// 设置 primary 属性值
abd.setPrimary(true);
} else if (Lazy.class == qualifier) {
// 设置 lazyInit 属性值
abd.setLazyInit(true);
} else {
/**
* 如果使用了除 @Primary 和 @Lazy 以外的其他注解
* 则为该 Bean 添加一个根据名字自动装配的限定符
*/
// 向 Map<String, AutowireCandidateQualifier> qualifiers 集合中添加值
abd.addQualifier(new AutowireCandidateQualifier(qualifier));
}
}
} /**
* 如果存在一个或多个用于自定义工厂的回调
*
* 此时 customizers 为 null,if 语句不执行
*/
if (customizers != null) {
for (BeanDefinitionCustomizer customizer : customizers) {
customizer.customize(abd);
}
} /**
* 获取 BeanDefinitionHolder 持有者容器
* 里面包含的属性值有 String beanName,BeanDefinition beanDefinition 和 String[] aliases 别名集合
*/
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(abd, beanName);
/**
* 解析代理模型
*/
definitionHolder = AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
/**
* 将最终获取到的 BeanDefinitionHolder 持有者容器中包含的信息注册给 BeanDefinitionRegistry
*
* AnnotationConfigApplicationContext 在初始化的时候通过调用父类的构造方法,实例化了一个 DefaultListableBeanFactory
* 这一步就是把 BeanDefinitionHolder 这个数据结构中包含的信息注册到 DefaultListableBeanFactory 中
*
* DefaultListableBeanFactory 实现了 BeanDefinitionRegistry
*
* 此时传入的 @Configuration 配置类已经注册到 DefaultListableBeanFactory 工厂中
*/
BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, this.registry);
}
这里比较重要的是最后一行代码,进行注册操作,代码如下:
public static void registerBeanDefinition(
BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry)
throws BeanDefinitionStoreException { // Register bean definition under primary name.
// 用类的主要名称注册 BeanDefinition
String beanName = definitionHolder.getBeanName();
/**
* 将 beanName 注册到 DefaultListableBeanFactory 工厂中
*/
registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // Register aliases for bean name, if any.
// 如果有别名则为 BeanName 注册别名
String[] aliases = definitionHolder.getAliases();
if (aliases != null) {
for (String alias : aliases) {
/**
* 注册别名
*/
registry.registerAlias(beanName, alias);
}
}
}
public void registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
throws BeanDefinitionStoreException { if (beanDefinition instanceof AbstractBeanDefinition) {
try {
((AbstractBeanDefinition) beanDefinition).validate();
} catch (BeanDefinitionValidationException ex) {
throw new BeanDefinitionStoreException(beanDefinition.getResourceDescription(), beanName,
"Validation of bean definition failed", ex);
}
} // 先从 Map<String, BeanDefinition> beanDefinitionMap 中获取一次
BeanDefinition existingDefinition = this.beanDefinitionMap.get(beanName);
// 已注册过则进行逻辑校验
if (existingDefinition != null) {
// 是否允许 BeanDefinition 覆盖
if (!isAllowBeanDefinitionOverriding()) {
// 不允许则抛异常
throw new BeanDefinitionOverrideException(beanName, beanDefinition, existingDefinition);
}
// 已存在的角色是否小于传入的 beanDefinition 角色
else if (existingDefinition.getRole() < beanDefinition.getRole()) {
// e.g. was ROLE_APPLICATION, now overriding with ROLE_SUPPORT or ROLE_INFRASTRUCTURE
if (logger.isInfoEnabled()) {
logger.info("Overriding user-defined bean definition for bean '" + beanName +
"' with a framework-generated bean definition: replacing [" +
existingDefinition + "] with [" + beanDefinition + "]");
}
}
// 如果传入的 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 + "]");
}
}
// 重新存入 Map<String, BeanDefinition> beanDefinitionMap 中
this.beanDefinitionMap.put(beanName, beanDefinition);
}
// 不存在
else {
// 是否已经启动 bean 创建
if (hasBeanCreationStarted()) {
// Cannot modify startup-time collection elements anymore (for stable iteration)
synchronized (this.beanDefinitionMap) {
// 注册到 Map<String, BeanDefinition> beanDefinitionMap 集合中
this.beanDefinitionMap.put(beanName, beanDefinition);
// 用新的 beanDefinitionNames 替换旧的
List<String> updatedDefinitions = new ArrayList<>(this.beanDefinitionNames.size() + 1);
updatedDefinitions.addAll(this.beanDefinitionNames);
updatedDefinitions.add(beanName);
this.beanDefinitionNames = updatedDefinitions;
// 从集合中移除该 bean 名称
removeManualSingletonName(beanName);
}
}
// 还未启动 bean 创建,即仍在启动注册阶段
else {
// 注册到 Map<String, BeanDefinition> beanDefinitionMap 集合中
this.beanDefinitionMap.put(beanName, beanDefinition);
// 添加 bean 名称到 beanDefinitionNames 数据集合中
this.beanDefinitionNames.add(beanName);
// 从集合中移除该 bean 名称
removeManualSingletonName(beanName);
}
this.frozenBeanDefinitionNames = null;
} if (existingDefinition != null || containsSingleton(beanName)) {
resetBeanDefinition(beanName);
}
}
register 方法执行完成,此时传入的 @Configuration 配置类已经注册到 DefaultListableBeanFactory 工厂中
那这一篇就先到这里,有问题或者错误欢迎大家沟通交流
下一篇从 refresh 方法开始往下跟代码
基于Spring注解的上下文初始化过程源码解析(一)的更多相关文章
- 基于Spring注解的上下文初始化过程源码解析(二)
上一篇看完了register方法的代码,继续跟后面代码 后面执行refresh方法,代码清单如下: public void refresh() throws BeansException, Illeg ...
- Spring系列(五) 容器初始化过程源码
IoC/DI 的概念 容器是Spring的核心之一(另一个核心是AOP). 有了容器, IOC才可能实现. 什么使IoC? IoC就是将类自身管理的与其由依赖关系的对象的创建/关联和管理交予容器实现, ...
- 关于 Spring 中 getBean 的全流程源码解析
作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! 一.前言 你提出问题,就要给出解决方案! 最近有粉丝小伙伴反馈,与自己的上级沟通总是遇到障碍, ...
- Spring IOC容器启动流程源码解析(四)——初始化单实例bean阶段
目录 1. 引言 2. 初始化bean的入口 3 尝试从当前容器及其父容器的缓存中获取bean 3.1 获取真正的beanName 3.2 尝试从当前容器的缓存中获取bean 3.3 从父容器中查找b ...
- Spring IOC 容器预启动流程源码探析
Spring IOC 容器预启动流程源码探析 在应用程序中,一般是通过创建ClassPathXmlApplicationContext或AnnotationConfigApplicationConte ...
- SpringSecurity 初始化流程源码
SpringSecurity 初始化流程源码 本篇主要讲解 SpringSecurity初始化流程的源码部分,包括核心的 springSecurityFilterChain 是如何创建的,以及在介绍哪 ...
- spring MVC cors跨域实现源码解析
# spring MVC cors跨域实现源码解析 > 名词解释:跨域资源共享(Cross-Origin Resource Sharing) 简单说就是只要协议.IP.http方法任意一个不同就 ...
- spring MVC cors跨域实现源码解析 CorsConfiguration UrlBasedCorsConfigurationSource
spring MVC cors跨域实现源码解析 spring MVC cors跨域实现源码解析 名词解释:跨域资源共享(Cross-Origin Resource Sharing) 简单说就是只要协议 ...
- Hadoop中Yarnrunner里面submit Job以及AM生成 至Job处理过程源码解析
参考 http://blog.csdn.net/caodaoxi/article/details/12970993 Hadoop中Yarnrunner里面submit Job以及AM生成 至Job处理 ...
随机推荐
- 长春理工大学第十四届程序设计竞赛(重现赛)F
F. Successione di Fixoracci 题目链接:https://ac.nowcoder.com/acm/contest/912/F 题目: 动态规划(Dynamic programm ...
- 基于 SpringBoot 的 FileService
fileservice file upload download 1.支持多种存储服务器上传.下载 2.支持大文件切片上传 3.存储记录信息使用 redis记录, 文件id可用于与业务数据库关联 4. ...
- [ERROR] Fatal error: Can't open and lock privilege tables: Table 'mysql.user' doesn't exist
这个问题是由于data的目录下没有安装数据库表 解决方法: vi /etc/my.cnf 修改为正确的datadir=“xxxxx”即可 然后service mysqld start service ...
- STM32学习的一些实例
第一讲:修炼STM32之乾坤大挪移术—— 如何用DMA神器搬运数据DMA,即直接存储器访问.DMA 传输方式无需 CPU 直接控制传输,通过硬件为 RAM 与 I/O 设备开辟一条直接传送数据的通路, ...
- spark入门(三)键值对操作
1 简述 Spark为包含键值对类型的RDD提供了一些专有的操作.这些RDD被称为PairRDD. 2 创建PairRDD 2.1 在sprk中,很多存储键值对的数据在读取时直接返回由其键值对数据组成 ...
- VMware下的Centos7实践Kvm虚拟化(通俗易懂)
虽然网上已经有很多关于kvm安装的教程了,但我还是看得头晕,有的教程里安装的包很多,有的很少,也没说明那些安装包的作用是干嘛的,用的命令也不一样,也没解释命令的意思是什么. 我重新写一个教程,尽量通俗 ...
- Java学习笔记之---构造方法
Java学习笔记之---构造方法 (一)构造方法的特性 构造方法不能被对象单独调用 构造方法与类同名且没有返回值 构造方法只能在对象实例化的时候被调用 当没有指定构造方法时,系统会自动添加无参的构造方 ...
- Centos7:yum安装MySQL5.7后如何设置root密码
Centos下安装软件的方式很简单,只需要通过yum install xxx命令即可.第一步当然检查是否有mysql的yum源,命令:yum list|grep mysql-community[主要还 ...
- 剑指offer第二版-10.斐波那契数列
面试题10:斐波那契数列 题目要求: 求斐波那契数列的第n项的值.f(0)=0, f(1)=1, f(n)=f(n-1)+f(n-2) n>1 思路:使用循环从下往上计算数列. 考点:考察对递归 ...
- cogs426血帆海盗(网络流打法)
这道题基本上来就能看的出来是网络流但难点在于它的结果貌似与最大流,最小割都没啥关系,我猜不少萌新像我一样想暴力枚举边(要是考试我就真这么做了),但很明显,出题人没打算让你这么水过,100000的数据不 ...