目录
1.初始化SpringApplication的initialize方法
2.调用deduceWebEnvironment来判断当前的应用是否是web应用,并设置到webEnvironment属性中。
3.找出所有的应用程序初始化器 getSpringFactoriesInstances + setInitializers
4.找出所有的应用程序事件监听器 getSpringFactoriesInstances + setListeners
5.调用deduceMainApplicationClass方法找出main类
1.SpringApplicationRunListeners类
2.SpringApplicationRunListener类:监听SpringApplication的run方法执行
补充:获取SpringApplicationRunListeners
4.Spring容器创建之后回调方法postProcessApplicationContext
6.Spring容器创建完成之后会调用afterRefresh方法
4.动态代理的实现方式(必考)是否使用过GCLB,和JDK的区别是什么?
6.Spring的@Transactional如何实现的(必考)
8.BeanFactory和ApplicationContext的联系和区别
10.Spring Cloud Zuul网关的调优策略有哪些?怎么实现其高可用?Zuul和Gataway,你们项目中是怎么选择的?项目中对Zuul网关层的要求是什么样的?
11.Spring Cloud Eureka和Nacos对比?怎么做选择?Eureka中高可用是怎么做的?进行的调优有哪些?原理是什么?
12.Spring Cloud 中常用的注解有哪些?怎么用的?
13.Spring Cloud中的组件有哪些?具体说说?微服务架构中用到的关键技术有哪些?
14.Spring Cloud Config配置架构是什么样的?可视化怎么做的?设计的业务有哪些?
备注:针对基本问题做一些基本的总结,不是详细解答!
1.Spring Boot与以前的Spring有什么区别?
具体可以见博客: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优化了开发过程,采用约定优于配置思想的自动化配置、启动依赖项目自动管理、简化部署并提供监控等功能,是开发过程变得简单。其核心优势体现在编码、配置、部署、监控等多个方面:
- 编码方面:只需要在maven中添加依赖并实现一个方法就可以提供微服务架构所推荐的RESTful风格接口。
- 配置方面:简单化—>1>把Spring中基于XML的功能配置方式转换为Java Config;2>把基于*.properties/*.xml文件部署环境配置转换成语言更为强大的*.yml;3>对常见的各种功能组件均提供了各种默认的starter依赖以简化Maven的配置。
- 部署方面:相较于传统模式下的war包,Spring Boot的部署既包含了业务代码和各种第三方类库,同时也内嵌了HTTP容器。新的部署方式包结构支持java-jar standalone.jar方式的一键启动,不需要预部署应用服务器,通过默认内嵌Tomcat降低对运行环境的基本要求。
- 监控方面:基于spring-boot-actuator组件,可以通过RESTful接口以及HATEOAS表现方式获取JVM性能指标、线程工作状态等运行信息。
2.Spring Boot启动加载过程是什么样的?
先进行总的分析汇总
Spring Boot通常有一个名为*Application的入口类,在入口类里有一个main方法,这个main方法其实就是一个标准的java应用的入口方法。在main方法中使用SpringApplication.run方法启动SpringBoot应用项目。
其中@SpringBootApplication是Spring Boot的核心注解,主要组合了@Configuration、@EnableAutoConfiguration、@ComponentScan。(如果不使用@SpringBootApplication注解,则可以使用在入口类上直接使用@Configuration、@EnableAutoConfiguration、@ComponentScan也能达到相同效果。)
其中几个注解的作用大致说一下:
- @Configuration:是做类似于spring xml 工作的注解,标注在类上,类似与以前的**.xml配置文件。
- @EnableAutoConfiguration:spring boot自动配置时需要的注解,会让Spring Boot根据类路径中的jar包依赖为当前项目进行自动配置。同时,它也是一个组合注解。
- 在@EnableAutoConfiguration中用了@Import注解导入EnableAutoConfigurationImportSelector类,而EnableAutoConfigurationImportSelector就是自动配置关键。(SpringBoot的自动配置:SpringBoot的一大特色就是自动配置,例如,添加了spring-boot-starter-web依赖,会自动添加Tomcat和SpringMVC的依赖,SpringBoot会对Tomcat和SpringMVC进行自动配置。又例如:添加了spring-boot-starter-data-jpa依赖,SpringBoot会自动进行JPA相关的配置。)
- @ComponentScan:告诉Spring 哪个packages 的用注解标识的类,会被spring自动扫描并且装入bean容器。SpringBoot会自动扫描@SpringBootApplication所在类的同级包以及下级包的Bean(如果为JPA项目还可以扫描标注@Entity的实体类),所以建议入口类放置在最外层包下。
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容器也已经初始化完成,各种监听器和初始化器也做了相应的工作。
- 具体运行SpringApplication,重点由SpringApplicationRunListeners和SpringApplicationRunListener类实现
- SpringApplicationRunListeners内部持有SpringApplicationRunListener集合和1个Log日志类。用于SpringApplicationRunListener监听器的批量执行。
- SpringApplicationRunListener类:监听SpringApplication的run方法执行。
- run具体的实现包括:配置并准备环境—>创建Spring容器上下文—>配置Spring容器上下文—>Spring容器创建之后回调方法postProcessApplicationContext—>初始化器开始工作—>Spring容器创建完成之后会调用afterRefresh方法
具体详细如下:
一、基本代码启动
启动代码很简单,直接如下便可以完成:
@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);
}
二、初始化SpringApplication
1.初始化SpringApplication的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();
}
2.调用deduceWebEnvironment
来判断当前的应用是否是web应用,并设置到webEnvironment
属性中。
deduceWebEnvironment
方法通过获取
javax.servlet.Servlet
org.springframework.web.context.ConfigurableWebApplicationContext
这两个类来判断,如果能获得这两个类则说明是web应用,否则不是。
3.找出所有的应用程序初始化器 getSpringFactoriesInstances +
setInitializers
调用getSpringFactoriesInstances
从spring.factories文件中找出key为ApplicationContextInitializer的类并实例化,然后调用setInitializers
方法设置到SpringApplication
的initializers
属性中。这个过程就是找出所有的应用程序初始化器。
当前的初始化器有如下几个:
DelegatingApplicationContextInitializer
ContextIdApplicationContextInitializer
ConfigurationWarningsApplicationContextInitializer
ServerPortInfoApplicationContextInitializer
SharedMetadataReaderFactoryContextInitializer
AutoConfigurationReportLoggingInitializer
补充:获取初始化器
初始化器的获取由SpringApplication.getSpringFactoriesInstances
方法完成:
- 读取ApplicationContextInitializer的实现类
- 实例化ApplicationContextInitializer的实现类
private <T> Collection<? extends T> getSpringFactoriesInstances(Class<T> type,
Class<?>[] parameterTypes, Object... args) {
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
// Use names and ensure unique to protect against duplicates
// 读取ApplicationContextInitializer的实现类
Set<String> names = new LinkedHashSet<String>(SpringFactoriesLoader.loadFactoryNames(type, classLoader));
// 实例化ApplicationContextInitializer的实现类
List<T> instances = createSpringFactoriesInstances(type, parameterTypes, classLoader, args, names);
AnnotationAwareOrderComparator.sort(instances);
return instances;
}
SpringFactoriesLoader.loadFactoryNames
方法获取ApplicationContextInitializer
接口实现的类:
- 获取接口类的名称
- 获取FACTORIES_RESOURCE_LOCATION(META-INF/spring.factories)的多个位置
- 从META-INF/spring.factories文件中加载配置
- 从配置中读取ApplicationContextInitializer的实现类
public static List<String> loadFactoryNames(Class<?> factoryClass, ClassLoader classLoader) {
// 获取接口类的名称
String factoryClassName = factoryClass.getName();
try {
// 获取FACTORIES_RESOURCE_LOCATION(META-INF/spring.factories)的多个位置
Enumeration<URL> urls = (classLoader != null ? classLoader.getResources(FACTORIES_RESOURCE_LOCATION) :
ClassLoader.getSystemResources(FACTORIES_RESOURCE_LOCATION));
List<String> result = new ArrayList<String>();
/**
* 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);
}
}
4.找出所有的应用程序事件监听器 getSpringFactoriesInstances + setListeners
调用getSpringFactoriesInstances
从spring.factories文件中找出key为ApplicationListener的类并实例化,然后调用setListeners
方法设置到SpringApplication
的listeners
属性中。这个过程就是找出所有的应用程序事件监听器。
当前的事件监听器有如下几个:
ConfigFileApplicationListener
AnsiOutputApplicationListener
LoggingApplicationListener
ClasspathLoggingApplicationListener
BackgroundPreinitializer
DelegatingApplicationListener
ParentContextCloserApplicationListener
ClearCachesApplicationListener
FileEncodingApplicationListener
LiquibaseServiceLocatorApplicationListener
补充:获取监听器
获取监听器的方法与获取初始化器的方法一致:
唯一的区别在于获取org.springframework.context.ApplicationListener
接口的实现类
5.调用deduceMainApplicationClass
方法找出main类
就是这里的SpringBootDemoApplication
类
三、运行SpringApplication
重点:SpringApplicationRunListeners和SpringApplicationRunListener类
1.SpringApplicationRunListeners类
SpringApplicationRunListeners内部持有SpringApplicationRunListener集合和1个Log日志类。用于SpringApplicationRunListener监听器的批量执行。
2.SpringApplicationRunListener类:监听SpringApplication的run方法执行
SpringApplicationRunListener用于监听SpringApplication的run方法的执行,它定义了5个步骤:
- starting:run方法执行的时候立马执行,对应的事件类型是ApplicationStartedEvent
- environmentPrepared:ApplicationContext创建之前并且环境信息准备好的时候调用,对应的事件类型是ApplicationEnvironmentPreparedEvent
- contextPrepared:ApplicationContext创建好并且在source加载之前调用一次,没有具体的对应事件
- contextLoaded:ApplicationContext创建并加载之后并在refresh之前调用,对应的事件类型是ApplicationPreparedEvent
- finished:run方法结束之前调用,对应事件的类型是ApplicationReadyEvent或ApplicationFailedEvent
SpringApplicationRunListener目前只有一个实现类EventPublishingRunListener,它把监听的过程封装成了SpringApplicationEvent事件并让内部属性ApplicationEventMulticaster接口的实现类SimpleApplicationEventMulticaster广播出去,广播出去的事件对象会被SpringApplication中的listeners属性进行处理。
所以说SpringApplicationRunListener和ApplicationListener之间的关系是通过ApplicationEventMulticaster广播出去的SpringApplicationEvent所联系起来的。
补充:获取SpringApplicationRunListeners
首先看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
。
四、run方法详细
初始化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);
}
}
具体步骤如:
1.配置并准备环境
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;
}
2.创建Spring容器上下文
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);
}
3.配置Spring容器上下文
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<Object> sources = getSources();
Assert.notEmpty(sources, "Sources must not be empty");
// 将sources加载到应用上下文中。最终调用的是AnnotatedBeanDefinitionReader.registerBean方法
load(context, sources.toArray(new Object[sources.size()]));
// 广播出ApplicationPreparedEvent事件给相应的监听器执行
// 执行EventPublishingRunListener.contextLoaded方法
listeners.contextLoaded(context);
}
4.Spring容器创建之后回调方法postProcessApplicationContext
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());
}
}
}
5.初始化器开始工作
首先调用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);
}
}
6.Spring容器创建完成之后会调用afterRefresh方法
protected void afterRefresh(ConfigurableApplicationContext context,
ApplicationArguments args) {
callRunners(context, args);
}
private void callRunners(ApplicationContext context, ApplicationArguments args) {
List<Object> runners = new ArrayList<Object>();
// 找出Spring容器中ApplicationRunner接口的实现类
runners.addAll(context.getBeansOfType(ApplicationRunner.class).values());
// 找出Spring容器中CommandLineRunner接口的实现类
runners.addAll(context.getBeansOfType(CommandLineRunner.class).values());
// 对runners进行排序
AnnotationAwareOrderComparator.sort(runners);
// 遍历runners依次执行
for (Object runner : new LinkedHashSet<Object>(runners)) {
if (runner instanceof ApplicationRunner) {
callRunner((ApplicationRunner) runner, args);
}
if (runner instanceof CommandLineRunner) {
callRunner((CommandLineRunner) runner, args);
}
}
}
3.Spring的IOC/AOP的实现(必考)
IOC相关知识见博客:对IOC的相关理解总结_说一下你对ioc的理解_张彦峰ZYF的博客-CSDN博客
AOP的实现方式:动态代理的实现方式
- JDK动态代理:利用反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
- CGlib动态代理:利用ASM(开源的Java字节码编辑库,操作字节码)开源包,将代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
- 区别:JDK代理只能对实现接口的类生成代理;CGlib是针对类实现代理,对指定的类生成一个子类,并覆盖其中的方法,这种通过继承类的实现方式,不能代理final修饰的类。
后面会自己补一个详细的。
4.动态代理的实现方式(必考)是否使用过GCLB,和JDK的区别是什么?
实现方式有两种:JDK动态代理和CGLIB动态代理
- JDK动态代理只能对实现了接口的类生成代理,而不能针对类;
- CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法(继承);
-
JDK动态代理:利用拦截器(拦截器必须实现InvocationHanlder)加上反射机制生成一个实现代理接口的匿名类,在调用具体方法前调用InvokeHandler来处理。
-
CGLiB动态代理:利用ASM开源包,对代理对象类的class文件加载进来,通过修改其字节码生成子类来处理。
知识背景:JDK和CGLIB动态代理总结
相关更细的知识见博客:代理模式的使用总结_张彦峰ZYF的博客-CSDN博客
- JDK动态代理只能对实现了接口的类生成代理,而不能针对类 ,使用的是 Java反射技术实现,生成类的过程比较高效。
- CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法 ,使用asm字节码框架实现,相关执行的过程比较高效,生成类的过程可以利用缓存弥补,因为是继承,所以该类或方法最好不要声明成final
- JDK代理是不需要第三方库支持,只需要JDK环境就可以进行代理,使用条件:实现InvocationHandler + 使用Proxy.newProxyInstance产生代理对象 + 被代理的对象必须要实现接口
- CGLib必须依赖于CGLib的类库,但是它需要类来实现任何接口代理的是指定的类生成一个子类,覆盖其中的方法,是一种继承但是针对接口编程的环境下推荐使用JDK的代理;
补充问题:
何时使用JDK还是CGLiB?
- 如果目标对象实现了接口,默认情况下会采用JDK的动态代理实现AOP。
- 如果目标对象实现了接口,可以强制使用CGLIB实现AOP。
- 如果目标对象没有实现了接口,必须采用CGLIB库,Spring会自动在JDK动态代理和CGLIB之间转换。
如何强制使用CGLIB实现AOP?
- 添加CGLIB库(aspectjrt-xxx.jar、aspectjweaver-xxx.jar、cglib-nodep-xxx.jar)
- 在Spring配置文件中加入<aop:aspectj-autoproxy proxy-target-class=”true”/>
JDK动态代理和CGLIB字节码生成的区别?
- JDK动态代理只能对实现了接口的类生成代理,而不能针对类。
- CGLIB是针对类实现代理,主要是对指定的类生成一个子类,覆盖其中的方法,并覆盖其中方法实现增强,但是因为采用的是继承,所以该类或方法最好不要声明成final,对于final类或方法,是无法继承的。
CGlib比JDK快?
使用CGLib实现动态代理,CGLib底层采用ASM字节码生成框架,使用字节码技术生成代理类,在jdk6之前比使用Java反射效率要高。唯一需要注意的是,CGLib不能对声明为final的方法进行代理,因为CGLib原理是动态生成被代理类的子类。
在jdk6、jdk7、jdk8逐步对JDK动态代理优化之后,在调用次数较少的情况下,JDK代理效率高于CGLIB代理效率,只有当进行大量调用的时候,jdk6和jdk7比CGLIB代理效率低一点,但是到jdk8的时候,jdk代理效率高于CGLIB代理,总之,每一次jdk版本升级,jdk代理效率都得到提升,而CGLIB代理消息确有点跟不上步伐。
Spring在选择用JDK还是CGLiB的依据:
- 当Bean实现接口时,Spring会用JDK的动态代理。
- 当Bean没有实现接口时,Spring使用CGlib是实现。
- 如果Bean实现了接口,强制使用CGlib时,(添加CGLIB库,在spring配置中加入<aop:aspectj-autoproxy proxy-target-class=”true”/>)。
5.Spring如何解决循环依赖(三级缓存)(必考)
循环依赖的产生和解决的前提
循环依赖的产生可能有很多种情况,例如:
- A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象
- A的构造方法中依赖了B的实例对象,同时B的某个field或者setter需要A的实例对象,以及反之
- A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象,以及反之
当然,Spring对于循环依赖的解决不是无条件的,首先前提条件是针对scope单例并且没有显式指明不需要解决循环依赖的对象,而且要求该对象没有被代理过。同时Spring解决循环依赖也不是万能,以上三种情况只能解决两种,第一种在构造方法中相互依赖的情况Spring也无力回天。
Spring循环依赖的理论依据其实是Java基于引用传递,当我们获取到对象的引用时,对象的field或者属性是可以延后设置的。
Spring单例对象的初始化其实可以分为三步:
- createBeanInstance, 实例化,实际上就是调用对应的构造方法构造对象,此时只是调用了构造方法,spring xml中指定的property并没有进行populate
- populateBean,填充属性,这步对spring xml中指定的property进行populate
- initializeBean,调用spring xml中指定的init方法,或者AfterPropertiesSet方法
会发生循环依赖的步骤集中在第一步和第二步。
对于单例对象来说,在Spring的整个容器的生命周期内,有且只存在一个对象,很容易想到这个对象应该存在Cache中,Spring大量运用了Cache的手段,在循环依赖问题的解决过程中甚至使用了“三级缓存”。“三级缓存”主要是指
/** Cache of singleton objects: bean name --> bean instance */
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
/** Cache of singleton factories: bean name --> ObjectFactory */
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);
/** Cache of early singleton objects: bean name --> bean instance */
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);
从字面意思来说:singletonObjects指单例对象的cache(第一级缓存),singletonFactories指单例对象工厂的cache(第三级缓存),earlySingletonObjects指提前曝光的单例对象的cache(第二级缓存)。以上三个cache构成了三级缓存,Spring就用这三级缓存巧妙的解决了循环依赖问题。
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放入单例缓存池中。
6.Spring的@Transactional如何实现的(必考)
@Transactional是spring中声明式事务管理的注解配置方式,@Transactional注解可以帮助我们把事务开启、提交或者回滚的操作,通过aop的方式进行管理。通过@Transactional注解就能让spring为我们管理事务,免去了重复的事务管理逻辑,减少对业务代码的侵入,使我们开发人员能够专注于业务层面开发。
实现@Transactional原理是基于spring aop,aop又是动态代理模式的实现。主要源码思路如下图:
具体源码分析不做总结,后期会单独总结一篇。
7.Spring的事务传播级别
事务传播行为(为了解决业务层方法之间互相调用的事务问题):当事务方法被另一个事务方法调用时,必须指定事务应该如何传播
例如:方法可能继续在现有事务中运行,也可能开启一个新事务并在自己的事务中运行。
在TransactionDefinition定义中包括了如下几个表示传播行为的常量:
支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRED: 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
- TransactionDefinition.PROPAGATION_SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
- TransactionDefinition.PROPAGATION_MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
不支持当前事务的情况:
- TransactionDefinition.PROPAGATION_REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NOT_SUPPORTED: 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
- TransactionDefinition.PROPAGATION_NEVER: 以非事务方式运行,如果当前存在事务,则抛出异常。
其他情况:
- TransactionDefinition.PROPAGATION_NESTED: 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
补充:隔离级别
TransactionDefinition 接口中定义了五个表示隔离级别的常量:
- TransactionDefinition.ISOLATION_DEFAULT: 使用后端数据库默认的隔离级别,Mysql 默认采用的REPEATABLE_READ隔离级别 Oracle 默认采用的 READ_COMMITTED隔离级别。
- TransactionDefinition.ISOLATION_READ_UNCOMMITTED: 最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读。
- TransactionDefinition.ISOLATION_READ_COMMITTED: 允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生。
- TransactionDefinition.ISOLATION_REPEATABLE_READ: 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生。
- TransactionDefinition.ISOLATION_SERIALIZABLE: 最高的隔离级别,完全服从ACID的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能,通常情况下也不会用到该级别。
8.BeanFactory和ApplicationContext的联系和区别
- BeanFactory是Spring里面最低层的接口,提供了最简单的容器的功能,只提供了实例化对象和拿对象的功能。
- ApplicationContext应用上下文,继承BeanFactory接口,它是Spring的一各更高级的容器,提供了更多的有用的功能。如国际化,访问资源,载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,消息发送、响应机制,AOP等。
- BeanFactory在启动的时候不会去实例化Bean,从容器中拿Bean的时候才会去实例化。
- ApplicationContext在启动的时候就把所有的Bean全部实例化了。它还可以为Bean配置lazy-init=true来让Bean延迟实例化
9.Spring的后置处理器
- BeanPostProcessor:Bean的后置处理器,主要在bean初始化前后工作。
- InstantiationAwareBeanPostProcessor:继承于BeanPostProcessor,主要在实例化bean前后工作; AOP创建代理对象就是通过该接口实现。
- BeanFactoryPostProcessor:Bean工厂的后置处理器,在bean定义(bean definitions)加载完成后,bean尚未初始化前执行。
- BeanDefinitionRegistryPostProcessor:继承于BeanFactoryPostProcessor。其自定义的方法postProcessBeanDefinitionRegistry会在bean定义(bean definitions)将要加载,bean尚未初始化前真执行,即在BeanFactoryPostProcessor的postProcessBeanFactory方法前被调用。
10.Spring Cloud Zuul网关的调优策略有哪些?怎么实现其高可用?Zuul和Gataway,你们项目中是怎么选择的?项目中对Zuul网关层的要求是什么样的?
详细内容查看:https://blog.csdn.net/xiaofeng10330111/article/details/87272495
Spring Cloud Zuul网关的调优策略
Zuul 1.0 是一个基于JVM的后端路由器,同时是一个建立在Servlet上的同步阻塞架构,故在使用时对这部分的优化工作是必要的,根据实践经验,对Zuul的优化分为以下几个类型:
- 容器优化:内置容器Tomcat与Undertow的比较与参数设置;
- 组件优化:内部集成的组件优化,如Hytrix线程隔离、Ribbon、HttpClient与OkHttp选择;
- JVM参数优化:适合于网关应用的JVM参数建议;
- 内部优化:一些原生参数或者内部源码,以更适当的方式进行重写。
高可用方案
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 建设情况,期待它能构建一个高活跃社区的、稳定的、适合中国特色(大流量、高并发)的微服务基础架构。
项目中对网关的要求
基本具备以下功能:认证和鉴权+压力控制+金丝雀测试+动态路由+负载均衡+静态响应处理+主动流量控制+限流+文件上传+参数转换+其他逻辑与业务处理等。
11.Spring Cloud Eureka和Nacos对比?怎么做选择?Eureka中高可用是怎么做的?进行的调优有哪些?原理是什么?
详细请查看:微服务架构-实现技术之具体实现工具与框架4:Spring Cloud Eureka原理与注意事项_张彦峰ZYF的博客-CSDN博客
都是服务注册发现中心,但是Nacos还可以用作配置中心,目前来看,建议使用Nacos,因为Eureka已经不在开源,而且性能上和高可用上没有Nacos方便。
相关调优方案见上面的博客。
12.Spring Cloud 中常用的注解有哪些?怎么用的?
- @Controller 控制层,里面有多个连接
- @Service 业务层,一般对于接口和实现
- @Qualifier 如果一个接口有多个实现,那么注入时候加上唯一标示
- @Repository 一般的dao层
- @Autowired 自动注入依赖
- @RequestMapping (value=’’,method={RequestMethod。GET或者POSt})绑定url
- @RequestParam (value=’’ required=false)绑定参数
- @ModelAttribute 一般用于controller层,呗注解的方法会在所以mapping执行之前执行,并且可以绑定参数到Model model里面。
- @Transactional (readOnly=true)注解式事务
- @Value(“${}”)可以注入properties里面的配置项
- @ControllerAdvice 是spring3提供的新注解,控制器增@ExceptionHandler 如果在controller方法遇到异常,就会调用含有此注解的方法。@EnableDiscoveryClient 与@EnableEurekaCLient 具有相同的功能,不同的事该注解同时可以注册Zookeper,也可用于服务发现,标注在主启动类上;
- @InitBinder 一般用于controller 可以将所有form 传递进来的string 进行html编码,防止xss攻击,比如可以将字符串类型的日期转换成date类型
- @EnableCaching 注解自动化配置合适的缓存管理器。
- @EnableWebSecurity 注解开启spring security的功能,集成websercrityconfigureadapter。
- @SringBootApplication相当于@Configuation、@EnableAutoConfiguation、@ComponentScan三个注解合用。
- @EnableDiscoveryClient 自定义服务发现的客服端
- @EnableAdminServer 使用admin监控应用。
- @EnableEurekaClient配置本应用将使用服务注册和服务发现,注意:注册和发现用这个注解。
- @EnableHystrix表示启动断路器,断路器依赖于服务注册和发现。
- @HystrixCommand注解方法失败后,系统将西东切换到fallbackMethod方法执行,
- @EnableAutoConfiguration spring boot自动配置,尝试根据你添加的jar依赖自动配置你的spring应用。
- @ComponentScan 表示将该类自动发现并注册bean 可以自动收集所有的spring组件
- @Comfiguration 相当于传统的xml配置文件
- @Import 导入其他配置类
- @ImportResource用来 加载xml配置文件
- @FeignClient注解中的fallbank属性指定回调类
- @ResController是@controller和@ResponseBody的结合体
- @EnableDiscoveryClient 与@EnableEurekaCLient 具有相同的功能,不同的事该注解同时可以注册Zookeper,也可用于服务发现,标注在主启动类上;
13.Spring Cloud中的组件有哪些?具体说说?微服务架构中用到的关键技术有哪些?
在介绍Spring Cloud 全家桶之前,首先要介绍一下Netflix ,Netflix 是一个很伟大的公司,在Spring Cloud项目中占着重要的作用,Netflix 公司提供了包括Eureka、Hystrix、Zuul、Archaius等在内的很多组件,在微服务架构中至关重要,Spring在Netflix 的基础上,封装了一系列的组件。
相关具体组件见:https://blog.csdn.net/xiaofeng10330111/article/details/87271644
关键技术及要求基本有:
微服务架构-实现技术之六大基础组件:服务通信+事件驱动+负载均衡+服务路由+API网关+配置管理
对应博客:微服务架构-实现技术之六大基础组件:服务通信+事件驱动+负载均衡+服务路由+API网关+配置管理_服务之间的通信现成的组件有哪些_张彦峰ZYF的博客-CSDN博客
微服务架构-实现技术之三大关键要素1服务治理:服务注册中心+服务发布与注册+服务发现与调用+服务监控
对应博客:微服务架构-实现技术之三大关键要素1服务治理:服务注册中心+服务发布与注册+服务发现与调用+服务监控_张彦峰ZYF的博客-CSDN博客
微服务架构-实现技术之三大关键要素2数据一致性:分布式事物+CAP&BASE+可靠事件模式+补偿模式+Sagas模式+TCC模式+最大努力通知模式+人工干预模式
对应博客:微服务架构-实现技术之三大关键要素2数据一致性:分布式事物+CAP&BASE+可靠事件模式+补偿模式+Sagas模式+TCC模式+最大努力通知模式+人工干预模式_张彦峰ZYF的博客-CSDN博客
微服务架构-实现技术之三大关键要素3服务可靠性:服务访问失败的原因和应对策略+服务容错+服务隔离+服务限流+服务降级
对应博客:微服务架构-实现技术之三大关键要素3服务可靠性:服务访问失败的原因和应对策略+服务容错+服务隔离+服务限流+服务降级_张彦峰ZYF的博客-CSDN博客
14.Spring Cloud Config配置架构是什么样的?可视化怎么做的?设计的业务有哪些?
具体见博客:微服务架构-实现技术之具体实现工具与框架8:Spring Cloud Config原理与注意事项_张彦峰ZYF的博客-CSDN博客
参考书籍、文献和资料
1.SpringBoot启动过程 | wangqi的blog Spring Boot启动加载过程讲的很详细
2.微服务架构-实现技术之具体实现工具与框架2:Spring Boot概览与核心原理_张彦峰ZYF的博客-CSDN博客
3.Spring Boot 学习笔记一(SpringBoot启动过程)_小沙弥修BUG的博客-CSDN博客
4.https://www.cnblogs.com/liubin1988/p/8909610.html
5.Java动态代理详解:JDK和CGLIB的区别和实现_Yanyan.He的博客-CSDN博客 动态代理的举例
6.https://www.cnblogs.com/wangenxian/p/10885309.html
7.https://www.cnblogs.com/gonjan-blog/p/6685611.html
8.Spring源码初探-IOC(4)-Bean的初始化-循环依赖的解决 – 简书
9.spring源码阅读–@Transactional实现原理_@transactional原理_一撸向北的博客-CSDN博客
10.Spring @Transactional工作原理详解_Java_软件编程 – 编程客栈
12.https://www.cnblogs.com/chongaizhen/p/11003832.html
13.https://blog.csdn.net/xiaofeng10330111/article/details/87272495
14.Spring Cloud Gateway VS Zuul 比较,怎么选择?_Java技术栈的博客-CSDN博客
15.微服务网关Zuul和Gateway的区别_gateway和zuul的区别与联系_莫明的编织者的博客-CSDN博客
16.Spring Cloud 常用注解注解_springcloud 常用注解_-Se7ven的博客-CSDN博客
17.微服务架构-实现技术之具体实现工具与框架4:Spring Cloud Eureka原理与注意事项_张彦峰ZYF的博客-CSDN博客
18.Spring Cloud全家桶主要组件及简要介绍_springcloud五大组件_徐刘根的博客-CSDN博客
19.【第二章】 IoC 之 2.1 IoC基础 ——跟我学Spring3 – 《亿级流量网站架构核心技术》~ – ITeye博客
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/35739.html