阅读之前要注意的东西:本文就是主打流水账式的源码阅读,主导的是一个参考,主要内容需要看官自己去源码中验证。全系列文章基于 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.上一步也提到,它只完成了实例化,但是如果有属性需要填充呢? 参考如下的例子:


  1. <beans>
  2. <bean id="myBean" class="demo.self.MySimpleBean">
  3. <!-- property 是常量 -->
  4. <property name="name" value="Bokerr"/>
  5. <property name="age" value="27"/>
  6. </bean>
  7. <bean id="myBean" class="demo.self.MyBean">
  8. <!-- property 是一个bean -->
  9. <property name="bean1"><ref bean="populate1"/></property>
  10. </bean>
  11. <bean id="populate1" class="demo.self.populateBean"/>
  12. </beans>

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

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

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

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

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

你需要记住: PropertyValues(它是 bean的全部 property 解析后得到的元数据信息)

它是来自于 mbd 的。(也就是 BeanDefinition),它的对象声明周期受到 BeanFactory 管理;

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

二、populateBean 的重要操作

  1. /**
  2. * Populate the bean instance in the given BeanWrapper with the property values
  3. * from the bean definition.
  4. *
  5. * @param beanName the name of the bean
  6. * @param mbd the bean definition for the bean
  7. * @param bw the BeanWrapper with bean instance
  8. */
  9. protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {
  10. if (bw == null) {
  11. if (mbd.hasPropertyValues()) {
  12. // bean 有属性需要被填充,但是对象包装器 bw 为空
  13. throw new BeanCreationException(
  14. mbd.getResourceDescription(), beanName, "Cannot apply property values to null instance");
  15. } else {
  16. // 包装器为空,且不需要 填充任何属性
  17. // Skip property population phase for null instance.
  18. return;
  19. }
  20. }
  21. // 给 InstantiationAwareBeanPostProcessors 最后一次机会,通过属性注入改变 bean.
  22. if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
  23. for (BeanPostProcessor bp : getBeanPostProcessors()) {
  24. if (bp instanceof InstantiationAwareBeanPostProcessor) {
  25. InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
  26. // 所有 InstantiationAwareBeanPostProcessor.postProcessAfterInstantiation()的返回值将决定
  27. // [是/否] 继续进行property属性填充
  28. if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
  29. return;
  30. }
  31. }
  32. }
  33. }
  34. // 所有需要注入的属性, 对应的bean缓存 【这里明确从 BeanDefinition 上获取的 property 的定义】
  35. PropertyValues pvs = (mbd.hasPropertyValues() ? mbd.getPropertyValues() : null);
  36. int resolvedAutowireMode = mbd.getResolvedAutowireMode();
  37. if (resolvedAutowireMode == AUTOWIRE_BY_NAME || resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
  38. MutablePropertyValues newPvs = new MutablePropertyValues(pvs);
  39. // Add property values based on autowire by name if applicable.
  40. if (resolvedAutowireMode == AUTOWIRE_BY_NAME) {
  41. // {重要操作1-根据Bean名称注入} [下文将展开详说]
  42. autowireByName(beanName, mbd, bw, newPvs); // 提取依赖的bean 保存到 pvs
  43. }
  44. // Add property values based on autowire by type if applicable.
  45. if (resolvedAutowireMode == AUTOWIRE_BY_TYPE) {
  46. // {重要操作1-根据Bean类型注入 <Class>} [下文将展开详说]
  47. autowireByType(beanName, mbd, bw, newPvs); // 提取依赖的bean 缓存到 pvs
  48. }
  49. pvs = newPvs;
  50. }
  51. // beanFactory 是否已经注册了: bean 实例化相关的 "后置处理器"
  52. boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
  53. // 是否需要依赖检查
  54. boolean needsDepCheck = (mbd.getDependencyCheck() != AbstractBeanDefinition.DEPENDENCY_CHECK_NONE);
  55. if (hasInstAwareBpps || needsDepCheck) {
  56. if (pvs == null) {
  57. pvs = mbd.getPropertyValues();
  58. }
  59. // 过滤属性描述符,为依赖检查做准备
  60. PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
  61. if (hasInstAwareBpps) {// 后处理器 ??
  62. for (BeanPostProcessor bp : getBeanPostProcessors()) {
  63. if (bp instanceof InstantiationAwareBeanPostProcessor) {
  64. InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
  65. // 主要这里是相同的后置处理器,不同的方法
  66. // 字段填充的前置检查
  67. pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
  68. if (pvs == null) {
  69. return;
  70. }
  71. }
  72. }
  73. }
  74. if (needsDepCheck) {// 依赖检查, depend-on 属性,spring-3.0弃用
  75. checkDependencies(beanName, mbd, filteredPds, pvs);
  76. }
  77. }
  78. if (pvs != null) {
  79. // 属性填充 {重要操作2} [下文将展开详说]
  80. applyPropertyValues(beanName, mbd, bw, pvs); // 将 propertyValue 应用/注入 到 bean 中
  81. }
  82. }

这里重点说两个操作:

  • 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名称注入

  1. /**
  2. * Fill in any missing property values with references to
  3. * other beans in this factory if autowire is set to "byName".
  4. *
  5. * @param beanName the name of the bean we're wiring up.
  6. * Useful for debugging messages; not used functionally.
  7. * @param mbd bean definition to update through autowiring
  8. * @param bw the BeanWrapper from which we can obtain information about the bean
  9. * @param pvs the PropertyValues to register wired objects with
  10. */
  11. protected void autowireByName(
  12. String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
  13. // 从 BeanDefinition 读取,非-常量注入的属性,这里返回的都是 property 指向别的 bean的场景
  14. // 这里会忽略: 基本类型、字符串常量等等 property
  15. String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
  16. for (String propertyName : propertyNames) {
  17. if (containsBean(propertyName)) {
  18. // 递归加载依赖的bean
  19. Object bean = getBean(propertyName);
  20. pvs.add(propertyName, bean);
  21. // 在容器中记录 bean 及其依赖的 bean之间的关系, 不妨进去瞅瞅
  22. registerDependentBean(propertyName, beanName);
  23. if (logger.isDebugEnabled()) {
  24. logger.debug("Added autowiring by name from bean name '" + beanName +
  25. "' via property '" + propertyName + "' to bean named '" + propertyName + "'");
  26. }
  27. } else {
  28. // 根据beanName 未定位到bean, 记录到日志。[说明该 property 所依赖的可能并不是一个 bean,可能只是一个普通的常量。]
  29. if (logger.isTraceEnabled()) {
  30. logger.trace("Not autowiring property '" + propertyName + "' of bean '" + beanName +
  31. "' by name: no matching bean found");
  32. }
  33. }
  34. }
  35. }

上文的内容很简单:

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

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

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

3.3 根据 Bean类型注入

  1. /**
  2. * Abstract method defining "autowire by type" (bean properties by type) behavior.
  3. * <p>This is like PicoContainer default, in which there must be exactly one bean
  4. * of the property type in the bean factory. This makes bean factories simple to
  5. * configure for small namespaces, but doesn't work as well as standard Spring
  6. * behavior for bigger applications.
  7. *
  8. * @param beanName the name of the bean to autowire by type
  9. * @param mbd the merged bean definition to update through autowiring
  10. * @param bw the BeanWrapper from which we can obtain information about the bean
  11. * @param pvs the PropertyValues to register wired objects with
  12. */
  13. protected void autowireByType(
  14. String beanName, AbstractBeanDefinition mbd, BeanWrapper bw, MutablePropertyValues pvs) {
  15. TypeConverter converter = getCustomTypeConverter(); // 获取自定义类型转换器
  16. if (converter == null) {
  17. converter = bw;
  18. }
  19. Set<String> autowiredBeanNames = new LinkedHashSet<>(4); // 缓存依赖关系映射
  20. // 从 BeanDefinition 读取,非-常量注入的属性,这里返回的都是 property 指向别的 bean的场景
  21. // 这里会忽略: 基本类型、字符串常量等等 property
  22. String[] propertyNames = unsatisfiedNonSimpleProperties(mbd, bw);
  23. for (String propertyName : propertyNames) {
  24. try {
  25. // 我们这是根据 bean Type 注入,所以这里浅浅的获取了一下, bean的类对象反射信息
  26. PropertyDescriptor pd = bw.getPropertyDescriptor(propertyName);
  27. // Don't try autowiring by type for type Object: never makes sense,
  28. // even if it technically is a unsatisfied, non-simple property.
  29. if (Object.class != pd.getPropertyType()) {
  30. MethodParameter methodParam = BeanUtils.getWriteMethodParameter(pd); // 探测属性指定的set 方法
  31. // Do not allow eager init for type matching in case of a prioritized post-processor.
  32. boolean eager = !(bw.getWrappedInstance() instanceof PriorityOrdered);
  33. // 依赖描述符
  34. DependencyDescriptor desc = new AutowireByTypeDependencyDescriptor(methodParam, eager);
  35. // 解析指定 beanName 的属性所匹配的值,
  36. // 并把解析到的属性名称存储在 autowiredBeanNames 中
  37. // 当存在多个属性被封装到一起时:
  38. // @Autowired List<A> aList; 会把 所有 A 类型的bean 都注入其中 autowiredBeans (复数个)
  39. Object autowiredArgument = resolveDependency(desc, beanName, autowiredBeanNames, converter); // 寻找类型匹配的 bean
  40. if (autowiredArgument != null) {
  41. pvs.add(propertyName, autowiredArgument);
  42. }
  43. for (String autowiredBeanName : autowiredBeanNames) {
  44. registerDependentBean(autowiredBeanName, beanName); // 注册依赖关系
  45. if (logger.isDebugEnabled()) {
  46. logger.debug("Autowiring by type from bean name '" + beanName + "' via property '" +
  47. propertyName + "' to bean named '" + autowiredBeanName + "'");
  48. }
  49. }
  50. autowiredBeanNames.clear();
  51. }
  52. } catch (BeansException ex) {
  53. throw new UnsatisfiedDependencyException(mbd.getResourceDescription(), beanName, propertyName, ex);
  54. }
  55. }
  56. }

这里是根据 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()

  1. /**
  2. * Apply the given property values, resolving any runtime references
  3. * to other beans in this bean factory. Must use deep copy, so we
  4. * don't permanently modify this property.
  5. *
  6. * @param beanName the bean name passed for better exception information
  7. * @param mbd the merged bean definition
  8. * @param bw the BeanWrapper wrapping the target object
  9. * @param pvs the new property values
  10. */
  11. protected void applyPropertyValues(String beanName, BeanDefinition mbd, BeanWrapper bw, PropertyValues pvs) {
  12. if (pvs.isEmpty()) {
  13. return; // 不需要任何的注入操作
  14. }
  15. if (System.getSecurityManager() != null && bw instanceof BeanWrapperImpl) {
  16. // 权限处理
  17. ((BeanWrapperImpl) bw).setSecurityContext(getAccessControlContext());
  18. }
  19. MutablePropertyValues mpvs = null;
  20. List<PropertyValue> original;
  21. if (pvs instanceof MutablePropertyValues) {
  22. // 实际上,如果执行过: autowireByType 和 autowireByName 后注入的 <property, propertyValue>
  23. mpvs = (MutablePropertyValues) pvs;
  24. if (mpvs.isConverted()) {
  25. // 根据 name / type 解析到的 bean 已经被 转化为 具体类型 那么直接进行注入操作
  26. // Shortcut: use the pre-converted values as-is.
  27. try {
  28. // 将解析完成的 <property,propertyValue> 逐个应用到包装器: BeanWrapper
  29. bw.setPropertyValues(mpvs);
  30. return;
  31. } catch (BeansException ex) {
  32. throw new BeanCreationException(
  33. mbd.getResourceDescription(), beanName, "Error setting property values", ex);
  34. }
  35. }
  36. original = mpvs.getPropertyValueList();
  37. } else {
  38. // 同样 applyPropertyValues 也可能在别的场景中被调用:
  39. // 该场景中,PropertyValues 尚未进行任意的 property 的注入操作
  40. // 所以进入当前分支,继续往下分析。
  41. original = Arrays.asList(pvs.getPropertyValues());
  42. }
  43. // 获取自定义-转换器 其实这个应该并不陌生,在 populateBean().autowireByType() 方法中见过
  44. // 其实到这里,可以简单做个推论了: 接下来的篇幅中,所作的事情,可能会跟 property 的注入操作类似
  45. TypeConverter converter = getCustomTypeConverter();
  46. if (converter == null) {
  47. converter = bw; // 默认使用 bean 包装器
  48. }
  49. // Spring表达式语言(SpEL) 解析器 #{bean.xxx} ${xx.key}
  50. // 可由 ApplicationContext 注入
  51. BeanDefinitionValueResolver valueResolver = new BeanDefinitionValueResolver(this, beanName, mbd, converter);
  52. // Create a deep copy, resolving any references for values.
  53. List<PropertyValue> deepCopy = new ArrayList<>(original.size());
  54. boolean resolveNecessary = false;
  55. for (PropertyValue pv : original) {
  56. // 遍历 property 属性,将需要转换的属性,转换为对应的类型
  57. if (pv.isConverted()) {
  58. // 已经转化过,直接添加 [BeanDefinition 缓存应用 或者 前边的环节已经处理过]
  59. deepCopy.add(pv);
  60. } else {
  61. // 先转换 后添加
  62. String propertyName = pv.getName();
  63. // 原始值
  64. Object originalValue = pv.getValue();
  65. // 包含SpEL表达式的解析
  66. Object resolvedValue = valueResolver.resolveValueIfNecessary(pv, originalValue);
  67. Object convertedValue = resolvedValue;
  68. // property 是否可覆盖?
  69. // 根据包装器 bw 判断 property 字段可写 && 并且 property 不是(索引/嵌套类型的属性)。
  70. // 自引入概念:property 应该是一个 "原子属性"
  71. boolean convertible = bw.isWritableProperty(propertyName) &&
  72. !PropertyAccessorUtils.isNestedOrIndexedProperty(propertyName);
  73. if (convertible) {
  74. // 可以写入,且是一个"原子属性", 那么就可以进行转化了
  75. // 这里不太适合再钻进去了,除非spring源码出bug了,否则如下的逻辑是不太有可能接触到的,
  76. // 可以简单做个盖棺定论: 这里会把 resolvedValue 转化为 property 字段所期望的值类型:
  77. // String: 2023-07-10 -> Date: 2023-07-10 00:00:00.000
  78. // String: false -> Boolean: false
  79. convertedValue = convertForProperty(resolvedValue, propertyName, bw, converter);
  80. }
  81. // Possibly store converted value in merged bean definition,
  82. // in order to avoid re-conversion for every created bean instance.
  83. if (resolvedValue == originalValue) {
  84. // 转换后的值(对象) 等价 原始值
  85. if (convertible) {
  86. // PropertyValue 是元数据 BeanDefinition 的一环,这里等价为缓存
  87. // 避免重复的转换操作
  88. pv.setConvertedValue(convertedValue);
  89. }
  90. deepCopy.add(pv);
  91. } else if (convertible && originalValue instanceof TypedStringValue &&
  92. !((TypedStringValue) originalValue).isDynamic() &&
  93. !(convertedValue instanceof Collection || ObjectUtils.isArray(convertedValue))) {
  94. // 转化后的值不等于原始值
  95. // && 支持转换
  96. // && 原始值是String
  97. // && 原始值不是动态的(常量)
  98. // && 转换后的值,不是集合、不是数组
  99. pv.setConvertedValue(convertedValue); // BeanDefinition 设置缓存
  100. deepCopy.add(pv);
  101. } else {
  102. // 上述情形之外的情况, 可以看到,区别是,这里没有被当作缓存设回 BeanDefinition 中
  103. resolveNecessary = true;
  104. deepCopy.add(new PropertyValue(pv, convertedValue));
  105. }
  106. }
  107. }
  108. if (mpvs != null && !resolveNecessary) {
  109. // mpvs 同样是 BeanDefinition 所包含的信息,这里也可视作缓存设置
  110. mpvs.setConverted();
  111. }
  112. // Set our (possibly massaged) deep copy.
  113. try {
  114. // 将解析完成的 <property,propertyValue> 逐个应用到包装器: BeanWrapper
  115. bw.setPropertyValues(new MutablePropertyValues(deepCopy));
  116. } catch (BeansException ex) {
  117. throw new BeanCreationException(
  118. mbd.getResourceDescription(), beanName, "Error setting property values", ex);
  119. }
  120. }

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

《系列二》-- 9、bean属性填充的更多相关文章

  1. 在Spring Bean实例过程中,如何使用反射和递归处理的Bean属性填充?

    作者:小傅哥 博客:https://bugstack.cn 沉淀.分享.成长,让自己和他人都能有所收获! <Spring 手撸专栏>目录 [x] 第 1 章:开篇介绍,我要带你撸 Spri ...

  2. Spring Boot实践——用外部配置填充Bean属性的几种方法

    引用:https://blog.csdn.net/qq_17586821/article/details/79802320 spring boot允许我们把配置信息外部化.由此,我们就可以在不同的环境 ...

  3. Spring 源码(16)Spring Bean的创建过程(7)属性填充

    知识回顾 上一篇介绍了Spring中三级缓存的singletonObjects.earlySingletonObjects.singletonFactories,Spring在处理循环依赖时在实例化后 ...

  4. Spring 系列教程之 bean 的加载

    Spring 系列教程之 bean 的加载 经过前面的分析,我们终于结束了对 XML 配置文件的解析,接下来将会面临更大的挑战,就是对 bean 加载的探索.bean 加载的功能实现远比 bean 的 ...

  5. 深入理解Spring系列之六:bean初始化

    转载 https://mp.weixin.qq.com/s/SmtqoELzBEdZLo8wsSvUdQ <深入理解Spring系列之四:BeanDefinition装载前奏曲>中提到,对 ...

  6. swoft| 源码解读系列二: 启动阶段, swoft 都干了些啥?

    date: 2018-8-01 14:22:17title: swoft| 源码解读系列二: 启动阶段, swoft 都干了些啥?description: 阅读 sowft 框架源码, 了解 sowf ...

  7. Spring原理系列一:Spring Bean的生命周期

    一.前言 在日常开发中,spring极大地简化了我们日常的开发工作.spring为我们管理好bean, 我们拿来就用.但是我们不应该只停留在使用层面,深究spring内部的原理,才能在使用时融汇贯通. ...

  8. Spring Ioc源码分析系列--容器实例化Bean的四种方法

    Spring Ioc源码分析系列--实例化Bean的几种方法 前言 前面的文章Spring Ioc源码分析系列--Bean实例化过程(二)在讲解到bean真正通过那些方式实例化出来的时候,并没有继续分 ...

  9. WPF入门教程系列二十三——DataGrid示例(三)

    DataGrid的选择模式 默认情况下,DataGrid 的选择模式为“全行选择”,并且可以同时选择多行(如下图所示),我们可以通过SelectionMode 和SelectionUnit 属性来修改 ...

  10. [Unity3D插件]2dtoolkit系列二 动画精灵的创建以及背景图的无限滚动

    经过昨天2dtoolkit系列教程一的推出,感觉对新手还有有一定的启发作用,引导学习使用unity 2dToolKit插件的使用过程,今天继续系列二——动画精灵的创建,以及背景图的无限循环滚动,在群里 ...

随机推荐

  1. [转帖]深入JVM - Code Cache内存池

    深入JVM - Code Cache内存池 1. 本文内容 本文简要介绍JVM的 Code Cache(本地代码缓存池). 2. Code Cache 简要介绍 简单来说,JVM会将字节码编译为本地机 ...

  2. [转帖]AMD处理器ZEN一代之国产化海光

    https://huataihuang.gitbook.io/cloud-atlas-draft/os/linux/kernel/cpu/amd_hygon   2020年国产化处理器受到了广泛的关注 ...

  3. [转帖]构建 TiFlash 副本

    https://docs.pingcap.com/zh/tidb/stable/create-tiflash-replicas#%E6%8C%89%E8%A1%A8%E6%9E%84%E5%BB%BA ...

  4. [转帖]PostgreSQL 压测工具pgbench

    1.命令 pgbench --help pgbench is a benchmarking tool for PostgreSQL. Usage:   pgbench [OPTION]... [DBN ...

  5. [转帖]利用Python调用outlook自动发送邮件

    ↓↓↓欢迎关注我的公众号,在这里有数据相关技术经验的优质原创文章↓↓↓ 使用Python发送邮件有两种方式,一种是使用smtp调用邮箱的smtp服务器,另一种是直接调用程序直接发送邮件.而在outlo ...

  6. 学到一个编码技巧:用重复写入代替if判断,减少程序分支

    作者:张富春(ahfuzhang),转载时请注明作者和引用链接,谢谢! cnblogs博客 zhihu Github 公众号:一本正经的瞎扯 近期阅读了rust标准库的hashbrown库(也就是一个 ...

  7. Vue基础系统文章06---导入和导出

    一.导入和导出 如果想要在一个Js文件中用另一个js文件的代码 1.将js文件中的变量和函数导出 let a = "aaaa" function show() { console. ...

  8. 压缩软件 WinRAR 去广告

    别去中国的那个代理网站下载 去国外的官网下载英文版或者湾湾版的, 这样用网上的rarreg.key文件方式就没有广告了, 不然中国的就是有广告. 这里是湾湾版的链接: https://pan.baid ...

  9. 在bat中切换盘符

    在bat代码中如何在不同的盘符中切换?直接输入盘符的名字,前面不要加cd,示例 cd %~dp0 d: cd D:\Python37 e: cd E:\Code\KSFramework c: cd C ...

  10. 手撕Vue-数据驱动界面改变上

    经过上一篇的介绍,已经实现了监听数据的变化,接下来就是要实现数据变化后,界面也跟着变化,这就是数据驱动界面改变. 想要实现数据变化之后更新UI界面,我们可以使用发布订阅模式来实现,先定义一个观察者类, ...