说在前面

前期回顾

sharding-jdbc源码解析 更新完毕

spring源码解析 更新完毕

spring-mvc源码解析 更新完毕

spring-tx源码解析 更新完毕

spring-boot源码解析 更新完毕

rocketmq源码解析 更新完毕

dubbbo源码解析 更新完毕

netty源码解析 更新完毕

spring源码架构更新完毕

spring-mvc源码架构更新完毕

springboot源码架构更新中

github https://github.com/tianheframe

sharding-jdbc源码解析 更新完毕

rocketmq源码解析 更新完毕

seata 源码解析 更新完毕

dubbo 源码解析 更新完毕

netty 源码解析 更新完毕

源码解析

org.springframework.boot.SpringApplicationRunListener 用于SpringApplication run方法的侦听器。springapplicationrunlistener是通过SpringFactoriesLoader加载的,它应该声明一个公共构造函数来接受一个SpringApplication实例和一个参数字符串[]。每次运行都将创建一个新的SpringApplicationRunListener实例。

  void starting();

在run方法首次启动时立即调用。可以用于非常早期的初始化。

  void environmentPrepared(ConfigurableEnvironment environment);

在准备好环境之后,但在创建ApplicationContext之前调用。

  void contextPrepared(ConfigurableApplicationContext context);

在创建和准备ApplicationContext之后调用,但在加载源之前调用。

  void contextLoaded(ConfigurableApplicationContext context);

在加载应用程序上下文之后调用,但在刷新应用程序上下文之前调用。

  void finished(ConfigurableApplicationContext context, Throwable exception);

在运行方法结束之前立即调用。

org.springframework.boot.context.event.EventPublishingRunListener SpringApplicationRunListener来发布SpringApplicationEvents。

为在实际刷新上下文之前触发的事件使用内部ApplicationEventMulticaster。

  private final SpringApplication application;

application

  private final SimpleApplicationEventMulticaster initialMulticaster;

initialMulticaster

public EventPublishingRunListener(SpringApplication application, String[] args) {    this.application = application;    this.args = args;    this.initialMulticaster = new SimpleApplicationEventMulticaster();    for (ApplicationListener> listener : application.getListeners()) {      this.initialMulticaster.addApplicationListener(listener);    }  }

添加ApplicationListener

public void starting() {    this.initialMulticaster        .multicastEvent(new ApplicationStartedEvent(this.application, this.args));  }

发布ApplicationStartedEvent

  @Override  public void environmentPrepared(ConfigurableEnvironment environment) {    this.initialMulticaster.multicastEvent(new ApplicationEnvironmentPreparedEvent(        this.application, this.args, environment));  }

发布ApplicationEnvironmentPreparedEvent

@Override  public void contextLoaded(ConfigurableApplicationContext context) {    for (ApplicationListener> listener : this.application.getListeners()) {      if (listener instanceof ApplicationContextAware) {        ((ApplicationContextAware) listener).setApplicationContext(context);      }      context.addApplicationListener(listener);    }    this.initialMulticaster.multicastEvent(        new ApplicationPreparedEvent(this.application, this.args, context));  }

添加listener,发布ApplicationPreparedEvent

@Override  public void finished(ConfigurableApplicationContext context, Throwable exception) {    SpringApplicationEvent event = getFinishedEvent(context, exception);    if (context != null && context.isActive()) {      // Listeners have been registered to the application context so we should 侦听器已经注册到应用程序上下文,因此我们应该这样做      // use it at this point if we can 如果可以的话,现在就用它      context.publishEvent(event);    }    else {      // An inactive context may not have a multicaster so we use our multicaster to 非活动上下文可能没有多播程序,因此我们使用多播程序      // call all of the context's listeners instead 而是调用上下文的所有侦听器      if (context instanceof AbstractApplicationContext) {        for (ApplicationListener> listener : ((AbstractApplicationContext) context)            .getApplicationListeners()) {          this.initialMulticaster.addApplicationListener(listener);        }      }      if (event instanceof ApplicationFailedEvent) {        this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());      }      this.initialMulticaster.multicastEvent(event);    }  }

发布ApplicationFailedEvent、ApplicationReadyEvent事件

  private SpringApplicationEvent getFinishedEvent(      ConfigurableApplicationContext context, Throwable exception) {    if (exception != null) {      return new ApplicationFailedEvent(this.application, this.args, context,          exception);    }    return new ApplicationReadyEvent(this.application, this.args, context);  }

获得完成事件

org.springframework.boot.builder.ParentContextCloserApplicationListener 侦听器,如果其父应用程序上下文已关闭,则该侦听器将关闭该应用程序上下文。它侦听refresh事件并从中获取当前上下文,然后侦听关闭事件并将其传播到层次结构中。

实现org.springframework.context.ApplicationContextAware接口。

  private ApplicationContext context;

ApplicationContext

@Override  public void setApplicationContext(ApplicationContext context) throws BeansException {    this.context = context;  }

重写org.springframework.context.ApplicationContextAware#setApplicationContext方法,设置ApplicationContext

@Override  public void onApplicationEvent(ParentContextAvailableEvent event) {    maybeInstallListenerInParent(event.getApplicationContext());  }

org.springframework.boot.builder.ParentContextCloserApplicationListener#maybeInstallListenerInParent

private void maybeInstallListenerInParent(ConfigurableApplicationContext child) {    if (child == this.context) {      if (child.getParent() instanceof ConfigurableApplicationContext) {        ConfigurableApplicationContext parent = (ConfigurableApplicationContext) child            .getParent();        parent.addApplicationListener(createContextCloserListener(child));      }    }  }

org.springframework.boot.builder.ParentContextCloserApplicationListener#createContextCloserListener 子类可以覆盖它来创建自己的ContextCloserListener子类。这仍然强制使用弱引用。

protected ContextCloserListener createContextCloserListener(      ConfigurableApplicationContext child) {    return new ContextCloserListener(child);  }

org.springframework.boot.context.FileEncodingApplicationListener 如果系统文件编码与环境中设置的期望值不匹配,应用程序侦听器将暂停应用程序启动。默认情况下没有效果,但是如果您设置spring。mandatory_file_encoding(或它的某些驼峰大小写或大写变体)到字符编码的名称(例如。然后这个初始化器在文件中抛出一个异常。编码系统属性不等于它。系统属性文件。编码通常由JVM设置,以响应LANG或LC_ALL环境变量。它(与其他依赖于平台的变量一起键入这些环境变量)用于编码JVM参数以及文件名和路径。在大多数情况下,您可以在命令行上覆盖文件编码系统属性(使用标准JVM特性),但也可以考虑将LANG环境变量设置为显式的字符编码值(例如。“en_GB.UTF-8”)。

org.springframework.boot.ClearCachesApplicationListener 清空缓存的ApplicationListener

org.springframework.boot.SpringApplicationRunListeners SpringApplicationRunListener的集合

  private final List listeners;

listeners

public void starting() {    for (SpringApplicationRunListener listener : this.listeners) {      listener.starting();    }  }

执行监听器的启动方法

public void environmentPrepared(ConfigurableEnvironment environment) {    for (SpringApplicationRunListener listener : this.listeners) {      listener.environmentPrepared(environment);    }  }

执行监听器的环境准备方法

public void contextPrepared(ConfigurableApplicationContext context) {    for (SpringApplicationRunListener listener : this.listeners) {      listener.contextPrepared(context);    }  }

执行监听器的上下文准备方法

public void contextLoaded(ConfigurableApplicationContext context) {    for (SpringApplicationRunListener listener : this.listeners) {      listener.contextLoaded(context);    }  }

执行上下文的加载方法

public void finished(ConfigurableApplicationContext context, Throwable exception) {    for (SpringApplicationRunListener listener : this.listeners) {      callFinishedListener(listener, context, exception);    }  }

org.springframework.boot.SpringApplicationRunListeners#callFinishedListener

private void callFinishedListener(SpringApplicationRunListener listener,      ConfigurableApplicationContext context, Throwable exception) {    try {      listener.finished(context, exception);    }    catch (Throwable ex) {      if (exception == null) {        ReflectionUtils.rethrowRuntimeException(ex);      }      if (this.log.isDebugEnabled()) {        this.log.error("Error handling failed", ex);      }      else {        String message = ex.getMessage();        message = (message != null) ? message : "no error message";        this.log.warn("Error handling failed (" + message + ")");      }    }  }

执行监听器的完成方法

org.springframework.boot.context.config.DelegatingApplicationListener 将委托给在context.listener下指定的其他侦听器的ApplicationListener。类环境属性。

  private SimpleApplicationEventMulticaster multicaster;

multicaster

@Override  public void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationEnvironmentPreparedEvent) {      List> delegates = getListeners(          ((ApplicationEnvironmentPreparedEvent) event).getEnvironment());      if (delegates.isEmpty()) {        return;      }      this.multicaster = new SimpleApplicationEventMulticaster();      for (ApplicationListener listener : delegates) {        this.multicaster.addApplicationListener(listener);      }    }    if (this.multicaster != null) {      this.multicaster.multicastEvent(event);    }  }

添加监听器,发布事件

private List> getListeners(      ConfigurableEnvironment environment) {    if (environment == null) {      return Collections.emptyList();    }//    查找监听器的类context.listener.classes    String classNames = environment.getProperty(PROPERTY_NAME);    List> listeners = new ArrayList>();    if (StringUtils.hasLength(classNames)) {      for (String className : StringUtils.commaDelimitedListToSet(classNames)) {        try {          Class> clazz = ClassUtils.forName(className,              ClassUtils.getDefaultClassLoader());          Assert.isAssignable(ApplicationListener.class, clazz, "class ["              + className + "] must implement ApplicationListener");          listeners.add((ApplicationListener) BeanUtils              .instantiateClass(clazz));        }        catch (Exception ex) {          throw new ApplicationContextException(              "Failed to load context listener class [" + className + "]",              ex);        }      }    }    AnnotationAwareOrderComparator.sort(listeners);    return listeners;  }

加载属性文件context.listener.classes属性指定的监听器类并初始化。

org.springframework.boot.context.config.ConfigFileApplicationListener EnvironmentPostProcessor 它通过从众所周知的文件位置加载属性来配置上下文环境。默认情况下,属性将从application.properties、application.yml文件在以下位置

classpath:

file:./

classpath:config/

file:./config/:

可以使用setSearchLocations(字符串)和setSearchNames(字符串)指定其他搜索位置和名称。

其他文件也将基于活动配置文件加载。例如,如果'web'配置文件是活跃的application-web.properties、application-web.yml将被考虑。

spring.config.name属性可用于指定要加载的替代名称和,spring.config.location属性可用于指定其他搜索位置或特定文件。

配置属性也绑定到SpringApplication。这使得动态设置SpringApplication属性成为可能,就像源一样 ("spring.main.sources" - a CSV list) 指示web环境的标志 ("spring.main.web_environment=true") 或者把旗帜关掉("spring.main.show_banner=false").

  private static final String DEFAULT_SEARCH_LOCATIONS = "classpath:/,classpath:/config/,file:./,file:./config/";

配置文件名前缀

  private static final String DEFAULT_NAMES = "application";

默认配置文件名

  public static final String ACTIVE_PROFILES_PROPERTY = "spring.profiles.active";

激活的文件名

  public static final String INCLUDE_PROFILES_PROPERTY = "spring.profiles.include";

引用的配置文件名

  public static final String CONFIG_NAME_PROPERTY = "spring.config.name";

配置文件名

  public static final String CONFIG_LOCATION_PROPERTY = "spring.config.location";

配置文件路径

  private final ConversionService conversionService = new DefaultConversionService();

conversionService

@Override  public boolean supportsEventType(Class extends ApplicationEvent> eventType) {    return ApplicationEnvironmentPreparedEvent.class.isAssignableFrom(eventType)        || ApplicationPreparedEvent.class.isAssignableFrom(eventType);  }

支持ApplicationEnvironmentPreparedEvent、ApplicationPreparedEvent事件

@Override  public void onApplicationEvent(ApplicationEvent event) {    if (event instanceof ApplicationEnvironmentPreparedEvent) {      onApplicationEnvironmentPreparedEvent(          (ApplicationEnvironmentPreparedEvent) event);    }    if (event instanceof ApplicationPreparedEvent) {      onApplicationPreparedEvent(event);    }  }

org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationEnvironmentPreparedEvent

private void onApplicationEnvironmentPreparedEvent(      ApplicationEnvironmentPreparedEvent event) {//    加载EnvironmentPostProcessor    List postProcessors = loadPostProcessors();    postProcessors.add(this);    AnnotationAwareOrderComparator.sort(postProcessors);    for (EnvironmentPostProcessor postProcessor : postProcessors) {//      加载org.springframework.boot.env.EnvironmentPostProcessor.postProcessEnvironment()方法      postProcessor.postProcessEnvironment(event.getEnvironment(),          event.getSpringApplication());    }  }

加载EnvironmentPostProcessor,执行org.springframework.boot.env.EnvironmentPostProcessor.postProcessEnvironment()方法,org.springframework.boot.context.config.ConfigFileApplicationListener#loadPostProcessors

List loadPostProcessors() {    return SpringFactoriesLoader.loadFactories(EnvironmentPostProcessor.class,        getClass().getClassLoader());  }

从BeanFactory中加载EnvironmentPostProcessor

org.springframework.boot.context.config.ConfigFileApplicationListener#onApplicationPreparedEvent

private void onApplicationPreparedEvent(ApplicationEvent event) {    this.logger.replayTo(ConfigFileApplicationListener.class);//    添加PropertySourceOrderingPostProcessor    addPostProcessors(((ApplicationPreparedEvent) event).getApplicationContext());  }
protected void bindToSpringApplication(ConfigurableEnvironment environment,      SpringApplication application) {    PropertiesConfigurationFactory binder = new PropertiesConfigurationFactory(        application);    binder.setTargetName("spring.main");//    设置conversionService    binder.setConversionService(this.conversionService);    binder.setPropertySources(environment.getPropertySources());    try {//      绑定属性      binder.bindPropertiesToTarget();    }    catch (BindException ex) {      throw new IllegalStateException("Cannot bind to SpringApplication", ex);    }  }

org.springframework.boot.bind.PropertiesConfigurationFactory#bindPropertiesToTarget

public void bindPropertiesToTarget() throws BindException {    Assert.state(this.propertySources != null, "PropertySources should not be null");    try {      if (logger.isTraceEnabled()) {        logger.trace("Property Sources: " + this.propertySources);      }      this.hasBeenBound = true;      doBindPropertiesToTarget();    }    catch (BindException ex) {      if (this.exceptionIfInvalid) {        throw ex;      }      PropertiesConfigurationFactory.logger          .error("Failed to load Properties validation bean. "              + "Your Properties may be invalid.", ex);    }  }

org.springframework.boot.bind.PropertiesConfigurationFactory#doBindPropertiesToTarget

private void doBindPropertiesToTarget() throws BindException {    RelaxedDataBinder dataBinder = (this.targetName != null)        ? new RelaxedDataBinder(this.target, this.targetName)        : new RelaxedDataBinder(this.target);    if (this.validator != null        && this.validator.supports(dataBinder.getTarget().getClass())) {      dataBinder.setValidator(this.validator);    }    if (this.conversionService != null) {//      设置conversionService      dataBinder.setConversionService(this.conversionService);    }    dataBinder.setAutoGrowCollectionLimit(Integer.MAX_VALUE);    dataBinder.setIgnoreNestedProperties(this.ignoreNestedProperties);    dataBinder.setIgnoreInvalidFields(this.ignoreInvalidFields);    dataBinder.setIgnoreUnknownFields(this.ignoreUnknownFields);    customizeBinder(dataBinder);    if (this.applicationContext != null) {      ResourceEditorRegistrar resourceEditorRegistrar = new ResourceEditorRegistrar(          this.applicationContext, this.applicationContext.getEnvironment());//      注册定制的属性修改器      resourceEditorRegistrar.registerCustomEditors(dataBinder);    }    Iterable relaxedTargetNames = getRelaxedTargetNames();    Set names = getNames(relaxedTargetNames);    PropertyValues propertyValues = getPropertySourcesPropertyValues(names,        relaxedTargetNames);    dataBinder.bind(propertyValues);    if (this.validator != null) {//      验证属性      dataBinder.validate();    }//    检查绑定的参数错误    checkForBindingErrors(dataBinder);  }

org.springframework.boot.context.config.ConfigFileApplicationListener.PropertySourceOrderingPostProcessor 实现org.springframework.beans.factory.config.BeanFactoryPostProcessor接口,覆盖BeanFactory中属性

private ConfigurableApplicationContext context;

context

@Override    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)        throws BeansException {      reorderSources(this.context.getEnvironment());    }

重写org.springframework.beans.factory.config.BeanFactoryPostProcessor#postProcessBeanFactory接口,覆盖属性

  @Override    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory)        throws BeansException {      reorderSources(this.context.getEnvironment());    }

org.springframework.boot.context.config.ConfigFileApplicationListener.Loader 加载激活的配置文件

private Queue profiles;

激活的待处理的配置文件

private List processedProfiles;

处理过的配置文件

public void load() {      this.propertiesLoader = new PropertySourcesLoader();      this.activatedProfiles = false;      this.profiles = Collections.asLifoQueue(new LinkedList());      this.processedProfiles = new LinkedList();      // Pre-existing active profiles set via Environment.setActiveProfiles() 通过Environment.setActiveProfiles()设置预先存在的活动概要文件      // are additional profiles and config files are allowed to add more if 是否允许添加更多的配置文件和配置文件      // they want to, so don't call addActiveProfiles() here. 它们想这样做,所以这里不要调用addActiveProfiles()。      Set initialActiveProfiles = initializeActiveProfiles();      this.profiles.addAll(getUnprocessedActiveProfiles(initialActiveProfiles));      if (this.profiles.isEmpty()) {        for (String defaultProfileName : this.environment.getDefaultProfiles()) {          Profile defaultProfile = new Profile(defaultProfileName, true);          if (!this.profiles.contains(defaultProfile)) {            this.profiles.add(defaultProfile);          }        }      }      // The default profile for these purposes is represented as null. We add it      // last so that it is first out of the queue (active profiles will then      // override any settings in the defaults when the list is reversed later).//这些用途的默认配置文件表示为null。我们添加它// last,以便它首先离开队列(然后活动配置文件将离开队列)//稍后列表反转时,覆盖默认设置中的任何设置)。      this.profiles.add(null);      while (!this.profiles.isEmpty()) {        Profile profile = this.profiles.poll();//        加载location类型的配置文件        for (String location : getSearchLocations()) {          if (!location.endsWith("/")) {            // location is a filename already, so don't search for more            // filenames            load(location, null, profile);          }          else {            for (String name : getSearchNames()) {              load(location, name, profile);            }          }        }        this.processedProfiles.add(profile);      }      addConfigurationProperties(this.propertiesLoader.getPropertySources());    }

加载配置文件,org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#initializeActiveProfiles 初始化激活的配置文件

private Set initializeActiveProfiles() {//      spring.profiles.active 加载指定的环境配置文件      if (!this.environment.containsProperty(ACTIVE_PROFILES_PROPERTY)//          spring.profiles.include 包含的文件名          && !this.environment.containsProperty(INCLUDE_PROFILES_PROPERTY)) {        return Collections.emptySet();      }      // Any pre-existing active profiles set via property sources (e.g. System 通过属性源(例如System)设置的任何预先存在的活动概要文件      // properties) take precedence over those added in config files. 属性)优先于配置文件中添加的属性。      SpringProfiles springProfiles = bindSpringProfiles(          this.environment.getPropertySources());//      加载激活的配置文件      Set activeProfiles = new LinkedHashSet(          springProfiles.getActiveProfiles());//      添加include的配置文件      activeProfiles.addAll(springProfiles.getIncludeProfiles());      maybeActivateProfiles(activeProfiles);      return activeProfiles;    }

org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#bindSpringProfiles(org.springframework.core.env.PropertySources) 构建spring配置文件

private SpringProfiles bindSpringProfiles(PropertySources propertySources) {      SpringProfiles springProfiles = new SpringProfiles();      RelaxedDataBinder dataBinder = new RelaxedDataBinder(springProfiles,          "spring.profiles");      dataBinder.bind(new PropertySourcesPropertyValues(propertySources, false));      springProfiles.setActive(resolvePlaceholders(springProfiles.getActive()));      springProfiles.setInclude(resolvePlaceholders(springProfiles.getInclude()));      return springProfiles;    }

org.springframework.boot.context.config.ConfigFileApplicationListener.Loader#getSearchLocations 获取location类型的配置文件

private Set<String> getSearchLocations() {      Set<String> locations = new LinkedHashSet<String>();      // User-configured settings take precedence, so we do them first 用户配置的设置优先,因此我们首先执行这些设置,spring.config.location 可以指定多个配置文件,多个配置文件用,分开,支持占位符      if (this.environment.containsProperty(CONFIG_LOCATION_PROPERTY)) {        for (String path : asResolvedSet(            this.environment.getProperty(CONFIG_LOCATION_PROPERTY), null)) {          if (!path.contains("$")) {            path = StringUtils.cleanPath(path);//            不是直接文件路径就采用file:本地文件协议            if (!ResourceUtils.isUrl(path)) {              path = ResourceUtils.FILE_URL_PREFIX + path;            }          }          locations.add(path);        }      }      locations.addAll(          asResolvedSet(ConfigFileApplicationListener.this.searchLocations,              DEFAULT_SEARCH_LOCATIONS));      return locations;    }
private Set getSearchNames() {      if (this.environment.containsProperty(CONFIG_NAME_PROPERTY)) {        return asResolvedSet(this.environment.getProperty(CONFIG_NAME_PROPERTY),            null);      }//      默认配置文件名 application      return asResolvedSet(ConfigFileApplicationListener.this.names, DEFAULT_NAMES);    }

获取默认的配置文件名 application

说在最后

本次解析仅代表个人观点,仅供参考。

扫码进入技术微信群

钉钉技术群

qq技术群

spring boot 源码_springboot源码架构解析listener相关推荐

  1. Spring Boot 核心原理与源码解析 - 目录

    准备重新写 SpringBoot 配置文件解析原理 , 先在这里把要写的内容记下来 Spring Boot 核心原理与源码解析 - 目录 1\何时解析\如何解析 application.propert ...

  2. 【Redis版】spring boot高性能实现二维码扫码登录(中)

    作者: 刘冬 来源:http://www.cnblogs.com/GoodHelper/p/8643071.html 前言 本打算用CountDownLatch来实现,但有个问题我没有考虑,就是当用户 ...

  3. spring boot高性能实现二维码扫码登录(中)——Redis版

    前言 本打算用CountDownLatch来实现,但有个问题我没有考虑,就是当用户APP没有扫二维码的时候,线程会阻塞5分钟,这反而造成性能的下降.好吧,现在回归传统方式:前端ajax每隔1秒或2秒发 ...

  4. 【订阅与发布机制版】spring boot高性能实现二维码扫码登录(下)

    点击上方[JAVA乐园],选择"置顶公众号",有内涵有价值的文章第一时间送达! 作者: 刘冬 来源:https://www.cnblogs.com/GoodHelper/p/865 ...

  5. spring boot 与 iview 前后端分离架构之开发环境基于docker的部署的实现(三十六)

    spring boot 与 iview 前后端分离架构之开发环境基于docker的后端的部署的实现(三十六) 公众号 基于docker的后端的部署 安装mysql数据库 创建数据库 安装redis 安 ...

  6. 使用Spring Boot和Kubernetes构建微服务架构

    "我喜欢编写身份验证和授权代码." 〜从来没有Java开发人员. 厌倦了一次又一次地建立相同的登录屏幕? 尝试使用Okta API进行托管身份验证,授权和多因素身份验证. 在本教程 ...

  7. spring boot 中使用 POP3协议读取并解析邮件

    spring boot 中使用 POP3协议读取并解析邮件 1.邮箱授权 QQ邮箱授权,打开 "设置" 切换到 "账户" 找到下图中设置,开启 "PO ...

  8. Spring Boot:(二)启动原理解析

    Spring Boot:(二)启动原理解析 前言 前面几章我们见识了SpringBoot为我们做的自动配置,确实方便快捷,但是对于新手来说,如果不大懂SpringBoot内部启动原理,以后难免会吃亏. ...

  9. Spring Boot 整合 SpringDataNeo4j 并封装工具类解析PathValue

    Spring Boot 整合 SpringDataNeo4j 并封装工具类解析PathValue 一.Neo4j 二.Neo4j客户端浏览器 三.maven依赖 四.节点/关系映射 1.NodePer ...

  10. Spring Boot - security 实战与源码分析

    2019独角兽企业重金招聘Python工程师标准>>> 一.实现步骤 1.在application.yml中添加起步依赖 2.自定义安全类 package com.example.d ...

最新文章

  1. 【java】java开发中的23种设计模式详解
  2. 微服务开发的12项要素
  3. ARUBA与蓝海无线PORTAL对接配置
  4. 关于缓存的几篇好文章
  5. 【转】ABP源码分析八:Logger集成
  6. html中最右边,html – 如何获得最右边的列填充剩余空间?
  7. 19岁黑客找到暴露 Facebook 页面管理员的缺陷,获4500美元奖励
  8. 第十章 嵌入式linux的调试技术
  9. Hadoop-MapReduce
  10. itext 表格宽度自适应_微信公众号推文中如何自定义添加表格?
  11. 多重选定怎么撤销_怎样取消电脑多重网络 - 卡饭网
  12. 【晒出你的第83行代码】社区用户@尼古拉斯雷的代码故事,和现在比起来以前的代码都是垃圾!...
  13. html 倒计时特效,JS节日倒计时特效(精确到毫秒)
  14. matlab 卡丹 公式,卡丹公式欺骗了五百年所有数学家
  15. mysql登陆错误2003(hy000)_mysql远程无法登陆出现ERROR2003(HY000)...解决方法_MySQL
  16. React Native三端同构
  17. 30个 英文SEO内容推广平台整理
  18. vivo手机便签如何快速彻底一键换机使用?
  19. Android横向滑动的listview
  20. 局域网中USB远程共享:USB/IP

热门文章

  1. SQL Server中的报表–结合T-SQL和DAX查询以生成有效的报表
  2. azure云数据库_Azure SQL数据库上的透明数据加密(TDE)
  3. 读取配置文件(configparser,.ini文件)
  4. HTML5 canvas 模拟事件
  5. 移动网络设备睁开均盘绕Linux睁开
  6. USBKiller (U盘病毒专杀工具)绿色特别版V2.3 b0825
  7. 如果不使用 SQL Mail,如何在 SQL Server 中发送电子邮件
  8. php设计要求,《PHP设计模式介绍》第十章 规范模式
  9. 入参为字符串用日期对象接收
  10. 链串实现功能(初始化、判断空串、串的赋值、串的连接、获取子串)