Spring高频面试题大总结(一)——IOC
目录
九、BeanFactory 和 ApplicationContext有什么区别?
十、BeanFactory 和FactoryBean有什么区别?
一、什么是Spring?
Spring是一个生态,可以构建java应用所需要的一切设施。
通常Spring指的是Spring Framework
Spring是一个轻量级的开源容器框架
Spring是为了解决企业级应用开发的业务逻辑层 和 其他各级对象和对象之间的耦合问题
Spring是一个IOC和AOP的容器框架
IOC:控制反转
AOP:面向切面编程
容器:包含并管理应用对象的生命周期(Spring可以解决对象与对象之间的耦合问题,所以Spring集中管理了对象的生命周期——创建、销毁...)
二、Spring的优缺点是什么?
Spring的优点
(1)方便解耦,简化开发
通过Spring提供的IoC容器,我们可以将对象之间的依赖关系交由Spring进行控制,避免硬编码所造成的过度程序耦合。
集中管理对象,对象和对象之间的耦合度减低,方便维护对象。
(2)AOP编程的支持
通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式管理,从而提供了更好的复用.
在不修改代码的情况下可以对业务代码进行增强,减少重复代码,提高开发效率,维护方便
(3)声明事物的支持
在Spring中,我们可以从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量。
提高开发效率,只需要一个简单注解@Transactional
(4)方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,在Spring里,测试不再是昂贵的操作,而是随手可做的事情。例如:Spring对Junit4支持,可以通过注解方便的测试Spring程序。
Spring实现测试使我们可以结合junit非常方便测试Spring Bean 、SpringMVC
(5)方便集成各种优秀框架
Spring不排斥各种优秀的开源框架,相反,Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框架 (如Struts,Hibernate、Hessian、Quartz)等的直接支持。
拥有非常强大粘合度、集成能力非常强,只需要简单配置就可以集成第三方框架
(6)降低Java EE API的使用难度
Spring对很多难用的Java EE API(如JDBC,JavaMail,远程调用等)提供了一个薄薄的封装层,通过Spring的简易封装,这些Java EE API的使用难度大为降低。
简化开发, 帮我封装很多功能性代码
Spring的缺点
从应用层面(使用上)来说是没有缺点的!
简化开发, 如果想深入到底层去了解就非常困难(上层使用越简单、底层封装得就越复杂)
由于spring 大而全(要集成这么多框架、提供非常非常多的扩展点,经过十多年的代码迭代) 代码量非常庞大,一百多万对于去深入学习源码带来了一定困难,Spring体量大,对于Spring的开发者后期的维护存在挑战。
三、什么是Spring IOC 容器?有什么作用?
什么是IOC?IOC的作用?
IOC就是控制反转,它把传统上由程序代码直接操控的对象的调用权交给容器,通过容器来实现对象组件的装配和管理。所谓“控制反转”的概念就是对组件对象控制权的转移,从程序代码本身转移到了外部容器。
Spring IOC 负责创建对象,管理对象(通过依赖注入(DI)装配对象、配置对象)并且管理这些对象的整个生命周期。
管理对象的创建和依赖关系的维护。对象的创建并不是一件简单的事,在对象关系比较复杂时,如果依赖关系需要程序猿来维护的话,那是相当头疼的。
解耦,由容器去维护具体的对象
托管了类的产生过程,比如我们需要在类的产生过程中做一些处理,最直接的例子就是代理,如果有容器程序可以把这部分处理交给容器,应用程序则无需去关心类是如何完成代理的。
简单的来说就是:
我们自己new一个对象,可能会导致程序耦合度过高,后期可能维护不便。引入IOC,将创建对象的控制权交给Spring的IOC容器;如果要去使用对象,通过DI(依赖注入)@Autowired注解 自动注入,就可以使用对象了!
IOC的优点
集中管理对象、方便维护 、降低耦合度
最小的代价和最小的侵入性使松散耦合得以实现
IOC容器支持加载服务时的饿汉式,初始化的懒加载
四、Spring IoC 的实现机制是什么?
Spring 中的 IoC 的实现原理就是工厂模式加反射机制。
工厂相对应的就是beanfactory的getBean()方法,反射就是Spring底层实例化对象所采用的机制
interface fruit{
public abstract void eat();
}
class Apple implements fruit {
@Override
public void eat() {
System.out.println("Apple");
}
}
class Orange implements fruit {
@Override
public void eat() {
System.out.println("Orange");
}
}
class Factory {
public static fruit getInstance(String ClassName) {
fruit f = null;
//通过反射创建对象
try {
// 相当于Spring中new了一个ClassPathXmlApplicationContext
f = (fruit)Class.forName(ClassName).newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return f;
}
}
class Test {
public static void main(String[] args) {
//getInstance时静态的,直接调用即可Factory.getInstance()
fruit f = Factory.getInstance("Apple");
if(f != null)
f.eat();
}
}
//输出 Apple
注意点:
- Class.forName(“”)返回的是类
- Class.forName(“”).newInstance()返回的是object
五、IOC和DI的区别是什么?
很多人把IOC和DI说成一个东西,笼统来说的话是没有问题的,但是本质上还是有所区别的,希望大家能够严谨一点,IOC和DI是从不同的角度描述的同一件事,IOC是从容器的角度描述,而DI是从应用程序的角度来描述,也可以这样说,IOC是依赖倒置原则的设计思想,而 DI是具体的实现方式(没有DI在Spring中你就拿不到对象)。
我们可以举一个例子来说明:
在面向对象设计的软件系统中,底层的实现都是由N个对象组成的,所有的对象通过彼此的合作,最终实现系统的业务逻辑。
有一个对象出了问题,就可能会影响到整个流程的正常运转。
现在,伴随着工业级应用的规模越来越庞大,对象之间的依赖关系也越来越复杂,经常会出现对象之间的多重依赖性关系,因此,架构师和设计师对于系统的分析和设计,将面临更大的挑战。对象之间耦合度过高的系统,必然会出现牵一发而动全身的情形。
大家看到了吧,由于引进了中间位置的“第三方”,也就是IOC容器,对象和对象之间没有了耦合关系。本来我需要找一个女朋友,然后往后余生都是她;但是现在有了“容器”,我只要到洗浴中心,点一个小姐(依赖注入)我就有女人了。
它起到了一种 类似“粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个“粘合剂”,对象与对象之间会彼此 失去联系,这就是有人把IOC容器比喻成“粘合剂”的由来
六、紧耦合和松耦合有什么区别?
紧耦合:是指类之间的高度依赖。即 我们之前在代码中直接new一个对象,这样子的代码过多,就会导致耦合度过高。
松耦合:可以通过 单一职责原则、接口分离原则、依赖倒置原则来实现。
我们现在来复习一下这几个设计模式的概念,可以看一下这里!!!
依赖倒置原则——面向对象设计原则 (biancheng.net)http://c.biancheng.net/view/1326.html单一职责原则:一个类应该有且仅有一个引起它变化的原因,否则类应该被拆分。即一个功能用一个类表示。
缺点:
- 一个职责的变化可能会削弱或者抑制这个类实现其他职责的能力;
- 当客户端需要该对象的某一个职责时,不得不将其他不需要的职责全都包含进来,从而造成冗余代码或代码的浪费。
接口分离原则:要求程序员尽量将臃肿庞大的接口拆分成更小的和更具体的接口,让接口中只包含客户感兴趣的方法。
即 一个类要给多个客户使用,那么可以为每个客户创建一个接口,然后这个类实现所有的接口;而不要只创建一个接口,其中包含所有客户类需要的方法,然后这个类实现这个接口。
依赖倒置原则:高层模块不应该依赖低层模块,两者都应该依赖其抽象;抽象不应该依赖细节,细节应该依赖抽象。其核心思想是:要面向接口编程,不要面向实现编程。IOC就实现了依赖倒置原则!!!
如何实现?
- 每个类尽量提供接口或抽象类,或者两者都具备。
- 变量的声明类型尽量是接口或者是抽象类。
- 任何类都不应该从具体类派生。
- 使用继承时尽量遵循里氏替换原则(子类可以扩展父类的功能,但不能改变父类原有的功能)
七、BeanFactory的作用
BeanFactory是Spring中非常核心的一个顶层接口(它没有实现其他接口);
它是Bean的“工厂”、它的主要职责就是生产Bean;
它实现了简单工厂的设计模式,通过调用getBean传入标识生产一个Bean;
它有非常多的实现类、每个工厂都有不同的职责(单一职责)功能。
最强大的工厂是:DefaultListableBeanFactory。Spring底层就是使用的该实现工厂进行生产Bean的。
BeanFactory它也是容器!Spring容器(管理着Bean的生命周期);Tomcat是Servlet的容器(它管理着Servlet的生命周期)
八、BeanDefinition的作用
它主要负责存储Bean的定义信息,决定Bean的生产方式。
<bean class="com.Harmony.User" id="user" scope="singleton" lazy="false" >
<property name="username" value="xushu">
</bean>
配置一个bean就会产生一个BeanDefinition。
后续BeanFactory根据这些信息就行生产Bean: 比如实例化 可以通过class进行反射进而得到实例对象 , 比如lazy 则不会在ioc加载时 创建Bean
九、BeanFactory 和 ApplicationContext有什么区别?
BeanFactory是Spring IOC中的基础容器,用来解析BeanDefination存放bean的容器,还会对外提供一些操作bean的接口,AplicationContext是BeanFactory的扩展容器,基于BeanFactory 将其和其他组件整合到了一起。
而FactoryBean是以bean结尾的,我们就可以知道它是一个bean,但是它是一个特殊的bean,可以用来生产bean的一个bean。
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是 BeanFactory的子接口。
BeanFactory:是Spring里面最顶层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。BeanFactory 简单粗暴,可以理解为就是个HashMap,Key 是 BeanName,Value 是 Bean 实例。通常只提供注册(put),获取(get)这两个功能。我们可以称之为 “低级容器”。
ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口。因此具 备了更多的功能。例如资源的获取,支持多种消息(例如 JSP tag 的支持),对 BeanFactory 多了工具级别的支持等 待。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。
- 如果说BeanFactory是Spring的心脏,那么ApplicationContext就是完整的躯体了,ApplicationContext由BeanFactory派生而来,提供了更多面向实际应用的功能。在BeanFactory中,很多功能需要以编程的方式实现,而在ApplicationContext中则可以通过配置实现。
- BeanFactorty接口提供了配置框架及基本功能,但是无法支持spring的aop功能和web应用。而ApplicationContext接口作为BeanFactory的派生,因而提供BeanFactory所有的功能。
十、BeanFactory 和FactoryBean有什么区别?
我们之前(九)所讲的BeanFactory与ApplicationContext的更具有可比性!它们都可以作为容器,并且ApplicationContext实现了BeanFactory的接口。
但是BeanFactory与FactoryBean两者除了名字像一点以外没有什么可比性!!!
非要说联系的话:
BeanFactory是一个工厂,也就是一个容器,是来管理和生产bean的;
FactoryBean是一个bean,但是它是一个特殊的bean,所以也是由BeanFactory来管理的, 它是一个接口,他必须被一个bean去实现。
一个Bean A如果实现了FactoryBean接口,那么A就变成了一个工厂,根据A的名称获取到的实际上是工厂调用getObject()返回的对象(工厂产生的对象),而不是A本身;如果要获取工厂A自身的实例,那么需要在名称前面加上'&'符号。
简单的说就是:
getObject(' name ')返回工厂中的实例
getObject(' &name ')返回工厂本身的实例
十一、Spring IOC容器的加载过程(超重点!!!)
从概念态到定义态的过程
1、实例化一个ApplicationContext的对象;
2:调用bean工厂后置处理器(invokeBeanFactoryPostProcessors)完成扫描;
3:循环解析扫描出来的类信息(扫描所有.class类,看看类上面有没有@Component,有就注册为BeanDefinition)
4、实例化一个BeanDefinition对象来存储解析出来的信息;
5、把实例化好的beanDefinition对象put到beanDefinitionMap当中缓存起来, 以便后面实例化bean;
6、再次调用其他bean工厂后置处理器;
从定义态到纯净态(还没有依赖注入)
7:当然spring还会干很多事情,比如国际化,比如注册BeanPostProcessor等等,如果我们只关心如何实例化一个bean的话那么这一步就是spring调用 finishBeanFactoryInitialization方法来实例化单例的bean,实例化之前spring要做验证, 需要遍历所有扫描出来的类,依次判断这个bean是否Lazy,是否prototype,是否 abstract等等;(单例、不是懒加载、不是抽象才会加载)
8:如果验证完成spring在实例化一个bean之前需要推断构造方法,因为spring实例化对象是通过构造方法反射,故而需要知道用哪个构造方法;
9:推断完构造方法之后spring调用构造方法反射实例化一个对象;注意这个时候对象已经实例化出来了,但是并不是一个完整的bean, 最简单的体现是这个时候实例化出来的对象属性是没有注入,所以不是一个完整的bean;
从纯净态到成熟态
10:spring处理合并后的beanDefinition
11:判断是否需要完成属性注入
12:如果需要完成属性注入,则开始注入属性初始化
13、判断bean的类型回调Aware接口
14、调用生命周期回调方法
15、如果需要代理则完成代理
创建完成
16、put到单例池——bean完成——存在spring容器当中
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)