【Spring Boot】夺名连环问(持续更新ing)

Spring的了解与特性

  • 简单介绍:快速开发Spring项目的脚手架。简化Spring应用的初始搭建以及开发过程。

  • 特性

    • 提供了很多内置的Starter结合自动配置,对主流框架的无配置集成、开箱即用。即不需要自己去引入很多依赖

    • 并且管理了常用的第三方依赖的版本,减少了版本冲突的问题

    • 简化开发,采用JavaConfig的方式可以使用0xml方式进行开发。

    • 内置Web容器无需依赖外部Web服务器,省略了Web.xml,直接运行就使Web应用。

    • 提供了监控功能,可以监控应用程序的运行状态、内存、线程池、HTTP请求统计等。

Spring与SpringBoot的关系

  • 两者都是Spring生态的产品

  • 前者是容器框架,后者是一个快速开发Spring的脚手架(真正意义上不算新的框架)

核心注解

  • @SpringBootApplication

    • @SpringBootConfiguration(标记为配置类的注解)

      • @Configuration

    • @EnableAutoConfiguration:向Spring容器中导入了一个Selector,用来加载ClassPath下SpringFactories中所定义的配置类,将这些自动加载为配置Bean。

    • @Conditional(待补充)

自动配置原理

  • 通过@SpringBootApplication中的@EnableAutoConfiguration

  • @EnableAutoConfiguration引入了@Import

  • 在Spring容器启动时会加载IoC容器解析@Import注解

  • @Import导入了@DeferredImportSeletor注解,会使SpringBoot的自动配置类的顺序在最后,方便扩展的覆盖

  • 读取META-INF/spring.factories文件

  • 过滤出所有的AutoConfigurationClass类型的类

  • 最后通过@Condition排除无效的自动配置类

为什么SpringBoot的jar可以直接运行

  • 部署SpringBoot的插件才能将package后的jar包使用:java -jar packageName运行

    • 也就是在pom.xml中要有<build>中的<plugins>中<plugin>的spring-boot-maven-plugin。如果没有这个插件在运行命令的时候会报没有主清单属性。

  • 使用插件打包后生成的其实是一个Fat jar(也就是jar包中还有jar包),包含了应用依赖的jar包和Spring Boot Loader相关的类。 

  • java - jar会去找jar中的MANIFEST.MF文件,在那里面找到真正的启动类(Main-Class)。

  • Fat jar的启动Main函数是JarLauncher,它负责创建一个LaunchedURLClassLoader加载boot-lib下面的jar,并以一个新的线程启动应用Main函数(其实就是找到MANIFEST中的Start-Class)。

  • 当然,为了去验证此说法,就需要引入依赖从而去看JarLauncher中的源码,引入此依赖后再package就可以查看源码了。

    <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-loader</artifactId>
    </dependency>

启动原理

  • 运行主启动类的main方法,会初始化SpringApplication,从spring.factories读取listener ApplicationContextInitializer。此primarySources就是当前SpringBoot项目的class文件。

  • 在初始化SpringApplication中对一些属性的初始化,比如开启日志记录、关闭懒加载等等。不同版本的Boot在初始化的时候不一样,主要是最下面的七行。以下讲解最下面五行的作用。就不根据源码展示了。

    • 将启动类primarySources放入到名为primartSrouces的LinkedHashSet集合中

    • 推算当前Web应用类型webApplicationType

    • 读取ApplicationContextInitializer初始化器

    • 读取ApplicationListener监听器

    • 将Main方法所在的类放入mainApplicationClass中

  • 接下来就是运行run()方法了。代码太多就不展示了。主要是做了以下的操作:

    • 从上面读取的ApplicationListener监听器去读取SpringApplicationRunListener监听器运行器

    • 发布ApplicationStartingEvent事件

    • 封装命令行参数ApplicationArguments

    • 读取环境配置信息

      • 根据webApplicationType创建环境变量对象

      • 配置环境变量

      • 将现有环境信息设置为@ConfigurationProperties的数据源 并且放在第一位

      • 发布ApplicationEnvironmentPreparedEvent事件

      • 将spring.main的配置绑定到SpringApplication属性上

      • 将现有的环境信息设置为@ConfigurationProperties的数据源 更新放在第一位

    • 设置忽略bean.spring.beaninfo.ignore

    • 打印Banner

    • 实例化Spring上下文 AnnotationConfigServletWebServerApplicationContext

    • 初始化失败分析器

    • 准备上下文:将启动类作为配置类进行读取 -> 将配置注册为BeanDefinition

      • 将当前环境信息设置到context

      • 调用ApplicationContextInitializer

      • 发布ApplicationContextInitializerdEvent事件

      • 打印启动信息和profile.active(测试/生产/开发)信息

      • 将applicationArguments、printedBanner注册为Bean

      • 设置不允许同名Bean

      • 设置是否懒加载Bean

      • 读取启动类为BeanDefinition

      • 将SpringBoot的监听器添加到context发布 ApplicationPreparedEvent事件

    • 加载IoC容器(refresh()方法)其中的十二个步骤。具体干了什么可以去看我另外几篇源码解读的博文。主要两个方法

      • invokeBeanFactoryPostProcessor() -> 解析@Import 加载所有的自动配置类

      • onRefresh() 创建内置的Servlet容器

    • 待扩展(刷新后的处理)

    • 发布ApplicationStartedEvent事件
    • 发布ApplicationReadyEvent事件
    • 如果启动异常会发送ApplicationFailedEvent事件

public ConfigurableApplicationContext run(String... args) {StopWatch stopWatch = new StopWatch();stopWatch.start();ConfigurableApplicationContext context = null;Collection<SpringBootExceptionReporter> exceptionReporters = new ArrayList();this.configureHeadlessProperty();// 从上面读取的ApplicationListener监听器去读取SpringApplicationRunListener监听器运行器SpringApplicationRunListeners listeners = this.getRunListeners(args);// 发布ApplicationStartingEvent事件listeners.starting();
​Collection exceptionReporters;try {// 封装命令行参数ApplicationArgumentsApplicationArguments applicationArguments = new DefaultApplicationArguments(args);// 读取环境配置信息ConfigurableEnvironment environment = this.prepareEnvironment(listeners, applicationArguments);// 设置忽略bean.spring.beaninfo.ignore       this.configureIgnoreBeanInfo(environment);// 打印BannerBanner printedBanner = this.printBanner(environment);// 实例化Spring上下文 AnnotationConfigServletWebServerApplicationContextcontext = this.createApplicationContext();// 初始化失败分析器exceptionReporters = this.getSpringFactoriesInstances(SpringBootExceptionReporter.class, new Class[]{ConfigurableApplicationContext.class}, context);// 准备上下文:将启动类作为配置类进行读取 -> 将配置注册为BeanDefinitionthis.prepareContext(context, environment, listeners, applicationArguments, printedBanner);// 加载IoC容器this.refreshContext(context);// 待扩展(刷新后的处理)this.afterRefresh(context, applicationArguments);stopWatch.stop();if (this.logStartupInfo) {(new StartupInfoLogger(this.mainApplicationClass)).logStarted(this.getApplicationLog(), stopWatch);}
​// 发布ApplicationStartedEvent事件listeners.started(context);this.callRunners(context, applicationArguments);} catch (Throwable var10) {this.handleRunFailure(context, var10, exceptionReporters, listeners);throw new IllegalStateException(var10);}
​try {// 发布ApplicationReadyEvent事件listeners.running(context);return context;} catch (Throwable var9) {this.handleRunFailure(context, var9, exceptionReporters, (SpringApplicationRunListeners)null);throw new IllegalStateException(var9);}
}

内置Tomcat启动原理 

  • 当依赖spring-boot-starter-web依赖时会在SpringBoot中添加:ServletWebServerFactoryAutoConfiguration(servlet容器自动配置类)

  • 该自动配置类通过@Import导入了可用的(通过@ConditionalOnClass判断决定使用哪一个)的Web容器工厂(默认Tomcat)

  • 在内嵌的Tomcat类中配置了一个TomcatServletWebServerFactory的Bean(Web容器工厂)

  • 它会在SpringBoot启动时加载IoC容器、在onRefrush()方法中创建内嵌的Tomcat并启动

外部Tomcat启动原理

  • SpringBoot启动后会加载SpringServletContainerInitializer类,此类会去加载实现了WebApplicationInitializer接口的类,而其中SpringBootServletInitializer这个类就实现了这个接口,而需要继承这个类去传入当前应用的启动类。

WebApplicationContext rootAppContext = this.createRootApplicationContext(servletContext);
  • 想要使用外部的Tomcat,就需要排除内部的Tomcat。

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-tomcat</artifactId></exclusion></exclusions>
</dependency>
  • 创建一个配置类去继承SpringBootServletInitializer去重写configure()方法,将当前模块的启动类传入进去 。

public class TomcatConfig extends SpringBootServletInitializer {@Overrideprotected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {return builder.sources(StudySpringApplication.class);}
}

如何自定义Starter

  • 首先知道什么是Starter:Starter又名启动器。众所周知SpringBoot是简化了企业级Spring框架的开发,采用了约定优于配置的理念,而这个理念的实现就是用Starter组件。其会把对应功能的jar包导入进来使得开发者无需在意版本冲突问题,而只需在乎业务逻辑即可,这是作为引入依赖方面的简化配置。而此处要介绍的Starter就是代替Spring的内部组件,使用自定义的组件去完成启动。步骤如下

    • 新建resources/META-INF/spring.factories。这是因为Spring Boot在启动的时候会去读取ClassPath下的这个文件信息。

    • 在spring.factories中去用伪SPI去扫描我们自定义的Starter启动器组件

读取配置文件的原理以及加载顺序

  • 通过事件监听的方式读取配置文件,而读取配置文件的类则是ConfigFileApplicationListener

读取配置文件的方式

  • 属性上使用注解@Value("${xxx.yyy}"),需要注意三点:

    • 当前类是交给IoC容器管理的Bean,否则@Value注解不生效。

    • 当前属性不能是static或者final修饰。

    • 如果配置文件中没有对应的xxx: yyy的话会报错,但是可以在@Value中给默认值比如@Value("${xxx.yyy:}"),这样默认为""

    • 缺点是一个属性对应一个,如果需要读取多个属性的话,效率低下。

  • 类上使用注解@ConfigurationProperties(prefix = "xxx")去指定配置文件中的前缀去读取多个,然后属性采用同名方式去进行批量匹配。相比于上面那种而言,如果遇到大量的配置信息,则效率会更高。

@ConfigurationProperties(prefix = "xxx")
public class MyYamlConfig {private String yyy;
}
  • 通过Spring的应用上下文ApplicationContext去获取Environment对象。但相较于直接获取Environment对象,这样显得多此一举,除非除了要使用ApplicationContext获取运行时环境还有其它的作用。毕竟ApplicationContext是Spring应用程序中的中央接口,由于继承了多个组件所以有多个核心的功能。

  • Spring Bean类中去注入IoC管理的Environment对象。可以使用@Resource、@Autowired来注入。然后在代码块中通过此对象的getProperty("xxx.yyy")返回String对象进行使用即可。但是如果不用自动装配的方式(也就是非Spring管理的Bean),就需要此配置类去实现EnvironmentAware接口重写setEnvironment()方法去获取Environment对象。

  • 配置类上获取外部properties后缀配置文件的方式,比如需要读取/resources/gok.properties文件。在配置类上使用注解@PropertySources。然后就可以在属性上使用@Value注解去读取。

@Configuraion
@PropertySources({@propertySource(value = "classpath:gok.properties", encoding = "UTF-8")})
public class MyYamlConfig {@Value("${xxx.yyy}")private String name;
}
  • 获取/resources/gok.yml文件。在被@Configuration注解的配置类去配置

@Configuration
public class MyYamlConfig {@Beanpublic static PropertySourcesPlaceholderConfigurer yamlConfigurer() {PropertySourcesPlaceholderConfigurer configurer = new PropertySourcesPlaceholderConfigurer();YamlPropertiesFactoryBean yaml = new yamlPropertiesFactoryBean();yaml.setResources(new ClassPathResource("gok.yml"));configurer.setProperties(Object.requireNonNull(yaml.getObject()));return configurer;}
}
  • 采用Java原生态的方式(InputStream流)去读取文件

public class MyYamlConfig {public void yamlConfigurer() {Properties props = new Properties();try {InputStreamReader reader = new InputStreamReader(this.getClass().getClassLoader().getResourceAsStream("gok.properties"),StandardCharsets.UTF_8);props.load(reader);} catch (IOException e) {}String name = props.getProperty("xxx.yyy");}
}

解决跨域的方式

  • 首先了解什么是跨域:两个URL的协议/域名/IP/端口不同就会产生跨域。跨域是在前端才会发生的问题,因为浏览器有一个同源策略,这是防止不安全的不同域访问,所以会抛出异常(也就是跨域问题)。

  • Jsoup

    • 前端使用Ajax的jsoup方式,同时后端每个接口的参数都接收一个String callback,并且将其放到JSONObject的第一个参数返回。仅支持GET请求,而且存在安全问题。

  • CORS

    • 前端无需多做处理,后端有三种方式可以进行实现

      • 在接口上使用@CrossOrign注解,并且可以带上请求的协议以及域名/IP以及端口,比如"http://localhost:8080"

  • 让配置类(@Configuration)去实现WebMvcConfigurer接口重写addCorsMappings()方法。这个的弊端主要是浏览器会自动添加一些附加的头信息,甚至还会多发送一次附加的请求,第一次就是"options",第二次才是接口真正调用的请求方法。

    @Override
    public void addCorsMappings(CorsRegistry registry) {registry.addMapping("/**") // 添加映射路径.allowedOriginPatterns("*") // 设置放行哪些原始域 SpringBoot2.4.4低版本使用.allowedOrigins.allowedMethods("GET","HEAD","POST","PUT","DELETE","OPTIONS") // 放行哪些请求方式.allowCredentials(true) // 是否发送Cookie.maxAge(3600).allowedHeaders("*"); // 暴露哪些原始请求头部信息
    }
  • Nginx

    • 使用反向代理不需要在前后端多做处理,不需要添加代码量。只需要在Nginx的nginx.conf文件的server中进行配置,这里就略微详细说明一下:listen 端口; location /前端路径/ { proxy_pass http://localhost:8080/后端路径 }。  

可以同时处理多少请求?

  • 在yml中的Tomcat中配置(因为这是Spring Boot默认的Web容器)。配置与请求线程相关的配置进行测试。

    server:tomcat:# 最大连接数max-connections: 30# 最大等待数accept-count: 10threads:# 最少线程数min-spare: 10# 最多线程数max: 50
  • 可以使用ApiPost/JMeter进行并发测试。如下所示就是测试结果。我们不难发现:首先执行了30个线程,而后先执行完的10个线程又再一次进来执行了。这说明了1s可以处理40次请求,并且这40次请求中可以一次性处理30次,而最多可以等待10次。即配置中的accept-count(最大等待数) + max-connections(最大连接数)。

  • 在spring-configuration-metadata.json中可以发现最大连接数默认为10000,最大等待数为100。注意:我打开的是SpringBoot2.2.1版本,每个版本可能不一样

默认的日志框架以及如何切换

  • 默认情况下SpringBoot会采用slf4j(日志接口) + logback(日志框架)完成日志的实现。那么我们从哪里可以得出来呢?看过源码的基本都知道使用LoggerFactory中获取Logger对象从而记录日志的,所以我也创建了此对象来记录日志。

  •  而这个LoggerFactory与Logger都是slf4j的日志接口,其中slf4j内有三种日志框架:logback(默认)、log4j、jul-to-slf4j

  •  那么如何做到切换默认的日志实现框架呢?这里有两种情况:

    • 将logback切换为lof4j2:这样就需要将logback的场景启动器排除,因为slf4j这个日志接口只能运行1个桥接器。并且添加log4j2的场景启动器

      <!-- 如果不是Web应用就在spring-boot-starter中排除依赖 -->
      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><!-- 排除logback的场景启动器 --><exclusion><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-logging</artifactId></exclusion></exclusions>
      </dependency><!-- Log4j2场景启动器 -->
      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-log4j2</artifactId>
      </dependency>
    • 将logback切换为log4j。就需要将logback的桥接器排除。

      <!-- 如果不是Web应用就在spring-boot-starter中排除依赖 -->
      <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId><exclusions><!-- 排除logback的场景启动器 --><exclusion><groupId>logback-classic</groupId><artifactId>ch.qos.logback</artifactId></exclusion></exclusions>
      </dependency><!-- Log4j桥接器 -->
      <dependency><groupId>org.slf4j</groupId><artifactId>slf4j-log4j12</artifactId>
      </dependency>

如何优化启动速度

  • 延迟初始化Bean

    • 一般在SpringBoot中拥有很多的耗时任务,比如数据库连接、初始化线程池创建等等。我们可以延迟这些操作的初始化,来达到优化启动速度的目的。可以在yml中配置:spring:main:lazy-initialization: true。来将所有的Bean延迟初始化(懒加载机制)。

  • 创建扫描索引(Spring 5.0+)

    • 通过编译时创建一个静态候选列表来提高大型应用程序的启动性能。在项目中使用@Indexed之后,编译打包后的时候会在项目中自动生成META-INF/spring.components文件。当Spring应用上下文执行ComponentScan扫描时,就会用CandidateComponentsIndexLoader读取这个文件并加载,转换为CandidateComponentsIndex对象。

    • 步骤如下:引入依赖、启动类加上注解@Indexed。首次编译运行后会在/target/classes/META-INF中生成文件

      <dependency><groupId>org.springframework</groupId><artifactId>spring-context-indexer</artifactId><optional>true</optional>
      </dependency>

  • 减少@ComponentScan即@SpringBootApplication扫描类的范围

  • 关闭SpringBoot的JMX监控,yml中设置:spring:jmx:enabled: false

  • 设置JVM参数 -noverify,不对类进行验证

  • 排除项目中多余的依赖jar

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/63774.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

冒泡排序(超详细!)(C语言)

大家好&#xff01;欢迎来到Mr.kanglong的CSDN博文&#xff0c;今天来讨论一下冒泡排序&#xff0c;在百度百科中&#xff0c;关于为什么叫冒泡排序是这样解释的&#xff1a;因为越小的元素会经由交换慢慢“浮”到数列的顶端&#xff08;升序或降序排列&#xff09;&#xff0c…

软件测试工程师面试如何描述自动化测试是怎么实现的?

软件测试工程师面试的时候&#xff0c;但凡简历中有透露一点点自己会自动化测试的技能点的描述&#xff0c;都会被面试官问&#xff0c;那你结合你的测试项目说说自动化测试是怎么实现的&#xff1f;一到这里&#xff0c;很多网友&#xff0c;包括我的学生&#xff0c;也都一脸…

python小白之matplotlib使用实战项目:随机漫步

文章目录 随机漫步1.1 创建RandomWalk类1.2 选择方向1.3 绘制随机漫步图1.4 模拟多次随机漫步1.5 设置随机漫步图样式1.5.1 给点着色1.5.2 重新绘制起点和终点1.5.3 隐藏坐标轴1.5.4 增加点数1.5.5 调整图片尺寸以适应屏幕 附录&#xff08;项目代码&#xff09;random_walk.py…

【云原生|Docker系列第3篇】Docker镜像的入门实践

欢迎来到Docker入门系列的第三篇博客&#xff01;在前两篇博客中&#xff0c;我们已经了解了什么是Docker以及如何安装和配置它。本篇博客将重点介绍Docker镜像的概念&#xff0c;以及它们之间的关系。我们还将学习如何拉取、创建、管理和分享Docker镜像&#xff0c;这是使用Do…

Leetcode-每日一题【剑指 Offer 28. 对称的二叉树】

题目 请实现一个函数&#xff0c;用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样&#xff0c;那么它是对称的。 例如&#xff0c;二叉树 [1,2,2,3,4,4,3] 是对称的。 1 / \ 2 2 / \ / \ 3 4 4 3 但是下面这个 [1,2,2,null,3,null,3] 则不是镜像对称…

[保研/考研机试] 猫狗收容所 C++实现

题目描述&#xff1a; 输入&#xff1a; 第一个是n&#xff0c;它代表操作序列的次数。接下来是n行&#xff0c;每行有两个值m和t&#xff0c;分别代表题目中操作的两个元素。 输出&#xff1a; 按顺序输出收养动物的序列&#xff0c;编号之间以空格间隔。 源代码&#xff…

【总结】Javaweb和Java项目的比较

&#x1f384;欢迎来到边境矢梦的csdn博文&#x1f384; &#x1f384;本文主要梳理Javaweb中的关键点和需要注意的地方&#x1f384; &#x1f308;我是边境矢梦&#xff0c;一个正在为秋招和算法竞赛做准备的学生&#x1f308; &#x1f386;喜欢的朋友可以关注一下&#x1f…

【iOS】autoreleasepool

来说一下最近在了解的autoreleasepool吧&#xff0c;我们可能平时书写过许多脑残代码&#xff0c;其有很多的缺陷但是我们可能当时学的比较浅就也不太了解&#xff0c;就像下面这样的&#xff1a; for (int i 0; i < 1000000; i) {NSNumber *num [NSNumber numberWithInt…

C语言 ——指针数组与数组指针

目录 一、二维数组 二、指针数组 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;书写方式 &#xff08;3&#xff09;指针数组模拟二维数组 三、数组指针 &#xff08;1&#xff09;概念 &#xff08;2&#xff09;使用数组指针打印一维数组 &#xff08;3&a…

搭建 Python 环境 | Python、PyCharm

计算机 计算机能完成的工作&#xff1a; 算术运算逻辑判断数据存储网络通信…更多的更复杂的任务 以下这些都可以称为 “计算机”&#xff1a; 一台计算机主要由以下这几个重要的组件构成 CPU 中央处理器&#xff1a;大脑&#xff0c;算术运算&#xff0c;逻辑判断 存储器&…

用PointNet分类3D点云

在本教程中&#xff0c;我们将学习如何训练PointNet进行分类。 我们将主要关注数据和训练过程&#xff1b; 展示如何从头开始编码 Point Net 的教程位于此处。 本教程的代码位于这个Github库中&#xff0c;我们将使用的笔记本位于这个Github库中。 一些代码的灵感来自于这个Git…

MyBatis操作数据库常用用法总结1

文章目录 1.单表查询1.1返回所有的表记录1.2根据id查找结果1.3根据名字查找结果 2.单表修改2.1修改密码 3.单表删除3.1根据id删除信息 4.单表增加&#xff08;根据业务情况返回&#xff09;4.1添加返回影响的行数4.2添加返回影响行数和id 5.多表查询&#xff08;多&#xff09;…