Spring全家桶源码解析--2.4 Spring bean 的依赖注入--@Resource

文章目录

  • 前言
  • 一、@Resource 作用:
  • 二、@Resource 源码实现:
    • 2.1 @Resource 注入点获取:
    • 2.2 @Resource 对注入点依赖注入:
  • 三、 总结


前言

Spring 中不仅可以使用Spring 包中的@Autowired 还可以使用java 层面提供的@Resource 进行依赖注入。


一、@Resource 作用:

@Resource注解是Java EE提供的一种用于进行依赖注入(Dependency Injection)的注解。它可以标注在字段、setter方法或构造函数上,用于指定需要注入的依赖对象。

@Resource注解的作用可以总结如下:

注入依赖对象:当使用@Resource注解标注在一个字段、setter方法或构造函数上时,它告诉Spring容器需要注入一个对应的依赖对象。

指定依赖对象名:可以通过name属性来指定依赖对象的名字。如果没有指定name属性,则默认使用字段名、方法名或构造函数参数名称作为依赖对象的名字。

解决依赖对象冲突:当有多个符合类型的依赖对象可用时,可以使用name属性来指定具体要注入的依赖对象名。

支持按照类型匹配和名称匹配:当没有指定name属性时,@Resource注解会首先按照字段类型或方法参数类型进行匹配,如果找到多个符合条件的依赖对象,会再根据字段名或方法名进行匹配。

需要注意的是,@Resource注解通常与Spring容器中的ApplicationContext一起使用。容器可以根据注解信息自动解析依赖关系,并将对应的依赖对象注入到被注解标注的字段、setter方法或构造函数中。

二、@Resource 源码实现:

2.1 @Resource 注入点获取:

同@Autowired 相同在spring 创建bean的过程中,通过applyMergedBeanDefinitionPostProcessors bean 定义的后置处理器来解析@Resource 注解并将解析的后属性和方法进行包装放入到集合中;
在这里插入图片描述
applyMergedBeanDefinitionPostProcessors 调用 CommonAnnotationBeanPostProcessor 中 postProcessMergedBeanDefinition方法 来负责 @Resource 注入点的工作;

public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
   super.postProcessMergedBeanDefinition(beanDefinition, beanType, beanName);
   // 通过 findResourceMetadata 寻找注入点
   InjectionMetadata metadata = this.findResourceMetadata(beanName, beanType, (PropertyValues)null);
   metadata.checkConfigMembers(beanDefinition);
}

findResourceMetadata 注入点寻找:

private InjectionMetadata findResourceMetadata(String beanName, Class<?> clazz, @Nullable PropertyValues pvs) {
    String cacheKey = StringUtils.hasLength(beanName) ? beanName : clazz.getName();
    InjectionMetadata metadata = (InjectionMetadata)this.injectionMetadataCache.get(cacheKey);
    if (InjectionMetadata.needsRefresh(metadata, clazz)) {
        synchronized(this.injectionMetadataCache) {
            metadata = (InjectionMetadata)this.injectionMetadataCache.get(cacheKey);
            if (InjectionMetadata.needsRefresh(metadata, clazz)) {
                if (metadata != null) {
                    metadata.clear(pvs);
                }
				// 获取注入点
                metadata = this.buildResourceMetadata(clazz);
                this.injectionMetadataCache.put(cacheKey, metadata);
            }
        }
    }

    return metadata;
}

buildResourceMetadata 注入点寻找:

private InjectionMetadata buildResourceMetadata(Class<?> clazz) {
    if (!AnnotationUtils.isCandidateClass(clazz, resourceAnnotationTypes)) {
        return InjectionMetadata.EMPTY;
    } else {
        List<InjectedElement> elements = new ArrayList();
        Class targetClass = clazz;

        do {
            List<InjectedElement> currElements = new ArrayList();
            // 属性注入
            ReflectionUtils.doWithLocalFields(targetClass, (field) -> {
                if (webServiceRefClass != null && field.isAnnotationPresent(webServiceRefClass)) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        throw new IllegalStateException("@WebServiceRef annotation is not supported on static fields");
                    }

                    currElements.add(new CommonAnnotationBeanPostProcessor.WebServiceRefElement(field, field, (PropertyDescriptor)null));
                } else if (ejbClass != null && field.isAnnotationPresent(ejbClass)) {
                    if (Modifier.isStatic(field.getModifiers())) {
                        throw new IllegalStateException("@EJB annotation is not supported on static fields");
                    }

                    currElements.add(new CommonAnnotationBeanPostProcessor.EjbRefElement(field, field, (PropertyDescriptor)null));
                } else if (field.isAnnotationPresent(Resource.class)) {
						// 存在@Resource 注解
                    if (Modifier.isStatic(field.getModifiers())) {
						// 如果是static 属性则直接报错@ Autired 不会报错
                        throw new IllegalStateException("@Resource annotation is not supported on static fields");
                    }

                    if (!this.ignoredResourceTypes.contains(field.getType().getName())) {
						// 不是忽略的,包装为ResourceElemen 的集合中进行返回
                        currElements.add(new CommonAnnotationBeanPostProcessor.ResourceElement(field, field, (PropertyDescriptor)null));
                    }
                }

            });
            // 方法注入处理
            ReflectionUtils.doWithLocalMethods(targetClass, (method) -> {
            	// 桥接方法则获取真正执行的方法
                Method bridgedMethod = BridgeMethodResolver.findBridgedMethod(method);
                if (BridgeMethodResolver.isVisibilityBridgeMethodPair(method, bridgedMethod)) {
                    if (method.equals(ClassUtils.getMostSpecificMethod(method, clazz))) {
                        PropertyDescriptor pdx;
                        if (webServiceRefClass != null && bridgedMethod.isAnnotationPresent(webServiceRefClass)) {
                            if (Modifier.isStatic(method.getModifiers())) {
                                throw new IllegalStateException("@WebServiceRef annotation is not supported on static methods");
                            }

                            if (method.getParameterCount() != 1) {
                                throw new IllegalStateException("@WebServiceRef annotation requires a single-arg method: " + method);
                            }

                            pdx = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                            currElements.add(new CommonAnnotationBeanPostProcessor.WebServiceRefElement(method, bridgedMethod, pdx));
                        } else if (ejbClass != null && bridgedMethod.isAnnotationPresent(ejbClass)) {
                            if (Modifier.isStatic(method.getModifiers())) {
                                throw new IllegalStateException("@EJB annotation is not supported on static methods");
                            }

                            if (method.getParameterCount() != 1) {
                                throw new IllegalStateException("@EJB annotation requires a single-arg method: " + method);
                            }

                            pdx = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                            currElements.add(new CommonAnnotationBeanPostProcessor.EjbRefElement(method, bridgedMethod, pdx));
                        } else if (bridgedMethod.isAnnotationPresent(Resource.class)) {
                        	// 方法上有@Resource 注解
                            if (Modifier.isStatic(method.getModifiers())) {
                            	// 不能修饰在 static 方法上
                                throw new IllegalStateException("@Resource annotation is not supported on static methods");
                            }
							// 如果是方法,则只能传入一个参数
							// @Resource注解要求方法只有一个参数是因为它的设计初衷是用于进行简单的依赖注入,一次只注入一个依赖项。

                            Class<?>[] paramTypes = method.getParameterTypes();
                            if (paramTypes.length != 1) {
                            	// 多个入参报错
                                throw new IllegalStateException("@Resource annotation requires a single-arg method: " + method);
                            }

                            if (!this.ignoredResourceTypes.contains(paramTypes[0].getName())) {
                                PropertyDescriptor pd = BeanUtils.findPropertyForMethod(bridgedMethod, clazz);
                                currElements.add(new CommonAnnotationBeanPostProcessor.ResourceElement(method, bridgedMethod, pd));
                            }
                        }
                    }

                }
            });
            elements.addAll(0, currElements);
            targetClass = targetClass.getSuperclass();
        } while(targetClass != null && targetClass != Object.class);

        return InjectionMetadata.forElements(elements, clazz);
    }
}

ResourceElement 对注入点的包装:

public ResourceElement(Member member, AnnotatedElement ae, @Nullable PropertyDescriptor pd) {
    super(member, pd);
    Resource resource = (Resource)ae.getAnnotation(Resource.class);
    String resourceName = resource.name();
    Class<?> resourceType = resource.type();
    this.isDefaultName = !StringUtils.hasLength(resourceName);
	//  Resource 中是否定义name 属性
    if (this.isDefaultName) {
		// 没有定义 则获取 属性的名称
        resourceName = this.member.getName();
		// 如果是set 方法则截取set 以后的值,并将第一个字母小写
        if (this.member instanceof Method && resourceName.startsWith("set") && resourceName.length() > 3) {
            resourceName = Introspector.decapitalize(resourceName.substring(3));
        }
    } else if (CommonAnnotationBeanPostProcessor.this.embeddedValueResolver != null) {
		// 如果定义了则直接获取
        resourceName = CommonAnnotationBeanPostProcessor.this.embeddedValueResolver.resolveStringValue(resourceName);
    }
		// 自己定义了type 类型,则进行检查
    if (Object.class != resourceType) {
		// 指定的type 和属性的type /入参的参数 类型不一致则报错
        this.checkResourceType(resourceType);
    } else {
		// 没有定义则,获取属性对应的
        resourceType = this.getResourceType();
    }

    this.name = resourceName != null ? resourceName : "";
    this.lookupType = resourceType;
    String lookupValue = resource.lookup();
    this.mappedName = StringUtils.hasLength(lookupValue) ? lookupValue : resource.mappedName();
    Lazy lazy = (Lazy)ae.getAnnotation(Lazy.class);
    this.lazyLookup = lazy != null && lazy.value();
}

至此注入点的扫描和获取 完成,下一步则bean实例化之后,对属性注入时,通过遍历注入点来完成 属性注入;

2.2 @Resource 对注入点依赖注入:

@Resource 对注入点依赖注入,遍历所有的注入点,按照属性/方法 执行不同的解析逻辑,都进入到 autowireResource 方法中,完成bean 的获取,最终通过反射的方式,将bean 进行依赖注入;

在bean 的 populateBean 方法中 通过postProcessProperties 方法进行依赖注入,CommonAnnotationBeanPostProcessor 中的postProcessProperties 方法为 @Resource 依赖注入的入口;

public PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName) {
    InjectionMetadata metadata = this.findResourceMetadata(beanName, bean.getClass(), pvs);

    try {
    	// 依赖注入
        metadata.inject(bean, beanName, pvs);
        return pvs;
    } catch (Throwable var6) {
        throw new BeanCreationException(beanName, "Injection of resource dependencies failed", var6);
    }
}

inject set方法和 属性 依赖注入:

protected void inject(Object target, @Nullable String requestingBeanName, @Nullable PropertyValues pvs) throws Throwable {
    if (this.isField) {
		//属性注入
        Field field = (Field)this.member;
		// 属性是私有的设置强制访问
        ReflectionUtils.makeAccessible(field);
		// 反射方法为属性赋值
        field.set(target, this.getResourceToInject(target, requestingBeanName));
    } else {
        if (this.checkPropertySkipping(pvs)) {
            return;
        }

        try {
        	// 获取方法
            Method method = (Method)this.member;
            // 方法 强制访问
            ReflectionUtils.makeAccessible(method);
            // 反射方法为入参赋值
            method.invoke(target, this.getResourceToInject(target, requestingBeanName));
        } catch (InvocationTargetException var5) {
            throw var5.getTargetException();
        }
    }

}

ResourceElement .getResourceToInject 依赖注入:

protected Object getResourceToInject(Object target, @Nullable String requestingBeanName) {
	// 是否有lazy 注解,有则生成代理对象,没有则直接获取真正的bean 对象
    return this.lazyLookup ? CommonAnnotationBeanPostProcessor.this.buildLazyResourceProxy(this, requestingBeanName) : CommonAnnotationBeanPostProcessor.this.getResource(this, requestingBeanName);
}

getResource 不是懒加载的bean :

protected Object getResource(CommonAnnotationBeanPostProcessor.LookupElement element, @Nullable String requestingBeanName) throws NoSuchBeanDefinitionException {
    String jndiName = null;
    if (StringUtils.hasLength(element.mappedName)) {
        jndiName = element.mappedName;
    } else if (this.alwaysUseJndiLookup) {
        jndiName = element.name;
    }

    if (jndiName != null) {
        if (this.jndiFactory == null) {
            throw new NoSuchBeanDefinitionException(element.lookupType, "No JNDI factory configured - specify the 'jndiFactory' property");
        } else {
            return this.jndiFactory.getBean(jndiName, element.lookupType);
        }
    } else if (this.resourceFactory == null) {
        throw new NoSuchBeanDefinitionException(element.lookupType, "No resource factory configured - specify the 'resourceFactory' property");
    } else {
		//从beanFactory 中获取bean
        return this.autowireResource(this.resourceFactory, element, requestingBeanName);
    }
}

autowireResource 获取bean:
获取bean的流程: 是否指定了name属性,如果定义直接根据name 去寻找bean ,如果没有定义name 名字,则先按照属性的名称去获取bean ,如果获取不到 则在按照属性的类型去获取bean(@ Resource 先按照 名称去获取了bean 的体现就在这里);

protected Object autowireResource(BeanFactory factory, CommonAnnotationBeanPostProcessor.LookupElement element, @Nullable String requestingBeanName) throws NoSuchBeanDefinitionException {
    String name = element.name;
    Object resource;
    Object autowiredBeanNames;
    if (factory instanceof AutowireCapableBeanFactory) {
        AutowireCapableBeanFactory beanFactory = (AutowireCapableBeanFactory)factory;
		// 属性的描述器
        DependencyDescriptor descriptor = element.getDependencyDescriptor();
		// 根据bean 的名字先进行获取
        if (this.fallbackToDefaultTypeMatch && element.isDefaultName && !factory.containsBean(name)) {
			// 如果没有指定 bean 的name 属性,并且beanFactory 也没有bean
            autowiredBeanNames = new LinkedHashSet();
			// 根据属性类型去寻找,这里resolveDependency  
			// 判断是否有@Value 注解 如果有就解析并且生成对象返回,没有@Value 继续走下一步
			// 看当前的属性,或者方法的入参,是有什么接收的,如果map,或者list 或者array 根据类型找到bean 并进行返回;
			// 如果是普通的注入 则进行优先级或者bean 名称的筛选,如果筛选后,没有得到bean则报错,筛选后得到bean 则进行返回
            resource = beanFactory.resolveDependency(descriptor, requestingBeanName, (Set)autowiredBeanNames, (TypeConverter)null);
            if (resource == null) {
                throw new NoSuchBeanDefinitionException(element.getLookupType(), "No resolvable resource object");
            }
        } else {
			// beanFactory 有对应的bean 则直接获取,
            resource = beanFactory.resolveBeanByName(name, descriptor);
            autowiredBeanNames = Collections.singleton(name);
        }
    } else {
        resource = factory.getBean(name, element.lookupType);
        autowiredBeanNames = Collections.singleton(name);
    }

    if (factory instanceof ConfigurableBeanFactory) {
        ConfigurableBeanFactory beanFactory = (ConfigurableBeanFactory)factory;
        Iterator var11 = ((Set)autowiredBeanNames).iterator();

        while(var11.hasNext()) {
            String autowiredBeanName = (String)var11.next();
            if (requestingBeanName != null && beanFactory.containsBean(autowiredBeanName)) {
                beanFactory.registerDependentBean(autowiredBeanName, requestingBeanName);
            }
        }
    }

    return resource;
}

这里的resolveDependency 逻辑和@Autowired 相同,都是按照bean 的类型去获取:

  • 如果获取到一个则直接返回;
  • 如果获取到多个并且使用Map/List/Array 接收则直接返回,如果使用普通对象接收,在通过name 进行筛选,如果筛选后只有一个则正常返回,如果没有或者有多个则报错处理;
  • 如果没有胡获取到则报错处理;

三、 总结

@Resource 获取bean 的情况总结:

  • 没有指定name 和type :
    在这里插入图片描述

  • 没有指定名称,指定type;先按照名称去找,如果找到则进行装配(如果和实际需要的类型不一致则会报错),如果找不到则按照类型取获取;
    在这里插入图片描述

  • 指定name,不指定type:直接按照名字去获取,找到一个自动装配(如果和实际需要的类型不一致则会报错,找不到则报错
    在这里插入图片描述

  • 同时指定name 和type:
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

阿里云学生及教师优惠活动,学生用户享3折购买优惠,教师享5折购买优惠

阿里云推出高校计划“云工开物”&#xff0c;助力高校师生云上“创世界”&#xff0c;学生用户享300元优惠券和3折购买优惠&#xff0c;教师享5折购买优惠。“云工开物”将倾力支持高校教师云上科研提速&#xff0c;取得有世界级影响力的成果&#xff1b;助力高校学生在云上探索…

无代码:解决非程序员的开发难题

最近&#xff0c;有个小型企业的负责人找上我&#xff0c;说他公司需要一个内部管理系统&#xff0c;来提高工作和协作效率&#xff0c;但他没有编程经验&#xff0c;也不打算花费大量时间和金钱雇佣专业的开发团队&#xff0c;他问我有没有什么解决方案。 针对这个问题&#…

FusionDiff:第一个基于扩散模型实现的多聚焦图像融合的论文

文章目录 1. 论文介绍2. 研究动机3. 模型结构3.1 网络架构3.2 前向扩散过程3.3 逆向扩散过程3.4 训练和推理过程 4. 小样本学习4. 实验结果 1. 论文介绍 题目&#xff1a;FusionDiff: Multi-focus image fusion using denoising diffusion probabilistic models 作者&#xf…

ARPG----C++学习记录05 Section9 动画蓝图,腿部ik

这节课比较难懂,我也不是很理解 动画蓝图 新建一个动画蓝图。首先新建一个人物蓝图的变量用来获取人物的属性&#xff0c;使用第一行蓝图来初始化&#xff0c;当人物为Echo时获取它的movement组件&#xff0c;存为变量。然后动画的每一帧都从movement组件里拿出xy的速度用作后边…

软件外包的需求整理技巧

在软件开发中&#xff0c;整理需求是确保项目成功的重要步骤之一。以下是一些整理需求的技巧&#xff0c;这些技巧有助于确保需求的清晰性、完整性和可行性&#xff0c;为项目的成功打下坚实的基础。北京木奇移动技术有限公司&#xff0c;专业的软件外包开发公司&#xff0c;欢…

有什么方法可以改善CRM实施投资回报?

数据统计显示&#xff0c;几乎70%以上CRM客户管理系统项目的投资回报是负数。这意味着超过半数的CRM项目的结果是失败的。那么我们有什么方法可以改善CRM实施投资回报吗&#xff1f;当然有&#xff0c;下面我们就来说一说。 如何改善CRM实施投资回报 首先&#xff0c;您选择的…

新品 | 飞凌嵌入式FCU2601工商业储能EMS能量控制单元发布

FCU2601嵌入式控制单元是飞凌嵌入式为锂电池储能行业设计的EMS能量控制单元产品&#xff0c;设计兼具高性能&#xff0c;多接口&#xff0c;低功耗&#xff0c;广泛满足各类储能系统的本地能源管理应用需求。 FCU2601嵌入式控制单元综合考虑到了储能行业不同场景的差异化需求&…

RT1170的ITM SWO配置,实现printf输出及PC指针的采样分析

最近公司准备启动一个新的项目&#xff0c;使用NXP的MIMXRT1170芯片作为主控&#xff0c;在熟悉芯片的过程中发现RT1176具备ITM和SWO功能模块&#xff0c;于是针对之前项目中因工程庞大导致调试困难的问题&#xff0c;决定使用SWO输出调试信息&#xff0c;这样既可以节省硬件的…

Python生成随机数插件Faker的用法

目录 引言 一、Faker库的安装 二、Faker库的基本用法 1、导入Faker类 2、创建Faker对象 3、使用Faker对象生成随机数据 三、Faker库的高级用法 1、自定义数据生成规则 2、使用子模块进行特定领域的数据生成 3、与其他库结合使用 四、Faker库的应用场景 1、单元测试…

虚拟化加密磁盘密钥设置方案浅析 — TKS1

虚拟化加密磁盘密钥设置方案浅析 前言密钥设置方案密钥管理服务-KMS密钥设置方案-TKS1 两级加密设计弱熵密码派生密钥切分存储 前言 虚拟化组件可以使用多种加密算法对虚拟机磁盘的原始内容进行加解密&#xff0c;比如AES、RSA、SM2/SM3/SM4等&#xff0c;用户写入的数据经过加…

医院安全(不良)事件管理系统源码 不良事件报告全套源码

不良事件管理系统是一种专为企业或组织设计的软件工具&#xff0c;用于跟踪、记录和管理不良事件。该系统可以有效地整合不良事件的收集、分类、分析和报告&#xff0c;帮助企业及时识别和处理不良事件&#xff0c;从而降低风险和损失。通过实时监控和自动化报告&#xff0c;该…

富文本组件vue-quill-editor使用

版本&#xff1a;"vue-quill-editor": "^3.0.6", 1.全局引入 import VueQuillEditor from vue-quill-editor import quill/dist/quill.core.css import quill/dist/quill.snow.css import quill/dist/quill.bubble.cssVue.use(VueQuillEditor) 2.使用&am…

轻量封装WebGPU渲染系统示例<30>- 绘制线段(源码)

原理说明&#xff1a; WebGPU提供了绘制基本线条非机制&#xff0c;只要render pipeline primitive对应的 topology属性指定为line-list或者line-strip即可绘制对应的线条。 当前示例源码github地址: https://github.com/vilyLei/voxwebgpu/blob/feature/rendering/src/voxg…

移远EC600U-CN开发板 11.14

控件探索-仪表&#xff08;lv.meter&#xff09; 1. 显示一个简单的仪表盘 def set_value(indic, v):meter.set_indicator_value(indic, v)# # A simple meter # meter lv.meter(scr) meter.center() meter.set_size(200, 200)# Add a scale first scale meter.add_scale()…

电机应用-编码器

目录 编码器 增量式编码器 绝对式编码器 混合式绝对式编码器 旋转编码器原理 增量式编码器原理 绝对式编码器原理 编码器基本参数 分辨率 精度 最大响应频率 信号输出形式 编码器 用来测量机械旋转或位移的传感器&#xff0c;能够测量机械部件在旋转或直线运动时的…

Ubuntu 24.04发布日期以定

导读Ubuntu 的下一个长期支持 (LTS) 版本 Ubuntu 24.04 的最终发布日期已确定&#xff0c;计划于 2024 年 4 月 25 日发布。 Ubuntu 的下一个长期支持 (LTS) 版本 Ubuntu 24.04 的最终发布日期已确定&#xff0c;计划于 2024 年 4 月 25 日发布。 除此之外&#xff0c;Ubuntu…

听我的,日志还是得好好打!

大家好&#xff0c;我是老三&#xff0c;不知道大家有没有经历过这样的场景&#xff0c;线上出了问题&#xff0c;着急火燎地去排查&#xff1a; 唉&#xff0c;问题可能出在这个地方&#xff0c;捞个日志看下&#xff0c;卧槽&#xff0c;怎么找不到……哪个**不打日志&#…

Mac M2/M3 芯片环境配置以及常用软件安装-前端

最近换了台新 Mac&#xff0c;所有的配置和软件就重新安装下&#xff0c;顺便写个文章。 一、环境配置 1. 安装 Homebrew 安装 Homebrew【Mac 安装 Homebrew】 通过国内镜像安装会比较快 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Ho…

如何定制开发软件 App?|小程序网站搭建

如何定制开发软件 App&#xff1f;|小程序网站搭建 随着移动互联网的快速发展&#xff0c;定制开发软件 App 成为了许多企业和个人的需求。无论是为了满足特定业务需求&#xff0c;还是为了提升用户体验&#xff0c;定制开发软件 App 都可以帮助我们实现目标。下面&#xff0c;…

管理类联考——数学——汇总篇——知识点突破——代数——函数、方程——记忆——一元二次函数

——一元二次函数——【图像→交点】 ——【 a x 2 b x c y ax^2bxcy ax2bxcy二次函数核心在于“图像”&#xff1a;整体可以由&#xff1a; 图像&#xff08;形状&#xff0c;上下&#xff0c;交点&#xff09; ⟹ \Longrightarrow ⟹ △ △ △ ⟹ \Longrightarrow ⟹ 抛…