通过BeanPostProcessor理解Spring中Bean的生命周期及AOP原理

Spring源码解析(十一)Spring扩展接口InstantiationAwareBeanPostProcessor解析

Spring bean的生命周期

Spring作为一个优秀的框架,拥有良好的可扩展性。Spring对对象的可扩展性主要就是依靠InstantiationAwareBeanPostProcessor和BeanPostProcessor来实现的。

  • InstantiationAwareBeanPostProcessor 主要是作用于实例化阶段。
  • BeanPostProcessor 主要作用与 初始化阶段。

注册BeanPostProcessor

InstantiationAwareBeanPostProcessor代表了Spring的另外一段生命周期:实例化。先区别一下Spring Bean的实例化和初始化两个阶段的主要作用:

1、实例化—-实例化的过程是一个创建Bean的过程,即调用Bean的构造函数,单例的Bean放入单例池中

2、初始化—-初始化的过程是一个赋值的过程,即调用Bean的setter,设置Bean的属性

之前的BeanPostProcessor作用于过程(2)前后,现在的InstantiationAwareBeanPostProcessor则作用于过程(1)前后;

InstantiationAwareBeanPostProcessor接口继承BeanPostProcessor接口,它内部提供了3个方法,再加上BeanPostProcessor接口内部的2个方法,所以实现这个接口需要实现5个方法。InstantiationAwareBeanPostProcessor接口的主要作用在于目标对象的实例化过程中需要处理的事情,包括实例化对象的前后过程以及实例的属性设置

  1. public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
  2. this();
  3. register(annotatedClasses);
  4. refresh();
  5. }

applicationContext构造方法中调用refresh()方法

refresh() 方法中这里主要关心两个放

  • registerBeanPostProcessors(beanFactory); 注册BeanPostProcessor
  • finishBeanFactoryInitialization(beanFactory); 注册余下的Singletions Bean
  1. public void refresh() throws BeansException, IllegalStateException {
  2. // Register bean processors that intercept bean creation.
  3. registerBeanPostProcessors(beanFactory);
  4. // Instantiate all remaining (non-lazy-init) singletons.
  5. finishBeanFactoryInitialization(beanFactory);
  6. }
  1. public static void registerBeanPostProcessors(
  2. ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
  3.  
  4. String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
  5.  
  6. // Register BeanPostProcessorChecker that logs an info message when
  7. // a bean is created during BeanPostProcessor instantiation, i.e. when
  8. // a bean is not eligible for getting processed by all BeanPostProcessors.
  9. int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
  10. beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount));
  11.  
  12. // Separate between BeanPostProcessors that implement PriorityOrdered,
  13. // Ordered, and the rest.
  14. List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
  15. List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
  16. List<String> orderedPostProcessorNames = new ArrayList<>();
  17. List<String> nonOrderedPostProcessorNames = new ArrayList<>();
  18. for (String ppName : postProcessorNames) {
  19. if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
  20. BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
  21. priorityOrderedPostProcessors.add(pp);
  22. if (pp instanceof MergedBeanDefinitionPostProcessor) {
  23. internalPostProcessors.add(pp);
  24. }
  25. }
  26. else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
  27. orderedPostProcessorNames.add(ppName);
  28. }
  29. else {
  30. nonOrderedPostProcessorNames.add(ppName);
  31. }
  32. }
  33.  
  34. // First, register the BeanPostProcessors that implement PriorityOrdered.
  35. sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
  36. registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors);
  37.  
  38. // Next, register the BeanPostProcessors that implement Ordered.
  39. List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>();
  40. for (String ppName : orderedPostProcessorNames) {
  41. BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
  42. orderedPostProcessors.add(pp);
  43. if (pp instanceof MergedBeanDefinitionPostProcessor) {
  44. internalPostProcessors.add(pp);
  45. }
  46. }
  47. sortPostProcessors(orderedPostProcessors, beanFactory);
  48. registerBeanPostProcessors(beanFactory, orderedPostProcessors);
  49.  
  50. // Now, register all regular BeanPostProcessors.
  51. List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>();
  52. for (String ppName : nonOrderedPostProcessorNames) {
  53. BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
  54. nonOrderedPostProcessors.add(pp);
  55. if (pp instanceof MergedBeanDefinitionPostProcessor) {
  56. internalPostProcessors.add(pp);
  57. }
  58. }
  59. registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors);
  60.  
  61. // Finally, re-register all internal BeanPostProcessors.
  62. sortPostProcessors(internalPostProcessors, beanFactory);
  63. registerBeanPostProcessors(beanFactory, internalPostProcessors);
  64.  
  65. // Re-register post-processor for detecting inner beans as ApplicationListeners,
  66. // moving it to the end of the processor chain (for picking up proxies etc).
  67. beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
  68. }

registerBeanPostProcessors

通过beanFactory.getBeanNamesForType来获取所有BeanPostProcessor。

BeanPostProcessor按优先级分为PriorityOrdered,Ordered和其他的,对他们分别进行操作。

  • 先beanFactory.getBean进性实例化,
  • 再使用sortPostProcessors() 进行排序,
  • 最后registerBeanPostProcessors()进行注册。

BeanFactory.getBean()(注册Bean)

  1. protected <T> T doGetBean(final String name, @Nullable final Class<T> requiredType,
  2. @Nullable final Object[] args, boolean typeCheckOnly) throws BeansException {
  3.  
  4. final String beanName = transformedBeanName(name);
  5. Object bean;
  6. //缓存
  7. // Eagerly check singleton cache for manually registered singletons.
  8. Object sharedInstance = getSingleton(beanName);
  9. if (sharedInstance != null && args == null) {
  10. bean = getObjectForBeanInstance(sharedInstance, name, beanName, null);
  11. }
  12.  
  13. else {
  14. // Fail if we're already creating this bean instance:
  15. // We're assumably within a circular reference.
  16. //判断循环引用,抛异常
  17. if (isPrototypeCurrentlyInCreation(beanName)) {
  18. throw new BeanCurrentlyInCreationException(beanName);
  19. }
  20.  
  21. // Check if bean definition exists in this factory.
  22. BeanFactory parentBeanFactory = getParentBeanFactory();
  23. // this.beanDefinitionMap.containsKey(beanName); 就是判断有没有BeanDefinition
  24. if (parentBeanFactory != null && !containsBeanDefinition(beanName)) {
  25. // Not found -> check parent.
  26. String nameToLookup = originalBeanName(name);
  27. if (parentBeanFactory instanceof AbstractBeanFactory) {
  28. return ((AbstractBeanFactory) parentBeanFactory).doGetBean(
  29. nameToLookup, requiredType, args, typeCheckOnly);
  30. }
  31. else if (args != null) {
  32. // Delegation to parent with explicit args.
  33. return (T) parentBeanFactory.getBean(nameToLookup, args);
  34. }
  35. else {
  36. // No args -> delegate to standard getBean method.
  37. return parentBeanFactory.getBean(nameToLookup, requiredType);
  38. }
  39. }
  40.  
  41. if (!typeCheckOnly) {
  42. markBeanAsCreated(beanName);
  43. }
  44.  
  45. try {
  46. final RootBeanDefinition mbd = getMergedLocalBeanDefinition(beanName);
  47. checkMergedBeanDefinition(mbd, beanName, args);
  48.  
  49. // Guarantee initialization of beans that the current bean depends on.
  50. // 获取bean的依赖,实例化bean前先实例化依赖。
  51. String[] dependsOn = mbd.getDependsOn();
  52. if (dependsOn != null) {
  53. for (String dep : dependsOn) {
  54. if (isDependent(beanName, dep)) {
  55. throw new BeanCreationException(mbd.getResourceDescription(), beanName,
  56. "Circular depends-on relationship between '" + beanName + "' and '" + dep + "'");
  57. }
  58. registerDependentBean(dep, beanName);
  59. try {
  60. getBean(dep);
  61. }
  62. catch (NoSuchBeanDefinitionException ex) {
  63. throw new BeanCreationException(mbd.getResourceDescription(), beanName,
  64. "'" + beanName + "' depends on missing bean '" + dep + "'", ex);
  65. }
  66. }
  67. }
  68. //创建实例
  69. // Create bean instance.
  70. if (mbd.isSingleton()) {
  71. sharedInstance = getSingleton(beanName, () -> {
  72. try {
  73. return createBean(beanName, mbd, args);
  74. }
  75. catch (BeansException ex) {
  76. // Explicitly remove instance from singleton cache: It might have been put there
  77. // eagerly by the creation process, to allow for circular reference resolution.
  78. // Also remove any beans that received a temporary reference to the bean.
  79. destroySingleton(beanName);
  80. throw ex;
  81. }
  82. });
  83. bean = getObjectForBeanInstance(sharedInstance, name, beanName, mbd);
  84. }
  85.  
  86. else if (mbd.isPrototype()) {
  87. // It's a prototype -> create a new instance.
  88. Object prototypeInstance = null;
  89. try {
  90. beforePrototypeCreation(beanName);
  91. prototypeInstance = createBean(beanName, mbd, args);
  92. }
  93. finally {
  94. afterPrototypeCreation(beanName);
  95. }
  96. bean = getObjectForBeanInstance(prototypeInstance, name, beanName, mbd);
  97. }
  98.  
  99. else {
  100. String scopeName = mbd.getScope();
  101. final Scope scope = this.scopes.get(scopeName);
  102. if (scope == null) {
  103. throw new IllegalStateException("No Scope registered for scope name '" + scopeName + "'");
  104. }
  105. try {
  106. Object scopedInstance = scope.get(beanName, () -> {
  107. beforePrototypeCreation(beanName);
  108. try {
  109. return createBean(beanName, mbd, args);
  110. }
  111. finally {
  112. afterPrototypeCreation(beanName);
  113. }
  114. });
  115. bean = getObjectForBeanInstance(scopedInstance, name, beanName, mbd);
  116. }
  117. }
  118. }
  119. }
  120.  
  121. // Check if required type matches the type of the actual bean instance.
  122. if (requiredType != null && !requiredType.isInstance(bean)) {
  123. try {
  124. T convertedBean = getTypeConverter().convertIfNecessary(bean, requiredType);
  125. if (convertedBean == null) {
  126. throw new BeanNotOfRequiredTypeException(name, requiredType, bean.getClass());
  127. }
  128. return convertedBean;
  129. }
  130. }
  131. return (T) bean;
  132. }

doGetBean

  • 先getSingleton()从缓存中获取Bean,如果没有则创建。
  • 创建过程先检查有无循环依赖,有则抛出异常。
  • 实例化bean前先实例化所依赖的对象。

createBean,调用的开端

  1. @Override
  2. protected Object createBean(String beanName, RootBeanDefinition mbd, Object[] args) throws BeanCreationException {
  3. // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
  4. Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
  5. if (bean != null) {
  6. return bean;
  7. }
  8. //省略....
  9. Object beanInstance = doCreateBean(beanName, mbdToUse, args);
  10. return beanInstance;
  11.  
  12. }

上面代码里面看到,在执行doCreateBean之前有resolveBeforeInstantiation方法;doCreateBean是创建bean的方法;
resolveBeforeInstantiation是 判断执行InstantiationAwareBeanPostProcessor.postProcessBeforeInstantiation的接方法实现;
下面看看执行的依据:

执行 postProcessBeforeInstantiation方法的时机

  1. /**
  2. * Apply before-instantiation post-processors, resolving whether there is a
  3. * before-instantiation shortcut for the specified bean.
  4. * @param beanName the name of the bean
  5. * @param mbd the bean definition for the bean
  6. * @return the shortcut-determined bean instance, or {@code null} if none
  7. */
  8. protected Object resolveBeforeInstantiation(String beanName, RootBeanDefinition mbd) {
  9. Object bean = null;
  10. //如果beforeInstantiationResolved还没有设置或者是false(说明还没有需要在实例化前执行的操作)
  11. if (!Boolean.FALSE.equals(mbd.beforeInstantiationResolved)) {
  12. // 判断是否有注册过InstantiationAwareBeanPostProcessor类型的bean
  13. if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
  14. Class<?> targetType = determineTargetType(beanName, mbd);
  15. if (targetType != null) {
  16. //执行
  17. bean = applyBeanPostProcessorsBeforeInstantiation(targetType, beanName);
  18. if (bean != null) {
  19. bean = applyBeanPostProcessorsAfterInitialization(bean, beanName);
  20. }
  21. }
  22. }
  23. mbd.beforeInstantiationResolved = (bean != null);
  24. }
  25. return bean;
  26. }
  1. protected Object applyBeanPostProcessorsBeforeInstantiation(Class<?> beanClass, String beanName) {
  2. for (BeanPostProcessor bp : getBeanPostProcessors()) {
  3. if (bp instanceof InstantiationAwareBeanPostProcessor) {
  4. InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
  5. Object result = ibp.postProcessBeforeInstantiation(beanClass, beanName);
  6. //只要有一个result不为null;后面的所有 后置处理器的方法就不执行了,直接返回(所以执行顺序很重要)
  7. if (result != null) {
  8. return result;
  9. }
  10. }
  11. }
  12. return null;
  13. }
  1. @Override
  2. public Object applyBeanPostProcessorsAfterInitialization(Object existingBean, String beanName)
  3. throws BeansException {
  4.  
  5. Object result = existingBean;
  6. for (BeanPostProcessor beanProcessor : getBeanPostProcessors()) {
  7. result = beanProcessor.postProcessAfterInitialization(result, beanName);
  8. //如果返回null;后面的所有 后置处理器的方法就不执行,直接返回(所以执行顺序很重要)
  9. if (result == null) {
  10. return result;
  11. }
  12. }
  13. return result;
  14. }

上面代码说明:

如果postProcessBeforeInstantiation方法返回了Object是null;那么就直接返回,调用doCreateBean方法();
如果postProcessBeforeInstantiation返回不为null;说明修改了bean对象;然后这个时候就立马执行postProcessAfterInitialization方法(注意这个是初始化之后的方法,也就是通过这个方法实例化了之后,直接执行初始化之后的方法;中间的实例化之后 和 初始化之前都不执行);
在调用postProcessAfterInitialization方法时候如果返回null;那么就直接返回,调用doCreateBean方法();(初始化之后的方法返回了null,那就需要调用doCreateBean生成对象了)
在调用postProcessAfterInitialization时返回不为null;那这个bean就直接返回给ioc容器了 初始化之后的操作 是这里面最后一个方法了;

通过上面的描述,我们其实可以在这里生成一个代理类:原文

postProcessAfterInstantiation调用的地方

代码往后面执行走到了populateBean里面;这个主要是给bean填充属性的;实例化已经在 pupulateBean之前已经完成了

  1. //实例化bean;选择不同策略来实例化bean
  2. instanceWrapper = createBeanInstance(beanName, mbd, args);
  1. protected void populateBean(String beanName, RootBeanDefinition mbd, BeanWrapper bw) {
  2.  
  3. //省略。。。。
  4. if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) {
  5. for (BeanPostProcessor bp : getBeanPostProcessors()) {
  6. if (bp instanceof InstantiationAwareBeanPostProcessor) {
  7. InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
  8. //执行postProcessAfterInstantiation方法
  9. if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) {
  10. continueWithPropertyPopulation = false;
  11. break;
  12. }
  13. }
  14. }
  15. }
  16. //省略....
  17.  
  18. //下面的代码是判断是否需要执行postProcessPropertyValues;改变bean的属性
  19. boolean hasInstAwareBpps = hasInstantiationAwareBeanPostProcessors();
  20. boolean needsDepCheck = (mbd.getDependencyCheck() != RootBeanDefinition.DEPENDENCY_CHECK_NONE);
  21.  
  22. if (hasInstAwareBpps || needsDepCheck) {
  23. PropertyDescriptor[] filteredPds = filterPropertyDescriptorsForDependencyCheck(bw, mbd.allowCaching);
  24. if (hasInstAwareBpps) {
  25. for (BeanPostProcessor bp : getBeanPostProcessors()) {
  26. if (bp instanceof InstantiationAwareBeanPostProcessor) {
  27. InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp;
  28. pvs = ibp.postProcessPropertyValues(pvs, filteredPds, bw.getWrappedInstance(), beanName);
  29. if (pvs == null) {
  30. return;
  31. }
  32. }
  33. }
  34. }
  35. if (needsDepCheck) {
  36. checkDependencies(beanName, mbd, filteredPds, pvs);
  37. }
  38. }
  39.  
  40. //这里才是正在讲 属性值 真正的设置的我们的实例对象里面;之前postProcessPropertyValues这个还只是单纯的改变PropertyValues
  41. //最后还是要通过PropertyValues 设置属性到实例对象里面的
  42. applyPropertyValues(beanName, mbd, bw, pvs);
  43.  
  44. }

这个postProcessAfterInstantiation返回值要注意,因为它的返回值是决定要不要调用postProcessPropertyValues方法的其中一个因素(因为还有一个因素是mbd.getDependencyCheck());如果该方法返回false,并且不需要check,那么postProcessPropertyValues就会被忽略不执行;如果返回true,postProcessPropertyValues就会被执行

postProcessPropertyValues调用的地方

原文

postProcessPropertyValues修改属性,但是要注意postProcessAfterInstantiation返回true;

InstantiationAwareBeanPostProcessor总结

1.   InstantiationAwareBeanPostProcessor接口继承BeanPostProcessor接口,它内部提供了3个方法,再加上BeanPostProcessor接口内部的2个方法,所以实现这个接口需要实现5个方法。InstantiationAwareBeanPostProcessor接口的主要作用在于目标对象的实例化过程中需要处理的事情,包括实例化对象的前后过程以及实例的属性设置
2.   postProcessBeforeInstantiation方法是最先执行的方法,它在目标对象实例化之前调用,该方法的返回值类型是Object,我们可以返回任何类型的值。由于这个时候目标对象还未实例化,所以这个返回值可以用来代替原本该生成的目标对象的实例(比如代理对象)。如果该方法的返回值代替原本该生成的目标对象,后续只有postProcessAfterInitialization方法会调用,其它方法不再调用;否则按照正常的流程走
3.   postProcessAfterInstantiation方法在目标对象实例化之后调用,这个时候对象已经被实例化,但是该实例的属性还未被设置,都是null。因为它的返回值是决定要不要调用postProcessPropertyValues方法的其中一个因素(因为还有一个因素是mbd.getDependencyCheck());如果该方法返回false,并且不需要check,那么postProcessPropertyValues就会被忽略不执行;如果返回true,postProcessPropertyValues就会被执行
4.   postProcessPropertyValues方法对属性值进行修改(这个时候属性值还未被设置,但是我们可以修改原本该设置进去的属性值)。如果postProcessAfterInstantiation方法返回false,该方法可能不会被调用。可以在该方法内对属性值进行修改
5.   父接口BeanPostProcessor的2个方法postProcessBeforeInitialization和postProcessAfterInitialization都是在目标对象被实例化之后,并且属性也被设置之后调用的
6.   Instantiation表示实例化,Initialization表示初始化。实例化的意思在对象还未生成,初始化的意思在对象已经生成

通过BeanPostProcessor理解Spring中Bean的生命周期的更多相关文章

  1. 深入理解Spring中bean的生命周期

    [Spring中bean的生命周期] bean的生命周期 1.以ApplocationContext上下文单例模式装配bean为例,深入探讨bean的生命周期: (1).生命周期图: (2).具体事例 ...

  2. 如果你每次面试前都要去背一篇Spring中Bean的生命周期,请看完这篇文章

    前言 当你准备去复习Spring中Bean的生命周期的时候,这个时候你开始上网找资料,很大概率会看到下面这张图: 先不论这张图上是否全面,但是就说这张图吧,你是不是背了又忘,忘了又背? 究其原因在于, ...

  3. JAVA面试题:Spring中bean的生命周期

    Spring 中bean 的生命周期短暂吗? 在spring中,从BeanFactory或ApplicationContext取得的实例为Singleton,也就是预设为每一个Bean的别名只能维持一 ...

  4. Spring中Bean的生命周期及其扩展点

    原创作品,可以转载,但是请标注出处地址http://www.cnblogs.com/V1haoge/p/6106456.html Spring中Bean的管理是其最基本的功能,根据下面的图来了解Spr ...

  5. 简:Spring中Bean的生命周期及代码示例

    (重要:spring bean的生命周期. spring的bean周期,装配.看过spring 源码吗?(把容器启动过程说了一遍,xml解析,bean装载,bean缓存等)) 完整的生命周期概述(牢记 ...

  6. Spring中 bean的生命周期

    为什么要了解Spring中 bean的生命周期? 有时候我们需要自定义bean的创建过程,因此了解Spring中 bean的生命周期非常重要. 二话不说先上图: 在谈具体流程之前先看看Spring官方 ...

  7. Spring官网阅读(十)Spring中Bean的生命周期(下)

    文章目录 生命周期概念补充 实例化 createBean流程分析 doCreateBean流程分析 第一步:factoryBeanInstanceCache什么时候不为空? 第二步:创建对象(crea ...

  8. 一次性讲清楚spring中bean的生命周期之二:FactoryBean的前世今生

    前言 在<spring中FactoryBean是什么bean>一文中,带着小伙伴学习了spring中的FactoryBean,了解了到了FactoryBean其实是一种生产Bean的bea ...

  9. Spring:Spring中bean的生命周期

    Spring中,从BeanFactory或ApplicationContext取得的实例为Singleton(单例模式),就是预设为每一个Bean的别名只能维持一个实例,而不是每次都产生一个新的对象使 ...

随机推荐

  1. Spring框架学习

    没有状态变化的对象(无状态对象):应当做成单例. Spring-framework的下载:http://repo.spring.io/release/org/springframework/sprin ...

  2. 利用React Native 从0到1 开发一款兼容IOS和android的APP(仿造京东)

    最近有一部电视剧叫做<微微一笑很傻逼>里面有个男猪脚,人们都叫他大神~我觉得吧~大神是相对的~所以~啥事都得谦虚! 好了 今天介绍的是如何从0到1利用React Native开发一款兼容I ...

  3. 基于 Python 和 Pandas 的数据分析(1)

    基于 Python 和 Pandas 的数据分析(1) Pandas 是 Python 的一个模块(module), 我们将用 Python 完成接下来的数据分析的学习. Pandas 模块是一个高性 ...

  4. Qt532.线程(_beginthread)

    1.(20180928)环境:Win7x64.Qt5.3.2 MSVC2010 OpenGL.ms2010 2.测试代码: ZC:我记得 之前在 VC6.vs08 上,还要选择 使用的是哪种 运行时线 ...

  5. [原][osg][osgEarth]EarthManipulator关于oe漫游器的handle部分解读以及修改(仿照谷歌,修改oe漫游器中focal(视角切换)功能 续 二)

    bool EarthManipulator::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa) ...

  6. Android之StrictMode

    1. StrictMode是什么? StrictMode is a developer tool which detects things you might be doing by accident ...

  7. 力扣(LeetCode) 66. 加一

    给定一个由整数组成的非空数组所表示的非负整数,在该数的基础上加一. 最高位数字存放在数组的首位, 数组中每个元素只存储一个数字. 你可以假设除了整数 0 之外,这个整数不会以零开头. 示例 1: 输入 ...

  8. OSI 七层和五层

  9. H5多媒体(用面向对象的方法控制视频、音频播放、暂停、延时暂停)

    视频,音频播放器会是我们在工作中用到的一些h5新标签,它自带一些属性,比如暂停播放,快进快退,但是,我们经常不用原生的样式或者方法,我们需要自定义这些按钮来达到我们需要的样式,也需要我们自定义来实现一 ...

  10. python--calc计算器的小程序

    x写一个计算器的小程序,正在筹备中......钱不够,演员未定,剧本暂无,请稍等