在前面两篇关于容器扩展点的文章中,我们已经完成了对BeanFactoryPostProcessorFactoryBean的学习,对于BeanFactoryPostProcessor而言,它能让我们对容器中的扫描出来的BeanDefinition做出修改以达到扩展的目的,而对于FactoryBean而言,它提供了一种特殊的创建Bean的手段,能让我们将一个对象直接放入到容器中,成为Spring所管理的一个Bean。而我们今天将要学习的BeanPostProcessor不同于上面两个接口,它主要干预的是Spring中Bean的整个生命周期(实例化—属性填充—初始化—销毁),关于Bean的生命周期将在下篇文章中介绍,如果不熟悉暂且知道这个概念即可,下面进入我们今天的正文。

按照惯例,我们先看看官网对BeanPostProcessor的介绍

官网介绍

从这段文字中,我们能获取到如下信息:

  1. BeanPostProcessor接口定义了两个回调方法,通过实现这两个方法我们可以提供自己的实例化以及依赖注入等逻辑。而且,如果我们想要在Spring容器完成实例化,配置以及初始化一个Bean后进行一些定制的逻辑,我们可以插入一个甚至更多的BeanPostProcessor的实现。
  2. 我们可以配置多个BeanPostProcessor,并且只要我们配置的BeanFactoryPostProcessor同时实现了Ordered接口的话,我们还可以控制这些BeanPostProcessor执行的顺序

我们通过一个例子来看看BeanPostProcessor的作用

应用举例

Demo如下:

// 自己实现了一个BeanPostProcessor
@Component
public class MyBeanPostProcessor implements BeanPostProcessor {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("indexService")) {
System.out.println(bean);
System.out.println("bean config invoke postProcessBeforeInitialization");
}
return bean;
} @Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (beanName.equals("indexService")) {
System.out.println(bean);
System.out.println("bean config invoke postProcessAfterInitialization");
}
return bean;
}
} @Component
public class IndexService {
@Autowired
LuBanService luBanService; @Override
public String toString() {
return "IndexService{" +
"luBanService=" + luBanService +
'}';
}
} public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
}
}

运行上面的程序:

IndexService{luBanService=com.dmz.official.extension.entity.LuBanService@5e025e70}
bean config invoke postProcessBeforeInitialization
IndexService{luBanService=com.dmz.official.extension.entity.LuBanService@5e025e70}
bean config invoke postProcessAfterInitialization

*从上面的执行结果我们可以得出一个结论,BeanPostProcessor接口中的两个方法的执行时机在属性注入之后。*因为从打印的结果我们可以发现,IndexService中的luBanService属性已经被注入了。

接口继承关系

由于BeanPostProcessor这个接口Spring本身内置的实现类就有很多,所以这里我们暂且不分析其实现类,就从接口的定义上来分析它的作用,其接口的UML类图如下:

  1. BeanPostProcessor,这个接口是我们Bean的后置处理器的顶级接口,其中主要包含了两个方法
// 在Bean初始化前调用
@Nullable
default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
// 在Bean初始化前调用
@Nullable
default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
return bean;
}
  1. InstantiationAwareBeanPostProcessor,继承了BeanPostProcessor接口,并在此基础上扩展了四个方法,其中方法postProcessPropertyValues已经在5.1版本中被废弃了
// 在Bean实例化之前调用
@Nullable
default Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException {
return null;
}
// 在Bean实例化之后调用
default boolean postProcessAfterInstantiation(Object bean, String beanName) throws BeansException {
return true;
} // 我们采用注解时,Spring通过这个方法完成了属性注入
@Nullable
default PropertyValues postProcessProperties(PropertyValues pvs, Object bean, String beanName)
throws BeansException {
return null;
} // 在5.1版本中已经被废弃了
@Deprecated
@Nullable
default PropertyValues postProcessPropertyValues(
PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName) throws BeansException {
return pvs;
}

大部分情况下我们在扩展时都不会用到上面的postProcessPropertiespostProcessPropertyValues,如果在某些场景下,不得不用到这两个方法,那么请注意,在实现postProcessProperties必须返回null,否则postProcessPropertyValues方法的逻辑不会执行。

  1. SmartInstantiationAwareBeanPostProcessor,继续扩展了上面的接口,并多提供了三个方法
// 预测Bean的类型,主要是在Bean还没有创建前我们可以需要获取Bean的类型
@Nullable
default Class<?> predictBeanType(Class<?> beanClass, String beanName) throws BeansException {
return null;
}
// Spring使用这个方法完成了构造函数的推断
@Nullable
default Constructor<?>[] determineCandidateConstructors(Class<?> beanClass, String beanName)
throws BeansException { return null;
}
// 主要为了解决循环依赖,Spring内部使用这个方法主要是为了让早期曝光的对象成为一个“合格”的对象
default Object getEarlyBeanReference(Object bean, String beanName) throws BeansException {
return bean;
}

这个接口中的三个方法一般是在Spring内部使用,我们可以关注上这个接口上的一段java doc

This interface is a special purpose interface, mainly for
internal use within the framework. In general, application-provided
post-processors should simply implement the plain {@link BeanPostProcessor}
interface or derive from the {@link InstantiationAwareBeanPostProcessorAdapter}
class

上面这段文字很明确的指出了这个接口的设计是为了一些特殊的目的,主要是在Spring框架的内部使用,通常来说我们提供的Bean的后置处理器只要实现BeanPostProcessor或者InstantiationAwareBeanPostProcessorAdapter即可。正常情况下,我们在扩展时不需要考虑这几个方法。

  1. DestructionAwareBeanPostProcessor,这个接口直接继承了BeanPostProcessor,同时多提供了两个方法,主要用于Bean在进行销毁时进行回调
// 在Bean被销毁前调用
void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException;
// 判断是否需要被销毁,默认都需要
default boolean requiresDestruction(Object bean) {
return true;
}
  1. MergedBeanDefinitionPostProcessor,这个接口也直接继承了BeanPostProcessor,但是多提供了两个方法。
// Spring内部主要使用这个方法找出了所有需要注入的字段,同时做了缓存
void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName); // 主要用于在BeanDefinition被修改后,清除容器中的缓存
default void resetBeanDefinition(String beanName) {
}

源码分析

我们带着两个问题去阅读源码:

  1. 容器中这么多BeanPostProcessor,它们会按什么顺序执行呢?
  2. BeanPostProcessor接口中这么多方法,它们的执行时机是什么时候呢?

接下来我们解决这两个问题

执行顺序

在Spring内部,当去执行一个BeanPostProcessor一般都是采用下面这种形式的代码:

for (BeanPostProcessor bp : getBeanPostProcessors()) {
// 判断属于某一类后置处理器
if (bp instanceof SmartInstantiationAwareBeanPostProcessor) {
// 执行逻辑
}
}
}

getBeanPostProcessors()获取到的BeanPostProcessor其实就是一个list集合,所以我们要分析BeanPostProcessor的执行顺序,其实就是分析这个list集合中的数据是通过什么样的顺序添加进来的,我们来看看之前说的一个Spring的执行流程图:



我们这次要分析的代码就是其中的3-6步骤,代码如下:

public static void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) {
// 1.获取容器中已经注册的Bean的名称,根据BeanDefinition中获取BeanName
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false);
// 2.通过addBeanPostProcessor方法添加的BeanPostProcessor以及注册到容器中的BeanPostProcessor的总数量
int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
// 3.添加一个BeanPostProcessorChecker,主要用于日志记录
beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount)); // 保存同时实现了BeanPostProcessor跟PriorityOrdered接口的后置处理器
List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
// 保存实现了MergedBeanDefinitionPostProcessor接口的后置处理器
List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
// 保存同时实现了BeanPostProcessor跟Ordered接口的后置处理器的名字
List<String> orderedPostProcessorNames = new ArrayList<>();
// 保存同时实现了BeanPostProcessor但没有实现任何排序接口的后置处理器的名字
List<String> nonOrderedPostProcessorNames = new ArrayList<>(); // 4.遍历所有的后置处理器的名字,并根据不同类型将其放入到上面申明的不同集合中
// 同时会将实现了PriorityOrdered接口的后置处理器创建出来
// 如果实现了MergedBeanDefinitionPostProcessor接口,放入到internalPostProcessors
for (String ppName : postProcessorNames) {
if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
priorityOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
} else if (beanFactory.isTypeMatch(ppName, Ordered.class)) {
orderedPostProcessorNames.add(ppName);
} else {
nonOrderedPostProcessorNames.add(ppName);
}
} // 5.将priorityOrderedPostProcessors集合排序
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
// 6.将priorityOrderedPostProcessors集合中的后置处理器添加到容器中
registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors); // 7.遍历所有实现了Ordered接口的后置处理器的名字,并进行创建
// 如果实现了MergedBeanDefinitionPostProcessor接口,放入到internalPostProcessors
List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>();
for (String ppName : orderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
orderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
// 排序及将其添加到容器中
sortPostProcessors(orderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, orderedPostProcessors); // 7.遍历所有实现了常规后置处理器(没有实现任何排序接口)的名字,并进行创建
// 如果实现了MergedBeanDefinitionPostProcessor接口,放入到internalPostProcessors
List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>();
for (String ppName : nonOrderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
nonOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
// 8.这里需要注意下,常规后置处理器不会调用sortPostProcessors进行排序
registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors); // 9.对internalPostProcessors进行排序并添加到容器中
sortPostProcessors(internalPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, internalPostProcessors); // 10.最后添加的这个后置处理器主要为了可以检测到所有的事件监听器
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
}

疑惑代码解读

下面对上述代码中大家可能存疑的地方进行一波分析:

  1. 获取容器中已经注册的Bean的名称,根据BeanDefinition中获取BeanName

这里主要是根据已经注册在容器中的BeanDefinition,这些BeanDefinition既包括程序员自己注册到容器中的,也包括Spring自己注册到容器中。注意这些后置处理器目前没有被创建,只是以BeanDefinition的形式存在于容器中,所以如果此时调用getBeanPostProcessors(),是拿不到这些后置处理器的,至于容器什么时候注册了后置处理器的BeanDefinition,大家可以先自行阅读1-1步骤的源码,我在后续文章中会分析,当前就暂时先跳过了

  1. 通过addBeanPostProcessor方法添加的BeanPostProcessor以及注册到容器中的BeanPostProcessor的总数量

这里主要是获取容器中已经存在的BeanPostProcessor的数量再加上已经被扫描出BeanDefinition的后置处理器的数量(这些后置处理器还没有被创建出来),最后再加1。这里就有两个问题

  • 容器中已经存在的BeanPostProcessor是从哪里来的?

分为两个来源,第一,容器启动时,自身调用了addBeanPostProcessor添加了后置处理器;第二,程序员手动调用了addBeanPostProcessor方法添加了后置处理器,第二种情况很少见,代码形如下面这种形式:

AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
ac.register(Config.class);
ac.getBeanFactory().addBeanPostProcessor(new MyBeanPostProcessor());
ac.refresh();

容器又是在什么时候添加的后置处理器呢?大家可以自己阅读第3-3步骤的源码。在第3-5步也添加了一个后置处理器,由于代码比较深,不建议大家现在去看,关注我后续的更新即可,这些问题都会在后面的文章中解决

  • 为什么最后还需要加1呢?

这个就跟我们将要分析的第三行代码相关

  1. 添加一个BeanPostProcessorChecker,主要用于日志记录

我们看下BeanPostProcessorChecker这个类的源码:

private static final class BeanPostProcessorChecker implements BeanPostProcessor {

		private static final Log logger = LogFactory.getLog(BeanPostProcessorChecker.class);

		private final ConfigurableListableBeanFactory beanFactory;

		private final int beanPostProcessorTargetCount;

		public BeanPostProcessorChecker(ConfigurableListableBeanFactory beanFactory, int beanPostProcessorTargetCount) {
this.beanFactory = beanFactory;
this.beanPostProcessorTargetCount = beanPostProcessorTargetCount;
} @Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
} @Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (!(bean instanceof BeanPostProcessor) && !isInfrastructureBean(beanName)
&& this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount) {
if (logger.isInfoEnabled()) {
logger.info("Bean '" + beanName + "' of type [" + bean.getClass().getName()
+ "] is not eligible for getting processed by all BeanPostProcessors "
+ "(for example: not eligible for auto-proxying)");
}
}
return bean;
} private boolean isInfrastructureBean(@Nullable String beanName) {
if (beanName != null && this.beanFactory.containsBeanDefinition(beanName)) {
BeanDefinition bd = this.beanFactory.getBeanDefinition(beanName);
return (bd.getRole() == RootBeanDefinition.ROLE_INFRASTRUCTURE);
}
return false;
} }

这段代码中我们主要需要关注的就是以下两个方法:

  • isInfrastructureBean,这个方法主要检查当前处理的Bean是否是一个Spring自身需要创建的Bean,而不是程序员所创建的Bean(通过@Component,@Configuration等注解或者XML配置等)。
  • postProcessAfterInitialization,我们可以看到这个方法内部只是做了一个判断,只要当前创建的Bean不是一个后置处理器并且不是一个Spring自身需要创建的基础的Bean,最后还有一个判断this.beanFactory.getBeanPostProcessorCount() < this.beanPostProcessorTargetCount,这是什么意思呢?其实意思就是说在创建这个Bean时容器中的后置处理器还没有完全创建完。这个判断也能解释我们上面遗留的一个问题。之所以要加1是为了方便判断,否则还需要进行等号判断。
  1. 上面代码段落中标注的4到7之间的代码就不解释了,比较简单,可能大家需要注意的就是在registerBeanPostProcessors方法中调用了一个addBeanPostProcessor(BeanPostProcessor beanPostProcessor)方法,我们看下这个方法的执行逻辑:
public void addBeanPostProcessor(BeanPostProcessor beanPostProcessor) {
Assert.notNull(beanPostProcessor, "BeanPostProcessor must not be null");
// 可以看到,后添加进来的beanPostProcessor会覆盖之前添加的
this.beanPostProcessors.remove(beanPostProcessor);
// 这个状态变量会影响之后的执行流程,我们只需要知道一旦添加了一个InstantiationAwareBeanPostProcessor就会将变量置为true即可
if (beanPostProcessor instanceof InstantiationAwareBeanPostProcessor) {
this.hasInstantiationAwareBeanPostProcessors = true;
}
if (beanPostProcessor instanceof DestructionAwareBeanPostProcessor) {
this.hasDestructionAwareBeanPostProcessors = true;
}
this.beanPostProcessors.add(beanPostProcessor);
}
  1. 注意下第8段代码,对于没有实现任何排序接口的后置处理器,Spring是不会进行排序操作的。即使你添加了@Order注解也没有任何作用。这里只是针对Spring Framework。
  2. 第10段代码中又添加了一个后置处理器,添加的这个后置处理器主要为了可以检测到所有的事件监听器,我们看下它的代码(这里只分析下它的几个核心方法):
// 1.singletonNames保存了所有将要创建的Bean的名称以及这个Bean是否是单例的映射关系
// 这个方法会在对象被创建出来后,属性注入之前执行
public void postProcessMergedBeanDefinition(RootBeanDefinition beanDefinition, Class<?> beanType, String beanName) {
this.singletonNames.put(beanName, beanDefinition.isSingleton());
} // 2.整个Bean创建过程中最后一个阶段执行,在对象被初始化后执行
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) {
if (bean instanceof ApplicationListener) {
// 3.判断当前这个Bean是不是单例,如果是的话,直接添加到容器的监听器集合中
Boolean flag = this.singletonNames.get(beanName);
if (Boolean.TRUE.equals(flag)) {
// 添加到容器的监听器集合中
this.applicationContext.addApplicationListener((ApplicationListener<?>) bean);
}
// 4.如果不是单例的,并且又是一个嵌套的Bean,那么打印日志,提示用户也就是程序员内嵌的Bean只有在单例的情况下才能作为时间监听器
else if (Boolean.FALSE.equals(flag)) {
if (logger.isWarnEnabled() && !this.applicationContext.containsBean(beanName)) {
// inner bean with other scope - can't reliably process events
logger.warn("Inner bean '" + beanName + "' implements ApplicationListener interface " +
"but is not reachable for event multicasting by its containing ApplicationContext " +
"because it does not have singleton scope. Only top-level listener beans are allowed " +
"to be of non-singleton scope.");
}
this.singletonNames.remove(beanName);
}
}
return bean;
}

这个后置处理器主要是针对事件监听器(Spring中的事件监听机制在后续文章中会做介绍,这里如果有的同学不知道的话只需要暂时记住有这个概念即可,把它当作Spring中一个特殊的Bean即可)。上面代码中的第3步第4步可能会让人比较迷惑,实际上在我之前画的执行流程图中的3-10步,Spring就已经注册过了一次监听器,在3-10步骤中,其实Spring已经通过String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false)这段代码拿到了所有的名字,那么我们思考一个问题,为什么Spring不直接根据这些名字去过滤创建的Bean,而要通过一个特定的后置处理器去进行处理呢?比如可以通过下面这种逻辑:

if(listenerBeanNames.contains(beanName)){
this.applicationContext.addApplicationListener(bean);
}

这里主要是因为一种特殊的Bean,它会由Spring来创建,自身确不在Spring容器中,这种特殊的Bean就是嵌套bean。注意这里说的是嵌套Bean,不是内部类,是由形如下面的XML配置的Bean

<bean class="com.dmz.official.service.IndexService" id="indexService">
<property name="luBanService">
<bean class="com.dmz.official.service.LuBanService"/>
</property>
<property name="dmzService" ref="dmzService"/>
</bean>

在上面的例子中,LuBanService就是一个嵌套的Bean。

假设我们上面的LuBanService是一个事件监听器,那么在getBeanNamesForType这个方法执行时,是无法获取到这个Bean的名称的。所以Spring专门提供了上述的那个后置处理器,用于处理这种嵌套Bean的情况,但是所提供的嵌套Bean必须要是单例的。

在分析执行时机时,我们先要知道Spring在创建一个Bean时要经历过哪些阶段,这里其实涉及到Bean的生命周期了,在下篇文章中我会专门分析Spring的生命周期,这篇文章中为了说明后置处理器的执行时机,先进行一些大致的介绍,整体创建Bean的流程可以画图如下:

总结

在这篇文章中,我们学习过了Spring中最后一个扩展点BeanPostProcessor,通过这篇文章,我们算是对BeanPostProcessor中的方法以及执行顺序有了大致的了解,但是到目前为止我们还不知道每个方法具体的执行时机是什么时候,这个问题我打算把它放到下篇文章中,结合官网中关于Spring生命周期回调方法的相关内容一起分析。到此为止对于官网中提到了三个容器的扩展点就学习完了,可以简单总结如下:

1、BeanPostProcessor,主要用于干预Bean的创建过程。

2、BeanFactroyPostProcessor,主要用于针对容器中的BeanDefinition

3、FactoryBean,主要用于将一个对象直接放入到Spring容器中,同时可以封装复杂的对象的创建逻辑

Spring官网阅读(八)容器的扩展点(三)(BeanPostProcessor)的更多相关文章

  1. Spring官网阅读(十八)Spring中的AOP

    文章目录 什么是AOP AOP中的核心概念 切面 连接点 通知 切点 引入 目标对象 代理对象 织入 Spring中如何使用AOP 1.开启AOP 2.申明切面 3.申明切点 切点表达式 excecu ...

  2. Spring官网阅读 | 总结篇

    接近用了4个多月的时间,完成了整个<Spring官网阅读>系列的文章,本文主要对本系列所有的文章做一个总结,同时也将所有的目录汇总成一篇文章方便各位读者来阅读. 下面这张图是我整个的写作大 ...

  3. Spring官网阅读(十七)Spring中的数据校验

    文章目录 Java中的数据校验 Bean Validation(JSR 380) 使用示例 Spring对Bean Validation的支持 Spring中的Validator 接口定义 UML类图 ...

  4. Spring官网阅读(十六)Spring中的数据绑定

    文章目录 DataBinder UML类图 使用示例 源码分析 bind方法 doBind方法 applyPropertyValues方法 获取一个属性访问器 通过属性访问器直接set属性值 1.se ...

  5. Spring官网阅读(一)容器及实例化

    从今天开始,我们一起过一遍Spring的官网,一边读,一边结合在路神课堂上学习的知识,讲一讲自己的理解.不管是之前关于动态代理的文章,还是读Spring的官网,都是为了之后对Spring的源码做更全面 ...

  6. Spring官网阅读(三)自动注入

    上篇文章我们已经学习了1.4小结中关于依赖注入跟方法注入的内容.这篇文章我们继续学习这结中的其他内容,顺便解决下我们上篇文章留下来的一个问题-----注入模型. 文章目录 前言: 自动注入: 自动注入 ...

  7. Spring官网阅读(二)(依赖注入及方法注入)

    上篇文章我们学习了官网中的1.2,1.3两小节,主要是涉及了容器,以及Spring实例化对象的一些知识.这篇文章我们继续学习Spring官网,主要是针对1.4小节,主要涉及到Spring的依赖注入.虽 ...

  8. Spring官网阅读(六)容器的扩展点(一)BeanFactoryPostProcessor

    之前的文章我们已经学习完了BeanDefinition的基本概念跟合并,其中多次提到了容器的扩展点,这篇文章我们就开始学习这方面的知识.这部分内容主要涉及官网中的1.8小结.按照官网介绍来说,容器的扩 ...

  9. Spring官网阅读(七)容器的扩展点(二)FactoryBean

    在上篇文章中我们已经对容器的第一个扩展点(BeanFactoryPostProcessor)做了一系列的介绍.其中主要介绍了Spring容器中BeanFactoryPostProcessor的执行流程 ...

随机推荐

  1. android学习笔记——计时器实现

    根据android疯狂讲义来写写代码,在博客里面将这些写过的代码汇总一下.实现的功能很简单:就是一个简单的计时器,点击启动按钮会开始计时,当计时到20秒时会自动停止计时. 界面如下: 界面代码: &l ...

  2. Starlims Client Request Portal 客户申请门户

    用户可以直接在starlims对外的"客户申请门户"上发起检验申请,并追踪检验进度等. 工作流程图示如下:

  3. Thinking in Java,Fourth Edition(Java 编程思想,第四版)学习笔记(四)之Operators

    At the lowest level, data in Java is manipulated using operators Using Java Operators An operator ta ...

  4. ES5和ES6基本介绍与面向对象的基本思想

    ES6和ES5基本介绍 let  const  关键词定义变量 let 定义变量 特点: let 定义的变量,不会进行预解析  let 定义的变量,与 forEach() 中的变量类似  每次执行都会 ...

  5. 【three.js第四课】自定义材料、贴图。

    1.先去下载6张不同的图片素材放到项目中. 2.在[three.js第三课]的代码基础上添加自定义的材料 //自定义材料 cubeMaterial 数组 //map:用于加载图片,THREE.Text ...

  6. L10机器

    机器翻译和数据集 机器翻译(MT):将一段文本从一种语言自动翻译为另一种语言,用神经网络解决这个问题通常称为神经机器翻译(NMT). 主要特征:输出是单词序列而不是单个单词. 输出序列的长度可能与源序 ...

  7. 一篇文章快速搞懂Redis的慢查询分析

    什么是慢查询? 慢查询,顾名思义就是比较慢的查询,但是究竟是哪里慢呢?首先,我们了解一下Redis命令执行的整个过程: 发送命令 命令排队 命令执行 返回结果 在慢查询的定义中,统计比较慢的时间段指的 ...

  8. Python算法题:金字塔

    代码如下: #Python金字塔练习 """ 最大层数:max_level 当前层数:current_level 金字塔正序时: 每层的空格=最大层数-当前层数 每层的星 ...

  9. SpringBoot+Netty+WebSocket实现实时通信

    这篇随笔暂时不讲原理,首先搭建起一个简单的可以实现通信的Demo.之后的一系列随笔会进行一些原理上的分享. 不过在这之前大家最好了解一下Netty的线程模型和NIO编程模型,会对它的整体逻辑有所了解. ...

  10. kubernetes的无状态服务和有状态服务介绍

    无状态服务 1)是指该服务运行的实例不会在本地存储需要持久化的数据,并且多个实例对于同一个请求响应的结果是完全一致的 2)多个实例可以共享相同的持久化数据.例如: nginx实例和tomcat实例 3 ...