SpringBoot微服组件
语雀完整版:
https://www.yuque.com/g/mingrun/embiys/tg4ykv/collaborator/join?token=qlyX7pFpOkClrFRn&source=doc_collaborator# 《SpringBoot&微服组件》
SpringBoot&微服组件
SpringBoot
自动配置原理
常用注解
- 组合注解
-
- 被其它注解 注解的注解叫做组合注解
-
- 元注解 注解其它注解的注解
- @Value
-
- 相当于Bean标签中的 property标签
-
- 取值可以是
-
-
- 字面量
-
-
-
- ${key} 从环境变量 或者全局配置文件中获取值
-
-
-
- #{SpEL}
-
- @ConfigurationProperties 【SpringBoot 提供】
-
- 从配置文件中 注入多个值
-
- 示例
@Component
@ConfigurationProperties(prefix = "person") // 获取person开头的配置
public class Person { private String name; private Integer age; private String sex;
}
- @Import 【Spring 提供】
-
- 作用:导入普通的Java类,并将其声明为大的Bean
-
- 直接导入普通的 Java 类
public class Circle { public void sayHi() { System.out.println("Circle sayHi()"); }
}
@Import({Circle.class})
@Configuration
public class MainConfig {
}
-
- 配合自定义的 ImportSelector
-
-
- 该接口中只有一个 selectImports 方法,用于返回全类名数组。所以利用该特性我们可以给容器动态导入 N 个 Bean。
-
public class MyImportSelector implements ImportSelector { @Override public String[] selectImports(AnnotationMetadata annotationMetadata) { return new String[]{"annotation.importannotation.waytwo.Triangle"}; }
}
@Import({Circle.class,MyImportSelector.class})
@Configuration
public class MainConfigTwo {
}
-
- 配合 ImportBeanDefinitionRegistrar
public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) { RootBeanDefinition rootBeanDefinition = new RootBeanDefinition(Rectangle.class); // 注册一个名字叫做 rectangle 的 bean beanDefinitionRegistry.registerBeanDefinition("rectangle", rootBeanDefinition); }
}
@Import({Circle.class, MyImportSelector.class, MyImportBeanDefinitionRegistrar.class})
@Configuration
public class MainConfigThree {
}
- @Conditional 【Spring提供】
-
- 使用该注解,可以在特定条件下才启用一些配置
-
- 案例
public class ConditionBean { public void sayHi() { System.out.println("ConditionBean sayHi()"); }
}
public class MyCondition implements Condition { @Override public boolean matches(ConditionContext conditionContext, AnnotatedTypeMetadata annotatedTypeMetadata) { //这里先直接返回truereturn true; }
}
@Configuration
@Conditional(MyCondition.class)
public class ConditionConfig { @Bean public ConditionBean conditionBean(){ return new ConditionBean(); }
}
然后测试,就会发现ConditionBean已经在容器中了
Spring提供的常见的Condition
SpringBoot 启动过程
- 当启动springboot应用程序的时候, 会先创建SpringApplication的对象, 在对象的构造方法中会进行某些参数的初始化工作,最主要的是判断当前应用程序的类型以及初始化器和监听器,在这个过程中会加载整个应用程序中的spring.factories文件,将文件的内容放到缓存对象中,方便后续获取。
- SpringApplication对象创建完成之后, 开始执行run方法,来完成整个启动,启动过程中最主要的有两个方法,第一个叫做prepareContext, 第二个叫做refreshContext,在这两个关键步骤中完整了自动装配的核心功能,前面的处理逻辑包含了上下文对象的创建,banner的打印, 异常报告期的准备等各个准备工作,方便后续来进行调用。
- 在prepareContext方法中主要完成的是对上下文对象的初始化操作,包括了属性值的设置,比如环境对象,在整个过程中有-个非常重要的方法,叫做load, load主要完成一 件事,将当前启动类做为一个beanDefinition注册到registry中,方便后续在进行BeanFactoryPostProcessor调用执行的时候, 找到对应的主类,来完成@SpringBootApplicaiton,@EnableAutoConfiguration等注解的解析工作
- 在refreshContext方法中会进行整个容器刷新过程, 会调用中spring中的refresh方法, refresh中有13个非常关键的方法,来完成整个spring应用程序的启动,在自动装配过程中,会调用invokeBeanFactoryPostProcessor方法,在此方法中主要是对ConfigurationClassPostProcessor类的处理, 这次是BFPP的子类也是BDRPP的子类,在调用的时候会先调用BDRPP中的postProcessBeanDefinitionRegistry方法,然后调用postProcessBeanFactory方法,在执行postProcessCeanDefinitionRegistry的时候回解析处理各种注解,包含@PropertySource,@ComponentScan,@ComponentScans, @Bean,@lmport等注解,最主要的是@lmport注解的解析
- 在解析@Import注解的时候,会有一个getlmports的方法, 从主类开始递归解析注解,把所有包含
@Import的注解都解析到,然后在processlmport方法中对Import的类进行分类, 此处主要识别的时候
AutoConfigurationlmportSelect归属于ImportSelect的子类,在后续过程中会调用
deferredlmportSelectorHandler中的process方法,来完整EnableAutoConfiguration的加载。
SpringBoot 自动配置过程
[SpringBoot自动配置原理.png](D:\JAVA\思维图\05-1 Spring一家人\SpringBoot自动配置原理.png)
这样讲 SpringBoot 自动配置原理,你应该能明白了吧 - 掘金 (juejin.cn)
聊聊 SpringBoot 自动装配原理 - 掘金 (juejin.cn)
- 概述
-
- 没有SpringBoot的情况下,如果要在项目中使用redis,就要加第三方依赖,然后手动配置,而在springboot中直接引入一个 starter即可
- 流程概述:进入@SpringBootApplication注解,就会看到@EnableAutoConfiguration,再进去有两个步骤
-
- 第一个步骤是加载 启动类和启动类所在的包和子包的所有Bean 到容器
-
- 第二个步骤是到 META-INF/spring.factories下加载所有的EnableAutoConfiguration到Spring容器中
- 其它总结
-
- SpringBoot启动会加载大量的自动配置类。
-
- 我们看需要的功能有没有SpringBoot默认写好的自动配置类。
-
- 我们再来看这个自动配置类中到底配置了那些组件(只要我们要用的组件有,我们就不需要再来配置了)。
-
- 给容器中自动配置类添加组件的时候,会从properties类中获取某些属性。我们就可以在配置文件中指定这些属性的值。
-
-
xxxAutoConfiguration
:自动配置类给容器中添加组件。
-
-
-
xxxProperties
:封装配置文件中相关属性。
-
- 自定义Starter流程
-
- 引入spring-boot-starter依赖
-
- 创建一个自己的AutoConfiguration(它的作用就是加载配置,然后返回Bean)
- 创建一个自己的AutoConfiguration(它的作用就是加载配置,然后返回Bean)
-
- 然后在你这个工程的 resources 包下创建
META-INF/spring.factories
文件
- 然后在你这个工程的 resources 包下创建
-
- 然后就可以在其它工程引用这个starter了
- 然后就可以在其它工程引用这个starter了
- SPI(Service Provider Interface)
-
- 高级开发必须理解的Java中SPI机制 - 简书 (jianshu.com)
深入理解SPI机制 - 简书 (jianshu.com)
- 高级开发必须理解的Java中SPI机制 - 简书 (jianshu.com)
-
- 是一种服务发现机制,通过在ClassPath路径下的META-INF/services文件夹查找文件,自动加载文件里所定义的类。
-
- 实现步骤
-
-
- 定义一个 接口和它的实现类们
-
-
-
- 在classpath下面加一个文件,文件名字是接口的全限定类名,内容是实现类的全限定类名
- 在classpath下面加一个文件,文件名字是接口的全限定类名,内容是实现类的全限定类名
-
-
-
- 调用代码
-
public static void main(String[] args) { //SPIService是上面所说的接口 有两个实现类//调用方式一 使用ServiceIterator<SPIService> providers = Service.providers(SPIService.class);while(providers.hasNext()) {SPIService ser = providers.next();ser.execute();}System.out.println("--------------------------------");//调用方式二 使用ServiceLoaderServiceLoader<SPIService> load = ServiceLoader.load(SPIService.class);Iterator<SPIService> iterator = load.iterator();while(iterator.hasNext()) {SPIService ser = iterator.next();ser.execute();}
}
-
-
- ServiceLoader,其中serviceLoader先判断缓存中 是否有已经实例化好的对象,如果没有就读取 META-INF/services/下的配置(可以扫描多个jar包路径下的),通过反射方法Class.forName()加载类对象,并用instance()方法将类实例化,然后再缓存(一个LinkedHashMap<String,S>类型)
-
-
- SPI优缺点
-
-
- 优点是 解耦合,与调用者分离了,而且调用者不用再关心具体的实现类 在什么路径,比如JDBC,之前是Class.forName("com.mysql.jdbc.Driver"),后来就直接使用 DriveManger
-
-
-
- 缺点就是如果用 ServiceLoader,遍历的时候,会把全部的实现都加载一遍
-
微服组件
Eureka
- 概述
-
- 主要包括两个组件 Eureka Server和Eureka client
- 流程流程说明
(1)服务提供者集成Eureka客户端,启动时,Eureka客户端发送注册请求到Eureka服务端;
(2)Eureka客户端每30秒发送一次续约请求到Eureka服务端;
(3)若Eureka服务端超过90秒未收到服务提供者的续约请求,则Eureka服务端会将其从服务列表中踢除;
(4)服务消费者集成Eureka客户端,启动时,Eureka客户端同样会发送注册请求到Eureka服务端;
(5)服务消费者中,Eureka客户端每隔30秒,会从Eureka服务端获取服务实例列表,其中,首次是全量查询,后续为增量查询;获取到实例列表后,Eureka客户端会将其写入到本地缓存中;
(6)服务消费者调用服务提供者时,Eureka会总本地缓存获取对应的实例,并进行接口调用。
- 多级缓存机制
-
- 拉取注册表
首先从ReadOnlyCacheMap里查缓存的注册表。若没有,就找ReadWriteCacheMap里缓存的注册表。如果还没有,就从内存中获取实际的注册表数据。
- 拉取注册表
-
- 注册表发生变更
会在内存中更新变更的注册表数据,同时过期掉ReadWriteCacheMap。此过程不会影响ReadOnlyCacheMap提供人家查询注册表。
一段时间内(默认30秒),各服务拉取注册表会直接读ReadOnlyCacheMap 30秒过后,Eureka Server的后台线程发现ReadWriteCacheMap已经清空了,也会清空ReadOnlyCacheMap中的缓存 下次有服务拉取注册表,又会从内存中获取最新的数据了,同时填充各个缓存。
- 注册表发生变更
-
- 多级缓存机制的优点是什么?
尽可能保证了内存注册表数据不会出现频繁的读写冲突问题。并且进一步保证对Eureka Server的大量请求,都是快速从纯内存走,性能极高。
- 多级缓存机制的优点是什么?
Ribbon
Spring-Cloud之Ribbon原理剖析 - 起岸星辰 - 博客园 (cnblogs.com)
- 内置的负载均衡策略
-
RoundRobinRule
:直接轮询的方案;即每次从server list中依次选择。
-
AvailabilityfileringRule
:根据服务器可用性来决定;比如某个服务器的并发请求过高,那么此时ribbon就会绕过不再访问;同时如果3次连接失败就会等待30秒后再次访问;如果不断失败,那么等待时间会不断变长,如果某个服务器的并发请求太高了,那么会绕过去,不再访问。
-
WeightedResponseTimeRule
:根据权重来分配;每个服务器都可以有权重,权重高就优先访问,如果某个服务器响应时间比较长,那么权重就会降低,减少访问。
-
ZoneAvoidanceRule
:根据区域和服务器来进行负载均衡,也就是根据机房来分配,里面再使用轮询的方式。默认的就是这个
-
BestAvailableRule
:忽略连接失败的服务器,同时尽量找并发比较低的服务器来请求。
-
RandomRule
:随机选择一个。
-
RetryRule
:在轮询的方案上增加重试机制;即通过轮询的方式选择一个服务器请求,在失败的时候会重新再找一个重试。
- 过程
-
- Ribbon主要就是 通过服务名拿到服务列表,然后再根据负载均衡策略选择一个服务,通过拦截器设置请求地址
-
- Iping机制:ribbon的IPing机制,就是每隔30秒去检查注册表中的服务 是否可用,不可用就移除
其中,Ribbon获取注册表是每30秒从Eureka Client拷贝一次注册表
- Iping机制:ribbon的IPing机制,就是每隔30秒去检查注册表中的服务 是否可用,不可用就移除
- 客户端负载均衡与服务端负载均衡
Fegin
- 实现思路
-
- 在类上标上注解(注解上包含要调用的接口信息),之后就会根据注解封装好请求信息,然后用ribbon获取真实的地址信息,请求发出后 获得结果再发序列化交给对象处理
- 核心机制
[Fegin核心机制.png](D:\JAVA\思维图\05-1 Spring一家人\微服组件\Fegin核心机制.png)
Nacos
阿里面试这样问:Nacos配置中心交互模型是 push 还是 pull ?(原理+源码分析) (qq.com)
- 配置中心
-
- 作用:对配置进行统一管理,修改配置后可以动态感知,无需重启
-
- 客户端与配置中心的交互:推与拉
-
-
- 推:与客户端建立长连接,一有变动立刻推送,缺点就是可能会因为网络问题出现假死,连接状态正常却无法通信,这就需要心跳机制来保证连接的可用性
-
-
-
- 拉:客户端轮询 向配置中心拉取数据,比如每隔3s一次,但这样做不到实时性,而且可能会服务端产生压力
-
-
-
-
- 长轮询与短轮询
-
-
-
-
-
-
- 短轮询:每隔几秒钟拉取一次配置,而配置一般是不修改的,而且这种方式更新会有延迟
-
-
-
-
-
-
-
- 长轮询:客户端请求过来后,如果配置变更,那就立刻响应,如果没有变更,那就直到超时时间过后在响应,然后客户端再发起下次的长连接
- 长轮询:客户端请求过来后,如果配置变更,那就立刻响应,如果没有变更,那就直到超时时间过后在响应,然后客户端再发起下次的长连接
-
-
-
-
- Nacos采用的就是 客户端拉的方式,而且是长轮询
-
- 配置中心的核心概念
-
-
- dataId是一份配置文件的id,key value格式存储,value是配置文件的内容
-
-
-
- groupId分组,用于一个环境下的不同分支的配置隔离
-
-
-
- nameSpace:dev、test这种不同的环境
-
- 架构设计
-
- 客户端控制台 将自己的配置推送到配置中心,配置中心会见配置持久化在MySQL中
-
- 然后客户端获取最新配置的时候 向服务端发起长轮询请求,获取配置后也会在本地存一份,有限读取