首页 > 其他分享 >《系列二》-- 9、bean属性填充

《系列二》-- 9、bean属性填充

时间:2023-07-13 15:15:03浏览次数:56  
标签:pvs 填充 -- beanName mbd bean bw property

目录

阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 spring 源码 5.x 版本。

写在开始前的话:

阅读spring 源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消化了:

  • beans
  • core
  • context

实际上我在博客里贴出来的还只是一部分内容,更多的内容,我放在了个人,fork自 spring 官方源码仓了; 而且对源码的学习,必须是要跟着实际代码层层递进的,不然只是干巴巴的文字味同嚼蜡。

https://gitee.com/bokerr/spring-framework-5.0.x-study

这个仓设置的公共仓,可以直接拉取。



Spring源码阅读系列--全局目录.md



一、概述: populateBean 在什么时候执行?

在getBean 的调用链路中,如果是从零开始创建一个bean, 按照潦草的说法大致如下:

  • 1.根据 beanName 从 beanFactory 读取 BeanDefinition

  • 2.对BeanDefinition 进行简单的加工、转化 -> RootBeanDefinition

  • 2.根据bean元数据(BeanDefinition) 中记录的信息,递归的去加载该bean 依赖的其它bean

  • 3.根据bean 的作用域: [prototype、singleton...等等] 决定具体的创建bean的行为。

    • singleton 需要应用三级缓存,保证全局唯一
    • prototype 每次直接创建全新bean
  • 4.bean的实例化: (所谓实例化,可以理解为,从java 堆上创建了一个'全新'的对象。)
    根据 BeanDefinition 获取: bean的类型、参数、自定义构造函数、 注入方式(直接注入: 无参构造函数,间接注入: FactoryBean、factory-bean + factory-method)。

    最终根据上述的信息的配置情况,实例化一个bean。

  • 5.上一步也提到,它只完成了实例化,但是如果有属性需要填充呢? 参考如下的例子:


<beans>
  <bean id="myBean" class="demo.self.MySimpleBean">
    <!-- property 是常量 -->
    <property name="name" value="Bokerr"/>
    <property name="age" value="27"/>
  </bean>

  <bean id="myBean" class="demo.self.MyBean">
    <!-- property 是一个bean -->
    <property name="bean1"><ref bean="populate1"/></property>
  </bean>
  <bean id="populate1" class="demo.self.populateBean"/>
</beans>

为什么说填充的就是 property 属性呢? 【详见后文,populateBean方法的第一个判断。】

bean 创建实例化之后,还处于一个 '空白' 状态,所以需要经过填充操作,将配置的 property 属性的值注入。

这就引入了本文需要讲述的操作:populateBean

这里有个很重要、很重要、很重要的起手式:

PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

你需要记住: PropertyValues(它是 bean的全部 property 解析后得到的元数据信息)
它是来自于 mbd 的。(也就是 BeanDefinition),它的对象声明周期受到 BeanFactory 管理;

下文中任意对 PropertyValues 及其属性的 set 操作,都可等效认为,实在对 BeanDefinition 作缓存操作。

二、populateBean 的重要操作

/**
 * Populate the bean instance in the given BeanWrapper with the property values
 * from the bean definition.
 *
 * @param beanName the name of the bean
 * @param mbd      the bean definition for the bean
 * @param bw       the BeanWrapper with bean instance
 */
protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
    if (bw == null) {
        if (mbd.hasPropertyValues()) {
        //  bean 有属性需要被填充,但是对象包装器 bw 为空
            throw new BeanCreationException(
                    mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
        } else {
            //  包装器为空,且不需要 填充任何属性
            // Skip property population phase for null instance.
            return;
        }
    }

    // 给 InstantiationAwareBeanPostProcessors  最后一次机会,通过属性注入改变 bean.
    if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
        for (BeanPostProcessor bp : getBeanPostProcessors()) {
            if (bp instanceof InstantiationAwareBeanPostProcessor) {
                InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                // 所有 InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()的返回值将决定
                // [是/否] 继续进行property属性填充
                if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
                    return;
                }
            }
        }
    }

    // 所有需要注入的属性, 对应的bean缓存 【这里明确从 BeanDefinition 上获取的 property 的定义】
    PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);

    int resolvedAutowireMode = mbd.getResolvedAutowireMode();
    if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
        MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
        // Add property values based on autowire by name if applicable.
        if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
            // {重要操作1-根据Bean名称注入}   [下文将展开详说]
            autowireByName(beanName, mbd, bw, newPvs);  //  提取依赖的bean  保存到 pvs
        }
        // Add property values based on autowire by type if applicable.
        if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
            // {重要操作1-根据Bean类型注入 <Class>}  [下文将展开详说]
            autowireByType(beanName, mbd, bw, newPvs);  //  提取依赖的bean  缓存到 pvs
        }
        pvs = newPvs;
    }

    // beanFactory 是否已经注册了: bean 实例化相关的 "后置处理器"
    boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
    // 是否需要依赖检查
    boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);

    if (hasInstAwareBpps || needsDepCheck) {
        if (pvs == null) {
            pvs = mbd.getPropertyValues();
        }
        // 过滤属性描述符,为依赖检查做准备
        PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
        if (hasInstAwareBpps) {//  后处理器 ?? 
            for (BeanPostProcessor bp : getBeanPostProcessors()) {
                if (bp instanceof InstantiationAwareBeanPostProcessor) {
                    InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
                    // 主要这里是相同的后置处理器,不同的方法
                    // 字段填充的前置检查
                    pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
                    if (pvs == null) {
                        return;
                    }
                }
            }
        }
        if (needsDepCheck) {//  依赖检查, depend-on 属性,spring-3.0弃用
            checkDependencies(beanName, mbd, filteredPds, pvs);
        }
    }

    if (pvs != null) {
        // 属性填充  {重要操作2} [下文将展开详说]
        applyPropertyValues(beanName, mbd, bw, pvs);  //  将  propertyValue 应用/注入 到 bean 中
    }
}

这里重点说两个操作:

  • 1.属性值的注入, 将propertyName视作beanName, 并尝试从spring容器中获取该bean, 如果成功获取则通过 PropertyValues:pvs 保存

autowireByName(beanName, mbd, bw, newPvs);
autowireByType(beanName, mbd, bw, newPvs);

  • 2.属性值的应用, 上述操作的后续只要存在 property 属性配置就进行 property 值的'应用'操作。

applyPropertyValues(beanName, mbd, bw, pvs);

三、重点操作一 propertyValue 的注入

3.1 根据 Bean名称注入

    /**
	 * Fill in any missing property values with references to
	 * other beans in this factory if autowire is set to "byName".
	 *
	 * @param beanName the name of the bean we're wiring up.
	 *                 Useful for debugging messages; not used functionally.
	 * @param mbd      bean definition to update through autowiring
	 * @param bw       the BeanWrapper from which we can obtain information about the bean
	 * @param pvs      the PropertyValues to register wired objects with
	 */
	protected void autowireByName(
			String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {

        // 从 BeanDefinition 读取,非-常量注入的属性,这里返回的都是 property 指向别的 bean的场景
        // 这里会忽略: 基本类型、字符串常量等等 property
		String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
		for (String propertyName : propertyNames) {
			if (containsBean(propertyName)) {
				// 递归加载依赖的bean
				Object bean = getBean(propertyName);
				pvs.add(propertyName, bean);
				// 在容器中记录 bean 及其依赖的 bean之间的关系, 不妨进去瞅瞅
				registerDependentBean(propertyName, beanName);
				if (logger.isDebugEnabled()) {
					logger.debug("Added autowiring by name from bean name '" + beanName +
							"' via property '" + propertyName + "' to bean named '" + propertyName + "'");
				}
			} else {
				// 根据beanName 未定位到bean, 记录到日志。[说明该 property 所依赖的可能并不是一个 bean,可能只是一个普通的常量。]
				if (logger.isTraceEnabled()) {
					logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName +
							"' by name: no matching bean found");
				}
			}
		}
	}

上文的内容很简单:

  • 获取所有的property中的非 【基本类型、常量】形式的 property
  • 然后遍历并逐个解析
  • 应用 containsBean 方法,判断该 propertyName 是否是一个beanName,如果不是则说明property 可能是常量。
  • registerDependentBean(propertyName, beanName); 见名知意,就是记录当前加载的bean,依赖 propertyName 指向的bean。

3.2 浅看一下,获取非'简单' 类型 property 的方法

    /**
	 * Return an array of non-simple bean properties that are unsatisfied.
	 * These are probably unsatisfied references to other beans in the
	 * factory. Does not include simple properties like primitives or Strings.
	 *
	 * @param mbd the merged bean definition the bean was created with
	 * @param bw  the BeanWrapper the bean was created with
	 * @return an array of bean property names
	 * @see org.springframework.beans.BeanUtils#isSimpleProperty
	 */
	protected String[] unsatisfiedNonSimpleProperties(AbstractBeanDefinition mbd, BeanWrapper bw) {
		Set<String> result = new TreeSet<>();
		PropertyValues pvs = mbd.getPropertyValues();
		PropertyDescriptor[] pds = bw.getPropertyDescriptors();
		for (PropertyDescriptor pd : pds) {
			// 有 setter方法
			// 		&& 没有别识别过的依赖
			// 			&& 已经解析过的property字段中不包含该字段
			// 				&& property的类型不是简单类型(Date、Number、CharSequence、Enum等等)
			if (pd.getWriteMethod() != null && !isExcludedFromDependencyCheck(pd) && !pvs.contains(pd.getName()) &&
					!BeanUtils.isSimpleProperty(pd.getPropertyType())) {
				result.add(pd.getName());
			}
		}
		return StringUtils.toStringArray(result);
	}

3.3 根据 Bean类型注入

    /**
	 * Abstract method defining "autowire by type" (bean properties by type) behavior.
	 * <p>This is like PicoContainer default, in which there must be exactly one bean
	 * of the property type in the bean factory. This makes bean factories simple to
	 * configure for small namespaces, but doesn't work as well as standard Spring
	 * behavior for bigger applications.
	 *
	 * @param beanName the name of the bean to autowire by type
	 * @param mbd      the merged bean definition to update through autowiring
	 * @param bw       the BeanWrapper from which we can obtain information about the bean
	 * @param pvs      the PropertyValues to register wired objects with
	 */
	protected void autowireByType(
			String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {

		TypeConverter converter = getCustomTypeConverter();  //  获取自定义类型转换器
		if (converter == null) {
			converter = bw;
		}
		Set<String> autowiredBeanNames = new LinkedHashSet<>(4);  //  缓存依赖关系映射
		// 从 BeanDefinition 读取,非-常量注入的属性,这里返回的都是 property 指向别的 bean的场景
		// 这里会忽略: 基本类型、字符串常量等等 property
		String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
		for (String propertyName : propertyNames) {
			try {
				// 我们这是根据 bean Type 注入,所以这里浅浅的获取了一下, bean的类对象反射信息
				PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
				// Don't try autowiring by type for type Object: never makes sense,
				// even if it technically is a unsatisfied, non-simple property.
				if (Object.class != pd.getPropertyType()) {
					MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd);  //  探测属性指定的set 方法 
					// Do not allow eager init for type matching in case of a prioritized post-processor.
					boolean eager = !(bw.getWrappedInstance() instanceof PriorityOrdered);

					//  依赖描述符
					DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager); 

					// 解析指定 beanName 的属性所匹配的值,
					// 并把解析到的属性名称存储在 autowiredBeanNames 中
					// 当存在多个属性被封装到一起时:
					// @Autowired List<A> aList;  会把 所有 A 类型的bean 都注入其中 autowiredBeans (复数个)
					Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter);  //  寻找类型匹配的  bean  
					if (autowiredArgument != null) {
						pvs.add(propertyName, autowiredArgument);
					}
					for (String autowiredBeanName : autowiredBeanNames) {
						registerDependentBean(autowiredBeanName, beanName);  //  注册依赖关系
						if (logger.isDebugEnabled()) {
							logger.debug("Autowiring by type from bean name '" + beanName + "' via property '" +
									propertyName + "' to bean named '" + autowiredBeanName + "'");
						}
					}
					autowiredBeanNames.clear();
				}
			} catch (BeansException ex) {
				throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);
			}
		}
	}

这里是根据 property 的类型来注入的,比起根据 propertyName 注入, 这里的操作复杂得多

  • 获取所有的property中的非 【基本类型、常量】形式的 property
  • 依次遍历
  • 结合 BeanDefinition 中的 property 定义,以及包装器BeanWrapper 中的记录信息, 得到 property的类型
  • 根据 bean类型描述进入方法: resolveDependency() 及其后文其实内容是相当丰富的,这里就不再展开了;
    该方式看似没有返回值,实际上你看下 desc 的构造,你就能发现有什么东西偷偷摸摸的进去了。

如果想要深究 resolveDependency() 方法,可以下载我个人fork 的spring源码仓; 我在源码里也加了详细注释。

https://gitee.com/bokerr/spring-framework-5.0.x-study

四、注入依赖的应用

书接上回 现在我们看第二个重点操作:

  • populateBean().applyPropertyValues()

    /**
	 * Apply the given property values, resolving any runtime references
	 * to other beans in this bean factory. Must use deep copy, so we
	 * don't permanently modify this property.
	 *
	 * @param beanName the bean name passed for better exception information
	 * @param mbd      the merged bean definition
	 * @param bw       the BeanWrapper wrapping the target object
	 * @param pvs      the new property values
	 */
	protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
		if (pvs.isEmpty()) {
			return;  //  不需要任何的注入操作
		}

		if (System.getSecurityManager() != null && bw instanceof BeanWrapperImpl) {
			// 权限处理
			((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext());
		}

		MutablePropertyValues mpvs = null;
		List<PropertyValue> original;

		if (pvs instanceof MutablePropertyValues) {
			// 实际上,如果执行过: autowireByType 和 autowireByName 后注入的 <property, propertyValue>
			mpvs = (MutablePropertyValues) pvs;
			if (mpvs.isConverted()) {
				// 根据 name / type 解析到的 bean 已经被 转化为 具体类型 那么直接进行注入操作
				// Shortcut: use the pre-converted values as-is.
				try {
					// 将解析完成的 <property,propertyValue> 逐个应用到包装器: BeanWrapper
					bw.setPropertyValues(mpvs);
					return;
				} catch (BeansException ex) {
					throw new BeanCreationException(
							mbd.getResourceDescription(), beanName, "Error setting property values", ex);
				}
			}
			original = mpvs.getPropertyValueList();
		} else {
			// 同样 applyPropertyValues 也可能在别的场景中被调用:
			// 该场景中,PropertyValues 尚未进行任意的 property 的注入操作
			// 所以进入当前分支,继续往下分析。
			original = Arrays.asList(pvs.getPropertyValues());
		}

		//  获取自定义-转换器 其实这个应该并不陌生,在 populateBean().autowireByType() 方法中见过
		// 其实到这里,可以简单做个推论了: 接下来的篇幅中,所作的事情,可能会跟 property 的注入操作类似
		TypeConverter converter = getCustomTypeConverter();
		if (converter == null) {
			converter = bw;  //  默认使用 bean 包装器 
		}
		// Spring表达式语言(SpEL) 解析器  #{bean.xxx} ${xx.key}
		// 可由 ApplicationContext 注入
		BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);

		// Create a deep copy, resolving any references for values.
		List<PropertyValue> deepCopy = new ArrayList<>(original.size());
		boolean resolveNecessary = false;
		for (PropertyValue pv : original) {
			//  遍历 property 属性,将需要转换的属性,转换为对应的类型
			if (pv.isConverted()) {
				// 已经转化过,直接添加 [BeanDefinition 缓存应用 或者 前边的环节已经处理过]
				deepCopy.add(pv);
			} else {
				// 先转换 后添加
				String propertyName = pv.getName();
				// 原始值
				Object originalValue = pv.getValue();
				// 包含SpEL表达式的解析
				Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
				Object convertedValue = resolvedValue;

				// property 是否可覆盖?
				// 根据包装器 bw 判断 property 字段可写 && 并且 property 不是(索引/嵌套类型的属性)。
				// 自引入概念:property 应该是一个 "原子属性"
				boolean convertible = bw.isWritableProperty(propertyName) &&
						!PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
				if (convertible) {
					// 可以写入,且是一个"原子属性", 那么就可以进行转化了
					// 这里不太适合再钻进去了,除非spring源码出bug了,否则如下的逻辑是不太有可能接触到的,
					// 可以简单做个盖棺定论: 这里会把 resolvedValue 转化为 property 字段所期望的值类型:
					// 		String: 2023-07-10   ->  Date: 2023-07-10 00:00:00.000
					// 		String: false        ->  Boolean: false
					convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
				}

				// Possibly store converted value in merged bean definition,
				// in order to avoid re-conversion for every created bean instance.
				if (resolvedValue == originalValue) {
					// 转换后的值(对象)  等价  原始值
					if (convertible) {
						// PropertyValue 是元数据 BeanDefinition 的一环,这里等价为缓存
						// 避免重复的转换操作
						pv.setConvertedValue(convertedValue);
					}
					deepCopy.add(pv);
				} else if (convertible && originalValue instanceof TypedStringValue &&
						!((TypedStringValue) originalValue).isDynamic() &&
						!(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
					// 转化后的值不等于原始值
					// 		&& 支持转换
					// 		&& 原始值是String
					// 		&& 原始值不是动态的(常量)
					// 		&&  转换后的值,不是集合、不是数组
					pv.setConvertedValue(convertedValue);  // BeanDefinition 设置缓存
					deepCopy.add(pv);
				} else {
					// 上述情形之外的情况, 可以看到,区别是,这里没有被当作缓存设回 BeanDefinition 中
					resolveNecessary = true;
					deepCopy.add(new PropertyValue(pv, convertedValue));
				}
			}
		}
		if (mpvs != null && !resolveNecessary) {
			// mpvs 同样是 BeanDefinition 所包含的信息,这里也可视作缓存设置
			mpvs.setConverted();
		}

		// Set our (possibly massaged) deep copy.
		try {
			// 将解析完成的 <property,propertyValue> 逐个应用到包装器: BeanWrapper
			bw.setPropertyValues(new MutablePropertyValues(deepCopy));
		} catch (BeansException ex) {
			throw new BeanCreationException(
					mbd.getResourceDescription(), beanName, "Error setting property values", ex);
		}
	}

重要信息都放在注释里了。

标签:pvs,填充,--,beanName,mbd,bean,bw,property
From: https://www.cnblogs.com/bokers/p/17542739.html

相关文章

  • Springboot实现注解判断权限
    Springboot实现注解判断权限今天记录一下使用springboot的注解来给方法加权限避免了每个方法都需要大量的权限判断超级好用√@目录Springboot实现注解判断权限1.创建权限注解2.定义一个权限的枚举类3.创建拦截器AOP校验权限poincut表达式介绍4.使用注解1.创建权限注解首先......
  • 华为云MetaStudio全新升级,盘古数字人大模型助力数字人自由
    摘要:基于盘古大模型能力,华为云MetaStudio数字内容生产线全新升级,推出数字人模型生成服务和模型驱动服务。近日,华为开发者大会2023(Cloud)在东莞拉开帷幕。基于盘古大模型能力,华为云MetaStudio数字内容生产线全新升级,推出数字人模型生成服务和模型驱动服务,旨在通过数字人服务......
  • 《系列二》-- 11、Aware是什么
    目录正文阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于spring源码5.x版本。写在开始前的话:阅读spring源码实在是一件庞大的工作,不说全部内容,单就最基本核心部分包含的东西就需要很长时间去消......
  • js的arguments到底是什么?
    js的arguments到底是什么?类数组对象:arguments众所周知,js是一门相当灵活的语言。当我们在js中在调用一个函数的时候,我们经常会给这个函数传递一些参数,js把传入到这个函数的全部参数存储在一个叫做arguments的东西里面,那么这到底是什么东西?在js中万物皆对象,甚至数组字符串函数都......
  • linux查看网络端口是万兆还是千兆
    1、ethtoolethtool网络接口名#ethtoolem4(网络接口名)Settingsforem4:Supportedports:[TP]Supportedlinkmodes:10baseT/Half10baseT/Full100baseT/Half100baseT/Full1000baseT/FullSupportsauto-negotiation:YesAdvertisedlinkmodes:10baseT/Half10baseT/Full1......
  • SAM(segment-anything)解读-整理中
    sam的一个很重要的作用,用来寻找关注点算法来源:meta数据集:训练数据集一共1100万张,包含11亿个mask训练gpu:256块(如果是个人特殊需求,就需要微调,而且也只能微调)sam如何获取训练集?模型评估速度: ......
  • ArrayList源码阅读
    ArrayList源码分析ArrayList简介ArrayList的底层是数组队列,相当于动态数组。与Java中的数组相比,它的容量能动态增长。在添加大量元素前,应用程序可以使用ensureCapacity操作来增加ArrayList实例的容量。这可以减少递增式再分配的数量。ArrayList继承于AbstractList,实现......
  • 虚树 学习笔记
    虚树学习笔记引入我们在解决树上问题时,往往都是对整棵树进行处理,或者每次询问都对一个点、点对进行处理,这类题型一般都可以通过dp、树剖解决;然而,有一类问题要求我们每次对树上一些关键点进行处理。这类问题的特点就是询问次数多,而询问的点的总数不多。可如果我们每次都把整棵......
  • 学习霍夫曼编码
    霍夫曼编码广泛用于数据压缩算法,其重要性不言而喻。内容:写一个程序读ASCII文件,统计各字符的频率并制定霍夫曼编码表,最后将此文件的内容根据编码表翻译为二进制文件,计算压缩率。代码清单:#include...typedefstruct{floatweight;intparent,lc,rc;}node;#define......
  • DB2数据库怎么查询到多条,但只取首行数据
    DB2数据库怎么查询到多条,但只取首行数据select*fromemployeeFETCHFIRST1ROWSONLY;解释:employee假设是一个数据库表,也就是说在你的查询语句后边加上FETCHFIRST1ROWSONLY就可以了。......