spring-基础

Spring相关问题整理

Spring常见面试题总结(超详细回答)

Spring由哪些模块组成?

Spring 总共大约有 20 个模块, 由 1300 多个不同的文件构成。
而这些组件被分别整合在核心容器(Core Container) 、 AOP(Aspect Oriented Programming)、设备支持(Instrmentation)和消息(Messaging)、数据访问与集成(Data Access/Integeration)、Web 、Test等 6 个模块中。 以下是 Spring 5 的模块结构图:

在这里插入图片描述
核心容器(Core Container):
spring core:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。
Spring Core:核心类库,提供IOC服务;

spring beans:提供了BeanFactory,是工厂模式的一个经典实现,Spring将管理对象称为Bean。

spring context:构建于 core 封装包基础上的 context 封装包,提供了一种框架式的对象访问方法。
Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);

数据访问与集成(Data Access/Integeration):JDBC、Transactions
spring jdbc:提供了一个JDBC的抽象层,消除了烦琐的JDBC编码和数据库厂商特有的错误代码解析, 用于简化JDBC。
Spring ORM:对现有的ORM框架的支持;

spring aop:提供了面向切面的编程实现,让你可以自定义拦截器、切点等。
Spring AOP:AOP服务;

Web:
spring Web:提供了针对 Web 开发的集成特性,例如文件上传,利用 servlet listeners 进行 ioc 容器初始化和针对 Web 的 ApplicationContext。
Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;

spring test:主要为测试提供支持的,支持使用JUnit或TestNG对Spring组件进行单元测试和集成测试。

Spring是什么?

Spring是一个轻量级的IoC和AOP容器框架,用来整合其他框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。

spring是如何简化应用开发的

使用Spring开发可以将Bean对象,Dao组件对象,Service组件对象等交给 Spring容器来管理,这样使得很多复杂的代码在Spring中开发却变得非常的优雅和简洁,有效的降低代码的耦合度,极大的方便项目的后期维护、升级和扩展。

Spring 框架中都用到了哪些设计模式?

(1)工厂模式:BeanFactory就是简单工厂模式的体现,用来创建对象的实例;

(2)单例模式:Bean默认为单例模式。

(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;

(4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。

(5)观察者模式(Observer Pattern):定义对象间的一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被自动更新,如Spring中listener的实现–ApplicationListener。

Spring框架中有哪些不同类型的事件?5种

Spring 提供了以下5种标准的事件:

(1)上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。

(2)上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。

(3)上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。

(4)上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。

(5)请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。

如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。

循环依赖

什么是循环依赖

  • 循环依赖就是循环引用,就是两个或多个Bean相互之间的持有对方,比如CircleA引用CircleB,CircleB引用CircleC,CircleC引用CircleA,则它们最终反映为一个环。此处不是循环调用,循环调用方法之间的环调用。

  • 循环调用是无法解决的,除非有终结条件,否则就是死循环,最终导致内存溢出错误。

  • Spring容器循环依赖包括构造器循环依赖和setter循环依赖,那Spring容器如何解决循环依赖呢?

Spring如何解决循环依赖

一、构造器循环依赖无法解决:
  • 表示通过构造器注入构成的循环依赖,此依赖是无法解决的,只能抛出BeanCurrentlyInCreationException异常表示循环依赖。

如在创建CircleA类时,构造器需要CircleB类,那将去创建CircleB,在创建CircleB类时又发现需要CircleC类,则又去创建CircleC,最终在创建CircleC时发现又需要CircleA;从而形成一个环,没办法创建。

Spring容器将每一个正在创建的Bean 标识符放在一个“当前创建Bean池”中,Bean标识符在创建过程中将一直保持在这个池中,因此如果在创建Bean过程中发现自己已经在“当前创建Bean池”里时将抛出BeanCurrentlyInCreationException异常表示循环依赖;而对于创建完毕的Bean将从“当前创建Bean池”中清除掉。

二、setter循环依赖:
  • 表示通过setter注入方式构成的循环依赖。

对于setter注入造成的依赖是通过Spring容器提前暴露刚完成构造器注入但未完成其他步骤(如setter注入)的Bean来完成的,而且只能解决单例作用域的Bean循环依赖。

   如下代码所示,通过提前暴露一个单例工厂方法,从而使其他Bean能引用到该Bean。

具体步骤如下:

   1、Spring容器创建单例“circleA” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“circleA” 标识符放到“当前创建Bean池”;然后进行setter注入“circleB”;

   2、Spring容器创建单例“circleB” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory”用于返回一个提前暴露一个创建中的Bean,并将“circleB” 标识符放到“当前创建Bean池”,然后进行setter注入“circleC”;

   3、Spring容器创建单例“circleC” Bean,首先根据无参构造器创建Bean,并暴露一个“ObjectFactory ”用于返回一个提前暴露一个创建中的Bean,并将“circleC” 标识符放到“当前创建Bean池”,然后进行setter注入“circleA”;进行注入“circleA”时由于提前暴露了“ObjectFactory”工厂从而使用它返回提前暴露一个创建中的Bean;

4、最后在依赖注入“circleB”和“circleA”,完成setter注入。

   对于“prototype”作用域Bean,Spring容器无法完成依赖注入,因为“prototype”作用域的Bean,Spring容器不进行缓存,因此无法提前暴露一个创建中的Bean。

对于“singleton”作用域Bean,可以通过“setAllowCircularReferences(false);”来禁用循环引用

Spring中修改allowBeanDefinitionOverriding和allowCircularReferences属性的两种方式

allowBeanDefinitionOverriding属性含义
设置是否允许通过注册具有相同名称的不同定义来覆盖bean定义,并自动替换前者。否则,将引发异常。默认值为“true”。

allowCircularReferences属性含义
设置是否允许bean之间的循环引用并自动尝试解析它们。
默认值为“true”。关闭此选项可在遇到循环引用时引发异常,完全不允许循环引用。

public class TestSpring {
    public static void main(String[] args) {
        ClassPathXmlApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean2.xml", "bean1.xml");
        applicationContext.setAllowBeanDefinitionOverriding(false);
        applicationContext.setAllowCircularReferences(false);
        applicationContext.refresh();
        User user = (User) applicationContext.getBean("user");
        System.out.println("name: " + user.getName());
    }
}

Spring如何解决循环依赖(三级缓存)(必考)

循环依赖的产生和解决的前提:互相引用

循环依赖的产生可能有很多种情况,例如:

A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象;
A的构造方法中依赖了B的实例对象,同时B的某个field或者setter需要A的实例对象,以及反之;
A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象,以及反之;

  • 当然,Spring对于循环依赖的解决不是无条件的,首先前提条件是针对scope单例、并且没有显式指明不需要解决循环依赖的对象,而且要求该对象没有被代理过。

  • 同时Spring解决循环依赖也不是万能,以上三种情况只能解决两种,第一种在构造方法中相互依赖的情况,Spring也无力回天

Spring循环依赖的理论依据

  • 其实是Java基于引用传递,当我们获取到对象的引用时,对象的field或者属性是可以延后设置的。

Spring单例对象的初始化其实可以分为三步:

createBeanInstance, 实例化,实际上就是调用对应的构造方法构造对象,此时只是调用了构造方法,spring xml中指定的property并没有进行populate
populateBean,填充属性,这步对spring xml中指定的property进行populate
initializeBean,调用spring xml中指定的init方法,或者AfterPropertiesSet方法

会发生循环依赖的步骤集中在第一步和第二步。

对于单例对象来说,在Spring的整个容器的生命周期内,有且只存在一个对象,很容易想到这个对象应该存在Cache中,Spring大量运用了Cache的手段,在循环依赖问题的解决过程中甚至使用了“三级缓存”。

“三级缓存”主要是指

/**一级缓存*/
/** Cache of singleton objects: bean name --> bean instance*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

/**三级缓存*/
/** Cache of singleton factories: bean name --> ObjectFactory*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

/**二级缓存*/
/** Cache of early singleton objects: bean name --> bean instance*/
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16);

从字面意思来说:singletonObjects指单例对象的cache(第一级缓存),singletonFactories指单例对象工厂的cache(第三级缓存), earlySingletonObjects指提前曝光的单例对象的cache(第二级缓存)。以上三个cache构成了三级缓存,Spring就用这三级缓存巧妙的解决了循环依赖问题。

Spring使用了三级缓存解决了循环依赖的问题

Bean创建的过程,首先Spring会尝试从缓存中获取,这个缓存就是指singletonObjects,主要调用的方法getSingleton,分析getSingleton的整个过程,Spring首先从singletonObjects(一级缓存)中尝试获取,如果获取不到并且对象在创建中,则尝试从earlySingletonObjects(二级缓存)中获取,如果还是获取不到并且允许从singletonFactories通过getObject获取,则通过singletonFactory.getObject()(三级缓存)获取。

在populateBean()给属性赋值阶段里面,Spring会解析你的属性,并且赋值。当发现,A对象里面依赖了B,此时又会走getBean方法,但这个时候,你去缓存中是可以拿的到的。因为我们在对createBeanInstance对象创建完成以后已经放入了缓存当中,所以创建B的时候发现依赖A,直接就从缓存中去拿,此时B创建完,A也创建完,一共执行了4次。至此Bean的创建完成,最后将创建好的Bean放入单例缓存池中。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值