Spring面试题总结
Spring如何设计容器的,BeanFactory 和 ApplicationContext 有什么区别?
二者都是IOC容器对象,是Spring对Bean做一个管理的容器,会维护这些bean
BeanFactory是Spring容器的基础接口,提供了基础的容器访问能力。
BeanFactory提供懒加载方式,只有通过getBean方法调用获取Bean才会进行实例化。
ApplicationContext继承自BeanFactory接口,ApplicationContext包含了BeanFactory中所有的功能。
ApplicationContext相较于BeanFactory的区别:
- 支持 AOP(BeanPostProcessor、BeanFactoryPostProcessor)。
- 事件发布机制(ApplicationEventPublisher)。
- ApplicationContext采用的是预加载,每个Bean都在ApplicationContext启动后实例化。
说下FactoryBean和BeanFactory有什么区别?
- BeanFactory:是 Bean 的工厂, ApplicationContext 的父类,IOC容器的核心,负责生产和管理 Bean 对象。
- FactoryBean:是 Bean,可以通过实现, FactoryBean 接口定制实例化,Bean 的逻辑,通过代理一个Bean对象,对方法前后做一些操作(通常是用来创建比较复杂的bean)。
FactoryBean
FactoryBean
是一个特殊的 Bean 工厂接口,用于 创建其他 Bean 实例。- 你可以通过实现
FactoryBean
接口来控制如何创建某个 Bean(例如,动态创建、延迟初始化等)。
功能:
FactoryBean
用于自定义 Bean 的创建过程,使得 Bean 的实例化更加灵活。- 实现
FactoryBean
后,你可以在 Spring 容器中声明一个FactoryBean
的 Bean,它的getObject()
方法会返回真正的 Bean 实例。
BeanFactory
BeanFactory
是 Spring 中最基础的接口之一,用于管理和获取 Bean 实例。BeanFactory
是 Spring IoC 容器的核心接口之一,负责 懒加载 和 实例化 管理。
功能:
BeanFactory
是用来获取 Bean 的容器,它负责管理 Spring 中的 Bean 定义,懒加载 Bean,只有在需要时才实例化 Bean。
了解事务传播行为吗?Spring事务中有几种事务传播行为?
事务传播行为是为了解决业务层方法之间互相调用的事务问题,简单的理解就是多个事务方法相互调用时,事务如何在这些方法间传播
举个例子,方法A是一个事务的方法,方法A执行过程中调用了方法B,那么方法B有无事务以及方法B对事务的要求不同都会对方法A的事务具体执行造成影响,同时方法A的事务对方法B的事务执行也有影响,这种影响具体是什么就由两个方法所定义的事务传播类型所决定。
在Spring源码中这七种类型被定义为了枚举。(前两种最常用)
事务行为 | 说明 |
PROPAGATION_REQUIRED | 支持当前事务。假设当前没有事务。就新建一个事务 |
PROPAGATION_SUPPORTS | 支持当前事务。假设当前没有事务,就以非事务方式运行 |
PROPAGATION_MANDATORY | 支持当前事务,假设当前没有事务,就抛出异常 |
PROPAGATION_REQUIRES NEW | 新建事务,假设当前存在事务。把当前事务挂起 |
PROPAGATION_NOT SUPPORTED | 以非事务方式运行操作。假设当前存在事务,就把当前事务挂起 |
PROPAGATION_NEVER | 以非事务方式运行,假设当前存在事务,则抛出异常 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操 作。 |
Spring的单例Bean是否有并发安全问题
当多个用户同时请求一个服务时,容器会给每一个请求分配一个线程,这时多个线程会并发执行该请求对应的业务逻辑,如果业务逻辑有对单例状态的修改(体现为此单例的成员属性),则必须考虑线程安全问题。
实际上大部分时候 spring bean 无状态的(比如 dao 类),所有某种程度上来说 bean 也是安全的,但如果 bean 有状态的话(比如view model对象),那就要开发者自己去保证线程安全了,最简单的就是改变 bean 的作用域,把“singleton"变更为"prototype”,这样请求 bean 相当于 new Bean()了,所以就可以保证线程安全了。
无状态bean和有状态bean
- 有实例成员变量的bean,可以保存数据,是非线程安全的
- 没有实例成员变量的bean,不能保存数据,是线程安全的
public class TestManagerImpl implements TestManager{
private User user;
//有一个记录信息的实例
public void deleteUser(user e)throws Exception {
//1
user =e:
prepareData(e);
}
public void prepareData(user e)throws Exception {
user =getuserByID(e.getId);
....
//使用user.getId();
:....
//2
.....
}
一个有状态的bean
}
如何解决有状态bean的线程安全问题
对于无状态的bean适合用单例模式
有状态的Bean在多线程环境下不安全,一般用Prototype模式或使用ThreadLocal解决线程安全问题
<bean id="testManager" class="com.lcc.TestManagerImpl" scope="singleton">
<bean id="testManager" class="com.lcc.TestManagerImpl" scope="prototype">
如果说,配置成prototype那就是多例bean,每一个线程过来都会new一个实例
如果说保留Singleton,那么需要对某个实例成员变量,加上ThreadLocal,将这个成员变量给到我们的ThreadLocal里,这样对于每一个线程都有一份属于自己的变量
Spring有哪些拓展点(IOC流程)
换句话说,你是否知道一个Bean是如何被创建出来的
1.首先我们基于xml或者注解的方式,定义哪些需要类需要创建Bean,然后加载BeanDefination到Map中,接下来第一个拓展点是BeanFactoryPostProcessor,这个后置处理器会作用在所有Bean身上,常见的:
-
ConfigurationClassPostProcessor:
- 这是Spring内部使用的处理器,用于处理基于Java配置类的注解(如
@Configuration
,@Bean
等)。 - 它负责扫描并注册所有带有
@Configuration
注解的类,并将它们作为bean定义添加到Spring容器中。
- 这是Spring内部使用的处理器,用于处理基于Java配置类的注解(如
程序中可以有多个beanFactory后置处理器,那么优先执行谁呢?那就按照下面这个规则
- 实现了优先级接口的
- 实现了排序接口的
- 按照配置文件顺序来执行
2.接下来就通过反射,将BeanDefinationMap中的对象进行实例化,实例化中存在什么内容呢,请看如下:
先填充属性,这一步会存在循环依赖问题,Spring通过三级缓存完成对象的提前暴露
接下来设置Aware接口的属性,再通过BeanPostProcessor的before方法对Spring进行扩展
接下来如果说这个bean实现了initializingBean的话,那么会执行AfterPropertiesSet方法
然后再执行init-method方法(<bean init-method>可以指定初始化方法走哪一个),最后执行BeanPostProcessor的after方法(如果需要动态代理,那么就在这个方法中生成)
常见的BeanPostProcessor如下:
说说Spring框架中Bean的生命周期
对象的实例化 -> 对象的初始化 -> 使用完整对象 -> 对象的销毁
使用完整对象
什么是循环依赖,Spring如何解决循环依赖
循环依赖(Circular Dependency) 指的是 两个或多个 Bean 之间相互依赖,导致 Spring 在创建 Bean 时出现死循环。例如:
A 依赖 B,B 也依赖 A,Spring 在实例化时会陷入循环创建。
1️⃣ 三级缓存(Three-level Cache)机制
Spring 在 实例化 Bean 时,提前暴露引用,通过三级缓存避免循环依赖:
- 一级缓存(singletonObjects):存放完全初始化的 Bean
- 二级缓存(earlySingletonObjects):存放已经实例化但未填充属性的 Bean
- 三级缓存(singletonFactories):存放 Bean 的 ObjectFactory,可以在必要时创建代理对象
2️⃣ 具体过程
- 实例化 A(但还没填充属性),将 A 的 ObjectFactory 放入 三级缓存。
- 实例化 B,发现它依赖 A,于是去缓存找 A。
- 从三级缓存中获取 A 的引用,提前暴露给 B,B 依赖注入成功。
- 回过头来完成 A 的初始化,最终 A 和 B 都创建成功。
循环依赖连环拷打
(1)构造器注入产生的循环依赖能够解决吗?
答:不能解决,因为 Spring 在实例化时就需要构造函数的参数,但这些参数本身还未被创建,导致死循环。
(2)多例Bean对象 setter注入产生的循环依赖能够解决吗?
答:不能解决,因为 Spring 不会缓存 Prototype Bean,每次 getBean() 都会创建一个新实例,无法通过 三级缓存机制 解决循环依赖。
(3)只有一级缓存能够解决循环依赖吗?
单从解决循环依赖的角度,能解决的,但是使用过程会有问题,比如:如果 Bean A 依赖 Bean B,而 Bean B 还未完成初始化就被 A 使用,可能导致空指针或数据异常。
(4)只有一级缓存和二级缓存能够解决循环依赖吗?
单从解决循环依赖的角度,能解决的,但仍然有 AOP 代理失效的问题。比如:A需要生成代理对象,但是判断生成代理对象的逻辑是在BeanPostProcessor的after,也就是生命周期最后阶段,但是A依赖于B的属性填充是在早期阶段,当使用二级缓存,A的原始半成品对象赋值给了B,这样B在调用相关的A方法,走的就不是AOP代理了
而三级缓存中,存的ObjectFactory中调用getObject会判断当前Bean是否需要生成代理对象,如果需要,那我会提前生成,而后的BeanPostProcessor就不会重复生成了
(5)只有一级缓存和三级缓存能够解决循环依赖吗?
如果省略了二级缓存,Spring 需要从三级缓存获取 Bean 时,每次都会重新调用 ObjectFactory 创建对象,导致多个不同实例被使用,进而影响应用逻辑。
具体问题
A
依赖B
,B
依赖A
,Spring 先创建A
的“半成品”放入三级缓存(singletonFactories
)。- 当
B
需要A
时,Spring 从三级缓存取出A
的ObjectFactory
,然后创建A
并返回给B
。 A
继续初始化完成,放入一级缓存(singletonObjects
)。- 问题:如果
A
还没有完成初始化,其他线程再次请求A
,会再次触发ObjectFactory
,创建一个新的A
,导致多个实例出现!
这样就违背了A的单例原则
Spring事务在哪几种情况下会失效?
1.数据库引擎不支持事务
以MySQL为例,5.5之前存储引擎默认是MyISAM,这是不支持事务的
2.没有被Spring管理
如果此时@Service注解注释掉,这个类就不会被加载成一个Bean,那么这个类就不会被Spring管理,事务自然就失效了
3.方法不是public
Spring事务也是通过动态代理来实现的,在对一个Bean进行初始化的过程中,在执行到第八个后置处理器AbstractAutoProxyCreator后,其中有个方法的判断,如果目标方法不是public,则TransactionAttribute返回null,即不支持事务
if(allowPublicMethodsOnly() && !Modifier,isPublic(method.getModifiers())){
return null;
}
4.方法用final修饰
Spring事务底层使用了aop,也就是通过jdk动态代理或者cglib,帮我们生成了代理类,在代理类中实现的事务功能
但如果某个方法用final修饰了,那么在它的代理类中,就无法重写该方法,从而实现事务功能
5.方法内部调用
Spring的事务是基于动态代理对象实现的,但是,this.updateStatus中的this是不是代理对象,所以transactional不会生效,可以通过以下方式解决(自己注入自己):
@Service
public class UserService{
@Autowired
private UserService userService
userSearvice.update...
}
6.异常被吃了
就是方法内部发生异常,但是被try...catch了
7.事务的传播行为
由于不同的事务传播行为,一些事务失效了
Bean注入容器有哪些方式
1.@Configuration+@Bean
2.包扫描
@ComponentScan扫描指定路径下标注了@Controller,@Service,@Repository,@Component的类
3.@Import注解导入
4.实现BeanDefinitionRegistryPostProcessor进行后置处理
在Spring容器启动的时候会执行BeanDefinitionRegistryPostProcessor的postProcessBeanDefinitionRegistry方法,就是等beanDefinition加载完成之后,对beanDefinition进行后置处理,可以在此进行调整IOC容器中的beanDefition,从而干扰到后面进行初始化bean。
比如我们可以手动向beanDefinitionRegistry中注册Person的BeanDefinition。那么后续Spring就会扫描到这个BeanDefinition并进行注册
5.可以通过注入FactoryBean
FactoryBean可以包装Person,那么我们注入了FactoryBean,也是可以通过这个FactoryBean从容器中获取Person的
说说AOP的实现原理
Spring的AOP实现原理其实很简单,就是通过动态代理实现的。如果我们为Spring的某个bean配置了切面,那么Spring在创建这个bean的时候,实际上创建的是这个bean的一个代理对象,我们后续对bean中方法的调用,实际上调用的是代理类重写的代理方法。而Spring的AOP使用了两种动态代理,分别是JDK的和CGLIB的动态代理
目标类实现了接口,采用JDK动态代理,否则采用CGLIB
Spring的AOP中有哪些Advice类型
说说Spring中@EnableAspectJAutoProxy的原理
@EnableAspectJAutoProxy 本质上 导入了 AspectJAutoProxyRegistrar,并 注册 AnnotationAwareAspectJAutoProxyCreator 这个 BeanPostProcessor。
这个后置处理器做以下事情:
- 在 Spring Bean 初始化之前,它会扫描所有
@Aspect
注解的 Bean。 - 识别切面方法(
@Before
,@After
,@Around
) 并自动创建代理对象。 - 根据
proxyTargetClass = true
选择 JDK 动态代理 或 CGLIB 代理。
说说Spring AOP自动动态代理的实现过程
在我们有了 Join point(连接点)、Pointcut(切点)、Advice(通知)以及 Aspecy)(切面)后,我们应该如何将他们"织入"我们的应用呢?在 Sping AOP 中提供了自动代理的实现,底层借助JDK 动态代理和 CGLIB 动态代理创建对象。
在 Spring IOC 中 Bean 的加载过程,在整个过程中,Bean 的实例化前和初始化后等生命周期阶段都提供了扩展点,会调用相应的BeanPostProcessor 处理器对 Bean 进行处理。当我们开启了 Aspecy 自动代理(例如通过 @EnableAspecyAutoProxy注解),则会往IOC容器中注册一个 AbstractAutoProxyCreator 自动代理对象,该对象实现了几种 BeanPostProcessor,例如在每个 Bean 初始化后会被调用,解析出当前 Spring上下文中所有的 Advisor(会缓存),如果这个 Bean 需要进行代理,则会通过JDK动态代理或者 CGLIB 动态代理创建一个代理对象并返回,所以得到的这个 Bean 实际上是一个代理对象。这样一来,开发人员只需要配置好 Aspect 相关信息,Spring 则会进行自动代理,和 Spring IOC 完美地整合在一起。
那再谈谈JDK动态代理和CGLIB动态代理的区别
JDK动态代理
如果目标类实现了接口,Spring AOP会选择使用JDK动态代理目标类。代理类根据目标类实现的接口动态生成,不需要自己编写,生成的动态代理类和目标类都实现相同的接口。JDK动态代理的核心是 InvocationHandler接口和 proxy 类。
特点:目标类必须有实现的接口。如果某个类没有实现接口,那么这个类就不能用JDK动态代理。
CGLIB动态代理
通过继承实现,如果目标类没有实现接口,那么SpringAOP会选择使用CGLB来动态代理目标类。CGLlB可以在运行时动态生成类的字节码,动态创建目标类的子类对象,在子类对象中增强目标类。
CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用CGLIB做动态代理的。
优点:目标类不需要实现特定的接口,更加灵活。
总结
何时采用哪种动态代理?
1.如果目标对象实现了接口,默认情况下会采用IDK的动态代理实现AOP
2.如果目标对象实现了接口,可以强制使用CGLIB实现AOP
3.如果目标对象没有实现了接口,必须采用CGLIB库
两者的区别
- 1.jdk动态代理使用jdk中的类Proxy来创建代理对象,它使用反射技术来实现,不需要导入其他依赖。cglib需要引入相关依赖:asm.jar,它使用字节码增强技术来实现。
- 2.当目标类实现了接口的时候Spring Aop默认使用jdk动态代理方式来增强方法,没有实现接口的时候使用cglib动态代理方式增强方法。