1. Spring中用到了哪些设计模式?
- 工厂模式:使用工程模式通过BeanFactory和ApplicationContext创建对象。
- 单例模式:Spring的容器中Bean默认都是单例的。
- 策略模式:继承自Resource下的有不同的实现类,会根据不同实现类访问资源。
ByteArrayResource、ClassPathResource、FileSystemResource、UrlResource、InputStreamResource
- 观察者模式:Spring的事件驱动模型使用的是观察者模式
Spring的事件驱动模型是基于观察者设计模式实现的。在该模型中,事件源(如应用程序、框架或第三方库)生成事件,事件由事件监听器(观察者)处理。Spring的事件机制包括一个事件发布者(ApplicationEventPublisher)和一个事件监听器(ApplicationListener),发布者负责发布事件,监听器负责处理事件。通过这种模型,Spring框架实现了松耦合、可扩展的应用程序架构。
- 代理模式:Spring的AOP就是通过代理实现,分为动态代理和静态代理
- 适配器模式:Spring AOP 的增强或通知 (Advice) 使用到了适配器模式、Spring MVC 中也是用到了适配器模式适配 Controller
2. FactoryBean与BeanFactory的区别
- BeanFactory是Spring框架最基本的接口,它是一个工厂,负责创建和管理Bean实例。FactoryBean是BeanFactory的一个扩展接口,它允许开发人员自定义Bean对象的创建过程。
- BeanFactory接口是Spring容器的核心,负责管理Bean对象的生命周期和依赖关系。而FactoryBean接口则是在BeanFactory接口的基础上提供了更多的灵活性,可以通过FactoryBean接口来实现AOP代理、动态代理等高级功能。
- 当Spring容器需要创建一个Bean对象时,它首先会检查这个Bean是否是一个FactoryBean。如果是,Spring容器会调用FactoryBean的getObject()方法来获取Bean对象实例,而不是直接调用Bean对象的构造方法或工厂方法。
下面截取了两者的部分代码用于分析:
1 | public interface BeanFactory { |
1 | public interface FactoryBean<T> { |
其中FactoryBean的getObject()
方法会返回该FactoryBean“生产”的对象实例,简单说就是有些对象实例化逻辑比较复杂,而我们又想自己实现的时候,就可以继承该接口并且重写getObject()
方法。
3. 为什么要FactoryBean
当某些对象的实例化过程过于烦琐,通过XML配置过于复杂,使我们宁愿使用Java代码来完成这个实例化过程的时候,或者,某些第三方库不能直接注册到Spring容器的时候,就可以实现。org.springframework.beans.factory.FactoryBean
接口,给出自己的对象实例化逻辑代码。当然,不使用FactoryBean,而像通常那样实现自定义的工厂方法类也是可以的。
这样说可能不好理解,我们来看一个例子:
1 | /** |
1 | <bean id="mapper" class="com.wangtao.spring.bean.MapperFactoryBean"> |
1 | public class BaseTest { |
从测试结果中得知,我们虽然配置的是MapperFactoryBean
的实例,但是根据id拿到的是getObject
方法创建的对象。其实在容器中创建的对象仍然是MapperFactoryBean
的实例,只是在获取的时候会判断这个结果对象是不是派生于FactoryBean
,如果是的话则返回getObject
方法创建的对象,并且这个对象并不是容器初始化时创建的,而是使用context.getBean()
方法时才创建。
如果想要获取FactoryBean
实例,需要这样写:
MapperFactoryBean mapper = context.getBean("&mapper", MapperFactoryBean.class)
;
即在bean的名字ID前加上&符号。
4. 能简单说一下 Spring IOC 的实现机制吗?
简单的Spring IOC流程大致如下:
5. 说说 BeanFactory 和 ApplicationContext?
简单说,ApplicantContext在BeanFactory的基础上,提供了很多扩展。
- BeanFactory(Bean 工厂)是 Spring 框架的基础设施,面向 Spring 本身。
- ApplicantContext(应用上下文)建立在 BeanFactory 基础上,面向使用 Spring 框架的开发者
BeanFactory是用于Bean的创建,管理Bean的生命周期等,并提供了从容器中获取Bean的诸多方法。
ApplicationContext除了拥有 BeanFactory支持的所有功能之外,还进一步扩展了基本容器的功能,包括BeanFactoryPostProcessor
、BeanPostProcessor
以及其他特殊类型bean的自动识别、容器启动后bean实例的自动初始化、 国际化的信息支持、容器内事件发布等。
6. BeanPostProcessor是什么?
它是用来拦截所有 bean 的初始化的,在 bean 的初始化之前,和初始化之后做一些事情。这点从 BeanPostProcessor 接口的定义也可以看出来:
1 | public interface BeanPostProcessor { |
7. 你知道 Spring 容器启动阶段会干什么吗?
Spring IOC容器工作过程分为两部分,一个是启动阶段,一个是Bean实例化阶段。

容器启动开始,首先会通过某种途径加载 Congiguration MetaData,在大部分情况下,容器需要依赖某些工具类(BeanDefinitionReader)对加载的 Configuration MetaData 进行解析和分析,并将分析后的信息组为相应的 BeanDefinition。
最后把这些保存了 Bean 定义必要信息的 BeanDefinition,注册到相应的 BeanDefinitionRegistry,这样容器启动就完成了。
8. Spring Bean的生命周期?
实例化——》属性赋值——》初始化——》使用——》销毁

- 实例化:图中第一步是实例化Bean,
- 属性赋值:图中第二步设置对象属性
- 初始化:5、6 步是真正的初始化,第 3、4 步为在初始化前执行,第 7 步在初始化后执行,初始化完成之后,Bean 就可以被使用了
- 销毁:第 8~10 步,第 8 步其实也可以算到销毁阶段,但不是真正意义上的销毁,而是先在使用前注册了销毁的相关调用接口,为了后面第 9、10 步真正销毁 Bean 时再执行相应的方法
spring为bean提供了两种初始化bean的方式,实现InitializingBean接口,实现afterPropertiesSet方法,或者在配置文件中同过init-method指定,两种方式可以同时使用。
9. Spring中Bean的作用域有哪些?
- singleton:单例的,在Spring容器中只存在一个实例,是Bean默认的作用域。
- prototype:每次创建都会返回新的的实例。
以下三个只存在Web容器中
- request:每次Http请求都会产生一个Bean,并且只在当前的Http Request中有效
- session:同一个Http Session共享一个Bean,不同的Http Session使用不同的Bean
- globalSession:同一个全局 Session 共享一个 Bean,只用于基于 Protlet 的 Web 应用,Spring5 中已经不存在了。
10. Spring 中的单例 Bean 会存在线程安全问题吗?
会存在。即便Spring IoC容器中的Bean是单例的,但是是线程共享的,所以不是线程安全的。
如果要解决单例Bean的线程安全问题,可以将变量保存在ThreadLocal中,实现变量在线程间的隔离。
11. Spring如何解决循环依赖?
最好的方式就是在设计时避免循环依赖。
但是如果有循环依赖,就需要像Spring一样依靠三层缓存区解决。
比如当A和B之间有循环依赖时,大致解决步骤如下:
- 首先将A放入三级缓存,表示A要开始实例化了,虽然还不完整,但是此时的目的就是曝光出来让大家知道。
- A在属性注入时,发现依赖了B,此时就会去实例化B
- 而B在属性注入的过程中,发现又依赖了A,就会去缓存中寻找A,在三级缓存中找到了A,虽然A不完善,但是存在,于是将A放入二级缓存,同时删除三级缓存的A,与此同时B也实例化完成,B放入一级缓存
- 接着A继续属性注入赋值,并在一级缓存拿到了B,于是A也进入一级缓存,删除二级缓存的A
这就是为什么Spring能解决Setter注入的循环依赖,因为分为实例化和属性注入几个步骤,简单说就是有一个时间差,所以能用缓存去解决。而构造器就不行了,直接在实例化时就注入了。
详细步骤:
1、getSingleton(“a”, true) 获取 a:会依次从 3 个级别的缓存中找 a,此时 3 个级别的缓存中都没有 a
2、将 a 丢到正在创建的 beanName 列表中(Set
3、实例化 a:A a = new A();这个时候 a 对象是早期的 a,属于半成品
4、将早期的 a 丢到三级缓存中(Map<String, ObjectFactory<?> > singletonFactories)
5、调用 populateBean 方法,注入依赖的对象,发现 setB 需要注入 b
6、调用 getSingleton(“b”, true) 获取 b:会依次从 3 个级别的缓存中找 a,此时 3 个级别的缓存中都没有 b
7、将 b 丢到正在创建的 beanName 列表中
8、实例化 b:B b = new B();这个时候 b 对象是早期的 b,属于半成品
9、将早期的 b 丢到三级缓存中(Map<String, ObjectFactory<?> > singletonFactories)
10、调用 populateBean 方法,注入依赖的对象,发现 setA 需要注入 a
11、调用 getSingleton(“a”, true) 获取 a:此时 a 会从第 3 级缓存中被移到第 2 级缓存,然后将其返回给 b 使用,此时 a 是个半成品(属性还未填充完毕)
12、b 通过 setA 将 11 中获取的 a 注入到 b 中
13、b 被创建完毕,此时 b 会从第 3 级缓存中被移除,然后被丢到 1 级缓存
14、b 返回给 a,然后 b 被通过 A 类中的 setB 注入给 a
15、a 的 populateBean 执行完毕,即:完成属性填充,到此时 a 已经注入到 b 中了
16、调用a= initializeBean("a", a, mbd)
对 a 进行处理,这个内部可能对 a 进行改变,有可能导致 a 和原始的 a 不是同一个对象了
17、调用getSingleton("a", false)
获取 a,注意这个时候第二个参数是 false,这个参数为 false 的时候,只会从前 2 级缓存中尝试获取 a,而 a 在步骤 11 中已经被丢到了第 2 级缓存中,所以此时这个可以获取到 a,这个 a 已经被注入给 b 了
18、此时判断注入给 b 的 a 和通过initializeBean
方法产生的 a 是否是同一个 a,不是同一个,则弹出异常
12. 为什么要三级缓存?二级不行吗?
- singletonObjects:第一级缓存,里面存放的都是创建好的成品Bean。
- earlySingletonObjects : 第二级缓存,里面存放的都是半成品的Bean。
- singletonFactories :第三级缓存, 不同于前两个存的是 Bean对象引用,此缓存存的bean 工厂对象,也就存的是 专门创建Bean的一个工厂对象。此缓存用于解决循环依赖
在Spring中需要三级缓存的主要原因是因为Spring AOP需要生成代理对象。如果不是Spring框架的话,使用二级缓存也是可以的。
简单说,Bean在初始化后(创建Bean的最后一个阶段),会判断是否需要创建代理对象。如果创建了代理,那么最终返回的就是代理实例的引用。
假如A和B存在循环依赖需要解决,三级缓存是会提前暴露对象(不完整的没有属性注入的对象),但是如果需要代理对象的话,在填充完属性后会创建代理对象的(代理对象需要在最后一个阶段创建),此时的Bean和提前暴露对象的Bean应该要一样,但是实际是不一样的,一个拥有属性,一个则是不完整的Bean。所以三级缓存才能保证不管什么时候使用的都是⼀个对象。
13. 说说 JDK 动态代理和 CGLIB 代理 ?
- JDK动态代理:
- JDK动态代理需要实现InvocationHandler
- 在实现该接口后,可以在前后写横切逻辑,调用invoke方法执行
- Proxy利用 InvocationHandler 动态创建一个符合目标类实现的接口的实例,生成目标类的代理对象。
1 | // 接口 |
1 | // 目标类 |
1 | public class ProxyFactory { |
1 | // 客户端 |
- Cglib动态代理:
- Cglib原理是生成一个子类,并在采用方法拦截父类执行,并织入横切逻辑在其中
- 相比JDK动态代理只能通过继承接口实现,Cglib则没有这个限制
- 如果目标类使用了final方法,Cglib则无法使用,因为他是通过创建子类进而实现的
- CgLib 创建的动态代理对象性能比 JDK 创建的动态代理对象的性能高不少,但是 CGLib 在创建代理对象时所花费的时间却比 JDK 多得多,所以对于单例的对象,因为无需频繁创建对象,用 CGLib 合适,反之,使用 JDK 方式要更为合适一些。
1 | // 目标类 |
1 | // 动态工厂 |
1 | public class Client { |
14. 说说 Spring AOP 和 AspectJ AOP 区别?
Spring AOP(运行时增强):
- 基于动态代理实现,默认如果使用接口的,用 JDK 提供的动态代理实现,如果是方法则使用 CGLIB 实现。
- 需要依赖IoC容器来管理,使用纯Java实现
- 由于动态代理时会生成代理实例对象,相应的方法调用也会增加栈调用的深度,因此性能相对没有AspectJ好
AspectJ AOP(编译时增强):
- 属于编译时增强,可以单独使用,也可以整合到其他框架,但是需要依赖单独的编译器
ajc
- 属于静态织入,有以下几个织入时机:
- 编译期织入:在编译时织入
- 编译后织入:在已经编译成class文件,并且打成Jar后,需要织入就要使用这种方式
- 类加载后织入:在加载类的时候
15. Spring的事务种类?
编程式事务和声明式事务。
编程式事务:
- 编程式事务管理使用 TransactionTemplate,需要显式执行事务
声明式事务:
- 基于AOP实现的,本质就是在方法执行前添加一个事务,在执行完成后,根据执行结果决定回滚或者提交
- 只需要在代码中添加@Transation注解即可
16. Spring 的事务隔离级别?
- DEFAULT(默认):使用数据库默认的隔离级别。对于大多数数据库,这通常是 READ_COMMITTED 级别。
- READ_UNCOMMITTED(读未提交):最低的隔离级别,在该级别下,一个事务可以读取另一个事务未提交的数据,这可能导致脏读、不可重复读和幻读等问题。
- READ_COMMITTED(读已提交):一个事务只能读取另一个事务已经提交的数据。这种隔离级别可以防止脏读,但是在并发情况下可能会导致不可重复读和幻读等问题。
- REPEATABLE_READ(可重复读):一个事务在多次执行相同的查询时,结果都是相同的。在该级别下,读取的数据是事务开始时的一致性视图,可以防止脏读和不可重复读,但是仍然存在幻读问题。
- SERIALIZABLE(串行化):最高的隔离级别,通过强制事务串行执行来避免并发问题。在该级别下,事务之间完全隔离,可以防止脏读、不可重复读和幻读等问题,但是会影响性能。
在 Spring 中,可以通过在事务注解 @Transactional 中指定 isolation 属性来设置隔离级别,例如:
1 | @Transactional(isolation = Isolation.READ_COMMITTED) |
17. 声明式事务的原理?
主要就是通过AOP动态代理。
简单说分三步:
- 查找@Transation注解,如果目标类是接口使用JDK代理,否则使用Cglib代理。
- 在执行该方法时,会调用相关的事务处理接口进行处理
**18. 声明式事务在哪些情况下会失效?
- 事务传播级别设置错误。有些事务传播会以非事务状态运行
- 被本类的其他方法调用。比如同一个类A调用了B,此时在B上加了@Transation注解,而A没有,此时调用A事务会失效,本质是因为AOP的原因,因为只有当事务方法被当前类以外的代码调用时,才会由 Spring 生成的代理对象来管理。
- rollBackFor设置出错时。因为Java中默认抛出Error或uncheck异常时才回滚事务。
- 应用在非public方法时。是因为代理对象(JDK和Cglib都会)会调用一个方法获取@Transation注解的信息,而此方法会检查目标方法的修饰符是否为 public,不是 public 则不会获取@Transactional 的属性配置信息。