Spring

/ JAVA面试 / 没有评论 / 507浏览

一、spring的启动流程

加载应用程序上下文
扫描应用程序中的所有组件
自动配置应用程序环境
启动嵌入式Web服务器

首先从main找到run()方法,在执行run()方法之前new一个SpringApplication对象 进入run()方法,创建应用监听器SpringApplicationRunListeners开始监听 然后加载SpringBoot配置环境(ConfigurableEnvironment),然后把配置环境(Environment)加入监听对象中 然后加载应用上下文(ConfigurableApplicationContext),当做run方法的返回对象 最后创建Spring容器,refreshContext(context),实现starter自动化配置和bean的实例化等工作。 ————————————————

二、spring的bean的生命周期

创建 -> 初始化 -> 使用 -> 销毁

创建前准-》创建实例阶段-》依赖注入阶段-》容器缓存阶段-》销毁实例阶段

1. 通过BeanDefinition获取bean的定义信息
2. 调用构造函数实例化bean
3. bean的依赖注入
4. 处理Aware接口(BeanNameAware、BeanFactoryAware、ApplicationContextAware)
5. Bean的后置处理器BeanPostProcessor-前置
6. 初始化方法(InitializingBean、init-method)
7. Bean的后置处理器BeanPostProcessor-后置
8. 销毁bean

三、SpringBoot 自配装配的流程

通过@EnableAutoConfiguration注解在类路径的META-INF/spring.factories文件中找到所有的对应配置类,然后将这些自动配置类加载到spring容器中。

1.0 SpringBoot 启动时,会扫描 META-INF/spring.factories 文件,获取所有自动配置类的全限定名
2.0 Spring Boot 根据项目的依赖关系和配置信息,选择并加载相应的自动配置类
    启动时,会扫描 META-INF/spring.factories 文件,获取所有自动配置类的全限定名
3.0 Spring Boot 根据项目的依赖关系和配置信息,选择并加载相应的自动配置类
4.0 自动配置类使用 @ConditionalOnXXX 注解来进行条件装配,通过判断特定条件是否满足来确定是否进行自动装配

@Import 注解导入了一个 AutoConfigurationImportSelector 类,这个 AutoConfigurationImportSelector 类实现了 DeferredImportSelector 接口,
关于 @Import 注解,我们这里不展开,以后有机会单独讲,这边只需要知道,AutoConfigurationImportSelector 类实现了 DeferredImportSelector 接口,
并且其内部类 AutoConfigurationGroup 实现了 DeferredImportSelector.Group 接口,
AutoConfigurationGroup 的 process 方法会在 SpringBoot 启动时被调用
里面包含了一个 loadSpringFactories 来加载资源文件,运用一些缓存机制,防止重复加载和读取文件,减少磁盘IO操作

四、SpringBoot2为什么默认使用CGLib不再使用JDK动态代理

1.0 不需要实现接口
2.0 性能:CGlib动态代理比JDK动态代理更快,JDK动态代理是通过反射来实现的,而CGLib动态代理是使用字节码生成技术,直接操作字节码,因此CGLib性能更高.
3.0 代理对象的创建:JDK动态代理只能代理实现了接口的类,它是通过Proxy类和InvocationHandler接口来创建代理对象.
    而CGLib动态代理可以代理任意类(final修饰的类和fina修饰的方法除外),它是通过Enhancer类来创建代理对象,无需接口.
4.0 调用方法:JDK动态代理对代理方法的调用是通过InvocationHandler来转发的,
    而CGLib动态代理是对代理方法通过FastClass机制来直接调用目标方法的,避免了一些额外的方法调用,从而提高了执行效率,这也是CGLib性能较高的原因之一.

五、如何优化SpringBoot启动速度?

1.0 延迟初始化bean springboot 2.2版本后 全局懒加载
2.0 创建扫描索引
3.0 关闭JMX
4.0 关闭分层编译 【使用的不多...】
5.0 AOP 切面尽量不用注解方式
6.0 排除项目多余的依赖 jar
7.0 swagger 扫描接口时,指定只扫描某个路径下的类
8.0 缩小 feign 客户端接口的扫描范围
9.0 关闭 endpoint 的一些监控功能



1.0 延迟初始化bean springboot 2.2版本后 全局懒加载
        配置文件:spring.main.lazy-initialization=true
        或者注入属性上打 @Lazy 注解
        大概优化 1 ~ 2 秒
2.0 创建扫描索引
        添加 spring-context-indexer 依赖包
        spring 5.0 之后提供 spring-context-indexer 功能,可以通过在编译时创建一个静态候选列表来提高大型应用程序的启动性能
        在项目的启动类上打上 @Indexed 注解之后,编译打包的时候就会在项目种自动生成 META-INF/spring-components 文件。
        当 Spring 应用上下文执行 ComponentScan 扫描时, 会读取索引文件,提高扫描速度。
        META-INF/spring-components 将会被 CandidateComponentsIndexLoader 读取并且加载,将其转换为 CandidateComponentsIndex 对象。
        大概优化 1 秒左右
    其它非重要:
3.0 关闭JMX
        Spring Boot 2.2.X 版本以下默认会开启 JMX,可以使用 jconsole 查看,对于我们无需这些监控的话可以手动关闭它。
        spring.jmx.enabled=false
4.0 关闭分层编译 【使用的不多...】
        Java8 之后的版本,默认打开多层编译,使用命令java -XX:+PrintFlagsFinal -version | grep CompileThreshold查看。
5.0 AOP 切面尽量不用注解方式
6.0 排除项目多余的依赖 jar
7.0 swagger 扫描接口时,指定只扫描某个路径下的类
8.0 缩小 feign 客户端接口的扫描范围
9.0 关闭 endpoint 的一些监控功能

六、SpringBoot 解决跨域问题的操作

跨域问题指的是不同站点之间,使用 ajax 无法相互调用的问题。跨域问题本质上是浏览器的一种保护机制,初衷是为了保证用户数据的安全,防止恶意网站窃取数据。

浏览器同源,就是域名、端口号、ip、采用的协议都相同,那么我们就是同源的
1.0 返回新的CorsFilter
2.0 重写 WebMvcConfigurer
3.0 使用注解 @CrossOrigin
4.0 手动设置响应头 (HttpServletResponse)
5.0 自定web filter 实现跨域

七、spring的扩展点

BeanPostProcessor:在Bean实例化后、初始化前后进行扩展操作。
BeanFactoryPostProcessor:在BeanFactory标准初始化后,所有Bean定义已经被加载但是还没有实例化的时候进行扩展操作。
ApplicationListener:监听Spring事件,进行相应的扩展操作。
InitializingBean:在Bean初始化后进行扩展操作。
FactoryBean:用于创建复杂的Bean实例。
BeanFactory:Bean工厂,用于管理Bean实例。
ConfigurableBeanFactory:可配置的Bean工厂,可以配置Bean的各种属性。
HandlerInterceptor:在请求处理前、后进行扩展操作。
SpringBoot扩展点(按执行先后顺序):
    1.0 org.springframework.context.ApplicationContextInitializer:
        使用场景:
            在最开始激活一些配置,或者利用这时候class还没被类加载器加载的时机,进行动态字节码注入等操作。
            这是整个spring容器在刷新之前初始化ConfigurableApplicationContext的回调接口。
            简单来说,就是在容器刷新之前调用此类的initialize方法。这个点允许被用户自己扩展。用户可以在整个spring容器还没被初始化之前做一些事情。
        使用方式(因为这时候spring容器还没被初始化,所以想要自己的扩展的生效):
            1.0 在启动类中用springApplication.addInitializers(new TestApplicationContextInitializer())语句加入
            2.0 配置文件配置context.initializer.classes=com.example.demo.TestApplicationContextInitializer
            3.0 Spring SPI扩展,在spring.factories 中加入
                org.springframework.context.ApplicationContextInitializer=com.example.demo.TestApplicationContextInitializer
    2.0 org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor:
        使用场景:
            可以在这里动态注册自己的beanDefinition,可以加载classpath之外的bean,这个接口在读取项目中的beanDefinition之后执行,提供一个补充的扩展点。
    3.0 org.springframework.beans.factory.config.BeanFactoryPostProcessor:
        使用场景:
            使用场景:修改已经注册的beanDefinition的元信息,这个接口是beanFactory的扩展接口,调用时机在spring在读取beanDefinition信息之后,实例化bean之前。
    4.0 org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessor:
        使用场景:
            比如对实现了某一类接口的bean在各个生命期间进行收集,或者对某个类型的bean进行统一的设值等等。这个扩展点非常有用 ,无论是写中间件和业务中,都能利用这个特性。

        BeanPostProcess只在bean的初始化阶段进行扩展(注入spring上下文前后),而改接口把可扩展的范围增加了实例化阶段和属性注入阶段,具体说明看下列代码示例方法注释。
    5.0 org.springframework.beans.factory.config.SmartInstantiationAwareBeanPostProcessor:
        使用场景:
            1.0 predictBeanType:该触发点发生在postProcessBeforeInstantiation之前(在图上并没有标明,因为一般不太需要扩展这个点),
                这个方法用于预测Bean的类型,返回第一个预测成功的Class类型,如果不能预测返回null;
                当你调用BeanFactory.getType(name)时当通过bean的名字无法得到bean类型信息时就调用该回调方法来决定类型信息。
            2.0 determineCandidateConstructors:该触发点发生在postProcessBeforeInstantiation之后,用于确定该bean的构造函数之用,
                返回的是该bean的所有构造函数列表。用户可以扩展这个点,来自定义选择相应的构造器来实例化这个bean。
            3.0 getEarlyBeanReference:该触发点发生在postProcessAfterInstantiation之后,当有循环依赖的场景,当bean实例化好之后,
                为了防止有循环依赖,会提前暴露回调方法,用于bean实例化的后置处理。这个方法就是在提前暴露的回调方法中触发。
    6.0 org.springframework.beans.factory.BeanFactoryAware:
        使用场景:
            可以对每个bean作特殊化的定制,也可以把BeanFactory拿到进行缓存,日后使用 。
            这个类只有一个触发点,发生在bean的实例化之后,但还未初始化之前,注入属性之前,也就是Setter之前。
    7.0 org.springframework.context.support.ApplicationContextAwareProcessor(6个扩展点):
        该类本身并没有扩展点,但是该类内部却有6个扩展点可供实现 ,这些类触发的时机在bean实例化之后,初始化之前。
        可以看到,该类用于执行各种驱动接口,在bean实例化之后,属性填充之后,通过执行以上红框标出的扩展接口,来获取对应容器的变量。
        使用场景:
            1.0 EnvironmentAware:用于获取EnvironmentAware的一个扩展类,这个变量非常有用, 可以获得系统内的所有参数。
            2.0 EmbeddedValueResolverAware:用于获取StringValueResolver的一个扩展类, StringValueResolver用于获取基于String类型的properties的变量,
                一般我们都用@Value的方式去获取,如果实现了这个Aware接口,把StringValueResolver缓存起来,通过这个类去获取String类型的变量,效果是一样的。
            3.0 ResourceLoaderAware:用于获取ResourceLoader的一个扩展类,ResourceLoader可以用于获取classpath内所有的资源对象,可以扩展此类来拿到ResourceLoader对象。
            4.0 ApplicationEventPublisherAware:用于获取ApplicationEventPublisher的一个扩展类,ApplicationEventPublisher可以用来发布事件,
                结合ApplicationListener来共同使用,下文在介绍ApplicationListener时会详细提到。这个对象也可以通过spring注入的方式来获得。
            5.0 MessageSourceAware:用于获取MessageSource的一个扩展类,MessageSource主要用来做国际化。
            6.0 ApplicationContextAware:用来获取ApplicationContext的一个扩展类,ApplicationContext应该是很多人非常熟悉的一个类了,
                就是spring上下文管理器,可以手动的获取任何在spring上下文注册的bean,我们经常扩展这个接口来缓存spring上下文,包装成静态方法。
                同时ApplicationContext也实现了BeanFactory,MessageSource,ApplicationEventPublisher等接口,也可以用来做相关接口的事情。
    8.0 org.springframework.beans.factory.BeanNameAware:
        使用场景:
            用户可以扩展这个点,在初始化bean之前拿到spring容器中注册的的beanName,来自行修改这个beanName的值。
            可以看到,这个类也是Aware扩展的一种,触发点在bean的初始化之前,也就是postProcessBeforeInitialization之前,
            这个类的触发点方法只有一个:setBeanName。
    9.0 javax.annotation.PostConstruct:
        使用场景:
            bean执行初始化逻辑。其作用是在bean的初始化阶段,如果对一个方法标注了@PostConstruct,会先调用这个方法。
            这里重点是要关注下这个标准的触发点,这个触发点是在postProcessBeforeInitialization之后,InitializingBean.afterPropertiesSet之前。
    10.0 org.springframework.beans.factory.InitializingBean:
        使用场景:
            用户实现此接口,来进行系统启动的时候一些业务指标的初始化工作。顾名思义,也是用来初始化bean的。
            InitializingBean接口为bean提供了初始化方法的方式,它只包括afterPropertiesSet方法,凡是继承该接口的类,在初始化bean的时候都会执行该方法。
            这个扩展点的触发时机在postProcessAfterInitialization之前。
    11.0 org.springframework.beans.factory.FactoryBean:
        使用场景:
            为要实例化的bean作一个代理,比如为该对象的所有的方法作一个拦截,在调用前后输出一行log,模仿ProxyFactoryBean的功能。
        FactoryBean接口对于Spring框架来说占用重要的地位,隐藏了实例化一些复杂bean的细节,给上层应用带来了便利,用户可以通过实现该接口定制实例化Bean的逻辑。
    12.0 org.springframework.beans.factory.SmartInitializingSingleton:
        使用场景:
            用户可以扩展此接口在对所有单例对象初始化完毕后,做一些后置的业务处理。这个接口中只有一个方法afterSingletonsInstantiated,
            其作用是在spring容器管理的所有单例对象(非懒加载对象)初始化完成之后调用的回调接口。其触发时机为postProcessAfterInitialization之后。
    13.0 org.springframework.boot.CommandLineRunner:
        使用场景:
            用户扩展此接口,进行启动项目之后一些业务的预处理。触发时机为整个项目启动完毕后,自动执行run(String... args)。
            如果有多个CommandLineRunner,可以利用@Order来进行排序。
    14.0 org.springframework.beans.factory.DisposableBean:
        使用场景:
            这个扩展点也只有一个方法:destroy(),其触发时机为当此对象销毁时,会自动执行这个方法。
            比如说运行applicationContext.registerShutdownHook时,就会触发这个方法。
    15.0 org.springframework.context.ApplicationListener:
        使用场景:
            ApplicationListener可以监听某个事件的event,穿插在启动调用中,我们可以自定义某个业务事件,来自己做一些内置事件的监听器来达到和前面一些触发点大致相同的事情。
        Spring主要的内置事件包括ContextRefreshedEvent、ContextStartedEvent、ContextStoppedEvent、ContextClosedEvent、RequestHandledEvent等。

八、循环依赖

一级缓存:单例池 二级缓存:半成品池 三级缓存:打破循环依赖 -> ObjectFactory创建实例

循环依赖其实就是循环引用,也就是两个或两个以上的 bean 互相持有对方,最终形成闭环。
比如 A 依赖于 B , B 依赖于A循环依赖在 spring 中是允许存在,spring 框架依据三级缓存已经解决了大部分的循环依赖
一级缓存:单例池,作用:缓存已经经历了完整的生命周期(生命周期还没走完),已经初始化完成的 bean 对象,属性名为:singletonObjects
二级缓存:半成品池,作用:存放已经创建好的单例对象,但是还没有进行属性的填充,属性名为:earlySingletonObjects
三级缓存:ObjectFactory,作用:表示对象工厂用来创建某个对象的工厂,这个对象还没有创建完成,只是一个半成品,
        当二级缓存中没有的时候,从三级缓存中获取,属性名为:singletonFactories

spring 循环依赖的解决:
    1.0 通过三级缓存解决循环依赖
    2.0 通过构造函数的循环依赖
构造函数的循环依赖:
    出现原因:由于bean的生命周期中构造函数是第一个执行的,spring框架并不能解决构造函数的的依赖注入
    解决方案:使用@Lazy进行懒加载,什么时候需要对象再进行bean对象的创建

九、spring aop 如何实现的 与 AspectJ 有什么区别

spring aop 基于动态代理 (继承类使用:cglib;实现接口使用:jdk动态代理) AspectJ 是编译期进行的操作,在生成字节码的时候 插入自定义的逻辑 需要单独使用额外的maven插件

在不修改源码的情况下,给程序统一添加额外的功能,

十、spring 事务传播机制

PROPAGATION_REQUIRED / REQUIRED (默认)
   支持当前事务,如果当前没有事务,则新建事务;如果当前存在事务,则加入当前事务,合并成一个事务。
   如果上下文中已经存在事务,那么就加入到事务中执行,如果当前上下文中不存在事务,则新建事务执行。所以这个级别通常能满足处理大多数的业务场景。
REQUIRES_NEW:
   新建事务,如果当前存在事务,则把当前事务挂起,这个方法会独立提交事务,不受调用者的事务影响,父级异常,它也是正常提交。
   如果不起作用,可能的原因如下:调用方与被调用方写在了同一个service类里面,需要用Spring的上下文获取对象。
NESTED
   如果当前存在事务,它将会成为父级事务的一个子事务,方法结束后并没有提交,只有等父事务结束才提交,如果当前没有事务,则新建事务
   如果它异常,父级可以捕获它的异常而不进行回滚,正常提交,但如果父级异常,它必然回滚,这就是和 REQUIRES_NEW 的区别SUPPORTS
   如果当前存在事务,则加入事务;如果当前不存在事务,则以非事务方式运行,这个和不写没区别
NOT_SUPPORTED
   以非事务方式运行;如果当前存在事务,则把当前事务挂起
MANDATORY
   如果当前存在事务,则运行在当前事务中;如果当前无事务,则抛出异常,也即父级方法必须有事务
NEVER
   以非事务方式运行,如果当前存在事务,则抛出异常,即父级方法必须无事务

一般用得比较多的是 PROPAGATION_REQUIRED , REQUIRES_NEW;

十一、Spring框架中用到了哪些设计模式?

Spring框架是一个非常重要的开源框架,它涉及到许多设计模式,以下是Spring框架中使用的一些设计模式: 单例模式:Spring框架中的bean默认是单例的,可以通过配置更改其作用域。单例模式保证了在整个应用程序中只有一个实例。 工厂模式:Spring框架中的BeanFactory是一个工厂模式的典型实现。BeanFactory负责实例化并管理应用程序中的对象。 **代理模式:**Spring框架中的AOP(面向切面编程)机制是通过代理模式来实现的。Spring中使用代理对象对目标对象进行包装,从而实现对目标对象的增强。 **观察者模式:**Spring框架中的事件驱动机制就是一个观察者模式的实现。事件源产生事件后,会通知已经注册的监听器进行处理。 **模板方法模式:**Spring框架中的JdbcTemplate是一个典型的模板方法模式的实现。JdbcTemplate定义了一系列操作数据库的基本方法,而具体的实现则由其子类完成。 适配器模式:Spring框架中的HandlerAdapter就是一个适配器模式的典型实现。HandlerAdapter负责将请求发送给处理器进行处理,从而使得不同类型的处理器可以被统一处理。 除了以上这些设计模式,Spring框架还使用了许多其他的设计模式,如策略模式、装饰器模式、命令模式等。这些设计模式的使用,使得Spring框架具有了更好的灵活性、可扩展性和可维护性。

十二、spring的三级缓存

十三、spring的核心注解

十四、SpringBoot 读取配置文件原理

十五、什么是Spring WebFlux,以及它与Spring MVC的主要区别是什么?

Spring WebFlux 是Spring 5引入的一个新的响应式框架,旨在为在Spring框架上构建响应式Web应用提供支持。它使用非阻塞I/O模型,能够处理长时间运行的异步任务和大量的并发请求,这使得它非常适合处理事件驱动和实时更新的应用,例如实时聊天应用或大规模的实时数据处理。

与Spring MVC 相比,Spring MVC是一个基于Servlet API构建的传统同步框架,它使用阻塞I/O模型处理HTTP请求。虽然Spring MVC也可以异步处理请求,但其核心仍然是为同步处理而设计的。 ————————————————

Spring MVC

Spring WebFlux

十六、spring boot 约定优于配置

核心思想就是让我们少些很多配置,只需要关注业务代码的编写就行

十七、spring boot中的starter是什么

帮我们整合其他组件注册到spring的组件。避免开发者自己去引入依赖

十八、spring bean的作用域有哪几种

五种:

1、singleton: bean在每个Spring ioc 容器只有一个实例

2、prorotype:一个bean的定义可以有多个实例

3、request:每次http请求都会创建一个,该bean作用域仅在基于web的Spring ApplicationContext情形下有效

4、session:在一个http session中 一个bean定义对应一个实例。该作用域仅在基于web 的spring applicationContext情形下有效

5、global-session:在一个全局的http session中,一个bean定义对应一个实例

单例,原型(多例),请求,会话,全局会话

其他问题

SpringBoot 默认实现的日志框架:logback
SpringBoot 中默认的日志级别是:INFO
    常见的日志级别由低到高分为:TRACE < DEBUG < INFO < WARN < ERROR < FATAL