框架篇(二)Spring面试题(一)

Spring面试题

Spring常见面试题总结(超详细回答)_张维鹏的博客-CSDN博客_spring面试题一个不错的总结!!!

1. 你是怎样理解Spring的

我和面试官的一个约会:打着视频,他问我,你会用Spring对吧,我心里想,对,会用。。。

他接着说,那你对Spring是怎样理解的,我说IOC 和 AOP。。。。。。。说了自己脑海里的。。。。

他说好,好!。。。。。

我。。。。。

。。。。。。然后我们就见了这一面。。。。。

然后我下定决心。。。好好看Spring的官网。。。。

学了这么久啦,该不会还有人不知道Spring官网怎么看吧。。。。其实下面的这些啥,是我自己看的,真正的理解,在最后面。。。

框架篇(二)Spring面试题(一)

官方文档上是这样说的。很明显也不是很理解。其实了解一样东西,我们首先更应该了解它的过去,这样才有可能和它融为一体。

框架篇(二)Spring面试题(一)

Spring是一个轻量级的开源的 J2EE框架。

框架篇(二)Spring面试题(一)

框架篇(二)Spring面试题(一)

它是一个容器框架,用来装 JAVA BEANjava对象),中间层框架(万能胶)可以起一个连接作用,比如说把 Strutshibernate粘合在一起运用,可以让我们的企业开发更快、更简洁。

框架篇(二)Spring面试题(一)

那狭义上的 Spring 也就是我们常说的: IOCAOP

Spring是一个轻量级的 控制反转IOC)和 面向切面(AOP)的容器框架:

  • 从大小与开销两方面而言Spring都是轻量级的。
  • 通过控制反转(IOC)的技术 达到松耦合的目的,指把创建对象过程交给 Spring 进行管理。
  • 提供了 面向切面编程的丰富支持, 允许通过分离应用的业务逻辑与系统级服务进行内聚性的开发AOP 用来封装多个类的公共行为,将那些与业务无关,却为业务模块所共同调用的逻辑封装起来,减少系统的重复代码,降低模块间的耦合度。 另外,AOP 还解决一些系统层面上的问题,比如日志、事务、权限等。
  • 包含并管理应用对象(Bean)的配置和生命周期,这个意义上是一个容器。
  • 将简单的组件配置、组合成为复杂的应用,这个意义上是一个框架。

框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)

看到这,我记得还有人问我: 你认为Spring有缺点吗?

我就想给你一拳👊,Spring有缺点吗。。。。。

不过下来还是看了看文档,好想还是有的,虽然我平时开发遇不到,只知道它的优点。。。。

还是先说一下优点吧。。。

框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)

框架篇(二)Spring面试题(一)
框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)

所以缺点在哪。。。。

框架篇(二)Spring面试题(一)

框架篇(二)Spring面试题(一)

我。。。对对对对对对对对 。。。。。。

2. 说一下你是怎么理解AOP的

简单来说就是统一处理某一”切面”(类)的问题的编程思想,比如统一处理日志、异常等。 这种在运行时,动态地将代码切入到类的指定方法、指定位置上的编程思想就是面向切面的编程。

复杂来说。。。。。。

系统是由许多不同的组件所组成的,每一个组件各负责一块特定功能。除了实现自身核心功能之外,这些组件还经常承担着额外的职责。例如日志、事务管理和安全这样的核心服务经常融入到自身具有核心业务逻辑的组件中去。

这些系统服务经常被称为横切关注点,因为它们会跨越系统的多个组件。

AOP是Spring提供的关键特性之一。

AOP即面向切面编程,是对 OOP编程的有效补充。

当我们需要为分散的对象引入公共行为的时候, OOP则显得无能为力。也就是说, OOP允许你定义从上到下的关系,但并不适合定义从左到右的关系。例如日志功能。

日志代码往往水平地散布在所有对象层次中,而与它所散布到的对象的核心功能毫无关系。在 OOP设计中,它导致了大量代码的重复,而不利于各个模块的重用。

AOP:将程序中的交叉业务逻辑(比如安全,日志,事务等),封装成一个切面,然后注入到目标对象(具体业务逻辑)中去。

AOP可以对某个对象或某些对象的功能进行增强,比如对对象中的方法进行增强,可以在执行某个方法之前额外的做一些事情,在某个方法执行之后额外的做一些事情。

说到这里其实就差不多了,我们来看看AOP的实现。

AOP实现的关键在于: 代理模式

AOP代理主要分为 静态代理动态代理

AOP代理,他是为了实现切面功能一个对象会被AOP框架创建出来。在Spring框架中AOP代理的默认方式是: 有接口,就使用基于接口的JDK动态代理,否则使用基于类的CGLIB动态代理。但是我们可以通过设置 proxy-target-class="true",完全使用CGLIB动态代理。

静态代理的代表为 AspectJ;动态代理则以Spring AOP为代表。

  1. AspectJ是 静态代理,也称为 编译时增强,AOP框架会在编译阶段生成AOP代理类,并将 AspectJ(切面) 织入到 Java 字节码中,运行的时候就是增强之后的AOP对象。
  2. Spring AOP使用的 动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了 目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

Spring AOP中的动态代理主要有两种方式, JDK动态代理和CGLIB动态代理

  • JDK动态代理 只提供接口的代理不支持类的代理,要求被代理类实现接口

JDK动态代理的核心是 InvocationHandler接口和 Proxy类,在获取代理对象时,使用 Proxy类来动态创建目标类的代理类(即最终真正的代理类,这个类继承自 Proxy并实现了我们定义的接口),当代理对象调用真实对象的方法时, InvocationHandler 通过 invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;

InvocationHandlerinvoke(Object proxy,Method method,Object[] args)proxy是最终生成的代理对象; method 是被代理目标实例的某个具体方法; args 是被代理目标实例某个方法的具体入参, 在方法反射调用时使用。

如果被代理类没有实现接口,那么Spring AOP会选择使用 CGLIB来动态代理目标类。

CGLIB(Code Generation Library), 是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP

CGLIB是通过 继承的方式做的动态代理,因此如果某个类被标记为 final,那么它是无法使用CGLIB做动态代理的。

静态代理与动态代理区别在于生成AOP代理对象的 时机不同,相对来说 AspectJ的静态代理方式具有更好的性能,但是AspectJ需要特定的编译器进行处理,而Spring AOP则无需特定的编译器处理。

IoC让相互协作的组件保持松散的耦合,而AOP编程允许你把遍布于应用各层的功能分离出来形成可重用的功能组件。

在学习AOP的过程中,我们会常常听到一些名词。

  1. 连接点( Join point):也有人叫接入点。指程序运行过程中所执行的方法。程序执行期的一个点,例如方法执行、类初始化、异常处理。 在Spring AOP中,接入点始终表示方法执行。
  2. 切面( Aspect):被抽取出来的公共模块,可以用来会横切多个对象。切面,由一系列切点、增强和引入组成的模块对象,可定义优先级,从而影响增强和引入的执行顺序。 Aspect切面可以看成 Pointcut 切点 和 Advice 通知 的结合,一个切面可以由多个切点和通知组成。

在Spring AOP中,切面可以在类上使用 @AspectJ 注解来实现。

  1. 切点( Pointcut):切点用于定义要对哪些 Join point进行拦截。 切点,用来匹配特定接入点的谓词(表达式),增强将会与切点表达式产生关联,并运行在任何切点匹配到的接入点上。 通过切点表达式匹配接入点是AOP的核心,Spring默认使用AspectJ的切点表达式。 切点分为 execution方式和 annotation方式。 execution方式可以用路径表达式指定对哪些方法拦截,比如指定拦截 add searchannotation方式可以指定被哪些注解修饰的代码进行拦截。
  2. 通知( Advice):也有人叫它增强。指要在连接点(Join Point)上执行的动作,即增强的逻辑,比如权限校验和、日志记录等。通知有各种类型,包括Around、Before、After、After returning、After throwing。包含Spring在内的许多AOP框架,通常会使用拦截器来实现增强,围绕着接入点维护着一个拦截器链。
  3. 目标对象( Target):目标对象,被一个或多个切面增强的对象。也叫作被增强对象。既然Spring AOP使用运行时代理(runtime proxies),那么目标对象就总是代理对象。
  4. 织入( Weaving):通过动态代理,在目标对象( Target)的方法(即连接点 Join point)中执行增强逻辑(Advice)的过程。 织入,将一个或多个切面与类或对象链接在一起创建一个被增强对象。织入能发生在编译时 (compile time )(使用AspectJ编译器),加载时(load time),或运行时(runtime) 。Spring AOP默认就是运行时织入,可以通过枚举AdviceMode来设置。
  5. 引入( Introduction):添加额外的方法或者字段到被通知的类。Spring允许引入新的接口(以及对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。

框架篇(二)Spring面试题(一)

网上有张非常形象的图,描述了各个概念所处的场景和作用,贴在这里供大家理解:

框架篇(二)Spring面试题(一)

3. 说一下你对IOC的理解

框架篇(二)Spring面试题(一)

IOC,是控制反转,它是一种设计思想,是为了解决高耦合等问题。

Ioc容器:实际上就是个 map(key,value),里面存的是各种对象(在xml里配置的bean节点、 @Repository@Service@Controller@Component),在项目启动的时候会读取配置文件里面的 bean节点,根据 全限定类名使用反射创建对象放到map里、扫描到打上上述注解的类还是通过反射创建对象放到map里。

这个时候map里就有各种对象了,接下来我们在代码里需要用到里面的对象时,再通过 DI注入(@Autowired、@Resource等注解,xml里bean节点内的 ref属性,项目启动的时候会读取xml节点 ref属性,根据 id注入,也会扫描这些注解,根据类型或id注入;id就是对象名)。

框架篇(二)Spring面试题(一)

控制反转:

问:它控制啥了。。。它反转啥了。。。

框架篇(二)Spring面试题(一)

没有引入IOC容器之前,对象A依赖于对象B,那么对象A在初始化或者运行到某一点的时候,自己必须主动去创建对象B或者使用已经创建的对象B。无论是创建还是使用对象B,控制权都在自己手上。

引入IOC容器之后,对象A与对象B之间失去了直接联系,当对象A运行到需要对象B的时候,IOC容器会主动创建一个对象B注入到对象A需要的地方。

通过前后的对比,不难看出来:

对象A获得依赖对象B的过程,由主动行为变为了被动行为,控制权颠倒过来了,这就是”控制反转”这个名称的由来。

全部对象的控制权全部上缴给”第三方”IOC容器,所以,IOC容器成了整个系统的关键核心,它起到了一种类似”粘合剂”的作用,把系统中的所有对象粘合在一起发挥作用,如果没有这个”粘合剂”,对象与对象之间会彼此失去联系,这就是有人把IOC容器比喻成”粘合剂”的由来。

框架篇(二)Spring面试题(一)

上面提到的 DI,是依赖注入。依赖注入的提出是对”控制反转”的解释,在原作者,他建议用”依赖注入”代替”控制反转”。

依赖注入:

“获得依赖对象的过程被反转了”。控制被反转之后,获得依赖对象的过程由自身管理变为了由IOC容器主动注入。

依赖注入是实现 IOC的方法,就是由 IOC容器在运行期间,动态地将某种依赖关系注入到对象之中。

Spring IOC的是怎么实现的?

它其实是 一个简单工厂设计模式(BeanFactory.getBean())+反射( 在实例化我们bean对象 )。

这是一个简单工厂的demo:

框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)

4. BeanFactory和ApplicationContext有什么区别

这两个都是Spring容器,我们可以通过 getBean()获取bean。对,有人问过我,Spring容器有哪些?

你说不看文档,还以为故意***难我。没想到呀。

框架篇(二)Spring面试题(一)
ApplicationContextBeanFactory的子接口。

ApplicationContext提供了更完整的功能:

  • 继承MessageSource,因此支持国际化。
  • 统一的资源文件访问方式。
  • 提供在监听器中注册bean的事件。
  • 同时加载多个配置文件。
  • 载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。

BeanFactroy采用的是 延迟加载形式来注入bean的,即只有在使用到某个bean时(调用 getBean()),才对该Bean进行加载实例化。

框架篇(二)Spring面试题(一)

这样,我们就不能发现一些存在的Spring的配置问题。

如果Bean的某一个属性没有注入, BeanFacotry加载后,直至第一次使用调用 getBean()方法才会抛出异常。所以就出现了 ApplicationContext

ApplicationContext它是在容器启动时,一次性创建了所有的bean

这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。

ApplicationContext 启动后预载入所有的单实例 bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。

当应用程序配置Bean较多时,程序启动较慢。

BeanFactory通常以编程的方式被创建, ApplicationContext还能以声明的方式创建,如使用ContextLoader。

BeanFactoryApplicationContext都支持 BeanPostProcessorBeanFactoryPostProcessor的使用,但两者之间的区别是: BeanFactory需要手动注册,而 ApplicationContext则是自动注册。

穿插一个问题BeanFactoryApplicationContext 中的 getBean()有什么区别?这个也遇到过。

先说一下:

共同点

Spring 提供了两种不同的 IoC 容器,一个是 BeanFactory,另外一个是 ApplicationContextBeanFactory 就是对象的一个工厂,而 ApplicationContext 是应用程序上下文,它就是直接翻译。

但是需要注意一点,什么事儿他们都是 Java 接口, ApplicationContext 就是 BeanFactory 的一个子接口,它需要 继承或实现自 BeanFactory

它们都可以用 XML 文件的方式来进行相关的配置,也支持属性的自动注入。

ListableBeanFactory 继承 BeanFactoryBeanFactoryApplicationContext 都提供了一种方式,使用 getBean() 的方式来获取 bean 对象,也就是在进行使用的时候, 使用方式是相同的

框架篇(二)Spring面试题(一)

不同点

当调用 getBean() 方法方法的时候, BeanFactory 仅实例化对象,而 ApplicationContext 在启动容器的时候会实例化单例(scope="singleton“) bean,不会等待调用 getBean() 的时候才进行实例化。

也就是两个方式在进行对象创建的时候,创建的时机是不同的,一定要把这点搞明白。你如果谁看过 Spring 源码应该比较清楚,每次当动好容器之后,会把容器里面预先准备好的这些单例对象提前进行实例化。

而如果你配置的是 prototype类型,在什么时候用的时候才会对它进行实地化。

BeanFactory 不支持国际化,即 i18n 的操作,而 ApplicationContext 提供了对他的支持。如果你的网站只是在国内进行使用没有任何问题。但如果你的网站需要在国际上进行访问,它一定要进行语言的切换,这个时候就需要用到我们的国际化配置了。

BeanFactoryApplicationContext 之间 另一个区别是能够将事件发布注册到容器中的 bean

有一个支持了监听器,另外一个并不支持监听器。

BeanFactory 的一个核心实现是 XMLBeanFactory,而 ApplicationContext 核心实现 ClassPathXMLApplication

Web 容器的环境,我们使用的是 WebApplicationContext,并且增加了 getServletContext 方法。

比如在使用的时候,特别是在 Spring MVC使用的时候,你获取到的容器都 WebApplicationContext,而不是 WebBeanFActory。把这两点要区分清楚了,因为你在学完 Spring 框架之后,基本上都会接触 Spring MVC。虽然现在在生产环境里面用的比较少了,但依然有些公司的传统项目里面还在用 Spring MVC,所以你要对这个机制有所了解。

如果使用 自动注入并使用并 BeanFactory,则需要使用 API 注册 AutowiredBeanPostProcessor。下面有个例子。

而如果使用 ApplicationContext,则可以直接通过 XML 进行配置。现在主流的开发一般都是注解的方式。

简而言之, BeanFactory 提供了基本的 IOCDI 的功能。而 ApplicationContext 提供了更高级别的功能BeanFactory 可用于测试和非生产使用,而 ApplicationContext 是功能更丰富的容器实现,应该优于 BeanFactory。也就是 BeanFactory 是一个父接口。但是我们在日常工作或使用中,推荐使用的是 ApplicationContext,因为里面做了很多接口的扩展,包括做了很多丰富的实践,让我们用起来更加舒服和更加方便。

框架篇(二)Spring面试题(一)

总结一下就是:

框架篇(二)Spring面试题(一)

在穿插一个问题:如果我要使用 BeanFactorygetBean()方法,能得到 bean 对象吗?

框架篇(二)Spring面试题(一)
框架篇(二)Spring面试题(一)

发现是不可以的。

框架篇(二)Spring面试题(一)

需要手动注册 BeanDefintion到工厂中去。

框架篇(二)Spring面试题(一)

5. FactroyBean 和 BeanFactory一样吗

BeanFactory是一个工厂,也就是一个容器,使用来管理和生产 bean 的。

框架篇(二)Spring面试题(一)

6. Spring IOC容器的加载流程

先来想一个问题:IOC容器加载在什么时候开始的?或者IOC容器的事怎样的创建?

框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)

先来个简单的压压惊:

其实IOC容器的加载过程也就是Spring bean的创建的过程。对于没看过源码来说还是很吃力的,这里我们可以简单给定义为4个过程:

框架篇(二)Spring面试题(一)

框架篇(二)Spring面试题(一)

这个只是简单版,方便理解的。下面的链接是有人带着看源码,比较舒服一点。

视频链接:阿里二面—SpringIOC容器加载流程_哔哩哔哩_bilibili

框架篇(二)Spring面试题(一)

框架篇(二)Spring面试题(一)

Spring IOC的加载过程 – 简书 (jianshu.com)

进过上面简单版的介绍,我们来看一下 源码是怎么样的一个流程,先来看一波测试类:

框架篇(二)Spring面试题(一)

其实不管是哪一种方式启动ioc容器,里面所走的方法都是差不多的。我们可以去看看,注解类的启动流程:

框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)

这是一个启动 Spring 容器创建 bean 的测试类。

框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)

它锁的是同步监视器对象,确保只有一个线程进来,刷新我们的 IOC 容器。

框架篇(二)Spring面试题(一)

这个就是它锁的对象。

接下来我们在看它里面的细节的操作:

框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)

总结:

Spring中的IOC容器启动主要是 AbstractApplicationContext中的 refresh()方法实现的。大体过程如下:

  1. 第一步,首先回执行一些容器刷新前的准备工作,如设置容器启动事件、一些状态标志位等。
  2. 第二步,创建容器对象,其实就是实例化 DefaultListableBeanFactory对象,这一步包含了bean定义信息的解析,解析后的属性都封装到 DefaultListableBeanFactory的成员属性中了,如 BeanDefinitionNames;
  3. 第三步,准备 bean工厂,实际上就是设置 beanFactory的一些属性。
  4. 第四步,Spring提供了 postProcessBeanFactory()方法给我们去扩展,例如我们可以注册一些特殊的 BeanPostProcessor后置处理器等操作。
  5. 第五步,执行 BeanFactoryPostProcessorbean工厂的后置处理器的 postProcessBeanFactory()增强的方法,使用三个不同的集合分别存放实现 PriorityOrdered接口、实现了 Ordered接口、不同的 BeanFactoryPostProcessor,经过排序后,执行了 BeanFactoryPostProcessor的回调 postProcessBeanFactory()
  6. 第六步,注册 BeanPostProcessor后置处理器,注意,这里还不会执行 BeanPostProcessor对应的增强方法;同样的,使用三个集合分别存放实现了 PriorityOrdered接口、实现了 Ordered接口、普通的 BeanPostProcessor(postProcessor)方法往 bean工厂中添加 BeanPostProcessor;
  7. 第七步,为上下文初始化 MessageSource,即国际化处理。
  8. 第八步,初始化事件多播器,即 ApplicationEventMulticaster,为后面的事件发布-监听做准备。
  9. 第九步,提供了一个模版方法 onRefresh(),留个子类初始化其他的bean。
  10. 第十步,注册Listener监听器。
  11. 第十一步,也是最关键的一步,这里回实例化所有剩下的非懒加载的单例bean,bean的生命周期也是从这里开始的。

具体一点就是,这里回去到之前解析到的所有bean名称集合,挨个调用 getBean(beanname)方法,然后经历 doGetBean()createBean()doCreateBean()创建bean的流程,接着会通过反射创建我们的bean实例对象,然后进行 populateBean()属性填充,属性填充完成后,回执行bean的初始化,初始化bean过程主要执行 Aware接口方法、执行初始化方法、回调 BeanPostProcessor后置增强器的方法等。

  1. 第十二步,完成上下文的刷新工作,如清除一些缓存、发布容器刷新完成的事件等。

7. Spring IOC有哪些扩展点,在什么时候调用

什么是扩展点?

说白了就是Spring IOC底层在加载的过程会对外提供很多的扩展接口,或者说一些的钩子方法。

那么到我们实现了这些接口,它就会帮我们在特定的点, 帮我们调用这些钩子方法,从而我们就可以对 ioc 的底层做扩展。

  1. 在注册bean的过程中,也叫注册bean定义的时候调用的扩展接口

框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)框架篇(二)Spring面试题(一)
BeanDefinitionRegistryPostProcessor。bean 信息注册的后置处理器(也有人教增强器)。他是最先开始调用。
  • 作用:动态的注册 BeanDefinition
  • 调用时机:IOC 在加载时注册 BeanDefinition 的时候调用,是最先开始调用的。

BeanFactoryPostProcessor。Bean工厂的后置处理器,是在 BeanDefinitionRegistryPostProcessor 之后。

  • 作用:在注册 BeanDefinition的可以对 BeanFactory扩展,是在 1 之后。
  • 调用时机:IOC 加载时注册 BeanDefinition 的时候会调用。

  • 在初始化过程中所调用的扩展接口

框架篇(二)Spring面试题(一)

补充: 生命周期的回调,一个是在初始化的时候,另一个是在销毁的时候,他们都提供了 3 种回调的方法。

  • 初始化的时候
  • @PostConstruct
  • @Bean(initMethod="init")
  • implents InitialzingBean 方法
  • 销毁的时候
  • @Bean(destoryMethod="destory")
  • @PreDestory
  • implents DispoableBean 方法

8. 什么是Spring Bean?JavaBean、Spring Bean和对象有什么区别?

框架篇(二)Spring面试题(一)

框架篇(二)Spring面试题(一)

9. 配置Bean几种方式

  1. xml<bean class="com.uin.UserService" id="userservice"></bean>
  2. 注解: @Component( @Controller@Service@Repostory ) 前提:需要配置扫描包

原理:反射调用构造方法
3. Java Config: @Bean 通常和 @Configuration配合一起使用。可以自己控制实例化过程,也就是自己new。
4. Java Config:@Import 3种方式 在Spring3.0之后使用。 springBean注册之@Import注解解析_这是一条海鱼的博客-CSDN博客 使用Spring注解@Import进行Bean的导入管理 | Java|Spring Security|Spring Boot|Spring Cloud|https://felord.cn 码农小胖哥的博客 https://mp.weixin.qq.com/s/uagetzCiRiPORlnD6dhnGA 一个练手的小demo
– @ Import(Person.class) 类上。主要提供配置类导入的功能
ImportSelector

10. 说一下Spring中bean的作用域

在显式的配置bean的作用域中,也就是在 xml 中, scope=""中配置。

隐式的配置的就是 @Scope;来配置。

Spring框架支持以下五种bean的作用域:

  • singleton : 也就是说在整个 Spring 应用中, bean 的实例只有一个。
  • prototype:一个bean的定义可以有多个实例。表示每次通过 Spring 容器获取 Bean 时,容器都会创建一个新的 Bean 实例。

一下这几个bean的必须在web的应用中才生效。

  • request:每次http请求都会创建一个bean,该作用域仅在基于web的Spring ApplicationContext情形下有效。 该作用域只在当前 HTTP Request 内有效。
  • session:同一个 HTTP Session 共享一个 Bean 实例,不同的 Session 使用不同的 Bean 实例。该作用域仅在基于web的Spring ApplicationContext情 形下有效。
  • application:同一个 Web 应用共享一个 Bean 实例,该作用域在当前 ServletContext 内有效。 与 singleton 类似,但 singleton 表示每个 IoC 容器中仅有一个 Bean 实例,而一个 Web 应用中可能会存在多个 IoC 容器,但一个 Web 应用只会有一个 ServletContext,也可以说 application 才是 Web 应用中货真价实的单例模式。

注意: 缺省的Spring bean 的作用域是Singleton。使用 prototype 作用域需要慎重的思考,因为频繁创建和销毁 bean 会带来很大的性能开销。

穿插一个问题: Spring bean的作用域为什么要默认的使用单例的bean?

由于不会每次都新创建新对象所以有一下几个性能上的优势:

  1. 减少了新生成实例的消耗新生成实例消耗。包括两方面,第一,spring会通过 反射或者 cglib来生成bean实例这都是耗性能的操作, 其次给对象分配内存也会涉及复杂算法。 而单例的bean,提高服务器内存的利用率 ,减少服务器内存消耗 。
  2. 减少jvm垃圾回收。由于不会给每个请求都新生成bean实例,所以自然回收的对象少了。
  3. 可以快速获取到bean。因为单例的获取bean操作除了第一次生成之外其余的都是从缓存(一级缓存)里获取的所以很快。

11. 说一下bean的实例化过程

百度一面:说说 Spring Bean 的实例化过程? (qq.com)这个博主我整个爱住!!!!虽然他有个错别字 BeanDefinition,他就是多个a。

自己注意⚠️!不影响我对他的爱❤️!

这里首先声明一下,Spring将管理的一个个的依赖对象称之为Bean,这从xml配置文件中也可以看出。

Spring IOC容器就好像一个生产产品的流水线上的机器,Spring创建出来的Bean就好像是流水线的终点生产出来的一个个精美绝伦的产品。既然是机器,总要先启动,Spring也不例外。因此Bean的一生从总体上来说可以分为两个阶段:

  • 容器启动阶段
  • Bean实例化阶段

容器的启动阶段做了很多的预热工作,为后面Bean的实例化做好了充分的准备,我们首先看一下容器的启动阶段都做了哪些预热工作。

  1. 配置元信息

我们说Spring IOC容器将对象实例的创建与对象实例的使用分离,我们的业务中需要依赖哪个对象不再依靠我们自己手动创建,只要向Spring要,Spring就会以注入的方式交给我们需要的依赖对象。

但是,你不干,我不干,总要有人干,既然我们将对象创建的任务交给了Spring,那么Spring就需要知道创建一个对象所需要的一些必要的信息。而这些必要的信息可以是Spring过去支持最完善的xml配置文件,或者是其他形式的例如properties的磁盘文件,也可以是现在主流的注解,甚至是直接的代码硬编码。总之,这些创建对象所需要的必要信息称为配置元信息。


  1. BeanDefinition

我们大家都知道,在Java世界中,万物皆对象,散落于程序代码各处的注解以及保存在磁盘上的xml或者其他文件等等配置元信息,在内存中总要以一种对象的形式表示,就好比我们活生生的人对应到Java世界中就是一个Person类,而Spring选择在内存中表示这些配置元信息的方式就是BeanDefinition,这里我们不会去分析BeanDefinition的代码,感兴趣的可以去看相关源码, 「这里我们只是需要知道配置元信息被加载到内存之后是以BeanDefinition的形存在的即可。」

  1. BeanDefinitionReader

大家肯定很好奇,我们是看得懂Spring中xml配置文件中一个个的Bean定义,但是Spring是如何看懂这些配置元信息的呢?这个就要靠我们的BeanDefinitionReader了。

不同的BeanDefintionReader就像葫芦兄弟一样,各自拥有各自的本领。如果我们要读取xml配置元信息,那么可以使用 XmlBeanDefintionReader。如果我们要读取properties配置文件,那么可以使用 PropertiesBeanDefinitionReader加载。而如果我们要读取注解配置元信息,那么可以使用 AnnotatedBeanDefinitionReader加载。我们也可以很方便的自定义 BeanDefintionReader来自己控制配置元信息的加载。例如我们的配置元信息存在于三界之外,那么我们可以自定义From天界之外 BeanDefintionReader

「总的来说,BeanDefintionReader的作用就是加载配置元信息,并将其转化为内存形式的BeanDefintion,存在某一个地方,至于这个地方在哪里,不要着急,接着往下看!」

  1. BeanDefinitionRegistry

执行到这里,总算不遗余力的将存在于各处的配置元信息加载到内存,并转化为BeanDefintion的形式,这样我们需要创建某一个对象实例的时候,找到相应的BeanDefintion然后创建对象即可。那么我们需要某一个对象的时候,去哪里找到对应的BeanDefintion呢?这种通过Bean定义的id找到对象的BeanDefintion的对应关系或者说映射关系又是如何保存的呢?这就引出了BeanDefinitionRegistry了。

Spring通过BeanDefintionReader将配置元信息加载到内存生成相应的BeanDefintion之后,就将其注册到BeanDefinitionRegistry中,BeanDefintionRegistry就是一个存放BeanDefintion的大篮子,它也是一种键值对的形式,通过特定的Bean定义的id,映射到相应的BeanDefintion。

  1. BeanFactoryPostProcessor

BeanFactoryPostProcessor是容器启动阶段Spring提供的一个扩展点,主要负责对注册到 BeanDefintionRegistry中的一个个的 BeanDefintion进行一定程度上的修改与替换。

例如我们的配置元信息中有些可能会修改的配置信息散落到各处,不够灵活,修改相应配置的时候比较麻烦,这时我们可以使用占位符的方式来配置。例如配置Jdbc的DataSource连接的时候可以这样配置:


BeanFactoryPostProcessor就会对注册到 BeanDefintionRegistry中的 BeanDefintion做最后的修改,替换$占位符为配置文件中的真实的数据。

至此,整个容器启动阶段就算完成了,容器的启动阶段的最终产物就是注册到 BeanDefintionRegistry中的一个个 BeanDefintion了,这就是Spring为Bean实例化所做的预热的工作。

让我们再通过一张图的形式回顾一下容器启动阶段都是搞了什么事吧。

框架篇(二)Spring面试题(一)

接着我们来看看,Bean的实例化阶段:

需要指出,容器启动阶段与Bean实例化阶段存在多少时间差,Spring把这个决定权交给了我们程序员(是不是瞬间开心了一点点!)。如果我们选择懒加载的方式,那么直到我们伸手向Spring要依赖对象实例之前,其都是以 BeanDefintionRegistry中的一个个的 BeanDefintion的形式存在,也就是Spring只有在我们需要依赖对象的时候才开启相应对象的实例化阶段。

而如果我们不是选择懒加载的方式,容器启动阶段完成之后,将立即启动Bean实例化阶段,通过隐式的调用所有依赖对象的getBean方法来实例化所有配置的Bean并保存起来。

  1. 对象的创建策略

到了这个时候,Spring就开始真刀真枪的干了,对象的创建采用了策略模式,借助我们前面 BeanDefintionRegistry中的 BeanDefintion,我们可以使用反射的方式创建对象,也可以使用CGlib字节码生成创建对象。同时我们可以灵活的配置来告诉Spring采用什么样的策略创建指定的依赖对象。Spring中Bean的创建是策略设计模式的经典应用。这个时候,内存中应该已经有一个我们想要的具体的依赖对象的实例了,但是故事的发展还没有我们想象中的那么简单。

  1. BeanWrapper –对象的外衣

Spring中的Bean并不是以一个个的本来模样存在的,由于Spring IOC容器中要管理多种类型的对象,因此为了统一对不同类型对象的访问, 「Spring给所有创建的Bean实例穿上了一层外套」,这个外套就是 BeanWrapper(关于 BeanWrapper的具体内容感兴趣的请查阅相关源码)。 BeanWrapper实际上是对反射相关API的简单封装,使得上层使用反射完成相关的业务逻辑大大的简化,我们要获取某个对象的属性,调用某个对象的方法,现在不需要在写繁杂的反射API了以及处理一堆麻烦的异常,直接通过 BeanWrapper就可以完成相关操作,简直不要太爽了。

框架篇(二)Spring面试题(一)
  1. 设置对象属性

上一步包裹在 BeanWrapper中的对象还是一个少不经事的孩子,需要为其设置属性以及依赖对象。

  • 对于基本类型的属性,如果配置元信息中有配置,那么将直接使用配置元信息中的设置值赋值即可,即使基本类型的属性没有设置值,那么得益于JVM对象实例化过程,属性依然可以被赋予默认的初始化零值。
  • 对于引用类型的属性,Spring会将所有已经创建好的对象放入一个Map结构中,此时Spring会检查所依赖的对象是否已经被纳入容器的管理范围之内,也就是Map中是否已经有对应对象的实例了。如果有,那么直接注入,如果没有,那么Spring会暂时放下该对象的实例化过程,转而先去实例化依赖对象,再回过头来完成该对象的实例化过程。

这里有一个Spring中的经典问题,那就是Spring是如何解决循环依赖的?
这里简单提一下,Spring是通过三级缓存解决循环依赖,并且只能解决Setter注入的循环依赖,请大家思考一下如何解决?为何只能是Setter注入?详细内容可以查阅相关博客,文档,书籍。
对,这里就是让你知道一下,没别的意思。
后面会有的!

  1. 检查Aware相关接口

我们知道,我们如果想要依赖Spring中的相关对象,使用Spring的相关API,那么可以实现相应的Aware接口,Spring IOC容器就会为我们自动注入相关依赖对象实例。

Spring IOC容器大体可以分为两种, BeanFactory提供IOC思想所设想所有的功能,同时也融入AOP等相关功能模块,可以说BeanFactory是Spring提供的一个基本的IOC容器。

ApplicationContext构建于 BeanFactory之上,同时提供了诸如容器内的时间发布、统一的资源加载策略、国际化的支持等功能,是Spring提供的更为高级的IOC容器。

讲了这么多,其实就是想表达对于 BeanFactory来说,这一步的实现是先检查相关的Aware接口,然后去Spring的对象池(也就是容器,也就是那个Map结构)中去查找相关的实例(例如对于 ApplicationContextAware接口,就去找 ApplicationContext实例),也就是说我们必须要在配置文件中或者使用注解的方式,将相关实例注册容器中, BeanFactory才可以为我们自动注入。

而对于 ApplicationContext,由于其本身继承了一系列的相关接口,所以当检测到Aware相关接口,需要相关依赖对象的时候, ApplicationContext完全可以将自身注入到其中, ApplicationContext实现这一步是通过下面要讲到的东东——BeanPostProcessor

框架篇(二)Spring面试题(一)

例如 ApplicationContext继承自 ResourceLoaderMessageSource,那么当我们实现 ResourceLoaderAwareMessageSourceAware相关接口时,就将其自身注入到业务对象中即可。

  1. BeanPostProcessor 前置处理

唉?刚才那个是什么Processor来?相信刚看这两个东西的人肯定有点晕乎了,我当初也是,不过其实也好区分,只要记住 BeanFactoryPostProcessor存在 于容器启动阶段,而 BeanPostProcessor存在于 对象实例化阶段BeanFactoryPostProcessor关注 「对象被创建之前」 那些配置的修修改改,缝缝补补,而 BeanPostProcessor阶段关注 「对象已经被创建之后」 的功能增强,替换等操作,这样就很容易区分了。

BeanPostProcessorBeanFactoryPostProcessor都是Spring在Bean生产过程中强有力的扩展点。

如果你还对它感到很陌生,那么你肯定知道Spring中著名的AOP(面向切面编程),其实就是依赖 BeanPostProcessor对Bean对象功能增强的。

BeanPostProcessor前置处理就是在要生产的Bean实例放到容器之前,允许我们程序员对Bean实例进行一定程度的修改,替换等操作。

前面讲到的ApplicationContext对于Aware接口的检查与自动注入就是通过 BeanPostProcessor实现的,在这一步Spring将检查Bean中是否实现了相关的Aware接口,如果是的话,那么就将其自身注入Bean中即可。

Spring中AOP就是在这一步实现的偷梁换柱,产生对于原生对象的代理对象,然后将对源对象上的方法调用,转而使用代理对象的相同方法调用实现的。

  1. 自定义初始化逻辑

在所有的准备工作完成之后,如果我们的Bean还有一定的初始化逻辑,那么Spring将允许我们通过两种方式配置我们的初始化逻辑:

  • InitializingBean
  • 配置 init-method参数

一般通过配置 init-method方法比较灵活。

  1. BeanPostProcessor 后置处理

与前置处理类似,这里是在Bean自定义逻辑也执行完成之后,Spring又留给我们的最后一个扩展点。我们可以在这里在做一些我们想要的扩展。

  1. 自定义销毁逻辑

这一步对应自定义初始化逻辑,同样有两种方式:

  • 实现 DisposableBean接口
  • 配置 destory-method参数。

这里一个比较典型的应用就是配置dataSource的时候 destory-method为数据库连接的 close()方法。

  1. 使用

经过了以上道道工序,我们终于可以享受Spring为我们带来的便捷了,这个时候我们像对待平常的对象一样对待Spring为我们产生的Bean实例,如果你觉得还不错的话,动手试一下吧!

  1. 调用回调销毁接口

Spring的Bean在为我们服务完之后,马上就要消亡了(通常是在容器关闭的时候),别忘了我们的自定义销毁逻辑,这时候Spring将以回调的方式调用我们自定义的销毁逻辑,然后Bean就这样走完了光荣的一生!

我们再通过一张图来一起看一看Bean实例化阶段的执行顺序是如何的?

框架篇(二)Spring面试题(一)

需要指出,容器启动阶段与Bean实例化阶段之间的桥梁就是我们可以选择自定义配置的延迟加载策略,如果我们配置了Bean的延迟加载策略,那么只有我们在真实的使用依赖对象的时候,Spring才会开始Bean的实例化阶段。
而如果我们没有开启Bean的延迟加载,那么在容器启动阶段之后,就会紧接着进入Bean实例化阶段,通过隐式的调用getBean方法,来实例化相关Bean。

12. Spring Bean的生命周期

在前面好多的面试题,都涉及到 bean 的生命周期,但是我觉得还是不够清晰,所以在这里在重新总结一些。

Spring Bean的生命周期指的是Bean从创建到初始化再到销毁的过程,这个过程由IOC容器管理。

一个完整的Bean生命周期可以参考Spring Bean生命周期。这里我们主要记录一些和Bean生命周期相关的细节。

Bean的初始化和销毁,在整个生命周期过程中,我们可以自定义Bean的初始化和销毁钩子函数(扩展接口),当Bean的生命周期到达相应的阶段的时候,Spring会调用我们自定义的Bean的初始化和销毁方法。

spring生命周期七个过程_面试官:请你讲解一下Spring Bean的生命周期_weixin_39911567的博客-CSDN博客不错!

自定义Bean初始化和销毁方法有多种方式,下面逐一介绍。

框架篇(二)Spring面试题(一)

Spring-Bean生命周期 – 知乎 (zhihu.com)也不错!

框架篇(二)Spring面试题(一)

其实在Spring bean的生命周期,只有四个。

要彻底搞清楚Spring的生命周期,首先要把这四个阶段牢牢记住。实例化和属性赋值对应构造方法和setter方法的注入,初始化和销毁是用户能自定义扩展的两个阶段。

  1. 实例化( Instantiation),实例化一个 bean 对象;
  2. 属性赋值( Populate),为 bean 设置相关属性和依赖;
  3. 初始化( Initialization),第 3~7 步,步骤较多,其中第 5、6 步为初始化操作,第 3、4 步为在初始化前执行,第 7 步在初始化后执行,该阶段结束,才能被用户使用;
  4. 销毁( Destruction),第8步不是真正意义上的销毁(还没使用呢),而是先在使用前注册了销毁的相关调用接口,为了后面第9、10步真正销毁 bean 时再执行相应的方法。

框架篇(二)Spring面试题(一)

值得注意的是,bean 的销毁的过程并不在ioc容器中,上面讲的ioc的加载流程有提到过。

实现了这些接口的Bean会切入到多个Bean的生命周期中。正因为如此,这些接口的功能非常强大,Spring内部扩展也经常使用这些接口,例如自动注入以及AOP的实现都和他们有关。

  • BeanPostProcessor
  • InstantiationAwareBeanPostProcessor

这两兄弟可能是Spring扩展中 最重要的两个接口!

InstantiationAwareBeanPostProcessor作用于 实例化阶段的前后, BeanPostProcessor作用于 初始化阶段的前后。

来看看上面这个图每一个步骤,对应的详细过程:

  1. 生成 BeanDefinition ->合并BeanDefinition->加载类 BeanDefinition

框架篇(二)Spring面试题(一)
框架篇(二)Spring面试题(一)
  1. 实例化(实例化前->实例化->BeanDefinition的后置处理器(BeanPostProcessor)->实例化后的方法)

框架篇(二)Spring面试题(一)

实例化又包括很多细节:第一步的话, AbstractAutowireCapableBeanFactory#createBeanInstance。第二步, supplier创建对象。第三步,工厂创建方法对象。第四步,推断构造方法。

框架篇(二)Spring面试题(一)

框架篇(二)Spring面试题(一)

框架篇(二)Spring面试题(一)
  1. 属性注入

框架篇(二)Spring面试题(一)
  1. 初始化

框架篇(二)Spring面试题(一)
  1. 销毁

框架篇(二)Spring面试题(一)

Original: https://www.cnblogs.com/bearbrick0/p/16067857.html
Author: BearBrick0
Title: 框架篇(二)Spring面试题(一)

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/572480/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • Spring Boot:实现MyBatis分页

    综合概述 想必大家都有过这样的体验,在使用Mybatis时,最头痛的就是写分页了,需要先写一个查询count的select语句,然后再写一个真正分页查询的语句,当查询条件多了之后,…

    Java 2023年5月30日
    075
  • JDK数组阻塞队列源码深入剖析

    JDK数组阻塞队列源码深入剖析 前言 在前面一篇文章从零开始自己动手写阻塞队列当中我们仔细介绍了阻塞队列提供给我们的功能,以及他的实现原理,并且基于谈到的内容我们自己实现了一个低配…

    Java 2023年6月8日
    082
  • 高并发场景案例分享(二)count实时查询之坑

    上一篇主要从设计层面,分享了一些小经验。 因软件系统有其复杂性和多样性,不同的场景、架构下,系统的瓶颈各不相同。 文章里的一些想法和设计并不通用,主要针对的是 高并发场景下海量数据…

    Java 2023年6月5日
    094
  • Java中 List、Set、Map 之间的区别

    一、List(列表) List的元素以线性方式存储,可以存放重复对象,List主要有以下两个实现类: ArrayList : 长度可变的数组,可以对元素进行随机的访问,向Array…

    Java 2023年5月29日
    078
  • JavaCV的摄像头实战之七:推流(带声音)

    借助JavaCV,完成本地摄像头和麦克风数据推送到媒体服务器的操作,并用VLC验证 欢迎访问我的GitHub 这里分类和汇总了欣宸的全部原创(含配套源码):https://gith…

    Java 2023年6月8日
    091
  • 理解:语法树,短语,直接短语,句柄

    概念: 语法树求短语、简单短语和句柄: 1)短语:子树的末端结点形成的符号串。 2)简单子树:只有一层分支的子树。 3)直接短语(简单短语):简单子树的末端结点形成的符号串。 4)…

    Java 2023年6月7日
    084
  • 确保某个BeanDefinitionRegistryPostProcessor Bean被最后执行的几种实现方式

    一、事出有因 二、解决方案困境 三、柳暗花明,终级解决方案 第一种实现方案 第二种实现方案 第三种实现方案 四、引发的思考 一、事出有因 ​ 最近有一个场景,因同一个项目中不同JA…

    Java 2023年6月9日
    064
  • MyBatis架构与源码分析<资料收集>

    1、架构与源码分析 :https://www.cnblogs.com/luoxn28/p/6417892.html 、https://www.cnblogs.com/wangdai…

    Java 2023年5月30日
    066
  • Nginx的简单配置(可直接使用)

    error_log logs/error.log; 全局错误日志定义类型error_log logs/error.log notice; error_log logs/error….

    Java 2023年6月16日
    076
  • 【数据结构】12.java源码关于ConcurrentHashMap

    目录 1.ConcurrentMap的内部结构 2.ConcurrentMap构造函数 3.元素新增策略4.元素删除5.元素修改和查找6.特殊操作7.扩容8.总结 1.Concur…

    Java 2023年6月5日
    087
  • 项目版本管理Git使用详细教程

    前言 记得刚开始做项目开发的时候都是一个人完成一个项目,单打独斗的开发,也不知道什么是团队开发,没有这个概念,随着工作后来知道公司里项目都是团队开发,这个时候这么多人怎么开发一个项…

    Java 2023年6月13日
    091
  • 经典实验–飞机大战小游戏

    ·一、需求设计 1.为检测C语言的学习成果,根据所学的C语言知识,设计程序:飞机大战小游戏; 2.自行定义变量,函数或结构体,编写源代码并进行编译运行测试; 3.根据编写的代码,自…

    Java 2023年6月15日
    052
  • spring cache之自定义keys的过期时间

    spring @cacheable注解默认不支持方法级别的缓存失效时间,只能通过配置来配置全局的失效时间 如果需要实现对方法级别的缓存支持失效时间机制,有一种比较简单的方法,spr…

    Java 2023年6月16日
    074
  • java_异常机制(二)

    1.异常对象处理完之后会怎样: 异常处理对象在异常处理完后,没有引用指向它,变成了不可达对象,Exception对象会在下一个垃圾回收过程中被回收掉。 它将在接下来JVM进行gc操…

    Java 2023年6月5日
    079
  • java进程占用CPU或者内存高问题排查

    排查步骤: 1. 使用top命令查看系统资源的使用情况, 命令: top 如图:排行前面的就是占用资源最多的 2.定位线程问题 方法一: top -Hp 查看线程 :使用命令 to…

    Java 2023年6月8日
    093
  • 别再重复造轮子了,几个值得应用到项目中的 Java 开源库送给你

    我是风筝,公众号「古时的风筝」。文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在里面。公众号回复『666』获取高清大图。 风筝我作为一…

    Java 2023年5月29日
    057
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球