目录
1.Spring Boot与以前的Spring有什么区别?
2.Spring Boot启动加载过程是什么样的?
先进行总的分析汇总
具体详细如下:
一、基本代码启动
二、初始化SpringApplication
1.初始化SpringApplication的initialize方法
2.调用deduceWebEnvironment来判断当前的应用是否是web应用,并设置到webEnvironment属性中。
3.找出所有的应用程序初始化器 getSpringFactoriesInstances + setInitializers
补充:获取初始化器
4.找出所有的应用程序事件监听器 getSpringFactoriesInstances + setListeners
补充:获取监听器
5.调用deduceMainApplicationClass方法找出main类
三、运行SpringApplication
1.SpringApplicationRunListeners类
2.SpringApplicationRunListener类:监听SpringApplication的run方法执行
补充:获取SpringApplicationRunListeners
四、run方法详细
1.配置并准备环境
2.创建Spring容器上下文
3.配置Spring容器上下文
4.Spring容器创建之后回调方法postProcessApplicationContext
5.初始化器开始工作
6.Spring容器创建完成之后会调用afterRefresh方法
3.Spring的IOC/AOP的实现(必考)
4.动态代理的实现方式(必考)是否使用过GCLB,和JDK的区别是什么?
知识背景:JDK和CGLIB动态代理总结
补充问题:
何时使用JDK还是CGLiB?
如何强制使用CGLIB实现AOP?
JDK动态代理和CGLIB字节码生成的区别?
CGlib比JDK快?
Spring在选择用JDK还是CGLiB的依据:
5.Spring如何解决循环依赖(三级缓存)(必考)
循环依赖的产生和解决的前提
Spring使用了三级缓存解决了循环依赖的问题
6.Spring的@Transactional如何实现的(必考)
7.Spring的事务传播级别
补充:隔离级别
8.BeanFactory和ApplicationContext的联系和区别
9.Spring的后置处理器
10.Spring Cloud Zuul网关的调优策略有哪些?怎么实现其高可用?Zuul和Gataway,你们项目中是怎么选择的?项目中对Zuul网关层的要求是什么样的?
11.Spring Cloud Eureka和Nacos对比?怎么做选择?Eureka中高可用是怎么做的?进行的调优有哪些?原理是什么?
12.Spring Cloud 中常用的注解有哪些?怎么用的?
13.Spring Cloud中的组件有哪些?具体说说?微服务架构中用到的关键技术有哪些?
14.Spring Cloud Config配置架构是什么样的?可视化怎么做的?设计的业务有哪些?
参考书籍、文献和资料
备注:针对基本问题做一些基本的总结,不是详细解答!
具体可以见博客:https://blog.csdn.net/xiaofeng10330111/article/details/87271456
Spring开发WEB应用程序过程广泛采用的固定开发模式:通常包括使用Maven、Gradle等工具搭建工程、web.xml定义Spring的DispatcherServlet、完成启动Spring MVC的配置文件、编写响应HTTP请求的Controller以及服务部署到Tomcat Web服务器等步骤。但是,基于传统Spring框架进行开发的开发过程中,逐渐暴露出一些问题,典型的就是过于复杂和繁重的配置工作。
Spring Boot优化了开发过程,采用约定优于配置思想的自动化配置、启动依赖项目自动管理、简化部署并提供监控等功能,是开发过程变得简单。其核心优势体现在编码、配置、部署、监控等多个方面:
Spring Boot通常有一个名为*Application的入口类,在入口类里有一个main方法,这个main方法其实就是一个标准的java应用的入口方法。在main方法中使用SpringApplication.run方法启动SpringBoot应用项目。
其中@SpringBootApplication是Spring Boot的核心注解,主要组合了@Configuration、@EnableAutoConfiguration、@ComponentScan。(如果不使用@SpringBootApplication注解,则可以使用在入口类上直接使用@Configuration、@EnableAutoConfiguration、@ComponentScan也能达到相同效果。)
其中几个注解的作用大致说一下:
spring-boot启动过程:
在这个静态方法中,创建并构造了SpringApplication对象,并调用该对象的run方法。
构造SpringApplication对象:主要是对一些属性附上初始值,关键在与SpringApplication对象的initialize方法。
deduceWebEnvironment
来判断当前的应用是否是web应用,并设置到webEnvironment
属性中。getSpringFactoriesInstances
从spring.factories文件中找出key为ApplicationContextInitializer的类并实例化,然后调用setInitializers
方法设置到SpringApplication
的initializers
属性中。getSpringFactoriesInstances
从spring.factories文件中找出key为ApplicationListener的类并实例化,然后调用setListeners
方法设置到SpringApplication
的listeners
属性中。deduceMainApplicationClass
方法找出main类初始化SpringApplication完成之后,调用run
方法运行,run方法执行完成之后,Spring容器也已经初始化完成,各种监听器和初始化器也做了相应的工作。
启动代码很简单,直接如下便可以完成:
@SpringBootApplication
public class SpringBootDemoApplication {
public static void main(String[] args) {
SpringApplication.run(SpringBootDemoApplication.class, args);
}
}
SpringApplication.run
方法实际执行的方法如下:
public static ConfigurableApplicationContext run(Object[] sources, String[] args) {
return new SpringApplication(sources).run(args);
}
initialize
方法SpringApplication的构造函数中调用了initialize
方法来初始化SpringApplication:
private void initialize(Object[] sources) {
if (sources != null && sources.length > 0) {
this.sources.addAll(Arrays.asList(sources));
}
this.webEnvironment = deduceWebEnvironment();
setInitializers((Collection) getSpringFactoriesInstances(
ApplicationContextInitializer.class));
setListeners((Collection) getSpringFactoriesInstances(ApplicationListener.class));
this.mainApplicationClass = deduceMainApplicationClass();
}
deduceWebEnvironment
来判断当前的应用是否是web应用,并设置到webEnvironment
属性中。deduceWebEnvironment
方法通过获取
javax.servlet.Servlet
org.springframework.web.context.ConfigurableWebApplicationContext
这两个类来判断,如果能获得这两个类则说明是web应用,否则不是。
getSpringFactoriesInstances +
setInitializers
调用getSpringFactoriesInstances
从spring.factories文件中找出key为ApplicationContextInitializer的类并实例化,然后调用setInitializers
方法设置到SpringApplication
的initializers
属性中。这个过程就是找出所有的应用程序初始化器。
当前的初始化器有如下几个:
DelegatingApplicationContextInitializer
ContextIdApplicationContextInitializer
ConfigurationWarningsApplicationContextInitializer
ServerPortInfoApplicationContextInitializer
SharedMetadataReaderFactoryContextInitializer
AutoConfigurationReportLoggingInitializer
初始化器的获取由SpringApplication.getSpringFactoriesInstances
方法完成:
private Collection extends T> getSpringFactoriesInstances(Class type,
Class>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
// 读取ApplicationContextInitializer的实现类
Set names = new LinkedHashSet(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 实例化ApplicationContextInitializer的实现类
List instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
SpringFactoriesLoader.loadFactoryNames
方法获取ApplicationContextInitializer
接口实现的类:
public static List loadFactoryNames(Class> factoryClass, ClassLoader classLoader) {
// 获取接口类的名称
String factoryClassName = factoryClass.getName();
try {
// 获取FACTORIES_RESOURCE_LOCATION(META-INF/spring.factories)的多个位置
Enumeration urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List result = new ArrayList();
/**
* urls有
* spring-boot/META-INF/spring.factories
* spring-beans/META-INF/spring.factories
* spring-boot-autoconfigure/META-INF/spring.factories
*
*/
while (urls.hasMoreElements()) {
URL url = urls.nextElement();
// 从META-INF/spring.factories文件中加载配置
Properties properties = PropertiesLoaderUtils.loadProperties(new UrlResource(url));
// 从配置中读取ApplicationContextInitializer的实现类
String factoryClassNames = properties.getProperty(factoryClassName);
result.addAll(Arrays.asList(StringUtils.commaDelimitedListToStringArray(factoryClassNames)));
}
return result;
}
catch (IOException ex) {
throw new IllegalArgumentException("Unable to load [" + factoryClass.getName() +
"] factories from location [" + FACTORIES_RESOURCE_LOCATION + "]", ex);
}
}
getSpringFactoriesInstances + setListeners
调用getSpringFactoriesInstances
从spring.factories文件中找出key为ApplicationListener的类并实例化,然后调用setListeners
方法设置到SpringApplication
的listeners
属性中。这个过程就是找出所有的应用程序事件监听器。
当前的事件监听器有如下几个:
ConfigFileApplicationListener
AnsiOutputApplicationListener
LoggingApplicationListener
ClasspathLoggingApplicationListener
BackgroundPreinitializer
DelegatingApplicationListener
ParentContextCloserApplicationListener
ClearCachesApplicationListener
FileEncodingApplicationListener
LiquibaseServiceLocatorApplicationListener
获取监听器的方法与获取初始化器的方法一致:
唯一的区别在于获取org.springframework.context.ApplicationListener
接口的实现类
deduceMainApplicationClass
方法找出main类就是这里的SpringBootDemoApplication
类
重点:SpringApplicationRunListeners和SpringApplicationRunListener类
SpringApplicationRunListeners内部持有SpringApplicationRunListener集合和1个Log日志类。用于SpringApplicationRunListener监听器的批量执行。
SpringApplicationRunListener用于监听SpringApplication的run方法的执行,它定义了5个步骤:
SpringApplicationRunListener目前只有一个实现类EventPublishingRunListener,它把监听的过程封装成了SpringApplicationEvent事件并让内部属性ApplicationEventMulticaster接口的实现类SimpleApplicationEventMulticaster广播出去,广播出去的事件对象会被SpringApplication中的listeners属性进行处理。
所以说SpringApplicationRunListener和ApplicationListener之间的关系是通过ApplicationEventMulticaster广播出去的SpringApplicationEvent所联系起来的。
首先看getRunListeners
方法:
private SpringApplicationRunListeners getRunListeners(String[] args) {
Class>[] types = new Class>[] { SpringApplication.class, String[].class };
return new SpringApplicationRunListeners(logger, getSpringFactoriesInstances(
SpringApplicationRunListener.class, types, this, args));
}
可以看到通过调用构造函数来实例化SpringApplicationRunListeners
,传入的参数有logger以及调用getSpringFactoriesInstance
获得的SpringApplicationRunListener
集合。
再看getSpringFactoriesInstance
方法,它和获取初始化器的方法一样:
获取的接口类型是org.springframework.boot.SpringApplicationRunListener
。
获取到的实现类为org.springframework.boot.context.event.EventPublishRunListener
。
初始化SpringApplication完成之后,调用run
方法运行,run方法执行完成之后,Spring容器也已经初始化完成,各种监听器和初始化器也做了相应的工作。
public ConfigurableApplicationContext run(String... args) {
StopWatch stopWatch = new StopWatch(); // 构造一个任务执行观察者
stopWatch.start(); // 开始执行,记录开始时间
ConfigurableApplicationContext context = null;
FailureAnalyzers analyzers = null;
configureHeadlessProperty();
// 获取SpringApplicationRunListeners,内部只有一个EventPublishingRunListener
SpringApplicationRunListeners listeners = getRunListeners(args);
// 封装成SpringApplicationEvent事件然后广播出去给SpringApplication中的listeners所监听
// 这里接受ApplicationStartedEvent事件的listener会执行相应的操作
listeners.starting();
try {
// 构造一个应用程序参数持有类
ApplicationArguments applicationArguments = new DefaultApplicationArguments(args);
// 准备并配置环境
ConfigurableEnvironment environment = prepareEnvironment(listeners, applicationArguments);
// 打印banner图形
Banner printedBanner = printBanner(environment);
// 创建Spring容器
context = createApplicationContext();
analyzers = new FailureAnalyzers(context);
// 配置Spring容器
prepareContext(context, environment, listeners, applicationArguments, printedBanner);
// 容器上下文刷新,详见Spring的启动分析
refreshContext(context);
// 容器创建完成之后调用afterRefresh方法
afterRefresh(context, applicationArguments);
// 调用监听器,广播Spring启动结束的事件
listeners.finished(context, null);
// 停止任务执行观察者
stopWatch.stop();
if (this.logStartupInfo) {
new StartupInfoLogger(this.mainApplicationClass).logStarted(getApplicationLog(), stopWatch);
}
return context;
}
catch (Throwable ex) {
handleRunFailure(context, listeners, analyzers, ex);
throw new IllegalStateException(ex);
}
}
具体步骤如:
private ConfigurableEnvironment prepareEnvironment(SpringApplicationRunListeners listeners, ApplicationArguments applicationArguments) {
// 创建应用程序的环境信息。如果是web程序,创建StandardServletEnvironment;否则,创建StandardEnvironment
ConfigurableEnvironment environment = getOrCreateEnvironment();
// 配置环境信息。比如profile,命令行参数
configureEnvironment(environment, applicationArguments.getSourceArgs());
// 广播出ApplicationEnvironmentPreparedEvent事件给相应的监听器执行
listeners.environmentPrepared(environment);
// 环境信息的校对
if (!this.webEnvironment) {
environment = new EnvironmentConverter(getClassLoader())
.convertToStandardEnvironmentIfNecessary(environment);
}
return environment;
}
protected ConfigurableApplicationContext createApplicationContext() {
Class> contextClass = this.applicationContextClass;
if (contextClass == null) {
try {
// 判断是否是web应用,
// 如果是则创建AnnotationConfigEmbeddedWebApplicationContext,否则创建AnnotationConfigApplicationContext
contextClass = Class.forName(this.webEnvironment
? DEFAULT_WEB_CONTEXT_CLASS : DEFAULT_CONTEXT_CLASS);
}
catch (ClassNotFoundException ex) {
throw new IllegalStateException(
"Unable create a default ApplicationContext, "
+ "please specify an ApplicationContextClass",
ex);
}
}
return (ConfigurableApplicationContext) BeanUtils.instantiate(contextClass);
}
private void prepareContext(ConfigurableApplicationContext context,
ConfigurableEnvironment environment, SpringApplicationRunListeners listeners,
ApplicationArguments applicationArguments, Banner printedBanner) {
// 设置Spring容器上下文的环境信息
context.setEnvironment(environment);
// Spring容器创建之后做一些额外的事
postProcessApplicationContext(context);
// SpringApplication的初始化器开始工作
applyInitializers(context);
// 遍历调用SpringApplicationRunListener的contextPrepared方法。目前只是将这个事件广播器注册到Spring容器中
listeners.contextPrepared(context);
if (this.logStartupInfo) {
logStartupInfo(context.getParent() == null);
logStartupProfileInfo(context);
}
// 把应用程序参数持有类注册到Spring容器中,并且是一个单例
context.getBeanFactory().registerSingleton("springApplicationArguments",
applicationArguments);
if (printedBanner != null) {
context.getBeanFactory().registerSingleton("springBootBanner", printedBanner);
}
// 加载sources,sources是main方法所在的类
Set
protected void postProcessApplicationContext(ConfigurableApplicationContext context) {
// 如果SpringApplication设置了实例命名生成器,则注册到Spring容器中
if (this.beanNameGenerator != null) {
context.getBeanFactory().registerSingleton(
AnnotationConfigUtils.CONFIGURATION_BEAN_NAME_GENERATOR,
this.beanNameGenerator);
}
// 如果SpringApplication设置了资源加载器,设置到Spring容器中
if (this.resourceLoader != null) {
if (context instanceof GenericApplicationContext) {
((GenericApplicationContext) context)
.setResourceLoader(this.resourceLoader);
}
if (context instanceof DefaultResourceLoader) {
((DefaultResourceLoader) context)
.setClassLoader(this.resourceLoader.getClassLoader());
}
}
}
首先调用getInitializers
方法获取之前取得的初始化器。之后调用初始化器的initialize
方法。
protected void applyInitializers(ConfigurableApplicationContext context) {
// 遍历每个初始化器,调用对应的initialize方法
for (ApplicationContextInitializer initializer : getInitializers()) {
Class> requiredType = GenericTypeResolver.resolveTypeArgument(
initializer.getClass(), ApplicationContextInitializer.class);
Assert.isInstanceOf(requiredType, context, "Unable to call initializer.");
initializer.initialize(context);
}
}
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
callRunners(context, args);
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List
IOC相关知识见博客:https://blog.csdn.net/xiaofeng10330111/article/details/105631666
AOP的实现方式:动态代理的实现方式
后面会自己补一个详细的。
实现方式有两种:JDK动态代理和CGLIB动态代理
JDK动态代理:利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
CGLiB动态代理:利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
相关更细的知识见博客:https://blog.csdn.net/xiaofeng10330111/article/details/105633821
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。
循环依赖的产生可能有很多种情况,例如:
当然,Spring对于循环依赖的解决不是无条件的,首先前提条件是针对scope单例并且没有显式指明不需要解决循环依赖的对象,而且要求该对象没有被代理过。同时Spring解决循环依赖也不是万能,以上三种情况只能解决两种,第一种在构造方法中相互依赖的情况Spring也无力回天。
Spring循环依赖的理论依据其实是Java基于引用传递,当我们获取到对象的引用时,对象的field或者属性是可以延后设置的。
Spring单例对象的初始化其实可以分为三步:
对于单例对象来说,在Spring的整个容器的生命周期内,有且只存在一个对象,很容易想到这个对象应该存在Cache中,Spring大量运用了Cache的手段,在循环依赖问题的解决过程中甚至使用了“三级缓存”。“三级缓存”主要是指
/** Cache of singleton objects: bean name --> bean instance */
private final Map singletonObjects = new ConcurrentHashMap(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map> singletonFactories = new HashMap>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map earlySingletonObjects = new HashMap(16);
从字面意思来说:singletonObjects指单例对象的cache(第一级缓存),singletonFactories指单例对象工厂的cache(第三级缓存),earlySingletonObjects指提前曝光的单例对象的cache(第二级缓存)。以上三个cache构成了三级缓存,Spring就用这三级缓存巧妙的解决了循环依赖问题。
Bean创建的过程,首先Spring会尝试从缓存中获取,这个缓存就是指singletonObjects,主要调用的方法getSingleton,分析getSingleton的整个过程,Spring首先从singletonObjects(一级缓存)中尝试获取,如果获取不到并且对象在创建中,则尝试从earlySingletonObjects(二级缓存)中获取,如果还是获取不到并且允许从singletonFactories通过getObject获取,则通过singletonFactory.getObject()(三级缓存)获取。
在populateBean()给属性赋值阶段里面Spring会解析你的属性,并且赋值,当发现,A对象里面依赖了B,此时又会走getBean方法,但这个时候,你去缓存中是可以拿的到的。因为我们在对createBeanInstance对象创建完成以后已经放入了缓存当中,所以创建B的时候发现依赖A,直接就从缓存中去拿,此时B创建完,A也创建完,一共执行了4次。至此Bean的创建完成,最后将创建好的Bean放入单例缓存池中。
@Transactional是spring中声明式事务管理的注解配置方式,@Transactional注解可以帮助我们把事务开启、提交或者回滚的操作,通过aop的方式进行管理。通过@Transactional注解就能让spring为我们管理事务,免去了重复的事务管理逻辑,减少对业务代码的侵入,使我们开发人员能够专注于业务层面开发。
实现@Transactional原理是基于spring aop,aop又是动态代理模式的实现。主要源码思路如下图:
具体源码分析不做总结,后期会单独总结一篇。
事务传播行为(为了解决业务层方法之间互相调用的事务问题):当事务方法被另一个事务方法调用时,必须指定事务应该如何传播
例如:方法可能继续在现有事务中运行,也可能开启一个新事务并在自己的事务中运行。
在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
支持当前事务的情况:
不支持当前事务的情况:
其他情况:
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
详细内容查看:https://blog.csdn.net/xiaofeng10330111/article/details/87272495
Spring Cloud Zuul网关的调优策略
Zuul 1.0 是一个基于JVM的后端路由器,同时是一个建立在Servlet上的同步阻塞架构,故在使用时对这部分的优化工作是必要的,根据实践经验,对Zuul的优化分为以下几个类型:
高可用方案
Spring Cloud Zuul网关高可用可以借助OpenResty整合的Nginx和Lua,使用Lua脚本模块与注册中心构建一个服务动态增减的机制,通过Lua获取注册中心状态为UP的服务,动态地加入到Nginx的负载均衡列表中,将其称之为“多层负载”。
其实也可以结合K8S特性去实现,这个之前玩K8S的时候试验过,是可以实现的!
Zuul和Gataway对比和选择
从底层源码上来看,Zuul构建于 Servlet 2.5,兼容 3.x,使用的是阻塞式的 API,不支持长连接,比如 websockets。另外
Spring Cloud Gateway构建于 Spring 5+,基于 Spring Boot 2.x 响应式的、非阻塞式的 API。同时,它支持 websockets,和 Spring 框架紧密集成,开发体验相对来说十分不错。
在微服务架构中网关上的选择,最好的方式是使用现在比较成熟的Spring Cloud套件,Zuul和Gataway都可以,最好提供了Spring Cloud Gateway网关,或是结合公司情况来开发一套适合自己的微服务套件,至少从网关上可以看出来其内部实现并不难,同时也比较期待开源项目Nacos、Spring Cloud Alibaba 建设情况,期待它能构建一个高活跃社区的、稳定的、适合中国特色(大流量、高并发)的微服务基础架构。
项目中对网关的要求
基本具备以下功能:认证和鉴权+压力控制+金丝雀测试+动态路由+负载均衡+静态响应处理+主动流量控制+限流+文件上传+参数转换+其他逻辑与业务处理等。
详细请查看:https://blog.csdn.net/xiaofeng10330111/article/details/87271881
都是服务注册发现中心,但是Nacos还可以用作配置中心,目前来看,建议使用Nacos,因为Eureka已经不在开源,而且性能上和高可用上没有Nacos方便。
相关调优方案见上面的博客。
在介绍Spring Cloud 全家桶之前,首先要介绍一下Netflix ,Netflix 是一个很伟大的公司,在Spring Cloud项目中占着重要的作用,Netflix 公司提供了包括Eureka、Hystrix、Zuul、Archaius等在内的很多组件,在微服务架构中至关重要,Spring在Netflix 的基础上,封装了一系列的组件。
相关具体组件见:https://blog.csdn.net/xiaofeng10330111/article/details/87271644
关键技术及要求基本有:
微服务架构-实现技术之六大基础组件:服务通信+事件驱动+负载均衡+服务路由+API网关+配置管理
对应博客:https://blog.csdn.net/xiaofeng10330111/article/details/85682513
微服务架构-实现技术之三大关键要素1服务治理:服务注册中心+服务发布与注册+服务发现与调用+服务监控
对应博客:https://blog.csdn.net/xiaofeng10330111/article/details/86770057
微服务架构-实现技术之三大关键要素2数据一致性:分布式事物+CAP&BASE+可靠事件模式+补偿模式+Sagas模式+TCC模式+最大努力通知模式+人工干预模式
对应博客:https://blog.csdn.net/xiaofeng10330111/article/details/86772650
微服务架构-实现技术之三大关键要素3服务可靠性:服务访问失败的原因和应对策略+服务容错+服务隔离+服务限流+服务降级
对应博客:https://blog.csdn.net/xiaofeng10330111/article/details/86772740
具体见博客:https://blog.csdn.net/xiaofeng10330111/article/details/87272559
1.https://blog.wangqi.love/articles/Spring/SpringBoot%E5%90%AF%E5%8A%A8%E8%BF%87%E7%A8%8B.html Spring Boot启动加载过程讲的很详细
2.https://blog.csdn.net/xiaofeng10330111/article/details/87271456
3.https://blog.csdn.net/zxzzxzzxz123/article/details/69941910
4.https://www.cnblogs.com/liubin1988/p/8909610.html
5.https://blog.csdn.net/weixin_38327420/article/details/85068641 动态代理的举例
6.https://www.cnblogs.com/wangenxian/p/10885309.html
7.https://www.cnblogs.com/gonjan-blog/p/6685611.html
8.https://www.jianshu.com/p/6c359768b1dc
9.https://blog.csdn.net/qq_20597727/article/details/84868035
10.http://www.cppcns.com/ruanjian/java/216206.html
11.https://www.ibm.com/developerworks/cn/java/j-master-spring-transactional-use/index.html
12.https://www.cnblogs.com/chongaizhen/p/11003832.html
13.https://blog.csdn.net/xiaofeng10330111/article/details/87272495
14.https://blog.csdn.net/youanyyou/article/details/90100546
15.https://blog.csdn.net/u010681191/article/details/99656413
16.https://blog.csdn.net/weixin_42685925/article/details/94456156
17.https://blog.csdn.net/xiaofeng10330111/article/details/87271881
18.https://blog.csdn.net/xlgen157387/article/details/77773908
19.https://www.iteye.com/blog/jinnianshilongnian-1413846