-
前言
❗表示必掌握,❔表示基本不会问 -
更新
1 | 24-06-10 初始记录 |
Spring
说说 Spring 常用的几个注解?
注解 | 使用 |
---|---|
@Component | 取代.xml 中的 |
@Controller | 衍生注解;用于 Controller 层 |
@Service | 衍生注解;用于 Service 层 |
@Repository | 衍生注解;用于 Dao 层 |
@Bean | 取代.xml 中工厂创建 bean 对象,方法的返回值成为 bean 对象。 |
@Autowired | 取代.xml 中 |
@Aspect | 定义在类上;设置当前类为切面类 |
@Before | 定义在方法上;标注当前方法作为前置通知 |
谈谈你对 Spring 的理解?
Spring 是一个完整的生态,不单单是一个技术框架。
❗Spring 框架中都用到了哪些设计模式?
工厂模式:BeanFactory 就是简单工厂模式的体现,用来创建对象的实例;
单例模式:Bean 默认为单例模式。
代理模式:Spring 的 AOP 功能用到了 JDK 的动态代理和 CGLIB 字节码生成技术;
模板方法:用来解决代码重复的问题。比如:RestTemplate,JmsTemplate,JpaTemplate。
观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如 Spring 中 listener 的实现–ApplicationListener。
Spring 两大核心思想是什么?
IOC:解耦,使代码复用,可维护性大幅提升。
AOP:提供切面编程,同样增强生产力。
❗说一说你对 IOC 的理解?
❗大白话:最原始的 tomcat+servlet 的编码原理。使用的时候 Myservice myservie = new MyserviceImpl()
。耦合度很高,修改一个类要修改很多文件。然后我们引入 Spring 这个框架。会去由框架 Spring 容器创建实例 Bean,对象间就会直接引用(依赖注入)。然后 Spring MVC(核心 Servlet Filter 去处理请求)。IOC 底层就是反射(+ 工厂模式)、通过类去创建对象。
IoC(Inversion Of Control)控制反转,其实是一种思想,用于解决程序间的耦合问题。【解耦】
原本我们创建对象是直接在类中,通过 new 的方式创建,控制权在于程序员自己,现在我们把 new 对象的工作交给 spring 完成,我们只需要通过配置文件进行配置即可。反转的是对象的创建权力。【便捷】
ioc 容器,可以简单理解为一个工厂,但是他的功能比普通工厂要强大很多,内部帮助我们完成了对象的创建和整个过程管理,同时提供了很多扩展机会。【可扩展】
❗说一说你对 AOP 的理解?
他有几个概念,可以做一个切面。在一些类的方法中,都先织入一些代码处理类似的逻辑(日志、权限认证、事务)。Spring 在运行的时候,会有一个动态代理的技术。他会动态生成一个类,把我们的类注入,实现织入的那些代码。
Spring AOP 里面的几个名词
(1)切面(Aspect):切面是通知和切点的结合。通知和切点共同定义了切面的全部内容。 在 Spring AOP 中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。
(2)连接点(Join point):指方法,在 Spring AOP 中,一个连接点总是代表一个方法的执行。 应用可能有数以千计的时机应用通知。这些时机被称为连接点。连接点是在应用执行过程中能够插入切面的一个点。这个点可以是调用方法时、抛出异常时、甚至修改一个字段时。切面代码可以利用这些点插入到应用的正常流程之中,并添加新的行为。
(3)通知(Advice):在 AOP 术语中,切面的工作被称为通知。
(4)切入点(Pointcut):切点的定义会匹配通知所要织入的一个或多个连接点。我们通常使用明确的类和方法名称,或是利用正则表达式定义所匹配的类和方法名称来指定这些切点。
(5)引入(Introduction):引入允许我们向现有类添加新方法或属性。
(6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。它通常是一个代理对象。也有人把它叫做被通知(adviced) 对象。 既然 Spring AOP 是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。
(7)织入(Weaving):织入是把切面应用到目标对象并创建新的代理对象的过程。
Spring 通知有哪些类型?
在 AOP 术语中,切面的工作被称为通知,实际上是程序执行时要通过 SpringAOP 框架触发的代码段。
Spring 切面可以应用 5 种类型的通知:
-
前置通知(Before):在目标方法被调用之前调用通知功能;
-
后置通知(After):在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
-
返回通知(After-returning ):在目标方法成功执行之后调用通知;
-
异常通知(After-throwing):在目标方法抛出异常后调用通知;
-
环绕通知(Around):通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
❗cglib 动态代理和 jdk 动态代理的区别
动态代理,其实就是动态的创建一个类出来,创建这个类的实例对象,在这个里面引用你真正自己写的类,所有方法的调用,都先走代理类对象,做一些代码上的增强。
如果类实现了某个接口,Spring AOP 会使用 jdk 动态代理。如果某个类没有实现接口,Spring AOP 会使用 cglib 动态代理。cglib 是生成类的一个子类,可以动态生成字节码,覆盖一些方法,进行方法增强。
Bean 对象的创建方式有哪几种?
-
通过 IoC 直接创建 bean 对象
-
通过 IoC 创建 bean 工厂,再通过 bean 工厂的方法创建 bean 对象
-
通过 IoC 创建 bean 工厂,再通过 bean 工厂的静态方法创建 bean 对象
❗Spring 中的 Bean 是不是线程安全的?
❔bean 的作用域可以分为 5 个范围:
-
singleton(单例):默认每个容器中只有一个实例对象
-
prototype:每次创建一个新的
-
request
-
session
-
global-session
不是,Spring 框架中的单例 bean 不是线程安全的。
Spring 中的 bean 默认是单例模式,Spring 框架并没有对单例 bean 进行多线程的封装处理。
实际上大部分时候 Spring Bean 无状态的(比如 dao 类),所以某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如 view model 对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton”(单例)变更为“prototype”(多例),这样请求 bean 相当于 new Bean() 了,所以就可以保证线程安全了。
有状态:就是有数据存储功能。
无状态:就是不会保存数据。
❗Spring Bean 的生命周期
-
首先,我们知道 bean 对象的创建不是由我们自己创建的,而是由 Spring 来给我们创建的,而我们需要做的是告诉 Spring 我们需要创建哪些 Bean 对象。我们可以通过 xml、注解等方式来提供创建 Bean 对象所以需要的信息,而 Spring 要创建 bean 对象,就首先得有一个 BeanFactory 工厂来创建 Bean 对象,如果这个 Bean 对象已经存在,则销毁清空 Bean 工厂的内容,如果不存在,就会通过 DefaultListableBeanFactory 方法先创建一个 Bean 对象工厂(即 BeanFactory)。
-
BeanFactory 要创建 Bean 对象,就需要 Bean 对象的相关信息,这些信息就是通过 xml 或注解方式获得,所以第二步是需要读取这些 bean 的相关信息,通过 LoadBeanDefinition 来加载配置文件,并将相关信息加载成一个个 BeanDefinition 对象(Bean 与 BeanDefinition 的关系就像类与字节码文件之间的关系)。
-
BeanDefinitions 生成后,会通过 invokeBeanFactoryPostProcessors 方法对所有的 BeanDefinitions 以及 BeanFactory 进行后置处理执行:
- 拿到当前应用上下文 beanFactoryPostProcessors 变量中的值,默认情况返回为空。
- 实例化并调用所有已注册的 BeanFactoryPostProcessor 信息,也就是 Bean 的相关定义。
-
BeanFactory 工厂的相关增强处理结束后,BanFactory 就会正式开始实 Bean 对象的实例化,由 InstantiateSingletons 方法通过反射创建 Bean 对象,其中会判断该对象是否是单例或者懒加载:如果不是单例或者不是懒加载,就采用 FactoryBean 单独构创建对象;如果是单例对象或懒加载,就由 BeanFactory 工厂直接反射创建对象。
-
BeanFactory 将对象实例化之后,Spring 会通过 BeanPostProcessor(bean 对象后置处理器)对 Bean 对象的初始化的前后进行后置处理,填充 Bean 对象的属性,完成初始化。
-
通过 Map 数据类型放这些 Bean 对象存放在 IOC 容器中。
-
bean 销毁:注册销毁的回调方法,当对象销毁时,会执行 destory-method 方法。
下面可以不看,感觉这块视频讲的乱七八糟的。
大白话:再注解里定义 bean,Spring 容器根据配置创建、管理 bean 之间的依赖关系等。生命周期:创建 ->使用 ->销毁。从 (1) 实例化 bean,(2) 设置依赖属性(依赖注入),过程中可能会进行动态代理的处理。(3) 处理 Aware 接口,如果 Bean 实现了 ApplicationContextAware 接口,Spring 容器会调用 setApplicationContext(ApplicationContext) 方法,传入 Spring 上下文。把容器注入给 Bean。(4) 如果要对 Bean 进行一些自定义的处理,可以让 Bean 实现 BeanPostProcessors 接口。(5) 如果 Bean 配置了 init-method,会根据配置进行初始化。(6) 清理阶段,如果 Bean 实现了 DisposableBean 这个接口,会调用其实现的 destroy() 方法。(7) 如果 Bean 的 Spring 配置中配置了 destory-method 属性,会在销毁时,进行调用。
Spring 为什么启动时要实例化几乎所有的 Bean,这样启动不是很消耗资源吗?
主要的好处有两个:性能、提前暴露问题。
启动时花费几十秒初始化好所有的 bean,处理好所有的依赖注入,在运行时就可以免去初始化这步了,首次访问响应速度自然更好。
提前暴露问题就是在启动时初始化 bean,可以检查循环依赖、bean 重复、bean 不存在等等一系列的问题,有问题直接报错启动失败,那你在部署的时候就能直接发现问题,而不是等线上运行一段时间后突然反馈有问题又得跑到服务器上看日志。
Spring 里面的 bean 为什么要注册,作用是什么?
注册就是把信息存起来,内部是基于集合实现的,注册方便之后对于信息的调取。
BeanFactory 和 FactoryBean 的区别是什么?
BeanFactory:由 Bean 工厂统一生成对象,相当于一个模子克隆出来。
FactoryBean:单独构造复杂对象,在 Spring 中 BeanFactory 进行实例化时,判断该对象不是单例或者不是懒加载形式,就改由 FactoryBean 来单独创建对象。
Spring 有哪些依赖注入法?
-
Set 方法注入:注入最简单,最常用的注入方式,支持注解 +xml。
-
构造器注入:是指带有参数的构造函数注入,支持注解 +xml
-
静态工厂的方式注入:通过调用静态工厂的方法来获取自己需要的对象,只支持 xml。
-
实例工厂的方式注入:获取对象实例的方法不是静态的,所以需要 new 一个工厂类,再调用普通的实例方法,只支持 xml。有两种实现方式:(1) 注解(@Autowired,@Resource,@Required)(2) 配置文件(xml)
❗Spring 的循环依赖是什么?
-
使用 context.getBean(A.class),旨在获取容器内的单例 A(若 A 不存在,就会走 A 这个 Bean 的创建流程),显然初次获取 A 是不存在的,因此走 A 的创建之路~
-
实例化 A(注意此处仅仅是实例化),并将它放进缓存(此时 A 已经实例化完成,已经可以被引用了)
-
初始化 A:@Autowired 依赖注入 B(此时需要去容器内获取 B)
-
为了完成依赖注入 B,会通过 getBean(B) 去容器内找 B。但此时 B 在容器内不存在,就走向 B 的创建之路~
-
实例化 B,并将其放入缓存。(此时 B 也能够被引用了)
-
初始化 B,@Autowired 依赖注入 A(此时需要去容器内获取 A)
-
此处重要:初始化 B 时会调用 getBean(A) 去容器内找到 A,上面我们已经说过了此时候因为 A 已经实例化完成了并且放进了缓存里,所以这个时候去看缓存里是已经存在 A 的引用了的,所以 getBean(A) 能够正常返回
-
B 初始化成功(此时已经注入 A 成功了,已成功持有 A 的引用了),return(注意此处 return 相当于是返回最上面的 getBean(B) 这句代码,回到了初始化 A 的流程中~)。
-
因为 B 实例已经成功返回了,因此最终 A 也初始化成功
-
到此,B 持有的已经是初始化完成的 A,A 持有的也是初始化完成的 B,完美~
使用@Autowired 注解自动装配的过程是怎样的?
使用@Autowired 注解来自动装配指定的 bean。在使用@Autowired 注解之前需要在 Spring 配置文件进行配置,<context:annotation-config />。
在启动 Spring IoC 时,容器自动装载了一个 AutowiredAnnotationBeanPostProcessor 后置处理器,当容器扫描到@Autowied、@Resource 或@Inject 时,就会在 IoC 容器自动查找需要的 bean,并装配给该对象的属性。在使用@Autowired 时,首先在容器中查询对应类型的 bean:
-
如果查询结果刚好为一个,就将该 bean 装配给@Autowired 指定的数据;
-
如果查询的结果不止一个,那么@Autowired 会根据名称来查找;
-
如果上述查找的结果为空,那么会抛出异常。解决方法时,使用 required=false。
❗Spring 的事务实现原理,对于事物传播机制的理解
原理:@Transactional
+ AOP
-
【常用】【默认】PROPAGATION_REQUIRED:如果当前没有事务,那就创建一个新事务,如果当前存在事务,那就加入该事务。
-
【常用】PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,那就以非事务执行。
-
PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛异常。
-
【场景】PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。
-
PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
-
PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,就抛异常。
-
【场景】PROPAGATION_NESTED:如果当前存在事务,就嵌套事务内执行,如果没有事务,就按 REQUIRED 属性执行。外层代码回滚,内层代码一起回滚;内层代码回滚,外层代码不一起回滚。
说一下 Spring 的事务隔离?
Spring 有五大隔离级别,默认值为 ISOLATION_DEFAULT(使用数据库的设置),其他四个隔离级别和数据库的隔离级别一致:
-
ISOLATION_DEFAULT:用底层数据库的设置隔离级别,数据库设置的是什么我就用什么;
-
ISOLATION_READ_UNCOMMITTED:未提交读,最低隔离级别、事务未提交前,就可被其他事务读取(会出现幻读、脏读、不可重复读);
-
ISOLATION_READ_COMMITTED:提交读,一个事务提交后才能被其他事务读取到(会造成幻读、不可重复读),SQL server 的默认级别;
-
ISOLATION_REPEATABLE_READ:可重复读,保证多次读取同一个数据时,其值都和事务开始时候的内容是一致,禁止读取到别的事务未提交的数据(会造成幻读),MySQL 的默认级别;
-
ISOLATION_SERIALIZABLE:序列化,代价最高最可靠的隔离级别,该隔离级别能防止脏读、不可重复读、幻读。
-
脏读 :表示一个事务能够读取另一个事务中还未提交的数据。比如,某个事务尝试插入记录 A,此时该事务还未提交,然后另一个事务尝试读取到了记录 A。
-
不可重复读 :是指在一个事务内,多次读同一数据。
-
幻读 :指同一个事务内多次查询返回的结果集不一样。比如同一个事务 A 第一次查询时候有 n 条记录,但是第二次同等条件下查询却有 n+1 条记录,这就好像产生了幻觉。发生幻读的原因也是另外一个事务新增或者删除或者修改了第一个事务结果集里面的数据,同一个记录的数据内容被修改了,所以数据行的记录就变多或者变少了。
画一张图说 Spring 的核心架构
// todo
没有从源码层面讲解,这块需要后面补充。
Spring MVC
看了一下别的问题都是比较古老的问题。应该不会问到,暂时不记录。
❗请描述 Spring MVC 的工作流程?描述一下 DispatcherServlet 的工作流程?
-
用户发送请求至前端控制器 DispatcherServlet;
-
DispatcherServlet 收到请求后,调用 HandlerMapping 处理器映射器,请求获取 Handle;
-
处理器映射器根据请求 url 找到具体的处理器,生成处理器对象及处理器拦截器 (如果有则生成) 一并返回给 DispatcherServlet;
-
DispatcherServlet 调用 HandlerAdapter 处理器适配器;
-
HandlerAdapter 经过适配调用具体处理器 (Handler,也叫后端控制器);
-
Handler 执行完成返回 ModelAndView;
-
HandlerAdapter 将 Handler 执行结果 ModelAndView 返回给 DispatcherServlet;
-
DispatcherServlet 将 ModelAndView 传给 ViewResolver 视图解析器进行解析;
-
ViewResolver 解析后返回具体 View;
-
DispatcherServlet 对 View 进行渲染视图(即将模型数据填充至视图中)
-
DispatcherServlet 响应用户。
Spring Boot
❗SringBoot 的核心注解是哪个?它主要由哪几个注解组成?
启动类上面的注解是@SpringBootApplication,它也是 Spring Boot 的核心注解,主要组合包含了以下 3 个注解:
-
@SpringBootConfiguration:组合了 @Configuration 注解,实现配置文件的功能。
-
@EnableAutoConfiguration:打开自动配置的功能,也可以关闭某个自动配置的选项,如关闭数据源自动配置功能: @SpringBootApplication(exclude = { DataSourceAutoConfiguration.class })。
-
@ComponentScan:Spring 组件扫描。
❗SpringBoot 的工作原理/自动配置/SPI 机制是怎么样子的?
SpringBoot 使用的@SpringApplication 注解,然后使用@EnableAutoConfiguration 以及@ComponentScan 自动装配,注解@EnableAutoConfiguration 使用了@Import 加载,使用@Import 导入的类会被 Spring 加载到 IOC 容器中,最后使用了 SpringFactoriesLoader 反射出 maven 中 META-INF 下 spring.factories,将反射的 bean 对象加载到 Spring 容器中(也就是 SpringBoot 的 SPI 机制)。
Spring Boot 是否可以使用 XML 配置 ?
Spring Boot 推荐使用 Java 配置而非 XML 配置,但是 Spring Boot 中也可以使用 XML 配置,通过 @ImportResource 注解可以引入一个 XML 配置。
Spring boot 核心配置文件是什么?bootstrap.properties 和 application.properties 有何区别 ?
单纯做 Spring Boot 开发,可能不太容易遇到 bootstrap.properties 配置文件,但是在结合 Spring Cloud 时,这个配置就会经常遇到了,特别是在需要加载一些远程配置文件的时侯。
Spring boot 核心的两个配置文件:
-
bootstrap (. yml 或者 . properties):boostrap 由父 ApplicationContext 加载的,比 applicaton 优先加载,配置在应用程序上下文的引导阶段生效。一般来说我们在 Spring Cloud Config 或者 Nacos 中会用到它。且 bootstrap 里面的属性不能被覆盖;
-
application (. yml 或者 . properties): 由 ApplicatonContext 加载,用于 spring boot 项目的自动化配置。
Spring Boot 的配置文件分类有哪几种?它们的优先级如何?
SpringBoot 是基于约定的,所以很多配置都有默认值,但如果想使用自己的配置替换默认配置的话,就可以使用 application.properties 或者 application.yml(application.yaml)进行配置。
默认配置文件名称:application
在同一级目录下优先级为:properties>yml > yaml
Spring Boot 打成的 jar 和普通的 jar 有什么区别 ?
Spring Boot 项目最终打包成的 jar 是可执行 jar ,这种 jar 可以直接通过 java -jar xxx.jar 命令来运行,这种 jar 不可以作为普通的 jar 被其他项目依赖,即使依赖了也无法使用其中的类。
Spring Boot 的 jar 无法被其他项目依赖,主要还是他和普通 jar 的结构不同。普通的 jar 包,解压后直接就是包名,包里就是我们的代码,而 Spring Boot 打包成的可执行 jar 解压后,在 \BOOT-INF\classes
目录下才是我们的代码,因此无法被直接引用。如果非要引用,可以在 pom.xml 文件中增加配置,将 Spring Boot 项目打包成两个 jar ,一个可执行,一个可引用。
Spring Boot 中如何实现定时任务 ?
定时任务也是一个常见的需求,Spring Boot 中对于定时任务的支持主要还是来自 Spring 框架。
在 Spring Boot 中使用定时任务主要有两种不同的方式,一个就是使用 Spring 中的 @Scheduled 注解,另一个则是使用第三方框架 Quartz(/kwɔːts/)。
使用 Spring 中的 @Scheduled (/'ʃedjuːld/)的方式主要通过 @Scheduled 注解来实现。
使用 Quartz ,则按照 Quartz 的方式,定义 Job 和 Trigger 即可。
如果是分布式定时任务,可以采用 XXL-Job。
❗画一张图说 Spring Boot 的核心架构
Spring Boot 本身是 Spring 项目发展到一定阶段后的产物。一开始是 Spring 框架,MyBatis,Spring MVC(SSM)做一些开发,打包部署到线上的 tomcat。tomcat 启动,接收 http 请求,转发给 Spring MVC 框架。
开发的时候还会去整合其他的一些框架(Redis、Elasticsearch、RabbitMQ…)等东西。使用 Spring Boot 可以简化之前的开发流程,之前框架的整合流程比较繁琐。
Spring Boot 内嵌的 web 服务器(比如 tomcat)可以直接把写好的代码运行(减少部署 tomcat)。
Spring Boot 比较重要的一个是自动装配,引入 stater 的依赖,会一定程度上自动完成相应的配置和定义(原先需要手工配置 xml 配置文件,定义一些 bean,写 sql 文件…)。
内链:[[SpringBoot启动流程.excalidraw]]
外链: