@Bean的处理流程,源码分析@Bean背后发生的事
文章目录
写在前面
日常开发中,我们都会使用@Bean定义一个Spring的Bean,那么这简单的一个注解,背后隐藏着多少的秘密?
今天我们就一起把这个秘密拆解开。
关键类ConfigurationClassPostProcessor
该场景只适用于基于注解方式启动的容器。
1、ConfigurationClassPostProcessor的注册
先上一段代码:
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();// 注册 Configuration Class
context.register(LiveBeansViewDemo.class);// 启动 Spring 应用上下文
context.refresh();// 关闭 Spring 应用上下文
context.close();
代码很简单,其中,@Bean的处理关键类ConfigurationClassPostProcessor的处理就隐藏在new AnnotationConfigApplicationContext()这个代码里,我们继续往下看:
// org.springframework.context.annotation.AnnotationConfigApplicationContext#AnnotationConfigApplicationContext()
public AnnotationConfigApplicationContext() {this.reader = new AnnotatedBeanDefinitionReader(this);this.scanner = new ClassPathBeanDefinitionScanner(this);
}
上面源码中,会new一个AnnotatedBeanDefinitionReader,而Springboot启动web项目也会new这样一个类,它们的处理逻辑基本是相同的。
在AnnotatedBeanDefinitionReader构造器中,会进行以下的处理:
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry) {this(registry, getOrCreateEnvironment(registry));
}
public AnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry, Environment environment) {Assert.notNull(registry, "BeanDefinitionRegistry must not be null");Assert.notNull(environment, "Environment must not be null");this.registry = registry;this.conditionEvaluator = new ConditionEvaluator(registry, environment, null);// 注册AnnotationConfigProcessorsAnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);
}
在源码最后,会调用工具类注册AnnotationConfigProcessors:
// org.springframework.context.annotation.AnnotationConfigUtils#registerAnnotationConfigProcessors(org.springframework.beans.factory.support.BeanDefinitionRegistry)
public static void registerAnnotationConfigProcessors(BeanDefinitionRegistry registry) {registerAnnotationConfigProcessors(registry, null);
}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);// 注册ConfigurationClassPostProcessor到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));}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;
}
上面源码我们可以分析出来,会依次注册ConfigurationClassPostProcessor、AutowiredAnnotationBeanPostProcessor、CommonAnnotationBeanPostProcessor、PersistenceAnnotationBeanPostProcessor、EventListenerMethodProcessor、DefaultEventListenerFactory等,很多BeanPostProcessor顾名思义,就能知道大体是干什么的。
我们今天主要是研究ConfigurationClassPostProcessor。
到此为止,ConfigurationClassPostProcessor已经注册到BeanFactory中作为一个BeanDefinition了。
2、ConfigurationClassPostProcessor的处理过程
ConfigurationClassPostProcessor实现了BeanDefinitionRegistryPostProcessor,就有了Bean的依赖查找和注入注册的能力,实现postProcessBeanDefinitionRegistry方法可以在 IOC容器启动的BeanFactory 后置处理阶段-invokeBeanFactoryPostProcessors阶段,进行调用,更多请移步Spring应用上下文生命周期详解及源码分析,Spring IOC容器启动及关闭过程超详细解释(上)。
我们来分析一下ConfigurationClassPostProcessor的postProcessBeanDefinitionRegistry方法:
// org.springframework.context.annotation.ConfigurationClassPostProcessor#postProcessBeanDefinitionRegistry
@Override
public 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);
}public void processConfigBeanDefinitions(BeanDefinitionRegistry registry) {List<BeanDefinitionHolder> configCandidates = new ArrayList<>();String[] candidateNames = registry.getBeanDefinitionNames();for (String beanName : candidateNames) {BeanDefinition beanDef = registry.getBeanDefinition(beanName);if (beanDef.getAttribute(ConfigurationClassUtils.CONFIGURATION_CLASS_ATTRIBUTE) != null) {if (logger.isDebugEnabled()) {logger.debug("Bean definition has already been processed as a configuration class: " + beanDef);}}else if (ConfigurationClassUtils.checkConfigurationClassCandidate(beanDef, this.metadataReaderFactory)) {configCandidates.add(new BeanDefinitionHolder(beanDef, beanName));}}// Return immediately if no @Configuration classes were foundif (configCandidates.isEmpty()) {return;}// Sort by previously determined @Order value, if applicableconfigCandidates.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 contextSingletonBeanRegistry sbr = null;if (registry instanceof SingletonBeanRegistry) {sbr = (SingletonBeanRegistry) registry;if (!this.localBeanNameGeneratorSet) {BeanNameGenerator generator = (BeanNameGenerator) sbr.getSingleton(AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR);if (generator != null) {this.componentScanBeanNameGenerator = generator;this.importBeanNameGenerator = generator;}}}if (this.environment == null) {this.environment = new StandardEnvironment();}// 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 {// 关键方法!(见(1))parser.parse(candidates);parser.validate();Set<ConfigurationClass> configClasses = new LinkedHashSet<>(parser.getConfigurationClasses());configClasses.removeAll(alreadyParsed);// 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());}// 关键方法!(见(2))this.reader.loadBeanDefinitions(configClasses);alreadyParsed.addAll(configClasses);candidates.clear();if (registry.getBeanDefinitionCount() > candidateNames.length) {String[] 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 classesif (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.((CachingMetadataReaderFactory) this.metadataReaderFactory).clearCache();}
}
上面源码中我们可以看出,在注释标注的Parse each @Configuration class的这一部分,是主要用于处理配置类的,关键代码就是使用了ConfigurationClassParser的parse方法,parse方法会调用processConfigurationClass方法,然后调用doProcessConfigurationClass方法,在该方法里,会统一处理@Component、@PropertySources以及@ComponentScans的扫描、@ImportResource。
// org.springframework.context.annotation.ConfigurationClassParser#parse(java.util.Set<org.springframework.beans.factory.config.BeanDefinitionHolder>)
public void parse(Set<BeanDefinitionHolder> configCandidates) {for (BeanDefinitionHolder holder : configCandidates) {BeanDefinition bd = holder.getBeanDefinition();try {if (bd instanceof AnnotatedBeanDefinition) {parse(((AnnotatedBeanDefinition) bd).getMetadata(), holder.getBeanName());}else if (bd instanceof AbstractBeanDefinition && ((AbstractBeanDefinition) bd).hasBeanClass()) {parse(((AbstractBeanDefinition) bd).getBeanClass(), holder.getBeanName());}else {parse(bd.getBeanClassName(), holder.getBeanName());}}catch (BeanDefinitionStoreException ex) {throw ex;}catch (Throwable ex) {throw new BeanDefinitionStoreException("Failed to parse configuration class [" + bd.getBeanClassName() + "]", ex);}}this.deferredImportSelectorHandler.process();
}
(1)parse方法中,@Bean方法的处理
并且还会处理@Bean标注的方法(本文只分析@Bean的处理),在此截取doProcessConfigurationClass对@Bean的处理部分:
// Process individual @Bean methods
Set<MethodMetadata> beanMethods = retrieveBeanMethodMetadata(sourceClass);
for (MethodMetadata methodMetadata : beanMethods) {configClass.addBeanMethod(new BeanMethod(methodMetadata, configClass));
}
其中,retrieveBeanMethodMetadata就是筛选出所有包含@Bean的方法,有个递归的过程:
// org.springframework.context.annotation.ConfigurationClassParser#retrieveBeanMethodMetadata
private Set<MethodMetadata> retrieveBeanMethodMetadata(SourceClass sourceClass) {AnnotationMetadata original = sourceClass.getMetadata();Set<MethodMetadata> beanMethods = original.getAnnotatedMethods(Bean.class.getName());if (beanMethods.size() > 1 && original instanceof StandardAnnotationMetadata) {// Try reading the class file via ASM for deterministic declaration order...// Unfortunately, the JVM's standard reflection returns methods in arbitrary// order, even between different runs of the same application on the same JVM.try {AnnotationMetadata asm =this.metadataReaderFactory.getMetadataReader(original.getClassName()).getAnnotationMetadata();Set<MethodMetadata> asmMethods = asm.getAnnotatedMethods(Bean.class.getName());if (asmMethods.size() >= beanMethods.size()) {Set<MethodMetadata> selectedMethods = new LinkedHashSet<>(asmMethods.size());for (MethodMetadata asmMethod : asmMethods) {for (MethodMetadata beanMethod : beanMethods) {if (beanMethod.getMethodName().equals(asmMethod.getMethodName())) {selectedMethods.add(beanMethod);break;}}}if (selectedMethods.size() == beanMethods.size()) {// All reflection-detected methods found in ASM method set -> proceedbeanMethods = selectedMethods;}}}catch (IOException ex) {logger.debug("Failed to read class file via ASM for determining @Bean method order", ex);// No worries, let's continue with the reflection metadata we started with...}}return beanMethods;
}
处理完retrieveBeanMethodMetadata方法之后,将@Bean的方法信息存入了configClass中。
(2)注册解析@Bean标注的方法
// org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitions
public void loadBeanDefinitions(Set<ConfigurationClass> configurationModel) {TrackedConditionEvaluator trackedConditionEvaluator = new TrackedConditionEvaluator();for (ConfigurationClass configClass : configurationModel) {loadBeanDefinitionsForConfigurationClass(configClass, trackedConditionEvaluator);}
}private void loadBeanDefinitionsForConfigurationClass(ConfigurationClass configClass, TrackedConditionEvaluator trackedConditionEvaluator) {if (trackedConditionEvaluator.shouldSkip(configClass)) {String beanName = configClass.getBeanName();if (StringUtils.hasLength(beanName) && this.registry.containsBeanDefinition(beanName)) {this.registry.removeBeanDefinition(beanName);}this.importRegistry.removeImportingClass(configClass.getMetadata().getClassName());return;}if (configClass.isImported()) {registerBeanDefinitionForImportedConfigurationClass(configClass);}// 以下是用于处理@Bean标注的方法的逻辑for (BeanMethod beanMethod : configClass.getBeanMethods()) {loadBeanDefinitionsForBeanMethod(beanMethod);}loadBeanDefinitionsFromImportedResources(configClass.getImportedResources());loadBeanDefinitionsFromRegistrars(configClass.getImportBeanDefinitionRegistrars());
}
我们继续看loadBeanDefinitionsForBeanMethod方法,该方法就是对@Bean标注的方法进行最终处理的逻辑:
// org.springframework.context.annotation.ConfigurationClassBeanDefinitionReader#loadBeanDefinitionsForBeanMethod
private void loadBeanDefinitionsForBeanMethod(BeanMethod beanMethod) {ConfigurationClass configClass = beanMethod.getConfigurationClass();MethodMetadata metadata = beanMethod.getMetadata();String methodName = metadata.getMethodName();// Do we need to mark the bean as skipped by its condition?// 判断Conditionif (this.conditionEvaluator.shouldSkip(metadata, ConfigurationPhase.REGISTER_BEAN)) {configClass.skippedBeanMethods.add(methodName);return;}if (configClass.skippedBeanMethods.contains(methodName)) {return;}// 再次校验是不是有@Bean注解AnnotationAttributes bean = AnnotationConfigUtils.attributesFor(metadata, Bean.class);Assert.state(bean != null, "No @Bean annotation attributes");// Consider name and any aliasesList<String> names = new ArrayList<>(Arrays.asList(bean.getStringArray("name")));String beanName = (!names.isEmpty() ? names.remove(0) : methodName);// Register aliases even when overriddenfor (String alias : names) {this.registry.registerAlias(beanName, alias);}// Has this effectively been overridden before (e.g. via XML)?if (isOverriddenByExistingDefinition(beanMethod, beanName)) {if (beanName.equals(beanMethod.getConfigurationClass().getBeanName())) {throw new BeanDefinitionStoreException(beanMethod.getConfigurationClass().getResource().getDescription(),beanName, "Bean name derived from @Bean method '" + beanMethod.getMetadata().getMethodName() +"' clashes with bean name for containing configuration class; please make those names unique!");}return;}ConfigurationClassBeanDefinition beanDef = new ConfigurationClassBeanDefinition(configClass, metadata);beanDef.setResource(configClass.getResource());beanDef.setSource(this.sourceExtractor.extractSource(metadata, configClass.getResource()));// 如果是静态方法,不需要配置类先加载,可以优先加载静态方法if (metadata.isStatic()) {// static @Bean methodif (configClass.getMetadata() instanceof StandardAnnotationMetadata) {beanDef.setBeanClass(((StandardAnnotationMetadata) configClass.getMetadata()).getIntrospectedClass());}else {beanDef.setBeanClassName(configClass.getMetadata().getClassName());}beanDef.setUniqueFactoryMethodName(methodName);}else {// instance @Bean method// 如果是实例方法,会先关联配置类,所以首先要配置类加载之后才能加载实例方法beanDef.setFactoryBeanName(configClass.getBeanName());beanDef.setUniqueFactoryMethodName(methodName);}if (metadata instanceof StandardMethodMetadata) {beanDef.setResolvedFactoryMethod(((StandardMethodMetadata) metadata).getIntrospectedMethod());}beanDef.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);beanDef.setAttribute(org.springframework.beans.factory.annotation.RequiredAnnotationBeanPostProcessor.SKIP_REQUIRED_CHECK_ATTRIBUTE, Boolean.TRUE);AnnotationConfigUtils.processCommonDefinitionAnnotations(beanDef, metadata);// 处理其他属性Autowire autowire = bean.getEnum("autowire");if (autowire.isAutowire()) {beanDef.setAutowireMode(autowire.value());}boolean autowireCandidate = bean.getBoolean("autowireCandidate");if (!autowireCandidate) {beanDef.setAutowireCandidate(false);}String initMethodName = bean.getString("initMethod");if (StringUtils.hasText(initMethodName)) {beanDef.setInitMethodName(initMethodName);}String destroyMethodName = bean.getString("destroyMethod");beanDef.setDestroyMethodName(destroyMethodName);// Consider scopingScopedProxyMode proxyMode = ScopedProxyMode.NO;AnnotationAttributes attributes = AnnotationConfigUtils.attributesFor(metadata, Scope.class);if (attributes != null) {beanDef.setScope(attributes.getString("value"));proxyMode = attributes.getEnum("proxyMode");if (proxyMode == ScopedProxyMode.DEFAULT) {proxyMode = ScopedProxyMode.NO;}}// Replace the original bean definition with the target one, if necessaryBeanDefinition beanDefToRegister = beanDef;if (proxyMode != ScopedProxyMode.NO) {BeanDefinitionHolder proxyDef = ScopedProxyCreator.createScopedProxy(new BeanDefinitionHolder(beanDef, beanName), this.registry,proxyMode == ScopedProxyMode.TARGET_CLASS);beanDefToRegister = new ConfigurationClassBeanDefinition((RootBeanDefinition) proxyDef.getBeanDefinition(), configClass, metadata);}if (logger.isTraceEnabled()) {logger.trace(String.format("Registering bean definition for @Bean method %s.%s()",configClass.getMetadata().getClassName(), beanName));}this.registry.registerBeanDefinition(beanName, beanDefToRegister);
}
相关文章:
@Bean的处理流程,源码分析@Bean背后发生的事
文章目录写在前面关键类ConfigurationClassPostProcessor1、ConfigurationClassPostProcessor的注册2、ConfigurationClassPostProcessor的处理过程(1)parse方法中,Bean方法的处理(2)注册解析Bean标注的方法写在前面 …...
[2025CVPR]DeepVideo-R1:基于难度感知回归GRPO的视频强化微调框架详解
突破视频大语言模型推理瓶颈,在多个视频基准上实现SOTA性能 一、核心问题与创新亮点 1.1 GRPO在视频任务中的两大挑战 安全措施依赖问题 GRPO使用min和clip函数限制策略更新幅度,导致: 梯度抑制:当新旧策略差异过大时梯度消失收敛困难:策略无法充分优化# 传统GRPO的梯…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...
大语言模型如何处理长文本?常用文本分割技术详解
为什么需要文本分割? 引言:为什么需要文本分割?一、基础文本分割方法1. 按段落分割(Paragraph Splitting)2. 按句子分割(Sentence Splitting)二、高级文本分割策略3. 重叠分割(Sliding Window)4. 递归分割(Recursive Splitting)三、生产级工具推荐5. 使用LangChain的…...
[Java恶补day16] 238.除自身以外数组的乘积
给你一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O(n) 时间复杂度…...
全志A40i android7.1 调试信息打印串口由uart0改为uart3
一,概述 1. 目的 将调试信息打印串口由uart0改为uart3。 2. 版本信息 Uboot版本:2014.07; Kernel版本:Linux-3.10; 二,Uboot 1. sys_config.fex改动 使能uart3(TX:PH00 RX:PH01),并让boo…...
tree 树组件大数据卡顿问题优化
问题背景 项目中有用到树组件用来做文件目录,但是由于这个树组件的节点越来越多,导致页面在滚动这个树组件的时候浏览器就很容易卡死。这种问题基本上都是因为dom节点太多,导致的浏览器卡顿,这里很明显就需要用到虚拟列表的技术&…...
Reasoning over Uncertain Text by Generative Large Language Models
https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829https://ojs.aaai.org/index.php/AAAI/article/view/34674/36829 1. 概述 文本中的不确定性在许多语境中传达,从日常对话到特定领域的文档(例如医学文档)(Heritage 2013;Landmark、Gulbrandsen 和 Svenevei…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...
Vite中定义@软链接
在webpack中可以直接通过符号表示src路径,但是vite中默认不可以。 如何实现: vite中提供了resolve.alias:通过别名在指向一个具体的路径 在vite.config.js中 import { join } from pathexport default defineConfig({plugins: [vue()],//…...
