Spring Ioc源码分析系列--Ioc容器注册BeanPostProcessor后置处理器以及事件消息处理

前言

上一篇分析了BeanFactoryPostProcessor的作用,那么这一篇继续在refresh()方法里游荡,相信对Spring熟悉点的朋友,在看完BeanFactoryPostProcessor后,一定会想到Spring里面还有个BeanPostProcessor,那这个东西是什么作用呢?下面会进行介绍,同时由于注册BeanPostProcessor的逻辑比较简单,这里会穿插一下BeanPostProcessor生效的时机和源码逻辑,实际上这部分应该是Bean实例化出现的逻辑。

介绍完这部分之后,会介绍Spring的消息源初始化、广播器的初始以及监听器的初始化。这部分工作完成之后,Spring容器就会进入到Bean的创建过程,因为准备工作已经做得差不多了,容器已经准备好,接下来就是初始化Bean放进去容器里面。

BeanFactoryPostProcessor和BeanPostProcessor之间的区别

这两个的区别还是很显而易见的,主要表现在应用的阶段不同BeanFactoryPostProcessor是对BeanDefinition直接生效的,这更加底层,也更加原始,所以直接使用BeanFactoryPostProcessor会比较少。BeanPostProcessor是对bean实例生效的,相对于对BeanDefinition的处理,这个阶段更加靠后,BeanFactoryPostProcessor阶段bean是尚未初始化出来的,BeanPostProcessor处理的时候已经生成了实例对象,BeanPostProcessor会在对象的实例基础上进行一个更进一步的加工。

不熟悉的朋友看起来可能有点抽象,那么这里举一个例子吧。

BeanFactoryPostProcessor的类比:

假设你要造一个杯子,那么杯子需要一份原材料列表,材质你可以选择铁、铜、金、银等等,样式你可以选择圆型、方形、椭圆等等。假设开始原料选择铁,形状为圆形,那么这一份原料列表对应的就是一个BeanDefinition。原料列表出来后,没什么问题就会按照这一份列表去创建一个杯子。但是有时候需要一些额外的操作,例如对某些BeanDefinition进行检查,假设有一个检查员BeanFactoryPostProcessor去检查每个BeanDefinition。他看到杯子的材质是铁,觉得有失身份,于是把材料改成了金子,于是后续再去创建杯子的时候,就是个金杯了。

BeanPostProcessor的类比:

BeanPostProcessor的处理阶段则要靠后,在上面杯子创建完成之后,才到了BeanPostProcessor出场。BeanPostProcessor会在实例的基础上进行一些加工,拿杯子来举例,上一个阶段拿到的是一个粗糙的杯子,这里会进行一些处理,例如给杯子加点花纹样式,给杯子抛光等等。**注意这些操作都是在一个已有的杯子上进行的,但是请注意,这不是绝对的。**BeanPostProcessor除了能对Bean进行深加工外,还能直接进行Bean替换,类比来说,就是换了个杯子,偷梁换柱。Spring Aop的功能就是这样实现的,把经过代理的Bean放了进去,替换了原有的Bean。

所以比较一下得出一个很明显的结论:

  • BeanFactoryPostProcessor对BeanDefinition生效

  • BeanPostProcessor对bean实例生效

源码分析

registerBeanPostProcessors(beanFactory)

话不多说,下面继续分析refresh()方法里面的子方法,上一篇分析到了第五个子方法,那这篇从第六个registerBeanPostProcessors(beanFactory)开始。

跟进代码,可以看到实现都委托给了PostProcessorRegistrationDelegate#registerBeanPostProcessors(beanFactory, this)方法。

	/**
* Instantiate and register all BeanPostProcessor beans,
* respecting explicit order if given.
* <p>Must be called before any instantiation of application beans.
*
* 实例化并注册所有 BeanPostProcessor bean,如果给定顺序,则按照顺序排序。
* <p>必须在应用程序 bean 的任何实例化之前调用。
*/
protected void registerBeanPostProcessors(ConfigurableListableBeanFactory beanFactory) {
PostProcessorRegistrationDelegate.registerBeanPostProcessors(beanFactory, this);
}

继续跟进,这个方法的逻辑也是比较简单的,跟上篇的BeanFactoryPostProcessor注册类似,这里也会按照优先级去对BeanPostProcessor进行排序然后按顺序进行注册。都是些家常套路了,可以跟着注释去看一下。值得注意的是,这里会额外加入两个BeanPostProcessor,分别为BeanPostProcessorCheckerApplicationListenerDetectorBeanPostProcessorChecker主要是用来记录一些日志,ApplicationListenerDetector是用来检测实现了ApplicationListener但是getBeanNamesForType()没探测出来的漏网之鱼。这里的漏网之鱼可能是一些动态注册的bean或者一些内部类,这里再次获取后会放入到applicationListeners集合里。

	public static void registerBeanPostProcessors(
ConfigurableListableBeanFactory beanFactory, AbstractApplicationContext applicationContext) { // 获取容器中所有的 BeanPostProcessor
String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanPostProcessor.class, true, false); // Register BeanPostProcessorChecker that logs an info message when
// a bean is created during BeanPostProcessor instantiation, i.e. when
// a bean is not eligible for getting processed by all BeanPostProcessors.
int beanProcessorTargetCount = beanFactory.getBeanPostProcessorCount() + 1 + postProcessorNames.length;
// 注册 BeanPostProcessorChecker,当 bean 不符合所有 BeanPostProcessor 处理的条件时,它会在 BeanPostProcessor 实例化期间创建 bean 时记录一条信息消息
beanFactory.addBeanPostProcessor(new BeanPostProcessorChecker(beanFactory, beanProcessorTargetCount)); // Separate between BeanPostProcessors that implement PriorityOrdered,
// Ordered, and the rest.
// 按照顺序分类区分 BeanPostProcessor
List<BeanPostProcessor> priorityOrderedPostProcessors = new ArrayList<>();
List<BeanPostProcessor> internalPostProcessors = new ArrayList<>();
List<String> orderedPostProcessorNames = new ArrayList<>();
List<String> nonOrderedPostProcessorNames = new ArrayList<>();
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);
}
} // First, register the BeanPostProcessors that implement PriorityOrdered.
// 首先注册实现了 PriorityOrdered 接口的 BeanPostProcessor
sortPostProcessors(priorityOrderedPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, priorityOrderedPostProcessors); // Next, register the BeanPostProcessors that implement Ordered.
// 其次注册实现了 Ordered 接口的 BeanPostProcessor
List<BeanPostProcessor> orderedPostProcessors = new ArrayList<>(orderedPostProcessorNames.size());
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); // Now, register all regular BeanPostProcessors.
// 现在到了注册没有实现上述接口的 BeanPostProcessor
List<BeanPostProcessor> nonOrderedPostProcessors = new ArrayList<>(nonOrderedPostProcessorNames.size());
for (String ppName : nonOrderedPostProcessorNames) {
BeanPostProcessor pp = beanFactory.getBean(ppName, BeanPostProcessor.class);
nonOrderedPostProcessors.add(pp);
if (pp instanceof MergedBeanDefinitionPostProcessor) {
internalPostProcessors.add(pp);
}
}
registerBeanPostProcessors(beanFactory, nonOrderedPostProcessors); // Finally, re-register all internal BeanPostProcessors.
// 最后,重新注册所有内部 BeanPostProcessor MergedBeanDefinitionPostProcessor。
sortPostProcessors(internalPostProcessors, beanFactory);
registerBeanPostProcessors(beanFactory, internalPostProcessors); // Re-register post-processor for detecting inner beans as ApplicationListeners,
// moving it to the end of the processor chain (for picking up proxies etc).
// 重新注册用于将内部 bean 检测为 ApplicationListeners 的后处理器,将其移动到处理器链的末尾(用于拾取代理等)。
beanFactory.addBeanPostProcessor(new ApplicationListenerDetector(applicationContext));
}

initMessageSource()

接下来继续进行下一步的准备工作,初始化消息源。这里是默认使用了父类的消息源,如果没有就初始化一个DelegatingMessageSource,这个DelegatingMessageSource会默认将所有的调用都委派到父容器的消息源去解析,如果没有父容器的消息源,那么它不会解析任何消息。

	/**
* Initialize the MessageSource.
* Use parent's if none defined in this context.
* 初始化消息源。如果没有在此上下文中定义,则使用父容器的。
*/
protected void initMessageSource() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 返回当前容器职工是否存在 messageSource,忽略祖先容器
if (beanFactory.containsLocalBean(MESSAGE_SOURCE_BEAN_NAME)) {
this.messageSource = beanFactory.getBean(MESSAGE_SOURCE_BEAN_NAME, MessageSource.class);
// Make MessageSource aware of parent MessageSource.
// 如果存在祖先,并且 messageSource 类型是 HierarchicalMessageSource,则获取祖先的 messageSource 设置到当前 messageSource 里。
if (this.parent != null && this.messageSource instanceof HierarchicalMessageSource) {
HierarchicalMessageSource hms = (HierarchicalMessageSource) this.messageSource;
if (hms.getParentMessageSource() == null) {
// Only set parent context as parent MessageSource if no parent MessageSource
// registered already.
hms.setParentMessageSource(getInternalParentMessageSource());
}
}
if (logger.isTraceEnabled()) {
logger.trace("Using MessageSource [" + this.messageSource + "]");
}
}
else {
// Use empty MessageSource to be able to accept getMessage calls.
// 本地不存在 messageSource,使用空 MessageSource 能够接受 getMessage 调用
DelegatingMessageSource dms = new DelegatingMessageSource();
dms.setParentMessageSource(getInternalParentMessageSource());
this.messageSource = dms;
beanFactory.registerSingleton(MESSAGE_SOURCE_BEAN_NAME, this.messageSource);
if (logger.isTraceEnabled()) {
logger.trace("No '" + MESSAGE_SOURCE_BEAN_NAME + "' bean, using [" + this.messageSource + "]");
}
}
}

initApplicationEventMulticaster()

初始化 ApplicationEventMulticaster,如果上下文中没有定义,则使用 SimpleApplicationEventMulticaster

	/**
* Initialize the ApplicationEventMulticaster.
* Uses SimpleApplicationEventMulticaster if none defined in the context.
*
* 初始化 ApplicationEventMulticaster。
* 如果上下文中没有定义,则使用 SimpleApplicationEventMulticaster。
*
* @see org.springframework.context.event.SimpleApplicationEventMulticaster
*/
protected void initApplicationEventMulticaster() {
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
// 如果本地容器里存在 applicationEventMulticaster,直接使用本地容器里的 applicationEventMulticaster
if (beanFactory.containsLocalBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME)) {
this.applicationEventMulticaster =
beanFactory.getBean(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, ApplicationEventMulticaster.class);
if (logger.isTraceEnabled()) {
logger.trace("Using ApplicationEventMulticaster [" + this.applicationEventMulticaster + "]");
}
}
else {
// 否则使用 SimpleApplicationEventMulticaster 广播器
this.applicationEventMulticaster = new SimpleApplicationEventMulticaster(beanFactory);
// 将给定的单例对象添加到该工厂的单例缓存中
beanFactory.registerSingleton(APPLICATION_EVENT_MULTICASTER_BEAN_NAME, this.applicationEventMulticaster);
if (logger.isTraceEnabled()) {
logger.trace("No '" + APPLICATION_EVENT_MULTICASTER_BEAN_NAME + "' bean, using " +
"[" + this.applicationEventMulticaster.getClass().getSimpleName() + "]");
}
}
}

这个事件广播器是干什么的呢?其实很简单,就是把一个事件广播到所有的ApplicationListener上。可以看一下里面的关键方法SimpleApplicationEventMulticaster#multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType),这里就是获取所有的listener,如果有异步线程池,则异步执行,否则逐个调用。

	@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
// 解析事件的类型,这个type会用于后续的 ListenerCacheKey 缓存 key 构建
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
// 获取线程池
Executor executor = getTaskExecutor();
// 逐个广播事件到 listener,就是将 listener 都遍历调用一遍
for (ApplicationListener<?> listener : getApplicationListeners(event, type)) {
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
} protected void invokeListener(ApplicationListener<?> listener, ApplicationEvent event) {
// 错误处理器,记录任务处理期间发生的错误
ErrorHandler errorHandler = getErrorHandler();
if (errorHandler != null) {
try {
doInvokeListener(listener, event);
}
catch (Throwable err) {
errorHandler.handleError(err);
}
}
else {
doInvokeListener(listener, event);
}
} private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
// 将事件传入listener,完成事件的监听回调
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
// ...
}
}

onRefresh()

这是个空方法,交给子类实现。这里可以用来初始化特定上下文子类中的其他特殊 bean,也是留出来的一个扩展口。

	/**
* Template method which can be overridden to add context-specific refresh work.
* Called on initialization of special beans, before instantiation of singletons.
*
* 可以重写以添加特定于上下文的刷新工作的模板方法。在单例实例化之前调用特殊 bean 的初始化。
*
* <p>This implementation is empty.
* @throws BeansException in case of errors
* @see #refresh()
*/
protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}

registerListeners()

上一步已经初始化完成了广播器,那接下来就是检查侦听器并注册它们。

事件可以按照注册的类型进行区分,可以分为以下三种:

  • 通过addApplicationListener()手动添加进去的
  • 容器里实现了ApplicationListener接口的
  • 容器启动早期需要的事件earlyApplicationEvents,早期事件是需要在这里直接发布的
	/**
* Add beans that implement ApplicationListener as listeners.
* Doesn't affect other listeners, which can be added without being beans.
*
* 添加实现 ApplicationListener 的 bean作为侦听器。
* 不影响其他监听器,可以添加而不是 bean。
*/
protected void registerListeners() {
// Register statically specified listeners first.
// 首先注册静态指定的监听器,也就是通过addApplicationListener(ApplicationListener<?> listener) 注册的listener。
for (ApplicationListener<?> listener : getApplicationListeners()) {
getApplicationEventMulticaster().addApplicationListener(listener);
} // Do not initialize FactoryBeans here: We need to leave all regular beans
// uninitialized to let post-processors apply to them!
// 这里只是获取beanName,是为了避免初始化 bean 导致后置处理器失效
String[] listenerBeanNames = getBeanNamesForType(ApplicationListener.class, true, false);
// 逐个注册listener
for (String listenerBeanName : listenerBeanNames) {
getApplicationEventMulticaster().addApplicationListenerBean(listenerBeanName);
} // Publish early application events now that we finally have a multicaster...
// 发布早期应用程序事件,因为我们终于有了一个广播器......
// 忍辱负重,早期事件存到了这里才能进行发布,因为之前没有广播器
Set<ApplicationEvent> earlyEventsToProcess = this.earlyApplicationEvents;
this.earlyApplicationEvents = null;
if (earlyEventsToProcess != null) {
// 逐个发布事件
for (ApplicationEvent earlyEvent : earlyEventsToProcess) {
getApplicationEventMulticaster().multicastEvent(earlyEvent);
}
}
}

finishBeanFactoryInitialization(beanFactory)

准备工作已经基本完成,接下来就到了finishBeanFactoryInitialization(beanFactory)方法了。从方法名可以看到,这个方法是负责完成此上下文的 bean 工厂的初始化,初始化所有剩余的单例 bean。可以看到这个方法开始也进行了一些准备工作,例如注册类型装换器、占位符处理器以及LoadTimeWeaverAware加载等。最后会调用beanFactory.preInstantiateSingletons()进行对象创建,由于这里是比较复杂的过程,会分几篇文章去详细分析,这篇文章就是大概从表面上走完refresh()方法的源码。

	/**
* Finish the initialization of this context's bean factory,
* initializing all remaining singleton beans.
*
* 完成此上下文的 bean 工厂的初始化,初始化所有剩余的单例 bean。
*
*/
protected void finishBeanFactoryInitialization(ConfigurableListableBeanFactory beanFactory) {
// Initialize conversion service for this context.
// 初始化一个ConversionService用于类型转换,这个ConversionService会在实例化对象的时候用到
if (beanFactory.containsBean(CONVERSION_SERVICE_BEAN_NAME) &&
beanFactory.isTypeMatch(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class)) {
beanFactory.setConversionService(
beanFactory.getBean(CONVERSION_SERVICE_BEAN_NAME, ConversionService.class));
} // Register a default embedded value resolver if no bean post-processor
// (such as a PropertyPlaceholderConfigurer bean) registered any before:
// at this point, primarily for resolution in annotation attribute values.
// 添加一个StringValueResolver,用于处理占位符,可以看到,默认情况下就是使用环境中的属性值来替代占位符中的属性
if (!beanFactory.hasEmbeddedValueResolver()) {
beanFactory.addEmbeddedValueResolver(strVal -> getEnvironment().resolvePlaceholders(strVal));
} // Initialize LoadTimeWeaverAware beans early to allow for registering their transformers early.
// 创建所有的LoadTimeWeaverAware
String[] weaverAwareNames = beanFactory.getBeanNamesForType(LoadTimeWeaverAware.class, false, false);
for (String weaverAwareName : weaverAwareNames) {
getBean(weaverAwareName);
} // Stop using the temporary ClassLoader for type matching.
// 静态织入完成后将临时的类加载器设置为null,所以除了创建LoadTimeWeaverAware时可能会用到临时类加载器,其余情况下都为空
beanFactory.setTempClassLoader(null); // Allow for caching all bean definition metadata, not expecting further changes.
// 将所有的配置信息冻结
beanFactory.freezeConfiguration(); // Instantiate all remaining (non-lazy-init) singletons.
// 开始进行真正的创建
beanFactory.preInstantiateSingletons();
}

finishRefresh()

到这里容器已经准备好了,bean也已经实例化完成,就差最后的一些事件通知和后续的兜底处理。这里比较重要的是会调用到所有实现了LifecycleProcessor#onRefresh()的Bean,在这里可以让生命周期Bean实现很多扩展。其次比较重要的是会发布一个ContextRefreshedEvent事件,通知所有监听器容器已经启动完成,这里就可以实现一些容器启动完成后的回调或者是一些任务等,任君发挥。

	/**
* Finish the refresh of this context, invoking the LifecycleProcessor's
* onRefresh() method and publishing the
* {@link org.springframework.context.event.ContextRefreshedEvent}.
*
* 完成容器的刷新启动,调用所有 LifecycleProcessor#onRefresh() 方法来发布 ContextRefreshedEvent 事件
*
*/
protected void finishRefresh() {
// Clear context-level resource caches (such as ASM metadata from scanning).
// 清除容器上下文级别的资源缓存(例如ASM扫描的元数据)
clearResourceCaches(); // Initialize lifecycle processor for this context.
// 初始化上下文的 lifecycle processor
initLifecycleProcessor(); // Propagate refresh to lifecycle processor first.
// 首先将刷新传播到生命周期处理器。
getLifecycleProcessor().onRefresh(); // Publish the final event.
// 发布最终事件。
publishEvent(new ContextRefreshedEvent(this)); // Participate in LiveBeansView MBean, if active.
// 参与 LiveBeansView MBean(如果处于活动状态)。
LiveBeansView.registerApplicationContext(this);
}

总结

本文的重点有点分散,更像是走马观花,但是分散里的重点毫无疑问是理解和区分BeanFactoryPostProcessorBeanPostProcessor之间的区别,文章开头通过一个例子去类比了一下这二者的作用阶段和分别可以完成什么工作,个人觉得还是比较贴切的,希望能够帮助到理解。

到这里已经基本把refresh()方法走了一遍,当然这里看到的大部分都是一些基础准备工作,最关键的Bean实例化是还没有开始分析的,Bean的实例化会后续分好几篇文章继续去分析。

今天这篇文章是比较简单的,没有太多逻辑,基本上都是一个一个小方法,嵌套不深,因为深入的我都不写了哈哈。

这系列写到这里,才完成了准备工作,接下来的Bean创建才是真正开始了重头戏。那接下来继续慢慢分析吧。

如果有人看到这里,那在这里老话重提。与君共勉,路漫漫其修远兮,吾将上下而求索。

Spring Ioc源码分析系列--Ioc容器注册BeanPostProcessor后置处理器以及事件消息处理的更多相关文章

  1. Spring Ioc源码分析系列--Ioc容器BeanFactoryPostProcessor后置处理器分析

    Spring Ioc源码分析系列--Ioc容器BeanFactoryPostProcessor后置处理器分析 前言 上一篇文章Spring Ioc源码分析系列--Ioc源码入口分析已经介绍到Ioc容器 ...

  2. Spring Ioc源码分析系列--Ioc的基础知识准备

    Spring Ioc源码分析系列--Ioc的基础知识准备 本系列文章代码基于Spring Framework 5.2.x Ioc的概念 在Spring里,Ioc的定义为The IoC Containe ...

  3. Spring Ioc源码分析系列--Ioc源码入口分析

    Spring Ioc源码分析系列--Ioc源码入口分析 本系列文章代码基于Spring Framework 5.2.x 前言 上一篇文章Spring Ioc源码分析系列--Ioc的基础知识准备介绍了I ...

  4. Spring Ioc源码分析系列--Bean实例化过程(一)

    Spring Ioc源码分析系列--Bean实例化过程(一) 前言 上一篇文章Spring Ioc源码分析系列--Ioc容器注册BeanPostProcessor后置处理器以及事件消息处理已经完成了对 ...

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

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

  6. Spring Ioc源码分析系列--前言

    Spring Ioc源码分析系列--前言 为什么要写这个系列文章 首先这是我个人很久之前的一个计划,拖了很久没有实施,现在算是填坑了.其次,作为一个Java开发者,Spring是绕不开的课题.在Spr ...

  7. Spring Ioc源码分析系列--Bean实例化过程(二)

    Spring Ioc源码分析系列--Bean实例化过程(二) 前言 上篇文章Spring Ioc源码分析系列--Bean实例化过程(一)简单分析了getBean()方法,还记得分析了什么吗?不记得了才 ...

  8. Spring Ioc源码分析系列--@Autowired注解的实现原理

    Spring Ioc源码分析系列--@Autowired注解的实现原理 前言 前面系列文章分析了一把Spring Ioc的源码,是不是云里雾里,感觉并没有跟实际开发搭上半毛钱关系?看了一遍下来,对我的 ...

  9. Spring Ioc源码分析系列--自动注入循环依赖的处理

    Spring Ioc源码分析系列--自动注入循环依赖的处理 前言 前面的文章Spring Ioc源码分析系列--Bean实例化过程(二)在讲解到Spring创建bean出现循环依赖的时候并没有深入去分 ...

随机推荐

  1. 『忘了再学』Shell基础 — 7、Bash基本功能(多命令顺序执行)

    目录 1.多命令执行符: 2.多命令执行符&& 3.多命令执行符|| 4.&&和||联合应用 Linux系统支持多条命令顺序执行,就是我可以依次输入多条命令后,统一按E ...

  2. swagger的作用和配置使用

    纯API项目中 引入swagger可以生成可视化的API接口页面     引入包 nuget包: Swashbuckle.AspNetCore(最新稳定版) 配置 1.配置Startup类Config ...

  3. shell脚本编程(一) 变量、条件判断、循环

    目录   1. shell脚本编程   2. 运行 Shell 脚本有两种方法   3. 变量   4. 本地变量   5. 环境变量   6. 参数变量   7. 多行注释   8. if条件判断 ...

  4. SQL语句中关于日期的操作(非常的有用)

    在SQL Server 里的日期数据,我们经常可以用 字段<='2008-5-20'这样的表达式,但在oracle却不可以,因为数据类型不一样 字段是date型,'2008-5-20'是字符型, ...

  5. H5的audio在ios系统的微信上不能自动播放的问题

    前几天有个需求,要在H5页面中添加背景音乐,本以为很easy,却也踩了一些坑,废话不多说,进入正题: 撸完代码测试的时候才发现在安卓手机上背景音乐可以正常播放,但在iphone里的微信和safari中 ...

  6. 多态polymorphism,向上转型和动态方法调度有什么用?

    多态有什么用?马 克  -   t   o - w   i  n:https://blog.csdn.net/qq_44639795/article/details/103117332我给大家想了两个 ...

  7. uniapp中websokcet封装和使用

    1.  websocket.js   封装代码 //是否已经连接上ws let isOpenSocket = false //心跳间隔,单位毫秒 let heartBeatDelay = 3000 l ...

  8. Qt 实现配置 OpenCV 环境,并实现打开图片与调用摄像头

    一.说明 所用QT版本:5.9.1 电脑配置:win10,64位系统 调用的是编译好的:OpenCV-MinGW-Build-4.1.0(稍后放链接) 在大学期间,由于项目需求需要用到QT+openc ...

  9. 如何在 Java 中实现无向环和有向环的检测

    无向环 一个含有环的无向图如下所示,其中有两个环,分别是 0-2-1-0 和 2-3-4-2: 要检测无向图中的环,可以使用深度优先搜索.假设从顶点 0 出发,再走到相邻的顶点 2,接着走到顶点 2 ...

  10. 【面试普通人VS高手系列】Fail-safe机制与Fail-fast机制分别有什么作用

    前段时间一个小伙伴去面试,遇到这样一个问题. "Fail-safe机制与Fail-fast机制分别有什么作用" 他说他听到这个问题的时候,脑子里满脸问号.那么今天我们来看一下,关于 ...