真的好奇害死猫!之前写过几个SpringBoot应用,但是一直没搞明白应用到底是怎么启动的,心里一直有点膈应。好吧,趁有空去看了下源码,写下这篇博客作为学习记录吧!

个人拙见,若哪里有理解不对的地方,请各路大神指正,小弟不胜感激!

一.应用启动类

@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}

开发SpirngBoot应用时,入口类就这简单的几行。但是却完成了N多服务的初始化、加载和发布。那么这几行代码究竟干了什么呢,SpringBoot应用到底是怎么启动的。

二.@SpringBootApplication注解

2.1.SpringBootApplication注解

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@SpringBootConfiguration
@EnableAutoConfiguration
@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
public @interface SpringBootApplication {

@SpringBootApplication=@SpringBootConfiguration+@EnableAutoConfiguration+@ComponentScan

2.2.@SpringBootConfiguration

/**
* Indicates that a class Spring Boot application
* {@link Configuration @Configuration}. Can be used as an alternative to the Spring's
* standard {@code @Configuration} annotation so that configuration can be found
* automatically (for example in tests).
*/
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Configuration
public @interface SpringBootConfiguration { }

SpringBootConfiguration注解和Spring的@Configuration注解作用一样。标注当前类是配置类,并会将当前类内声明的一个或多个以@Bean注解标记的方法的实例纳入到spring容器中。比如容器加载时,会生成Hello的Bean加载到IOC容器中。

@SpringBootConfiguration
public class ExampleConfig {
@Bean
public void Hello(){
System.out.println("hello");
}
}

2.3.@EnableAutoConfiguration

@SuppressWarnings("deprecation")
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage
@Import(EnableAutoConfigurationImportSelector.class)
public @interface EnableAutoConfiguration { }

这个注解是SpringBoot能进行自动配置的关键。@Import注解用于导入配置类,我们看下导入类EnableAutoConfigurationImportSelector。容器刷新时,会调用AutoConfigurationImportSelector类的selectImports方法,扫描META-INF/spring.factories文件自动配置类(key为EnableAutoConfiguration),然后Spring容器处理配置类。(对Spring的一些加载过程不清晰,我是相当的迷啊)

2.4.@ComponentScan

@ComponentScan(excludeFilters = {
@Filter(type = FilterType.CUSTOM, classes = TypeExcludeFilter.class),
@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class) })
/**
* Configures component scanning directives for use with @{@link Configuration} classes.
* Provides support parallel with Spring XML's {@code <context:component-scan>} element.
*
* <p>Either {@link #basePackageClasses} or {@link #basePackages} (or its alias
* {@link #value}) may be specified to define specific packages to scan. If specific
* packages are not defined, scanning will occur from the package of the
* class that declares this annotation.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Repeatable(ComponentScans.class)
public @interface ComponentScan

@ComponentScan扫描指定的包路径,若未指定包路径,则以声明这个注解的类作为基本包路径。比如@SpringBootApplication就没有指定包路径,则DemoApplication的包路径将作为扫描的基本包路径,因此强烈建议将主类放在顶层目录下。

excludeFilters属性指定哪些类型不符合组件扫描的条件,会在扫描的时候过滤掉。

@Filter(type = FilterType.CUSTOM, classes = AutoConfigurationExcludeFilter.class)

比如上面这段代码。@Filter声明了过滤器类型类为自定义类型(需要实现TypeFilter接口),过滤器为AutoConfigurationExcludeFilter。当match方法为true,返回扫描类对象,否则过滤掉。但是要注意@ComponentScan的key为excludeFilters,因此这些类型将在包扫描的时候过滤掉,也就是说,ComponentScan在扫描时,发现当前扫描类满足macth的条件(match返回true),是不会将该类加载到容器的。

	//metadataReader  表示读取到的当前正在扫描的类的信息
//metadataReaderFactory 表示可以获得到其他任何类的信息
@Override
public boolean match(MetadataReader metadataReader,
MetadataReaderFactory metadataReaderFactory) throws IOException {
return isConfiguration(metadataReader) && isAutoConfiguration(metadataReader);
}
//该类是带有Configuration注解的配置类
private boolean isConfiguration(MetadataReader metadataReader) {
return metadataReader.getAnnotationMetadata()
.isAnnotated(Configuration.class.getName());
}
//该类是否为spring.factory配置的自动配置类
private boolean isAutoConfiguration(MetadataReader metadataReader) {
return getAutoConfigurations()
.contains(metadataReader.getClassMetadata().getClassName());
}

三.run(DemoApplication.class, args)解析

3.1.进入SpringApplication

public static ConfigurableApplicationContext run(Class<?>[] primarySources,
String[] args) {
return new SpringApplication(primarySources).run(args);
}

我们根据DemoApplication跟进代码,发现其调用的SpringApplication类的run方法。这个方法就干了2件事:一是创建SpringApplication对象,二是启动SpringApplication。

3.2.SpringApplication构造器分析

1.构造器

public SpringApplication(Class<?>... primarySources) {
this(null, primarySources);
}
/**
* Create a new {@link SpringApplication} instance. The application context will load
* beans from the specified primary sources
*/
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
this.resourceLoader = resourceLoader;
Assert.notNull(primarySources, "PrimarySources must not be null");
this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
//根据应用是否存在某些类推断应用类型,分为响应式web应用,servlet类型web应用和非web应用,在后面用于确定实例化applicationContext的类型
this.webApplicationType = WebApplicationType.deduceFromClasspath();
//设置初始化器,读取spring.factories文件key ApplicationContextInitializer对应的value并实例化
//ApplicationContextInitializer接口用于在Spring上下文被刷新之前进行初始化的操作
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class)); //设置监听器,读取spring.factories文件key ApplicationListener对应的value并实例化
// interface ApplicationListener<E extends ApplicationEvent> extends EventListener
//ApplicationListener继承EventListener,实现了观察者模式。对于Spring框架的观察者模式实现,它限定感兴趣的事件类型需要是ApplicationEvent类型事件 setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
//没啥特别作用,仅用于获取入口类class对象
this.mainApplicationClass = deduceMainApplicationClass();
}

在构造器里主要干了2件事,一个设置初始化器,二是设置监听器。

2.设置初始化器

setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
private <T> Collection<T> getSpringFactoriesInstances(Class<T> type) {
return getSpringFactoriesInstances(type, new Class<?>[] {});
} private <T> Collection<T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = getClassLoader();
// Use names and ensure unique to protect against duplicates
Set<String> names = new LinkedHashSet<>(
//从类路径的META-INF处读取相应配置文件spring.factories,然后进行遍历,读取配置文件中Key(type)对应的value
SpringFactoriesLoader.loadFactoryNames(type, classLoader));
//将names的对象实例化
List<T> instances = createSpringFactoriesInstances(type, parameterTypes,
classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}

根据入参type类型ApplicationContextInitializer.class从类路径的META-INF处读取相应配置文件spring.factories并实例化对应Initializer。上面这2个函数后面会反复用到。

org.springframework.context.ApplicationContextInitializer=\
org.springframework.boot.context.ConfigurationWarningsApplicationContextInitializer,\
org.springframework.boot.context.ContextIdApplicationContextInitializer,\
org.springframework.boot.context.config.DelegatingApplicationContextInitializer,\
org.springframework.boot.web.context.ServerPortInfoApplicationContextInitializer

3.设置监听器

setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));

和设置初始化器一个套路,通过getSpringFactoriesInstances函数实例化监听器。

org.springframework.context.ApplicationListener=\
org.springframework.boot.ClearCachesApplicationListener,\
org.springframework.boot.builder.ParentContextCloserApplicationListener,\
org.springframework.boot.context.FileEncodingApplicationListener,\
org.springframework.boot.context.config.AnsiOutputApplicationListener,\
org.springframework.boot.context.config.ConfigFileApplicationListener,\
org.springframework.boot.context.config.DelegatingApplicationListener,\
org.springframework.boot.context.logging.ClasspathLoggingApplicationListener,\
org.springframework.boot.context.logging.LoggingApplicationListener,\
org.springframework.boot.liquibase.LiquibaseServiceLocatorApplicationListener

3.3.run(String... args)解析

1.run函数

/**
* Run the Spring application, creating and refreshing a new ApplicationContext
*/ public ConfigurableApplicationContext run(String... args) {
//计时器
StopWatch stopWatch = new StopWatch();
stopWatch.start(); ConfigurableApplicationContext context = null;
Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>(); //设置java.awt.headless系统属性为true,Headless模式是系统的一种配置模式。
// 在该模式下,系统缺少了显示设备、键盘或鼠标。但是服务器生成的数据需要提供给显示设备等使用。
// 因此使用headless模式,一般是在程序开始激活headless模式,告诉程序,现在你要工作在Headless mode下,依靠系统的计算能力模拟出这些特性来
configureHeadlessProperty(); //获取监听器集合对象
SpringApplicationRunListeners listeners = getRunListeners(args); //发出开始执行的事件。
listeners.starting(); try {
//根据main函数传入的参数,创建DefaultApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//根据扫描到的监听器对象和函数传入参数,进行环境准备。
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments); configureIgnoreBeanInfo(environment);
Banner printedBanner = printBanner(environment); context = createApplicationContext(); //和上面套路一样,读取spring.factories文件key SpringBootExceptionReporter对应的value
exceptionReporters = getSpringFactoriesInstances(
SpringBootExceptionReporter.class,
new Class[] { ConfigurableApplicationContext.class }, context); prepareContext(context, environment, listeners, applicationArguments,
printedBanner); //和上面的一样,context准备完成之后,将触发SpringApplicationRunListener的contextPrepared执行
refreshContext(context); //其实啥也没干。但是老版本的callRunners好像是在这里执行的。
afterRefresh(context, applicationArguments); stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass)
.logStarted(getApplicationLog(), stopWatch);
}
//发布ApplicationStartedEvent事件,发出结束执行的事件
listeners.started(context);
//在某些情况下,我们希望在容器bean加载完成后执行一些操作,会实现ApplicationRunner或者CommandLineRunner接口
//后置操作,就是在容器完成刷新后,依次调用注册的Runners,还可以通过@Order注解设置各runner的执行顺序。
callRunners(context, applicationArguments);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, listeners);
throw new IllegalStateException(ex);
} try {
listeners.running(context);
}
catch (Throwable ex) {
handleRunFailure(context, ex, exceptionReporters, null);
throw new IllegalStateException(ex);
}
return context;
}

2.获取run listeners

SpringApplicationRunListeners listeners = getRunListeners(args);

和构造器设置初始化器一个套路,根据传入type SpringApplicationRunListener去扫描spring.factories文件,读取type对应的value并实例化。然后利用实例化对象创建SpringApplicationRunListeners对象。

org.springframework.boot.SpringApplicationRunListener=\
org.springframework.boot.context.event.EventPublishingRunListener

EventPublishingRunListener的作用是发布SpringApplicationEvent事件。

EventPublishingRunListener更像是被监听对象,这个命名让我有点迷。

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {
......
@Override
public void starting() {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
} @Override
public void environmentPrepared(ConfigurableEnvironment environment) {
this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(
this.application, this.args, environment));
}
........ }

3.发出开始执行的事件

listeners.starting();

继续跟进starting函数,

public void starting() {
this.initialMulticaster.multicastEvent(
new ApplicationStartingEvent(this.application, this.args));
}
//获取ApplicationStartingEvent类型的事件后,发布事件
@Override
public void multicastEvent(final ApplicationEvent event, @Nullable ResolvableType eventType) {
ResolvableType type = (eventType != null ? eventType : resolveDefaultEventType(event));
for (final ApplicationListener<?> listener : getApplicationListeners(event, type)) {
Executor executor = getTaskExecutor();
if (executor != null) {
executor.execute(() -> invokeListener(listener, event));
}
else {
invokeListener(listener, event);
}
}
}
//继续跟进invokeListener方法,最后调用ApplicationListener监听者的onApplicationEvent处理事件
private void doInvokeListener(ApplicationListener listener, ApplicationEvent event) {
try {
listener.onApplicationEvent(event);
}
catch (ClassCastException ex) {
.....
}
}

这个后面也会反复遇到,比如listeners.running(context)。

这里是典型的观察者模式。

//观察者:监听<E extends ApplicationEvent>类型事件
ApplicationListener<E extends ApplicationEvent> extends EventListener //事件类型:
Event extends SpringApplicationEvent extends ApplicationEvent extends EventObject //被观察者:发布事件
EventPublishingRunListener implements SpringApplicationRunListener

SpringApplication根据当前事件Event类型,比如ApplicationStartingEvent,查找到监听ApplicationStartingEvent的观察者EventPublishingRunListener,调用观察者的onApplicationEvent处理事件。

4.环境准备

//根据main函数传入的参数,创建DefaultApplicationArguments对象
ApplicationArguments applicationArguments = new DefaultApplicationArguments(
args);
//根据扫描到的listeners对象和函数传入参数,进行环境准备。
ConfigurableEnvironment environment = prepareEnvironment(listeners,
applicationArguments);

ApplicationArguments提供运行application的参数,后面会作为一个Bean注入到容器。这里重点说下prepareEnvironment方法做了些什么。

private ConfigurableEnvironment prepareEnvironment(
SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments) { // Create and configure the environment
ConfigurableEnvironment environment = getOrCreateEnvironment(); configureEnvironment(environment, applicationArguments.getSourceArgs()); //和listeners.starting一样的流程
listeners.environmentPrepared(environment); //上述完成了环境的创建和配置,传入的参数和资源加载到environment //绑定环境到SpringApplication
bindToSpringApplication(environment);
if (!this.isCustomEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertEnvironmentIfNecessary(environment, deduceEnvironmentClass());
}
ConfigurationPropertySources.attach(environment);
return environment;
}

这段代码核心有3个。

  1. configureEnvironment,用于基本运行环境的配置。
  2. 发布事件ApplicationEnvironmentPreparedEvent。和发布ApplicationStartingEvent事件的流程一样。
  3. 绑定环境到SpringApplication

5.创建ApplicationContext

context = createApplicationContext();

传说中的IOC容器终于来了。

在实例化context之前,首先需要确定context的类型,这个是根据应用类型确定的。应用类型webApplicationType在构造器已经推断出来了。

protected ConfigurableApplicationContext createApplicationContext() {
Class<?> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
switch (this.webApplicationType) {
case SERVLET:
//应用为servlet类型的web应用
contextClass = Class.forName(DEFAULT_SERVLET_WEB_CONTEXT_CLASS);
break;
case REACTIVE:
//应用为响应式web应用
contextClass = Class.forName(DEFAULT_REACTIVE_WEB_CONTEXT_CLASS);
break;
default:
//应用为非web类型的应用
contextClass = Class.forName(DEFAULT_CONTEXT_CLASS);
}
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiateClass(contextClass);
}

获取context类型后,进行实例化,这里根据class类型获取无参构造器进行实例化。

public static <T> T instantiateClass(Class<T> clazz) throws BeanInstantiationException {
Assert.notNull(clazz, "Class must not be null");
if (clazz.isInterface()) {
throw new BeanInstantiationException(clazz, "Specified class is an interface");
}
try {
//clazz.getDeclaredConstructor()获取无参的构造器,然后进行实例化
return instantiateClass(clazz.getDeclaredConstructor());
}
catch (NoSuchMethodException ex) {
.......
}

比如web类型为servlet类型,就会实例化org.springframework.boot.web.servlet.context.

AnnotationConfigServletWebServerApplicationContext类型的context。

6.context前置处理阶段

private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
//关联环境
context.setEnvironment(environment); //ApplicationContext预处理,主要配置Bean生成器以及资源加载器
postProcessApplicationContext(context); //调用初始化器,执行initialize方法,前面set的初始化器终于用上了
applyInitializers(context);
//发布contextPrepared事件,和发布starting事件一样,不多说
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
} // Add boot specific singleton beans
ConfigurableListableBeanFactory beanFactory = context.getBeanFactory();
//bean, springApplicationArguments,用于获取启动application所需的参数
beanFactory.registerSingleton("springApplicationArguments", applicationArguments); //加载打印Banner的Bean
if (printedBanner != null) {
beanFactory.registerSingleton("springBootBanner", printedBanner);
} if (beanFactory instanceof DefaultListableBeanFactory) {
((DefaultListableBeanFactory) beanFactory)
.setAllowBeanDefinitionOverriding(this.allowBeanDefinitionOverriding);
}
// Load the sources,根据primarySources加载resource。primarySources:一般为主类的class对象
Set<Object> sources = getAllSources();
Assert.notEmpty(sources, "Sources must not be empty");
//构造BeanDefinitionLoader并完成定义的Bean的加载
load(context, sources.toArray(new Object[0]));
//发布ApplicationPreparedEvent事件,表示application已准备完成
listeners.contextLoaded(context);
}

7.刷新容器

private void refreshContext(ConfigurableApplicationContext context) {
refresh(context);
// 注册一个关闭容器时的钩子函数,在jvm关闭时调用
if (this.registerShutdownHook) {
try {
context.registerShutdownHook();
}
catch (AccessControlException ex) {
// Not allowed in some environments.
}
}
}

调用父类AbstractApplicationContext刷新容器的操作,具体的还没看。

@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh(); // Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); // Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory); try {
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory); // Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory); // Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory); // Initialize message source for this context.
initMessageSource(); // Initialize event multicaster for this context.
initApplicationEventMulticaster(); // Initialize other special beans in specific context subclasses.
onRefresh(); // Check for listener beans and register them.
registerListeners(); // Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory); // Last step: publish corresponding event.
finishRefresh();
}
......
}

8.后置操作,调用Runners

后置操作,就是在容器完成刷新后,依次调用注册的Runners,还可以通过@Order注解设置各runner的执行顺序。

Runner可以通过实现ApplicationRunner或者CommandLineRunner接口。

	private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<>();
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
AnnotationAwareOrderComparator.sort(runners);
for (Object runner : new LinkedHashSet<>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}

根据源码可知,runners收集从容器获取的ApplicationRunner和CommandLineRunner类型的Bean,然后依次执行。

9.发布ApplicationReadyEvent事件

listeners.running(context);

应用启动完成,可以对外提供服务了,在这里发布ApplicationReadyEvent事件。流程还是和starting时一样。

SpringBoot应用启动过程分析的更多相关文章

  1. 001-快速搭建Spring web应用【springboot 2.0.4】-gradle、springboot的启动过程分析、gradle多模块构建

    一.概述 学习<精通Spring MVC4>书籍笔记 二.笔记 1.快速构建Spring starter web项目几种方式 1>使用Spring Tool Suite生成Start ...

  2. SpringBoot启动过程分析

    我们知道,SpringBoot程序的启动很简单,代码如下: @SpringBootApplication public class Application { public static void m ...

  3. ASP.Net Core MVC6 RC2 启动过程分析[偏源码分析]

    入口程序 如果做过Web之外开发的人,应该记得这个是标准的Console或者Winform的入口.为什么会这样呢? .NET Web Development and Tools Blog ASP.NE ...

  4. 开机SystemServer到ActivityManagerService启动过程分析

    开机SystemServer到ActivityManagerService启动过程 一 从Systemserver到AMS zygote-> systemserver:java入层口: /** ...

  5. Neutron分析(2)——neutron-server启动过程分析

    neutron-server启动过程分析 1. /etc/init.d/neutron-server DAEMON=/usr/bin/neutron-server DAEMON_ARGS=" ...

  6. linux视频学习7(ssh, linux启动过程分析,加解压缩,java网络编程)

    回顾数据库mysql的备份和恢复: show databases; user spdb1; show tables; 在mysql/bin目录下 执行备份: ./mysqldump -u root - ...

  7. Activity启动过程分析

    Android的四大组件中除了BroadCastReceiver以外,其他三种组件都必须在AndroidManifest中注册,对于BroadCastReceiver来说,它既可以在AndroidMa ...

  8. Spark Streaming应用启动过程分析

    本文为SparkStreaming源码剖析的第三篇,主要分析SparkStreaming启动过程. 在调用StreamingContext.start方法后,进入JobScheduler.start方 ...

  9. ActivityManagerService启动过程分析

    之前讲Android的View的绘制原理和流程的时候,讲到过在Android调用setContentView之后,Android调用了一个prepreTravle的方法,这里面就提到了Activity ...

随机推荐

  1. 手摸手,带你用vue实现后台管理权限系统及顶栏三级菜单显示

    手摸手,带你用vue实现后台管理权限系统及顶栏三级菜单显示 效果演示地址 项目demo展示 重要功能总结 权限功能的实现 权限路由思路: 根据用户登录的roles信息与路由中配置的roles信息进行比 ...

  2. 使用富文本编辑器Kindeditor

    今天在做需求的时候,遇到有一个字段,需要保存带有格式的内容,决定使用富文本框编辑器Kindeditor来实现,解决方法如下: 登录官网下载控件包: http://kindeditor.net/down ...

  3. 头部姿态估计 - Android

    概括 通过Dlib获得当前人脸的特征点,然后通过旋转平移标准模型的特征点进行拟合,计算标准模型求得的特征点与Dlib获得的特征点之间的差,使用Ceres不断迭代优化,最终得到最佳的旋转和平移参数. A ...

  4. 在linux中用同一个版本的R 同时安装 Seurat2 和 Seurat3

    在linux中用同一个版本的R 同时安装 Seurat 2 和 Seurat 3 Seurat  作为单细胞分析中的重量级R包,有多好用用,用过的人都知道.Seurat 分析流程基本涵盖了单细胞分析中 ...

  5. 帝国CMS(EmpireCMS) v7.5 前台XSS漏洞分析

    帝国CMS(EmpireCMS) v7.5 前台XSS漏洞分析 一.漏洞描述 该漏洞是由于javascript获取url的参数,没有经过任何过滤,直接当作a标签和img标签的href属性和src属性输 ...

  6. Zabbix-设置自动发现规则实例

    一.前文 此篇文章,主要针对自动发现规则中使用snmpv2类型发现 zabbix官方解读,可当参考:   https://www.zabbix.com/documentation/4.0/zh/man ...

  7. Django上线部署之IIS

    环境: 1.Windows Server 2016 Datacenter 64位 2.SQL Server 2016 Enterprise 64位 3.Python 3.6.0 64位 4.admin ...

  8. Example With JdbcDaoSupport

    By extended the JdbcDaoSupport, set the datasource and JdbcTemplate in your class is no longer requi ...

  9. mysql函数拼接查询concat函数

    //查询表managefee_managefee的年year 和 month ,用concat函数拼成year-month.例如将2017和1 拼成2017-01.. select CONCAT(a. ...

  10. netty无缝切换rabbitmq、activemq、rocketmq实现聊天室单聊、群聊功能

    netty的pipeline处理链上的handler:需要IdleStateHandler心跳检测channel是否有效,以及处理登录认证的UserAuthHandler和消息处理MessageHan ...