SpringBoot系列之【启动流程详解】

2023-05-16

文章目录

  • 前言
  • 一、SpringBoot特点
  • 二、SpringBoot启动流程图
  • 三、SpringBoot启动类入口
    • 1.注解:@SpringBootApplication
      • @SpringBootConfiguration
      • @EnableAutoConfiguration
      • @ComponentScan
    • 2.main方法
      • SpringApplication#run
      • 实例化SpringApplication对象
      • SpringApplication.run(java.lang.String...)
  • 三、SpringBoot启动事件
    • EventPublishingRunListener
  • 四、SpringIOC 容器初始化过程
  • 五、结语


前言

几年前初出茅庐时,获得了一个心仪大厂的面试邀约,面试前准备万分,即使信心十足,面试当场还是有些紧张,导致发挥不如预期,至今仍有些许遗憾。(*>﹏<*)

这个【SpringBoot启动流程】就是当初没回答好的部分,这次来补上,机会虽然错过,但知识绝不能错过,以下整理相关知识点。

一、SpringBoot特点

这边先简单扼要叙述一下SpringBoot的强大之处:

  • 可以创建独立的Spring应用程序,并且基于其Maven或Gradle插件,可以创建可执行的JARs和WARs
  • 内嵌Tomcat或Jetty等Servlet容器
  • 提供自动配置的“starter”项目对象模型(POMS)以简化Maven配置
  • 尽可能自动配置Spring容器
  • 提供准备好的特性,如指标、健康检查和外部化配置
  • 绝对没有代码生成,不需要XML配置。

二、SpringBoot启动流程图

让我们先来看看SpringBoot的启动流程图,如下图所示:
在这里插入图片描述

三、SpringBoot启动类入口

用过SpringBoot的技术人员很显而易见的两者之间的差别就是视觉上很直观的:SpringBoot有自己独立的启动类(独立程序)

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

这段代码的核心为注解定义(@SpringBootApplication)和类定义(SpringApplication.run

1.注解:@SpringBootApplication

注解内部源码如下:

@Target({ElementType.TYPE}) // 注解的适用范围,其中TYPE用于描述类、接口(包括包注解类型)或enum声明
@Retention(RetentionPolicy.RUNTIME) // 注解的生命周期,保留到class文件中(三个生命周期)
@Documented // 表明这个注解应该被javadoc记录
@Inherited // 子类可以继承该注解
@SpringBootConfiguration // 继承了Configuration,表示当前是注解类
@EnableAutoConfiguration // 开启springboot的注解功能,springboot的四大神器之一,其借助@import的帮助
@ComponentScan(excludeFilters = {@Filter( // 扫描路径设置
    type = FilterType.CUSTOM,
    classes = {TypeExcludeFilter.class}
), @Filter(
    type = FilterType.CUSTOM,
    classes = {AutoConfigurationExcludeFilter.class}
)}
)
public @interface SpringBootApplication {
...
}

在其中比较重要的有三个注解,分别是:

  1. @SpringBootConfiguration
  2. @EnableAutoConfiguration
  3. @ComponentScan

下面先来说说第一个注解:

@SpringBootConfiguration

根据Javadoc可知,该注解作用就是将当前的类作为一个JavaConfig,然后触发注解@EnableAutoConfiguration和@ComponentScan的处理,本质上与@Configuration注解没有区别

Java Configuration的配置形式注入bean,参考以下代码:

@Configuration
public class FakeConfiguration{
    @Bean
    public FakeService mockService(){
        return new FakeServiceImpl();
    }
}

@Configuration标注当前类是Java Config配置类,会被扫描并加载到IoC容器。

这边额外简单补充一下 @Configuration@Component的区别:

  1. @Component注解的范围最广,所有类都可以注解,而 @Configuration一般注解在这样的类上:这个类里面有 @Value注解的成员变量和@Bean注解的方法,就是一个配置类。
  2. 英语字面上意义不同,Configuration为配置,Component为组件,都定义在类的上方,也代表着此类声明的意义。

@EnableAutoConfiguration

此注解顾名思义是可以自动配置,所以应该是springboot中最为重要的注解

在spring框架中就提供了各种以@Enable开头的注解,例如: @EnableScheduling、@EnableCaching、@EnableMBeanExport等; @EnableAutoConfiguration的理念和做事方式其实一脉相承简单概括一下就是,借助@Import的支持,收集和注册特定场景相关的bean定义

  • @EnableScheduling是通过@Import将Spring调度框架相关的bean定义都加载到IoC容器【定时任务、时间调度任务】
  • @EnableMBeanExport是通过@Import将JMX相关的bean定义加载到IoC容器【监控JVM运行时状态】

@EnableAutoConfiguration也是借助@Import的帮助,将所有符合自动配置条件的bean定义加载到IoC容器。

@EnableAutoConfiguration作为一个复合注解,其自身定义关键信息如下:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
@AutoConfigurationPackage 【重点注解】
@Import({AutoConfigurationImportSelector.class}) 【重点注解】
public @interface EnableAutoConfiguration {
    String ENABLED_OVERRIDE_PROPERTY = "spring.boot.enableautoconfiguration";
    Class<?>[] exclude() default {};
    String[] excludeName() default {};
}

还有其中比较重要的一个类就是:AutoConfigurationImportSelector.class

  • @AutoConfigurationPackage
    • 注册当前启动类的根 package
    • 注册 org.springframework.boot.autoconfigure.AutoConfigurationPackagesBean 的定义。
  • @Import(AutoConfigurationImportSelector.class)
    • 可以看到实现了 DeferredImportSelector 接口,该接口继承自 ImportSelector,根据 Javadoc 可知,多用于导入被 @Conditional 注解的Bean,之后会进行 filter 操作
    • AutoConfigurationImportSelector.AutoConfigurationGroup#process 方法,SpringBoot 启动时会调用该方法,进行自动装配
      • SpringApplication#run(java.lang.String…)
      • SpringApplication#refreshContext(即 Spring IOC 容器初始化的过程中)
      • ConfigurationClassParser#parse
      • AutoConfigurationImportSelector.AutoConfigurationGroup#process
  • 通过 SpringFactoriesLoader#loadFactoryNames 获取应考虑的自动配置名称。
  • 通过 filter 过滤掉当前环境不需要自动装配的类,各种 @Conditional 不满足就被过滤掉。
  • 需要自动装配的全路径类名注册到 SpringIOC 容器,自此 SpringBoot 自动装配完成

SpringBoot 自动装配的核心注解,在 Spring 框架中就提供了各种以 @Enable 开头的注解,例如: @EnableCircuitBreaker@EnableScheduling 等;

@ComponentScan

  • ComponentScan的功能其实就是自动扫描并加载符合条件的组件(比如@Component@Repository等)或者bean定义。
  • 我们可以通过 basePackages 等属性来细粒度的定制 @ComponentScan 自动扫描的范围,如果不指定,则默认Spring框架实现会从声明 @ComponentScan 所在类的package进行扫描,所以 SpringBoot 的启动类最好是放在根package下,我们自定义的类就放在对应的子package下,这样就可以不指定 basePackages

2.main方法

main方法里调用org.springframework.boot.SpringApplication.run(java.lang.Class<?>, java.lang.String…)方法。

SpringApplication#run

调用另外一个同名的重载方法run

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

实例化SpringApplication对象

  1. 首先会实例化SpringApplication一个对象。
  2. 构造方法里初始化一些属性,比如webApplicationType,比如"SERVLET",初始化一些listeners
public static ConfigurableApplicationContext run(Class<?>[] primarySources, String[] args) {
	return new SpringApplication(primarySources).run(args);
}
public SpringApplication(ResourceLoader resourceLoader, Class<?>... primarySources) {
	this.resourceLoader = resourceLoader;
	Assert.notNull(primarySources, "PrimarySources must not be null");
	this.primarySources = new LinkedHashSet<>(Arrays.asList(primarySources));
	this.webApplicationType = WebApplicationType.deduceFromClasspath();
	setInitializers((Collection) getSpringFactoriesInstances(ApplicationContextInitializer.class));
	setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
	this.mainApplicationClass = deduceMainApplicationClass();
}

SpringApplication.run(java.lang.String…)

经典的观察者模式

  1. 创建一个StopWatch实例,用来记录SpringBoot的启动时间。
  2. 通过SpringFactoriesLoader加载listeners:比如EventPublishingRunListener。
  3. 发布SprintBoot开始启动事件(EventPublishingRunListener#starting())。
  4. 创建和配置environment(environmentPrepared())。
  5. 打印SpringBoot的banner和版本。
  6. 创建对应的ApplicationContext:Web类型,Reactive类型,普通的类型(非Web)。
  7. prepareContext:
    1. 准备ApplicationContext,Initializers设置到ApplicationContext(contextPrepared())。
    2. 打印启动日志,打印profile信息(如dev, test, prod)。
    3. 最终会调用到AbstractApplicationContext#refresh方法,实际上就是Spring IOC容器的创建过程,并且会进行自动装配的操作,以及发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成(contextLoaded())
  8. afterRefresh hook方法。
  9. stopWatch停止计时,日志打印总共启动的时间。
  10. 发布SpringBoot程序已启动事件(started())。
  11. 调用ApplicationRunner和CommandLineRunner。
  12. 最后发布就绪事件ApplicationReadyEvent,标志着SpringBoot可以处理就收的请求了(running())。
public ConfigurableApplicationContext run(String... args) {
    // 创建一个StopWatch实例,用来记录SpringBoot的启动时间
 StopWatch stopWatch = new StopWatch();
 stopWatch.start();
 ConfigurableApplicationContext context = null;
 Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList<>();
 configureHeadlessProperty();
    // 通过SpringFactoriesLoader加载listeners:比如EventPublishingRunListener
 SpringApplicationRunListeners listeners = getRunListeners(args);
    // 发布SprintBoot启动事件:ApplicationStartingEvent
 listeners.starting();
 try {
  ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
     // 创建和配置environment,发布事件:SpringApplicationRunListeners#environmentPrepared
  ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
  configureIgnoreBeanInfo(environment);
     // 打印SpringBoot的banner和版本
  Banner printedBanner = printBanner(environment);
     // 创建对应的ApplicationContext:Web类型,Reactive类型,普通的类型(非Web)
  context = createApplicationContext();
  exceptionReporters = getSpringFactoriesInstances(SpringBootExceptionReporter.class,
    new Class[] { ConfigurableApplicationContext.class }, context);
     // 准备ApplicationContext,Initializers设置到ApplicationContext后发布事件:ApplicationContextInitializedEvent
     // 打印启动日志,打印profile信息(如dev, test, prod)
     // 调用EventPublishingRunListener发布ApplicationContext加载完毕事件:ApplicationPreparedEvent
  prepareContext(context, environment, listeners, applicationArguments, printedBanner);
     // 最终会调用到AbstractApplicationContext#refresh方法,实际上就是Spring IOC容器的创建过程,并且会进行自动装配的操作
     // 以及发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成
  refreshContext(context);
     // hook方法
  afterRefresh(context, applicationArguments);
     // stopWatch停止计时,日志打印总共启动的时间
  stopWatch.stop();
  if (this.logStartupInfo) {
   new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
  }
     // 发布SpringBoot程序已启动事件ApplicationStartedEvent
  listeners.started(context);
     // 调用ApplicationRunner和CommandLineRunner
  callRunners(context, applicationArguments);
 }
 catch (Throwable ex) {
  handleRunFailure(context, ex, exceptionReporters, listeners);
  throw new IllegalStateException(ex);
 }

 try {
     // 最后发布就绪事件ApplicationReadyEvent,标志着SpringBoot可以处理就收的请求了
  listeners.running(context);
 }
 catch (Throwable ex) {
  handleRunFailure(context, ex, exceptionReporters, null);
  throw new IllegalStateException(ex);
 }
 return context;
}

三、SpringBoot启动事件

  • SpringApplicationRunListeners的唯一实现是EventPublishingRunListener。
  • 整个SpringBoot的启动,流程就是各种事件的发布,调用EventPublishingRunListener中的方法。
  • 只要明白了EventPublishingRunListener中事件发布的流程,也就明白了SpringBoot启动的大体流程。

EventPublishingRunListener

方法说明如下:

public class EventPublishingRunListener implements SpringApplicationRunListener, Ordered {

 private final SpringApplication application;

 private final String[] args;

 private final SimpleApplicationEventMulticaster 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);
  }
 }

 @Override
 public int getOrder() {
  return 0;
 }

    // SpringBoot启动事件
 @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));
 }

    // 准备ApplicationContext
 @Override
 public void contextPrepared(ConfigurableApplicationContext context) {
  this.initialMulticaster
    .multicastEvent(new ApplicationContextInitializedEvent(this.application, this.args, context));
 }

    // 发布ApplicationContext已经refresh事件,标志着ApplicationContext初始化完成
 @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));
 }

    // SpringBoot已启动事件
 @Override
 public void started(ConfigurableApplicationContext context) {
  context.publishEvent(new ApplicationStartedEvent(this.application, this.args, context));
  AvailabilityChangeEvent.publish(context, LivenessState.CORRECT);
 }

    // "SpringBoot现在可以处理接受的请求"事件
 @Override
 public void running(ConfigurableApplicationContext context) {
  context.publishEvent(new ApplicationReadyEvent(this.application, this.args, context));
  AvailabilityChangeEvent.publish(context, ReadinessState.ACCEPTING_TRAFFIC);
 }

 @Override
 public void failed(ConfigurableApplicationContext context, Throwable exception) {
  ApplicationFailedEvent event = new ApplicationFailedEvent(this.application, this.args, 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);
    }
   }
   this.initialMulticaster.setErrorHandler(new LoggingErrorHandler());
   this.initialMulticaster.multicastEvent(event);
  }
 }

 private static class LoggingErrorHandler implements ErrorHandler {

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

  @Override
  public void handleError(Throwable throwable) {
   logger.warn("Error calling ApplicationEventListener", throwable);
  }
 }
}

四、SpringIOC 容器初始化过程

由于现在大都是用SpringBoot开发,所以呢,Spring IOC 初始化的源码,就是AnnotationConfigApplicationContext中的源码,IoC的初始化就是该类实例创建的过程

创建的过程 (AnnotationConfigApplicationContext的构造方法)

  1. 给我们的Bean,创建与之对应的Bean定义,然后把他们放入 ConcurrentHashMap(key:beanName和value:beanDefinition)中;Bean定义实际上包括一些Bean的信息,比如BeanName, Scope, 是否被@Primary注解修饰,是否是@Lazy,以及@Description等注解。
  2. refresh()方法: 创建IOC需要的资源
  3. 初始化BeanFactory, set一些属性,如BeanClassLoadersystemEnvironment
  4. 如果是SpringBoot程序,会调用方法进行自动装配:AutoConfigurationImportSelector.AutoConfigurationGroup#process。
  5. 注册MessageSource,国际化相关的资源,到ApplicationContext。
  6. 注册ApplicationListener到ApplicationContext。
  7. 实例化化lazy-init的Bean。
  8. 最后,publish相关的事件,ApplicationContext 就初始化完成,整个IOC容器初始化完成(IOC容器的本质就是初始化BeanFactory和ApplicationContext),就可以从IOC容器中获取Bean自动注入了。

五、结语

已经工作了一段时间了,回头看看这些知识点,发现自己仍有许多不足之处,通过这次学习,不再让自己停留在“会用”的阶段,而是进一步理解其中原理,注重实践,自己还是得抓紧脚步跟上大佬的脚步。(☆_☆)

在这里插入图片描述

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot系列之【启动流程详解】 的相关文章

  • LiveData 的生命周期 - viewLifecycleOwner / this

    在给定所有者的生命周期内将给定的观察者添加到观察者列表中 事件在主线程上调度 如果 LiveData 已经有数据集 xff0c 它将被传递给观察者 方法 xff1a observe LifecycleOwner Observer 使用示例
  • 设计模式——生产者消费者模式

    1 基本概括 2 主要介绍 2 1 概念 生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题 生产者和消费者彼此之间不直接通讯 xff0c 而通过阻塞队列来进行通讯 xff0c 所以生产者生产完数据之后不用等待消费者处理 xff
  • Arch linux安装deepin-wine和微信(wechat)

    一 保证有国内的镜像源 xff0c 不然下载很慢 xff0c 没有国内包的镜像源 xff0c 则软件列表会少很多国内的软件 如果没有国内镜像源 xff0c 请修改镜像源 sudo vim etc pacman d mirrorlist s
  • KVM虚拟化(一)

    一 KVM虚拟化架构 1 主流虚拟机架构 图中对比了几种主流虚拟化技术架构 xff1a ESXi Xen与KVM xff0c 其主要差别在与各组件 xff08 CPU 内存 磁盘与网络IO xff09 的虚拟化与调度管理实现组件有所不同 在
  • docker Centos 7 安装 xfce4 桌面 + x11vnc + novnc

    docker Centos 7 安装 xfce4 桌面 43 x11vnc 43 novnc 启动容器环境变量xfce4 桌面安装 启动 Xvfb安装 启动 xfce4 桌面 x11vnc安装 x11vnc启动 x11vnc novnc 上
  • linux C++ 环境下的ActiveMQ学习

    ActiveMQ 1 概述 ActiveMQ 是Apache出品 xff0c 最流行的 功能强大的即时通讯和集成模式的开源服务器 ActiveMQ 是一个完全支持JMS1 1和J2EE 1 4规范的 JMS Provider实现 提供客户端
  • LUbuntu16.10安装及自动使用root登录(Ubuntu类似)

    LUbuntu是一款新的轻量级Ubuntu发行版 xff0c 结合LXDE使得LUbuntu安装 运行速度极快 xff0c 硬件资源要求很低 xff0c 支持X86和ARM架构处理器 可以在http lubuntu net 根据自己电脑的系
  • 【源码分享】-wpf界面源代码分享

    好久以前做过一段时间的界面编程 C 43 43 C Wpf的都做过一点 xff0c 见 个人博客中UI类目 所以资料里面关于界面编程的代码会多一些 xff0c 有时间就整理发出来共享学习 13年的时候用WPF写过一个简单的小程序作爬虫来获取
  • 十大 Python GUI 框架比较

    图形用户界面 GUI 是任何 Web 应用程序中最重要的部分之一 有人会说最重要的部分 xff0c 因为今天的老练用户很快就会放弃可用性差的应用程序 Python 应用程序也是如此 新手 Python 程序员在掌握了 Python 编程的基
  • 如何用ps将图片修改成指定大小

    这篇文章主要介绍如何用ps将图片修改成指定大小 xff1a 一 按原像素比例 修改图片的尺寸 1 先来看看它原来的尺寸 xff0c 如图所示 500X481px 2 首先用ps将图片文件打开 xff0c 使用ps菜单命令 xff1a lt
  • docker 安装rabbitMQ(最详细)

    docker 安装rabbitMQ 1 rabbitMQ介绍 RabbitMQ是一套开源 xff08 MPL xff09 的消息队列服务软件 xff0c 是由 LShift 提供的一个 Advanced Message Queuing Pr
  • 阿里云-ECS云服务器跨地域部署k8s集群

    阿里云 ECS云服务器跨地域部署k8s集群 一 背景介绍二 环境准备2 1 ECS云服务资源清单2 2 K8s软件列表 三 阿里云ECS服务器网络问题3 1 问题阐述3 2 解决方案 四 服务节点调整 xff08 master xff0c
  • 代码命名规范是一种责任也是一种精神(工匠精神)

    代码命名规范之美 规范概述命名规范管理类命名BootstrapProcessorManagerHolderFactoryProviderRegistrarEngineServiceTask 传播类命名ContextPropagator 回调
  • Kubernetes集群搭建(高可用)附全自动脚本

    高可用Kubernetes集群搭建 一 前言介绍 1 1 重要概念 二 系统架构 2 1 架构基本需求 2 2 架构图 三 环境准备 3 1 云服务或虚拟机清单 3 2 网络配置 针对使用vmware虚拟机用户需要操作 3 3 虚拟机网络设
  • k8s 集群部署ingress-nginx

    k8s 集群部署ingress nginx 环境准备 helm方式安装ingress nginx master边缘节点 创建命名空间和部署ingress 第一个ingress 例子 查看ingress controller配置 其实就是对应
  • k8s 集群部署traefik

    k8s 集群部署traefik k8s 集群部署traefik 环境准备 下载traefik helm方式安装traefik master边缘节点 执行安装 查看安装结果 安装traefik dashboard dashboard安装和查看
  • Linux系统(Centos)安装tomcat和部署Web项目

    文章结构 1 准备工作 2 在Linux下安装Tomcat8 0 3 Linux中配置tomcat的服务器启动和关闭和配置tomcat的开机启动 4 给tomcat配置用户名和密码登录 5 使用IDEA打包Maven托管的WEB项目 6 将
  • 如何判断蓝牙设备类型

    我们在开发Android的蓝牙应用时 xff0c 可能需要知道扫描到的蓝牙设备是什么类型 xff0c 然后过滤掉不符合要求的设备 xff0c 只保留符合要求的设备 xff0c 例如我们在车载系统上开发蓝牙电话应用时 xff0c 我们希望只显
  • Aspose.Words for Java 体验

    公司中要做一些导出word的工作 xff0c 经别人推荐 xff0c 使用了Aspose Words for Java xff0c 感觉很好用 xff0c 美中不足的地方就是 xff0c 它是收费软件 原理吗 xff1f 比较常规 xff0
  • 汽车制造行业OEM Tier1 Tier2指代什么?

    OEM OEM是Original Equipment Manufacturer的缩写 xff0c 通常指设备厂商 主机厂 整车厂 例如 xff1a 宝马 奔驰 奥迪 大众 丰田 国内汽车主机厂排名前十分别是上汽集团 东风汽车 北京汽车 长安

随机推荐

  • Android应用系统签名方法

    1 应用配置sharedUserId 在AndroidManifest xml文件根节点中加入属性 xff1a android sharedUserId 61 34 android uid system 34 2 找到系统签名文件 plat
  • Android 读取CPU/GPU运行参数(MTK平台)

    一 使用场景 Android运行一段世时间后 xff0c 系统出现卡顿 二 分析 amp 定位问题 系统卡顿 xff0c 同时又发现芯片温度很高 xff0c 怀疑是温度过高导致CPU降频 xff0c 因此我们要将一段时间内CPU的运行信息打
  • Android签名 (二) 制作签名文件

    你可能想知道 通过这篇博客可以解决哪些问题以及学到什么 xff1a 1 公司开发一个新的app xff0c 如何创建一个应用签名 xff1f 2 为了安全性 xff0c Android系统不想使用Google给的原生签名 xff0c 如何定
  • Android签名 (一) 查看签名信息

    你可能想知道 通过这篇博客可以解决哪些问题以及学到什么 xff1a 1 如果我们有一个应用 xff0c 如何查看应用的签名信息 xff1f 2 如果我们有签名原始文件 xff0c 如何查看签名文件中的签名信息 xff1f 这篇博客介绍了如何
  • L8 U3 职业生涯

    Module 1 谈论你最近的工作 1 描述工作 描述工作的形容词 让我们来看一下可以用来描述工作 xff0c 项目和客户的形容词 用enjoyable 和 engaging来描述你觉得有意思的工作或者项目 For me sales is
  • L8 U4 商务旅行

    Module 1 计划商务旅行 1 处理信息 You mean with Lindstrom 你是说和林特罗姆 xff1f Yes with Lindstrom 是的 xff0c 和林特罗姆 Exactly 完全正确 You mean yo
  • L8 U5 产品和创新

    Module 1 产品特点 1 产品特点 询问产品特性 在询问商店中的产品时 xff0c 您可能首先想询问其质量水平 top of the range 高档的middle of the range 中档的good value for mon
  • Android打包jar的两种方法

    方法一 xff1a 使用Android Studio打包 方法二 xff1a 使用Android Build System打包 一 Android Studio打包 1 新建Android Library xff0c 取名为opensdk
  • LCS 下载插件

    难度简单2收藏分享切换为英文接收动态反馈 小扣打算给自己的 VS code 安装使用插件 xff0c 初始状态下带宽每分钟可以完成 1 个插件的下载 假定每分钟选择以下两种策略之一 使用当前带宽下载插件将带宽加倍 xff08 下载插件数量随
  • 调用webservice异常总结

    发布和调用Webservice很简单 xff0c 但小问题依然不断 xff0c 特总结如下 xff1a 一 Java调用 net的webService产生 服务器未能识别 HTTP 标头 SOAPAction 的值 错误 解决方案 xff1
  • 去除Evaluation Warning : The document was created with Spire.PDF for Java.

    去除Evaluation Warning The document was created with Spire PDF for Java 最近项目中有一个需求需要把PDF文件添加页码 xff0c 最终在网上找到了用Spire去添加的方法
  • 第二章 简单网页的爬取与Xpath、Json使用 2021-09-09

    爬虫系列总目录 本章节介绍爬虫中使用的基础库用于选择 xff0c 过滤页面信息 包括requests xff0c bs4 xff0c xpath xff0c 正则表达式re xff0c json 等内容 xff0c 能够实现对简单页面的获取
  • 解决crontab定时任务多次执行

    今天使用linux crontab定时任务时 xff0c 可能由于配置不妥 xff0c 任务多执行一次 xff0c 如下是我程序的日志记录 xff08 执行了两次 xff0c 我设置的是每分钟执行1次 xff09 解决如下 重启cronta
  • ruoyi对接CAS统一身份认证

    暂定逻辑如下 xff1a 搭建CAS服务器端 xff1a 项目地址 xff1a https gitee com weigang wu cas server webapp git 项目里有二开的说明文档 xff0c 如 xff1a 按照自定义
  • Linux 系统启动过程

    简介 Linux 系统启动过程大致分为5个阶段 xff1a 1 内核的引导 2 运行 init 3 系统初始化 4 建立终端 5 用户登录系统 内核引导 计算机通电后 xff0c 首先会进行 BIOS 开机自检 xff0c 然后根据BIOS
  • Ubuntu 修改$PS1 自定义命令提示符

    文章更新于 xff1a 2020 03 25 文章目录 一 自定义命令提示符1 可修改的是那部分 xff1f 2 修改 PS1 变量3 PS1 变量格式4 如何修改背景颜色5 修改字体 二 Enjoy xff01 一 自定义命令提示符 1
  • C#byte类型

    byte类型的范围是0 255 转换为二进制是00000000 11111111 C 中对byte类型的处理还是很特殊的 下面用几行简单的代码来说明问题 byte x 61 1 byte y 61 2 byte z 61 x 43 y er
  • JVM内存分配机制详解

    目录 1 对象创建流程 TODO 1 1 jvm分配内存 1 2 设置对象头 1 2 1 对象头实例 1 2 2 指针压缩 2 JVM对象内存分配 2 1 逃逸分析和标量替换 2 1 1 逃逸分析和标量替换实战 2 2 eden区分配内存过
  • 【已解决】Windows10安装报:Windows检测到EFI系统分区格式化为NIFS,将EFI系统分区格式化为FAT32,然后重新启动安装

    手动擦除驱动器并将其转换为 GPT 关闭电脑 xff0c 然后放入 Windows 安装 DVD 或 USB 密钥 在 UEFI 模式下将电脑启动到 DVD 或 USB 密钥 有关详细信息 xff0c 请参阅 启动到 UEFI 模式或传统
  • SpringBoot系列之【启动流程详解】

    文章目录 前言一 SpringBoot特点二 SpringBoot启动流程图三 SpringBoot启动类入口1 注解 xff1a 64 SpringBootApplication 64 SpringBootConfiguration 64