面试题_Spring
温故而知新墙
2024 / 5 / 15
什么是Spring
Spring 是⼀种轻量级开发框架,旨在提⾼开发⼈员的开发效率以及系统的可维护性
我们⼀般说 Spring 框架指的都是 Spring Framework,它是很多模块的集合,使⽤这些模块可以 很⽅便地协助我们进⾏开发。
Spring 官网列出的 Spring 的6个特征:
- 核心技术︰依赖注入(DI),AOP,事件(events),资源,i18n,验证,数据绑定,类型转换,SpEL。
- 测试︰模拟对象,TestContext框架,Spring MVC测试, WebTestClient。
- 数据访问︰事务,DAO支持,JDBC,ORM,编组XML。
- Web支持:Spring MVC和Spring WebFlux Web框架。
- 集成︰远程处理,JMS,JCA,JMX,电子邮件,任务,调度,缓存。
- 语言: Kotlin(科特林),Groovy,动态语言。
重要模块
- Spring Core: 基础,可以说 Spring 其他所有的功能都需要依赖于该类库。主要提供 IoC 依 赖注⼊功能。
- Spring Aspects : 该模块为与AspectJ的集成提供⽀持。
- Spring AOP :提供了⾯向切⾯的编程实现。
- Spring JDBC : Java数据库连接。
- Spring JMS :Java消息服务。
- Spring ORM : ⽤于⽀持Hibernate等ORM⼯具。
- Spring Web : 为创建Web应⽤程序提供⽀持。
- **Spring Test **: 提供了对 JUnit 和 TestNG 测试的⽀持。
@RestController vs @Controller
Controller
返回⼀个⻚⾯
@RestController
返回JSON 或 XML 形式数据
IoC
IoC(Inverse of Control:控制反转)是⼀种设计思想,就是将原本在程序中⼿动创建对象的控制权,交由Spring框架来管理。 IoC 在其他语⾔中也有应⽤,并⾮ Spring 特有。 **IoC 容器是 Spring ⽤来实现 IoC 的载体**, IoC 容器实际上就是个Map(key,value),Map 中存放的是各 种对象。
将对象之间的相互依赖关系交给 IoC 容器来管理,并由 IoC 容器完成对象的注⼊。这样可以很大程度上简化应⽤的开发,把应⽤从复杂的依赖关系中解放出来。 IoC 容器就像是⼀个⼯⼚⼀ 样,当我们需要创建⼀个对象的时候,只需要配置好配置⽂件/注解即可,完全不⽤考虑对象是如 何被创建出来的。 在实际项⽬中⼀个 Service 类可能有⼏百甚⾄上千个类作为它的底层,假如我们需要实例化这个 Service,你可能要每次都要搞清这个 Service 所有底层类的构造函数,这可 能会把⼈逼疯。如果利⽤ IoC 的话,你只需要配置好,然后在需要的地⽅引⽤就⾏了,这⼤⼤增 加了项⽬的可维护性且降低了开发难度。
Spring 时代我们⼀般通过 XML ⽂件来配置 Bean,后来开发⼈员觉得 XML ⽂件来配置不太好, 于是 SpringBoot 注解配置就慢慢开始流⾏起来。
将一个类声明为 Bean 的注解有哪些?
@Component
:通用的注解,可标注任意类为Spring
组件。如果一个 Bean 不知道属于哪个层,可以使用@Component
注解标注。@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。@Controller
: 对应 Spring MVC 控制层,主要用于接受用户请求并调用Service
层返回数据给前端页面。
@Configuration VS @Bean
如果没有@Configuration
注解,@Bean
注解通常不会按预期工作来定义Spring Bean。在Spring框架中,@Configuration
和@Bean
通常是一起使用的。
@Configuration
:表明这个类是一个配置类,它告诉Spring这是一个使用Java配置类的方式来定义Bean的类。@Bean
:在配置类中用于声明一个或多个Bean,并驱动Bean的实例化、配置和初始化。
1 |
|
1 | public class BatchTaskServiceImpl<hello> implements BatchTaskService |
@Component 和 @Bean 的区别是什么?
@Component
注解作用于类,而@Bean
注解作用于方法。@Component
通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用@ComponentScan
注解定义要扫描的路径从中找出标识了自动装配到 Spring 的 bean 容器中的类)。@Bean
注解通常是我们在标有该注解的方法中定义要产生哪个bean,@Bean
告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。@Bean
注解比@Component
注解的自定义性更强,而且很多地方我们只能通过@Bean
注解来注册 bean。比如当我们引用第三方库中的类需要装配到Spring
容器时,则只能通过@Bean
来实现。
@Bean
注解使用示例:
1 |
|
上面的代码相当于下面的 xml 配置
1 | <beans> |
@Autowired 和 @Resource 的区别是什么?
@Autowired
是 Spring 提供的注解,@Resource
是 JDK 提供的注解。
Autowired
默认的注入方式为byType
(根据类型进行匹配),@Resource
默认注入方式为 byName
(根据名称进行匹配)。
当一个接口存在多个实现类的情况下,@Autowired
和@Resource
都需要通过名称才能正确匹配到对应的 Bean。Autowired
可以通过 @Qualifier
注解来显式指定名称,@Resource
可以通过 name
属性来显式指定名称。
@Autowired
支持在构造函数、方法、字段和参数上使用。@Resource
主要用于字段和方法上的注入,不支持在构造函数或参数上使用。
不懂看JavaGuide
举个例子,SmsService
接口有两个实现类: SmsServiceImpl1
和 SmsServiceImpl2
,且它们都已经被 Spring 容器所管理。
1 | // 报错,smsService不是实现类而是接口,byName 和 byType 都无法匹配到 bean |
如何创建一个Bean
当然,当在Spring中创建Bean时,可以通过多种方式指定Bean的ID或名称。以下是几种常见的形式,并补充了关于如何指定Bean ID的内容:
- 基于XML的配置
在XML配置文件中,使用<bean>
标签的id
属性来指定Bean的ID。
1 | xml复制代码 |
在这个例子中,myBeanId
就是Bean的ID,你可以在应用程序中通过ApplicationContext
的getBean("myBeanId")
方法来获取这个Bean的实例。
- 基于注解的配置
在使用注解配置时,通常不需要显式指定Bean的ID,因为Spring会默认使用类名(首字母小写)作为Bean的ID。但是,你可以使用@Component
注解的value
属性(或@Service
、@Repository
、@Controller
等其他特定注解的value
属性)来指定一个自定义的Bean ID。
1 |
|
在这个例子中,customBeanId
就是Bean的ID。
- 基于Java的配置
在使用Java配置时,你可以通过@Bean
注解的方法名来隐式地指定Bean的ID。Spring会默认使用该方法名(首字母小写)作为Bean的ID。但是,你也可以通过@Bean
注解的name
属性来指定一个或多个自定义的Bean ID。
1 |
|
在这个例子中,anotherBeanId
就是Bean的ID。注意,你还可以指定多个ID,只需在name
属性中使用逗号分隔即可。
- 使用
@Profile
进行条件化配置
@Profile
注解允许你根据激活的Spring Profile来定义Bean。这可以用于在不同的环境中(如开发、测试和生产)使用不同的配置。
Bean 的作用域有哪些?
Spring 中 Bean 的作用域通常有下面几种:
- singleton : IoC 容器中只有唯一的 bean 实例。Spring 中的 bean 默认都是单例的,是对单例设计模式的应用。
- prototype : 每次获取bean都会创建一个新的 bean 实例。也就是说,连续
getBean()
两次,得到的是不同的 Bean 实例。 - request (仅 Web 应用可用): 每一次 HTTP 请求都会产生一个新的 bean(请求 bean),该 bean 仅在当前 HTTP request 内有效。
- session (仅 Web 应用可用) : 每一次(来自新 session) 的 HTTP 请求都会产生一个新的 bean(会话 bean),该 bean 仅在当前 HTTP session 内有效。
- application/global-session (仅 Web 应用可用):每个 Web 应用在启动时创建一个 Bean(应用 Bean),该 bean 仅在当前应用启动时间内有效。
- websocket (仅 Web 应用可用):每一次 WebSocket 会话产生一个新的 bean。
Bean 的生命周期了解么?
- 创建 Bean 的实例:Bean 容器首先会找到配置文件中的 Bean 定义,然后使用 Java 反射 API 来创建 Bean 的实例。
- Bean 属性赋值/填充:为 Bean 设置相关属性和依赖。
3.Bean 初始化:
- 如果 Bean 实现了
BeanNameAware
接口,调用setBeanName()
方法,传入 Bean 的名字。 - 如果 Bean 实现了
BeanClassLoaderAware
接口,调用setBeanClassLoader()
方法,传入ClassLoader
对象的实例。 - 与上面的类似,如果实现了其他
*.Aware
接口,就调用相应的方法。 - ==如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor
对象,执行postProcessBeforeInitialization()
方法== - 如果 Bean 实现了
InitializingBean
接口,执行afterPropertiesSet()
方法。 - 如果 Bean 在配置文件中的定义包含
init-method
属性,执行指定的方法。} - ==如果有和加载这个 Bean 的 Spring 容器相关的
BeanPostProcessor
对象,执行postProcessAfterInitialization()
方法。==
4.销毁 Bean:销毁并不是说要立马把 Bean 给销毁掉,而是把 Bean 的销毁方法先记录下来,将来需要销毁 Bean 或者销毁容器的时候,就调用这些方法去释放 Bean 所持有的资源。
- 如果 Bean 实现了
DisposableBean
接口,执行destroy()
方法。 - 如果 Bean 在配置文件中的定义包含
destroy-method
属性,执行指定的方法
AOP
AOP(Aspect-Oriented Programming:面向切面编程)能够将那些与核心业务无关,却为核心业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可拓展性和可维护性。
Spring AOP就是基于动态代理的,如果要代理的对象,实现了某个接口,那么Spring AOP会使用JDK Proxy,根据目标类的接口动态生成一个代理对象,而对于没有实现接口的对象,就无法使用JDK Proxy 去进行代理了,这时候Spring AOP会使用Cglib生成一个被代理对象的子类来作为代理对象,如下图所示:
- JDK动态代理:JDK原生的实现方式,需要被代理的目标类必须实现接口!他会根据目标类的接口动态生成一个代理对象!代理对象和目标对象有相同的接口!(拜把子)
- cglib:通过继承被代理的目标类实现代理,所以不需要目标类实现接口!(认干爹)
代理方式可以解决附加功能代码干扰核心代码和不方便统一维护的问题!,他主要是将附加功能代码提取到代理中执行,不干扰目标核心代码!
当然你也可以使用 AspectJ !Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。
Aop
Spring AOP 和 AspectJ AOP 有什么区别?
Spring AOP 属于运行时增强,而 AspectJ 是编译时增强。
Spring AOP 基于代理(Proxying),而 AspectJ 基于字节码操作(Bytecode Manipulation)。
Spring AOP 已经集成了 AspectJ ,AspectJ 应该算的上是 Java 生态系统中最完整的 AOP 框架了。AspectJ 相比于 Spring AOP 功能更加强大,但是 Spring AOP 相对来说更简单,
如果我们的切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择 AspectJ ,它比 Spring AOP 快很多
AspectJ 定义的通知类型有哪些?
- Before(前置通知):目标对象的方法调用之前触发
- After (后置通知):目标对象的方法调用之后触发
- AfterReturning(返回通知):目标对象的方法调用完成,在返回结果值之后触发
- AfterThrowing(异常通知):目标对象的方法运行中抛出 / 触发异常后触发。AfterReturning 和 AfterThrowing 两者互斥。如果方法调用成功无异常,则会有返回值;如果方法抛出了异常,则不会有返回值。
- Around (环绕通知):编程式控制目标对象的方法调用。环绕通知是所有通知类型中可操作范围最大的一种,因为它可以直接拿到目标对象,以及要执行的方法,所以环绕通知可以任意的在目标对象的方法调用前后搞事,甚至不调用目标对象的方法
SpringMVC
流程说明(重要):
- 客户端(浏览器)发送请求,直接请求到
DispatcherServlet
。 DispatcherServlet
根据请求信息调⽤HandlerMapping
,HandlerMapping
根据 URL 去匹配查找能处理的Handler
(也就是我们平常说的Controller
控制器)- ,接着
DispatcherServlet
会调用HandlerAdapter
去处理Handler
,也就是 会根据Handler
来调⽤真正的处理器开处理请求,并处理相应的业务逻 辑。 - 处理器处理完业务后,会返回⼀个
ModelAndView
对象给DispatcherServlet
,,Model
是返回的数据对 象,View
是个逻辑上的View
。 - 接着
ViewResolve
r 会根据逻辑View
查找实际的View
。 DispaterServlet
把返回的Model
传给View
(视图渲染)。- 最后把
View
返回给请求者(浏览器)
Spring 事务
是的,你说得对。在Spring Boot应用程序中,事务管理器的配置通常是自动的,所以你不需要显式地配置PlatformTransactionManager
bean。Spring Boot会根据你添加的数据源依赖(如JPA、JDBC等)自动配置一个合适的事务管理器。
你只需添加必要的Spring Boot启动器依赖(如spring-boot-starter-data-jpa
),然后在你的服务类中使用@Transactional
注解即可。Spring Boot会自动处理事务的创建、提交和回滚。
Spring 管理事务的方式有几种?
- 编程式事务:**在代码中硬编码(**在分布式系统中推荐使用) : 通过
TransactionTemplate
或者TransactionManager
手动管理事务,事务范围过大会出现事务未提交导致超时,因此事务要比锁的粒度更小。 - 声明式事务:在配置文件中配置(单体应用或者简单业务系统推荐使用) : 实际是通过 AOP 实现(基于
@Transactional
的全注解方式使用最多)
声明式事务又分为两种:
1.基于XML的声明式事务
2.基于注解的声明式事务
Spring 事务中的隔离级别有哪几种?
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 的隔离级别。所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别
Spring 事务中哪⼏种事务传播⾏为?
⽀持当前事务的情况:
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
。
@Transactional(rollbackFor = Exception.class)注解了解吗?
Exception
分为运行时异常 RuntimeException
和非运行时异常。事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。
当 @Transactional
注解作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。
在 @Transactional
注解中如果不配置 rollbackFor
属性,那么事物只会在遇到 RuntimeException
的 时候才会回滚,加上 rollbackFor=Exception.class
,可以让事物在遇到⾮运⾏时异常时也回滚。
关于spring事务
如果用户没有指定事务管理机制,或者开启事务增强的注解。spring也会根据上下文自动开启的
- 如果是同一个类中
- 父方法带有@Transaction注解
- 子方法带有@Transaction注解,想要让他生效,必须使用代理机制才能生效(事务传播行为也为生效),或者采用新建一个bean再去调用,类似于不在同一个类调用事务方法一样(看下文)
- 子方法不带有@Transaction,没有
@Transactional
注解的方法将不会启动新的事务,也不会加入到任何已存在的事务中。它们将在没有事务的上下文中执行。
- 父方法没有带有@Transaction注解
- 子方法带有@Transaction注解,想要让他生效,必须使用代理机制才能生效,或者采用新建一个bean再去调用,类似于不在同一个类调用事务方法一样
- 子方法不带有@Transaction,没有
@Transactional
注解的方法将不会启动新的事务,也不会加入到任何已存在的事务中。它们将在没有事务的上下文中执行
- 父方法带有@Transaction注解
- 如果不在同一个类中,使用传统的代理机制
- 父方法带有@Transaction注解,
- 子方法带有@Transaction注解,根据事务传播行为来说,如果父方法有事务,就加入,如果没有就新建自己独立!
- 子方法不带有@Transaction,没有
@Transactional
注解的方法将不会启动新的事务,也不会加入到任何已存在的事务中。它们将在没有事务的上下文中执行。
- 父方法没有带有@Transaction注解
- 子方法带有@Transaction注解,子方法的事务机制独立,就像普通的Main方法使用事务一样简单。
- 子方法不带有@Transaction,没有
@Transactional
注解的方法将不会启动新的事务,也不会加入到任何已存在的事务中。它们将在没有事务的上下文中执行
- 父方法带有@Transaction注解,
也就说,如果一个方法不带有Transaction注解的话,那么无论是什么方法调用它,这个方法都不会运行在事务管理机制中