SpringBoot源码解析(十):应用上下文AnnotationConfigServletWebServerApplicationContext构造方法

SpringBoot源码解析(十):应用上下文AnnotationConfigServletWebServerApplicationContext构造方法

SpringBoot源码系列文章

SpringBoot源码解析(一):SpringApplication构造方法

SpringBoot源码解析(二):引导上下文DefaultBootstrapContext

SpringBoot源码解析(三):启动开始阶段

SpringBoot源码解析(四):解析应用参数args

SpringBoot源码解析(五):准备应用环境

SpringBoot源码解析(六):打印Banner

SpringBoot源码解析(七):应用上下文结构体系

SpringBoot源码解析(八):Bean工厂接口体系

SpringBoot源码解析(九):Bean定义接口体系

SpringBoot源码解析(十):应用上下文AnnotationConfigServletWebServerApplicationContext构造方法


目录

前言

在前文中,我们了解了应用上下文Bean工厂以及Bean定义的核心组件功能,接下来,我们将深入探讨应用上下文的构造方法。

SpringBoot版本2.7.18SpringApplication的run方法的执行逻辑如下,本文将详细介绍第6小节:创建应用程序上下文
// SpringApplication类方法publicConfigurableApplicationContextrun(String... args){// 记录应用启动的开始时间long startTime =System.nanoTime();// 1.创建引导上下文,用于管理应用启动时的依赖和资源DefaultBootstrapContext bootstrapContext =createBootstrapContext();ConfigurableApplicationContext context =null;// 配置无头模式属性,以支持在无图形环境下运行// 将系统属性 java.awt.headless 设置为 trueconfigureHeadlessProperty();// 2.获取Spring应用启动监听器,用于在应用启动的各个阶段执行自定义逻辑SpringApplicationRunListeners listeners =getRunListeners(args);// 启动开始方法(发布开始事件、通知应用监听器ApplicationListener) listeners.starting(bootstrapContext,this.mainApplicationClass);try{// 3.解析应用参数ApplicationArguments applicationArguments =newDefaultApplicationArguments(args);// 4.准备应用环境,包括读取配置文件和设置环境变量ConfigurableEnvironment environment =prepareEnvironment(listeners, bootstrapContext, applicationArguments);// 配置是否忽略 BeanInfo,以加快启动速度configureIgnoreBeanInfo(environment);// 5.打印启动BannerBanner printedBanner =printBanner(environment);// 6.创建应用程序上下文 context =createApplicationContext();// 设置应用启动的上下文,用于监控和管理启动过程 context.setApplicationStartup(this.applicationStartup);// 7.准备应用上下文,包括加载配置、添加 Bean 等prepareContext(bootstrapContext, context, environment, listeners, applicationArguments, printedBanner);// 8.刷新上下文,完成 Bean 的加载和依赖注入refreshContext(context);// 9.刷新后的一些操作,如事件发布等afterRefresh(context, applicationArguments);// 计算启动应用程序的时间,并记录日志Duration timeTakenToStartup =Duration.ofNanos(System.nanoTime()- startTime);if(this.logStartupInfo){newStartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), timeTakenToStartup);}// 10.通知监听器应用启动完成 listeners.started(context, timeTakenToStartup);// 11.调用应用程序中的 `CommandLineRunner` 或 `ApplicationRunner`,以便执行自定义的启动逻辑callRunners(context, applicationArguments);}catch(Throwable ex){// 12.处理启动过程中发生的异常,并通知监听器handleRunFailure(context, ex, listeners);thrownewIllegalStateException(ex);}try{// 13.计算应用启动完成至准备就绪的时间,并通知监听器Duration timeTakenToReady =Duration.ofNanos(System.nanoTime()- startTime); listeners.ready(context, timeTakenToReady);}catch(Throwable ex){// 处理准备就绪过程中发生的异常handleRunFailure(context, ex,null);thrownewIllegalStateException(ex);}// 返回已启动并准备就绪的应用上下文return context;}

源码入口

  • 这里核心内容就是new AnnotationConfigServletWebServerApplicationContext()
// 6.创建应用程序上下文 context =createApplicationContext();
在这里插入图片描述
  • 无参构造
publicclassAnnotationConfigServletWebServerApplicationContextextendsServletWebServerApplicationContextimplementsAnnotationConfigRegistry{// 用于读取注解的Bean定义读取器privatefinalAnnotatedBeanDefinitionReader reader;// 用于扫描类路径并注册Bean定义的扫描器privatefinalClassPathBeanDefinitionScanner scanner;...// 无参构造函数publicAnnotationConfigServletWebServerApplicationContext(){// 初始化注解Bean定义读取器this.reader =newAnnotatedBeanDefinitionReader(this);// 初始化类路径Bean定义扫描器this.scanner =newClassPathBeanDefinitionScanner(this);}...}

AnnotationConfigServletWebServerApplicationContext类图如下,其实就是应用上下文核心接口Application的实现类,那么注解Bean定义读取器类路径Bean定义扫描器构造传入的this即Application。

在这里插入图片描述

一、初始化注解Bean定义读取器

publicclassAnnotatedBeanDefinitionReader{// Bean定义注册表,用于管理和注册Bean定义privatefinalBeanDefinitionRegistry registry;// 条件评估器,用于判断是否满足某些条件privateConditionEvaluator conditionEvaluator;// 构造方法,接收BeanDefinitionRegistry作为参数publicAnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry){// 调用带有Environment参数的构造方法,环境对象通过注册表自动创建this(registry,getOrCreateEnvironment(registry));}// 构造方法,接收BeanDefinitionRegistry和Environment作为参数publicAnnotatedBeanDefinitionReader(BeanDefinitionRegistry registry,Environment environment){// 校验参数,确保注册表和环境不为空Assert.notNull(registry,"BeanDefinitionRegistry must not be null");Assert.notNull(environment,"Environment must not be null");// 初始化Bean定义注册表this.registry = registry;// 初始化条件评估器this.conditionEvaluator =newConditionEvaluator(registry, environment,null);// 注册注解配置处理器,用于处理注解配置AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry);}...}

1、BeanDefinitionRegistry(Bean定义注册接口)

这里的Bean定义注册表BeanDefinitionRegistry就是AnnotatedBeanDefinitionReader构造传入的AnnotationConfigServletWebServerApplicationContext

BeanDefinitionRegistry是Spring容器中用于管理Bean定义的核心接口,支持动态注册移除查询别名管理,常用于扩展和动态操作容器内的 Bean 定义。

// 此接口是 Bean 定义注册的核心,用于动态管理 Bean 定义(注册、移除、查询等)publicinterfaceBeanDefinitionRegistryextendsAliasRegistry{// 向注册表中注册一个新的 BeanDefinitionvoidregisterBeanDefinition(String beanName,BeanDefinition beanDefinition)throwsBeanDefinitionStoreException;// 移除给定名称的 BeanDefinitionvoidremoveBeanDefinition(String beanName)throwsNoSuchBeanDefinitionException;// 返回给定名称的 BeanDefinitionBeanDefinitiongetBeanDefinition(String beanName)throwsNoSuchBeanDefinitionException;// 检查此注册表是否包含具有给定名称的 BeanDefinition。booleancontainsBeanDefinition(String beanName);// 返回此注册表中定义的所有 Bean 的名称String[]getBeanDefinitionNames();// 返回注册表中定义的 Bean 的数量。intgetBeanDefinitionCount();// 确定给定的 Bean 名称是否已经在此注册表中使用(即是否有本地 Bean 或别名注册了此名称)booleanisBeanNameInUse(String beanName);}/** * 管理别名的通用接口(提供了注册、删除、查询别名的方法) * * 别名是一种机制,用于为已有的名称提供额外的标识, * 通常用于配置中增加灵活性,例如为同一个 Bean 定义多个名称 */publicinterfaceAliasRegistry{// 为给定的名称注册一个别名voidregisterAlias(String name,String alias);// 从注册表中删除指定的别名voidremoveAlias(String alias);// 确定给定的名称是否被定义为别名(而不是实际注册的组件名称)booleanisAlias(String name);// 返回给定名称的所有别名(如果定义了别名)String[]getAliases(String name);}

2、获取环境对象Environment

// AnnotatedBeanDefinitionReader类方法privatestaticEnvironmentgetOrCreateEnvironment(BeanDefinitionRegistry registry){// 校验注册表对象不为空Assert.notNull(registry,"BeanDefinitionRegistry must not be null");// 如果注册表实现了EnvironmentCapable接口,则直接获取其中的Environmentif(registry instanceofEnvironmentCapable){return((EnvironmentCapable) registry).getEnvironment();}// 如果注册表不具备Environment,则创建并返回一个标准的Environment对象returnnewStandardEnvironment();}

在创建应用上下文之前,SpringBoot源码解析(五):准备应用环境中有详细介绍应用环境Environment的初始化。

在这里插入图片描述

3、注册注解配置处理器

在这里插入图片描述
publicstaticSet<BeanDefinitionHolder>registerAnnotationConfigProcessors(BeanDefinitionRegistry registry,@NullableObject source){// 获取默认的ListableBeanFactory对象// 这里获取到的就是应用上下文中AnnotationConfigServletWebServerApplicationContext的// 父类GenericApplicationContext的构造中new DefaultListableBeanFactory()DefaultListableBeanFactory beanFactory =unwrapDefaultListableBeanFactory(registry);if(beanFactory !=null){// AnnotationAwareOrderComparator:这是一个带有注解感知的比较器,用来排序Bean的依赖// 它比标准的比较器多了对@Order注解的支持,可以确保按照注解指定的优先级排序依赖关系if(!(beanFactory.getDependencyComparator()instanceofAnnotationAwareOrderComparator)){ beanFactory.setDependencyComparator(AnnotationAwareOrderComparator.INSTANCE);}// ContextAnnotationAutowireCandidateResolver:这个解析器是用于处理基于注解的自动注入(例如@Autowired注解)// 它负责在自动注入过程中,决定哪些Bean可以作为自动注入的候选者if(!(beanFactory.getAutowireCandidateResolver()instanceofContextAnnotationAutowireCandidateResolver)){ beanFactory.setAutowireCandidateResolver(newContextAnnotationAutowireCandidateResolver());}}// 创建一个 LinkedHashSet 用于存储 BeanDefinitionHolder// BeanDefinitionHolder: 用于持有 BeanDefinition 及其名称和别名Set<BeanDefinitionHolder> beanDefs =newLinkedHashSet<>(8);// 注册 ConfigurationClassPostProcessor,用于处理 @Configuration 注解if(!registry.containsBeanDefinition(CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME)){RootBeanDefinition def =newRootBeanDefinition(ConfigurationClassPostProcessor.class); def.setSource(source);// 注册后置处理器就是将Bean定义添加到bean工厂beanDefinitionMap缓存中,下面细说 beanDefs.add(registerPostProcessor(registry, def,CONFIGURATION_ANNOTATION_PROCESSOR_BEAN_NAME));}// 注册 AutowiredAnnotationBeanPostProcessor,用于处理 @Autowired 注解if(!registry.containsBeanDefinition(AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME)){RootBeanDefinition def =newRootBeanDefinition(AutowiredAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def,AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME));}// 如果 JSR-250 支持存在,注册 CommonAnnotationBeanPostProcessor,用于处理 @Resource 等注解if(jsr250Present &&!registry.containsBeanDefinition(COMMON_ANNOTATION_PROCESSOR_BEAN_NAME)){RootBeanDefinition def =newRootBeanDefinition(CommonAnnotationBeanPostProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def,COMMON_ANNOTATION_PROCESSOR_BEAN_NAME));}// 如果 JPA 支持存在,注册 PersistenceAnnotationBeanPostProcessor,用于处理 JPA 相关的注解// JPA是一个 Java 标准规范,用于简化对象与关系数据库之间的映射和数据持久化操作if(jpaPresent &&!registry.containsBeanDefinition(PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME)){RootBeanDefinition def =newRootBeanDefinition();try{ def.setBeanClass(ClassUtils.forName(PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME,AnnotationConfigUtils.class.getClassLoader()));}catch(ClassNotFoundException ex){thrownewIllegalStateException("Cannot load optional framework class: "+PERSISTENCE_ANNOTATION_PROCESSOR_CLASS_NAME, ex);} def.setSource(source); beanDefs.add(registerPostProcessor(registry, def,PERSISTENCE_ANNOTATION_PROCESSOR_BEAN_NAME));}// 注册 EventListenerMethodProcessor,用于处理 @EventListener 注解if(!registry.containsBeanDefinition(EVENT_LISTENER_PROCESSOR_BEAN_NAME)){RootBeanDefinition def =newRootBeanDefinition(EventListenerMethodProcessor.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def,EVENT_LISTENER_PROCESSOR_BEAN_NAME));}// 注册 DefaultEventListenerFactory 是事件监听器的工厂类,它实际负责创建监听器对象并将其注册到 Spring 的事件发布机制中// 当 EventListenerMethodProcessor 找到带有 @EventListener 注解的方法时,它会通过 DefaultEventListenerFactory 创建该方法对应的监听器if(!registry.containsBeanDefinition(EVENT_LISTENER_FACTORY_BEAN_NAME)){RootBeanDefinition def =newRootBeanDefinition(DefaultEventListenerFactory.class); def.setSource(source); beanDefs.add(registerPostProcessor(registry, def,EVENT_LISTENER_FACTORY_BEAN_NAME));}// 返回所有注册的 BeanDefinitionHolderreturn beanDefs;}

3.1、获取默认Bean工厂

这里的registry就是注解应用上下文AnnotationConfigServletWebServerApplicationContext,从上面的类图可知GenericApplicationContext是父类之一。

// AnnotationConfigUtils类方法@NullableprivatestaticDefaultListableBeanFactoryunwrapDefaultListableBeanFactory(BeanDefinitionRegistry registry){if(registry instanceofDefaultListableBeanFactory){return(DefaultListableBeanFactory) registry;}elseif(registry instanceofGenericApplicationContext){return((GenericApplicationContext) registry).getDefaultListableBeanFactory();}else{returnnull;}}
在这里插入图片描述

DefaultListableBeanFactory是Spring框架中的一个核心类,负责管理创建应用上下文中的所有Bean,提供了Bean定义的注册、查找和生命周期管理功能。

publicclassDefaultListableBeanFactoryextendsAbstractAutowireCapableBeanFactoryimplementsConfigurableListableBeanFactory,BeanDefinitionRegistry,Serializable{// ==================== 核心属性 ====================// 存储所有注册的 BeanDefinition,键为 beanName,值为对应的 BeanDefinitionprivatefinalMap<String,BeanDefinition> beanDefinitionMap =newConcurrentHashMap<>(256);// 记录所有注册的beanName,保持注册顺序(通过配置文件、注解、或约定方式将 Bean 注册到容器中,由 Spring 自动完成)privatevolatileList<String> beanDefinitionNames =newArrayList<>(256);...}
获取到默认Bean工厂以后,设置了两个重要属性

AnnotationAwareOrderComparator:这是一个带有注解感知的比较器,用来排序Bean的依赖。它比标准的比较器多了对@Order注解的支持,可以确保按照注解指定的优先级排序依赖关系。

ContextAnnotationAutowireCandidateResolver:这个解析器是用于处理基于注解的自动注入(例如@Autowired注解)。它负责在自动注入过程中,决定哪些Bean可以作为自动注入的候选者。

3.2、注册后置处理器(注册Bean定义)

上面创建了很多RootBeanDefinition,这些Bean定义只做Spring内部使用,用于处理注解配置
  • ConfigurationClassPostProcessor,用于处理@Configuration@Bean@Import@ComponentScan等注解
  • AutowiredAnnotationBeanPostProcessor,用于处理@Autowired@Value注解
  • CommonAnnotationBeanPostProcessor,用于处理@PostConstruct@PreDestroy@Resource注解
  • EventListenerMethodProcessor,用于处理@EventListener注解
创建完RootBeanDefinition后,还需要将其添加到应用上下文缓存

Bean定义的角色设置为2,表示框架内部的实现类,用户无需关心,平常我们创建的组件默认角色就是0,用于实现具体的业务逻辑。

// AnnotationConfigUtils类方法privatestaticBeanDefinitionHolderregisterPostProcessor(BeanDefinitionRegistry registry,RootBeanDefinition definition,String beanName){ definition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);// 注册Bean定义 registry.registerBeanDefinition(beanName, definition);returnnewBeanDefinitionHolder(definition, beanName);}

BeanDefinitionRegistry registry是应用上下文,调用注册Bean定义方法registerBeanDefinition,实际就是调用默认Bean工厂的注册Bean定义的方法,上面有提到默认Bean工厂DefaultListableBeanFactory核心属性beanDefinitionMap就是存储所有注册的BeanDefinition

在这里插入图片描述

二、初始化类路径Bean定义扫描器

1、注册默认注解过滤器

通过ClassPathBeanDefinitionScanner的构造方法,最终跳转到最后一个构造方法,核心操作是注册默认的注解扫描过滤器

在这里插入图片描述

注册默认的注解过滤器,确保在组件扫描时,能够识别特定的注解。添加@Component注解的过滤器,允许扫描带有@Component注解的类,@Controller@Service@Repository也会被扫描到。

// ClassPathScanningCandidateComponentProvider类方法protectedvoidregisterDefaultFilters(){// 添加 @Component 注解的过滤器,允许扫描带有 @Component 注解的类this.includeFilters.add(newAnnotationTypeFilter(Component.class));// @Component 是 Spring 框架的通用组件注解(只关心这个就可以)// @ManagedBean 是 Java EE 的托管 Bean 注解// 而 @Named 是 CDI(Jakarta EE)的标准化注解// 获取类加载器ClassLoader cl =ClassPathScanningCandidateComponentProvider.class.getClassLoader();try{// 尝试加载 javax.annotation.ManagedBean 注解类,如果存在,添加为过滤器this.includeFilters.add(newAnnotationTypeFilter(((Class<?extendsAnnotation>)ClassUtils.forName("javax.annotation.ManagedBean", cl)),false)); logger.trace("JSR-250 'javax.annotation.ManagedBean' found and supported for component scanning");}catch(ClassNotFoundException ex){// 如果 JSR-250 API 未找到,跳过该注解支持}try{// 尝试加载 javax.inject.Named 注解类,如果存在,添加为过滤器this.includeFilters.add(newAnnotationTypeFilter(((Class<?extendsAnnotation>)ClassUtils.forName("javax.inject.Named", cl)),false)); logger.trace("JSR-330 'javax.inject.Named' annotation found and supported for component scanning");}catch(ClassNotFoundException ex){// 如果 JSR-330 API 未找到,跳过该注解支持}}

2、自定义包扫描

此方法将在后续通过传入包路径调用,并返回注册的Bean定义集合。具体源码后续调用时候详细

protectedSet<BeanDefinitionHolder>doScan(String... basePackages){// 检查 basePackages 是否为空,如果为空则抛出异常Assert.notEmpty(basePackages,"At least one base package must be specified");// 用于存储扫描到的 Bean 定义Set<BeanDefinitionHolder> beanDefinitions =newLinkedHashSet<>();// 遍历传入的每个包路径进行扫描for(String basePackage : basePackages){// 查找当前包路径下符合条件的候选组件Set<BeanDefinition> candidates =findCandidateComponents(basePackage);// 遍历候选组件for(BeanDefinition candidate : candidates){// 获取该 Bean 定义的作用域元数据,并设置作用域ScopeMetadata scopeMetadata =this.scopeMetadataResolver.resolveScopeMetadata(candidate); candidate.setScope(scopeMetadata.getScopeName());// 生成 Bean 的名称String beanName =this.beanNameGenerator.generateBeanName(candidate,this.registry);// 如果候选 Bean 是 AbstractBeanDefinition 类型,进行后处理if(candidate instanceofAbstractBeanDefinition){postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);}// 如果候选 Bean 是 AnnotatedBeanDefinition 类型,处理注解配置if(candidate instanceofAnnotatedBeanDefinition){AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);}// 如果当前 Bean 名称和定义符合要求,则继续处理if(checkCandidate(beanName, candidate)){// 创建一个 BeanDefinitionHolder 来包装 Bean 定义和 Bean 名称BeanDefinitionHolder definitionHolder =newBeanDefinitionHolder(candidate, beanName);// 根据作用域元数据,可能应用代理模式 definitionHolder =AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder,this.registry);// 将处理好的 Bean 定义添加到集合中 beanDefinitions.add(definitionHolder);// 将 Bean 定义注册到容器中registerBeanDefinition(definitionHolder,this.registry);}}}// 返回所有注册的 Bean 定义return beanDefinitions;}

总结

本文详细介绍了SpringBoot应用上下文AnnotationConfigServletWebServerApplicationContext的构造方法中初始化两个重要组件:一是注解Bean定义读取器,创建一些特殊Bean定义(Spring内部使用,也叫后置处理器),用于处理@Configuration、@Autowired等注解;二是类路径Bean定义扫描器,用于扫描给定路径下的@Component注解组件,将其转换为Bean定义。

Read more

Qwen3-VL-8B Web系统完整指南:chat.html前端+proxy_server+vLLM全链路解析

Qwen3-VL-8B Web系统完整指南:chat.html前端+proxy_server+vLLM全链路解析 1. 系统概览:一个开箱即用的AI聊天工作流 你有没有试过部署一个真正能用、界面清爽、响应流畅的本地大模型聊天系统?不是那种跑通了但卡顿、报错、连不上、调不通的“半成品”,而是打开浏览器就能聊、输入就出结果、关机重启也不掉链子的完整体验? Qwen3-VL-8B Web系统就是为此而生——它不是概念验证,也不是开发中间件,而是一套可直接投入日常使用的端到端AI对话基础设施。从你在浏览器里点击chat.html那一刻起,消息就已悄然穿过代理层、抵达vLLM推理引擎、加载Qwen3-VL-8B模型、完成计算并实时返回,整个过程无需手动配置Nginx、不碰OpenAPI密钥、不改一行前端AJAX地址。 它把三个常被割裂的部分——看得见的界面、管得着的流量、算得快的模型——严丝合缝地拧成一股绳。前端不裸连后端,后端不直面浏览器,所有通信都经由proxy_server.py统一调度。这种设计看似多了一层,实则换来三重确定性: * 你改前端CSS,不影响后端启动; *

.NET 的 WebApi 项目必要可配置项都有哪些?

.NET 的 WebApi 项目必要可配置项都有哪些?

目录 一、数据库配置 (一)选择合适的数据库提供程序 (二)配置数据库连接字符串 (三)数据库迁移(以 EF Core 为例) 二、依赖注入配置 (一)理解依赖注入 (二)注册服务 (三)使用依赖注入 三、Swagger 配置 (一)安装 Swagger 相关包 (二)配置 Swagger 服务 (三)启用 Swagger 中间件 四、接口接收和输出大小写配置 (一)接口接收大小写配置 (二)接口输出大小写配置 五、跨域配置 (一)什么是跨域 (二)配置跨域 六、身份验证与授权配置

下载海康视频插件后,浏览器显示此站点正在尝试打开webcontrol浏览器中间件

已下载并安装海康的【VideoWebPlugin.exe】视频插件后,页面显示弹窗【要打开webcontrol浏览器中间件吗?xxxx://xxxxxx 想打开此应用】且页面仍然没有监控视频 解决步骤: 1、选中弹窗中的【xxxx://xxxxxx】这个地址并复制; 2、打开谷歌浏览器在地址栏输入【chrome://flags】,若是edge浏览器会自动跳转到【edge://flags】,其他浏览器同理; 3、在搜索框中输入【Insecure origins treated as secure】; 4、在输入框粘贴刚复制的【xxxx://xxxxxx】地址,并将【已停用】改为【已启用】,后点击右下角【重新启动】按钮重启浏览器; 5、重启后刷新该页面会在地址栏下方显示弹窗,点击【允许】按钮即可正常显示监控视频; *若点击的是【屏蔽】

前端Canvas:让你的网站更具视觉冲击力

前端Canvas:让你的网站更具视觉冲击力 毒舌时刻 前端Canvas?这不是游戏开发才用的吗? "Canvas性能差,我不用"——结果错过了丰富的视觉效果, "Canvas太复杂了,我学不会"——结果只能用静态图片, "我用CSS就够了,要Canvas干嘛"——结果无法实现复杂的动画效果。 醒醒吧,Canvas不是游戏开发的专利,前端也可以用它来创建丰富的视觉效果! 为什么你需要这个? * 丰富的视觉效果:创建动态图形、动画和游戏 * 高性能:直接操作像素,性能优异 * 交互性:支持鼠标、触摸等交互 * 数据可视化:绘制图表、仪表盘等 * 跨平台:在所有现代浏览器中运行 反面教材 // 反面教材:简单的Canvas绘制 function drawCircle() { const canvas = document.getElementById('canvas'