当前位置:首页 >> 编程语言 >> 【Spring Boot】夺名连环问(持续更新ing),联想天骄s1500x

【Spring Boot】夺名连环问(持续更新ing),联想天骄s1500x

cpugpu芯片开发光刻机 编程语言 1
文件名:【Spring Boot】夺名连环问(持续更新ing),联想天骄s1500x 【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注解的配置类去配置

@Configurationpublic 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",第二次才是接口真正调用的请求方法。

@Overridepublic 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

协助本站SEO优化一下,谢谢!
关键词不能为空
同类推荐
«    2025年12月    »
1234567
891011121314
15161718192021
22232425262728
293031
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
搜索
最新留言
文章归档
网站收藏
友情链接