spring面试题
一、基础
1.什么是spring?
spring是一个简化java企业级开发的一个框架,内部包含了很多技术,比如:控制反转&依赖注入、面向切面编程、spring事务管理、通过spring集成其他框架、springmvc、springboot、springcloud等等,这些都是围绕简化开发展开的技术。
2.spring容器?
spring容器的概念,容器这个名字起的相当好,容器可以放很多东西,我们的程序启动的时候会创建spring容器,会给spring容器一个清单,清单中列出了需要创建的对象以及对象依赖关系,spring容器会创建和组装好清单中的对象,然后将这些对象存放在spring容器中,当程序中需要使用的时候,可以到容器中查找获取,然后直接使用。
3.IOC:控制反转
使用者之前使用B对象的时候都需要自己去创建和组装,而现在这些创建和组装都交给spring容器去给完成了,使用者只需要去spring容器中查找需要使用的对象就可以了;这个过程中B对象的创建和组装过程被反转了,之前是使用者自己主动去控制的,现在交给spring容器去创建和组装了,对象的构建过程被反转了,所以叫做控制反转;IOC是是面相对象编程中的一种设计原则,主要是为了降低系统代码的耦合度,让系统利于维护和扩展。
4.DI:依赖注入
依赖注入是spring容器中创建对象时给其设置依赖对象的方式,比如给spring一个清单,清单中列出了需要创建B对象以及其他的一些对象(可能包含了B类型中需要依赖对象),此时spring在创建B对象的时候,会看B对象需要依赖于哪些对象,然后去查找一下清单中有没有包含这些被依赖的对象,如果有就去将其创建好,然后将其传递给B对象;可能B需要依赖于很多对象,B创建之前完全不需要知道其他对象是否存在或者其他对象在哪里以及被他们是如何创建,而spring容器会将B依赖对象主动创建好并将其注入到B中去,比如spring容器创建B的时候,发现B需要依赖于A,那么spring容器在清单中找到A的定义并将其创建好之后,注入到B对象中。
5.IOC容器
IOC容器是具有依赖注入功能的容器,负责对象的实例化、对象的初始化,对象和对象之间依赖关系配置、对象的销毁、对外提供对象的查找等操作,对象的整个生命周期都是由容器来控制。我们需要使用的对象都由ioc容器进行管理,不需要我们再去手动通过new的方式去创建对象,由ioc容器直接帮我们组装好,当我们需要使用的时候直接从ioc容器中直接获取就可以了。
那么spring ioc容器是如何知道需要管理哪些对象呢?
需要我们给ioc容器提供一个配置清单,这个配置支持xml格式和java注解的方式,在配置文件中列出需要让ioc容器管理的对象,以及可以指定让ioc容器如何构建这些对象,当spring容器启动的时候,就会去加载这个配置文件,然后将这些对象给组装好以供外部访问者使用。
这里所说的IOC容器也叫spring容器。
6.Bean概念
由spring容器管理的对象统称为Bean对象。Bean就是普通的java对象,和我们自己new的对象其实是一样的,只是这些对象是由spring去创建和管理的,我们需要在配置文件中告诉spring容器需要创建哪些bean对象,所以需要先在配置文件中定义好需要创建的bean对象,这些配置统称为bean定义配置元数据信息,spring容器通过读取这些bean配置元数据信息来构建和组装我们需要的对象。
7.Spring容器使用步骤
- 引入spring相关的maven配置
- 创建bean配置文件,比如bean xml配置文件
- 在bean xml文件中定义好需要spring容器管理的bean对象
- 创建spring容器,并给容器指定需要装载的bean配置文件,当spring容器启动之后,会加载这些配置文件,然后创建好配置文件中定义好的bean对象,将这些对象放在容器中以供使用
- 通过容器提供的方法获取容器中的对象,然后使用
8.容器创建bean实例有多少种?
- 通过反射调用构造方法创建bean对象
- 通过静态工厂方法创建bean对象
- 通过实例工厂方法创建bean对象
- 通过FactoryBean创建bean对象
通过反射调用构造方法创建bean对象
调用类的构造方法获取对应的bean实例,是使用最多的方式,这种方式只需要在xml bean元素中指定class属性,spring容器内部会自动调用该类型的构造方法来创建bean对象,将其放在容器中以供使用。
通过静态工厂方法创建bean对象
我们可以创建静态工厂,内部提供一些静态方法来生成所需要的对象,将这些静态方法创建的对象交给spring以供使用。
通过实例工厂方法创建bean对象
让spring容器去调用某些对象的某些实例方法来生成bean对象放在容器中以供使用。
通过FactoryBean来创建bean对象
前面我们学过了BeanFactory接口,BeanFactory是spring容器的顶层接口,而这里要说的是FactoryBean,也是一个接口,这两个接口很容易搞混淆,FactoryBean可以让spring容器通过这个接口的实现来创建我们需要的bean对象。
9.说一下 spring 的事务隔离级别?
Spring的事务隔离级别是指在并发环境下,事务之间相互隔离的程度。Spring框架支持多种事务隔离级别,可以根据具体的业务需求来选择适合的隔离级别。以下是常见的事务隔离级别:
- DEFAULT(默认):使用数据库默认的事务隔离级别。通常为数据库的默认隔离级别,如Oracle为READ COMMITTED,MySQL为REPEATABLE READ。
- READ_UNCOMMITTED:最低的隔离级别,允许读取未提交的数据。事务可以读取其他事务未提交的数据,可能会导致脏读、不可重复读和幻读的问题。
- READ_COMMITTED:保证一个事务只能读取到已提交的数据。事务读取的数据是其他事务已经提交的数据,避免了脏读的问题。但可能会出现不可重复读和幻读的问题。
- REPEATABLE_READ:保证一个事务在同一个查询中多次读取的数据是一致的。事务期间,其他事务对数据的修改不可见,避免了脏读和不可重复读的问题。但可能会出现幻读的问题。
- SERIALIZABLE:最高的隔离级别,保证事务串行执行,避免了脏读、不可重复读和幻读的问题。但会降低并发性能,因为事务需要串行执行。
通过@Transactional注解的isolation属性来指定事务隔离级别。
10.说一下Spring的事务传播行为
事务的传播特性指的是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行?
Spring框架提供了多种事务传播行为:
- REQUIRED:如果当前存在事务,则加入该事务,如果当前没有事务,则创建一个新的事务。这是最常用的传播行为,也是默认的,适用于大多数情况。
- REQUIRES_NEW:无论当前是否存在事务,都创建一个新的事务。如果当前存在事务,则将当前事务挂起。适用于需要独立事务执行的场景,不受外部事务的影响。
- SUPPORTS:如果当前存在事务,则加入该事务,如果当前没有事务,则以非事务方式执行。适用于不需要强制事务的场景,可以与其他事务方法共享事务。
- NOT_SUPPORTED:以非事务方式执行,如果当前存在事务,则将当前事务挂起。适用于不需要事务支持的场景,可以在方法执行期间暂时禁用事务。
- MANDATORY:如果当前存在事务,则加入该事务,如果当前没有事务,则抛出异常。适用于必须在事务中执行的场景,如果没有事务则会抛出异常。
- NESTED:如果当前存在事务,则在嵌套事务中执行,如果当前没有事务,则创建一个新的事务。嵌套事务是外部事务的一部分,可以独立提交或回滚。适用于需要在嵌套事务中执行的场景。
- NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。适用于不允许在事务中执行的场景,如果存在事务则会抛出异常。
通过@Transactional注解的propagation属性来指定事务传播行为 。
11.Spring-AOP通知?
Spring切面可以应用5种类型的通知:
- 前置通知:在目标方法被调用之前调用通知功能;
- 后置通知:在目标方法完成之后调用通知,此时不会关心方法的输出是什么;
- 返回通知:在目标方法成功执行之后调用通知;
- 异常通知:在目标方法抛出异常后调用通知;
- 环绕通知:通知包裹了被通知的方法,在被通知的方法调用之前和调用之后执行自定义的行为。
12.spring 自动装配 bean 有哪些方式
- 根据名称自动装配(byName):Spring容器会根据Bean的名称自动将相应的依赖注入到需要的地方。在XML配置中,可以使用autowire="byName"来启用byName自动装配。
- 根据类型自动装配(byType):Spring容器会根据Bean的类型自动将相应的依赖注入到需要的地方。在XML配置中,可以使用autowire="byType"来启用byType自动装配。
- 构造函数自动装配(constructor):Spring容器会根据构造函数的参数类型自动将相应的依赖注入到构造函数中。在XML配置中,可以使用autowire="constructor"来启用构造函数自动装配。
- 自动装配注解(Autowired):通过在需要自动装配的字段、构造函数或方法上使用@Autowired注解,Spring容器会自动将相应的依赖注入到标注了@Autowired的位置。
13.Spring框架中的单例bean是线程安全的吗
在Spring框架中, 由于单例Bean在整个Spring上下文只有一个实例,因此在多线程环境下访问该实例时,需要确保Bean的状态是线程安全的。如果单例Bean的状态是可变的,并且多个线程同时修改该状态,可能会导致线程安全问题。
为了确保单例Bean的线程安全性,可以采取以下几种方式:
- 避免在单例Bean中使用可变的实例变量,或者确保对这些变量的访问是线程安全的,例如使用同步机制(如synchronized关键字)或使用线程安全的数据结构。
- 尽量避免在单例Bean中使用共享的外部资源,如数据库连接、文件等。如果必须使用共享资源,需要确保对这些资源的访问是线程安全的(如使用ThreadLocal等)来保证线程安全。
- 使用无状态的单例Bean。无状态的单例Bean不包含任何实例变量,只包含方法和局部变量,因此不会有线程安全问题。
- 采用多例Bean。将bean的作用域改为"property"即每次使用创建一个新的实例,这样可以有效避免单例共享造成线程不安全。
14.谈谈你对Spring的理解
可以从2个层面理解Spring:
- 首先Spring是一个生态:可以构建企业级应用程序所需的一切基础设施
- 但是,通常Spring指的就是Spring Framework,它有两大核心:
- IOC 和 DI 的支持
Spring 的核心就是一个大的工厂容器,可以维护所有对象的创建和依赖关系,Spring 工厂用于生成 Bean,并且管理 Bean 的生命周期,实现高内聚低耦合的设计理念。
- AOP 编程的支持
Spring 提供了面向切面编程,面向切面编程允许我们将横切关注点从核心业务逻辑中分离出来,实现代码的模块化和重用。可以方便的实现对程序进行权限拦截、运行监控、日志记录等切面功能。
除了这两大核心还提供了丰富的功能和模块, 数据访问、事务管理、Web开发等。数据访问模块提供了对数据库的访问支持,可以方便地进行数据库操作。事务管理模块提供了对事务的管理支持,确保数据的一致性和完整性。Web开发模块则提供了构建Web应用程序的工具和框架,简化了Web开发的过程。
总结一句话:它是一个轻量级、非入侵式的控制反转 (IoC) 和面向切面 (AOP) 的容器框架。
15.SpringMVC的拦截器和过滤器有什么区别?执行顺序?
拦截器和过滤器在Web应用中都扮演着请求和响应处理的角色,但它们之间存在一些关键区别。
首先,归属不同。拦截器是SpringMVC框架的一部分,而过滤器是Servlet规范的一部分。拦截器主要用于对控制器层的请求进行处理,它们提供了更细粒度的控制,可以在请求进入控制器之前和之后执行特定的逻辑,例如身份验证、日志记录和权限检查。过滤器独立于SpringMVC,用于处理通用的请求和响应内容,例如字符编码、压缩和安全性。
其次,执行顺序也不同。拦截器的执行顺序由配置文件中的顺序决定,可以有多个拦截器,它们按照配置的顺序依次执行。而过滤器的执行顺序由web.xml文件中的配置顺序决定,同样可以有多个过滤器,按照配置的顺序执行。一般来说,首先执行过滤器,然后再执行拦截器。
最后,用途不同。拦截器用于对SpringMVC的请求和响应进行特定的业务处理,通常与控制器层的请求处理有关。过滤器用于对所有Servlet请求和响应进行通用性的处理,通常关注请求和响应内容,而不涉及具体的业务逻辑。
总的来说,了解拦截器和过滤器之间的这些区别非常重要。在面试中,这种理解将有助于说明您在Web应用程序中如何处理请求和响应以及如何利用SpringMVC和Servlet规范的不同功能。
16.Spring 框架中都用到了哪些设计模式?
- 简单工厂:
-
- BeanFactory:Spring的BeanFactory充当工厂,负责根据配置信息创建Bean实例。它是一种工厂模式的应用,根据指定的类名或ID创建Bean对象。
- 工厂方法:
-
- FactoryBean:FactoryBean接口允许用户自定义Bean的创建逻辑,实现了工厂方法模式。开发人员可以使用FactoryBean来创建复杂的Bean实例。
- 单例模式:
-
- Bean实例:Spring默认将Bean配置为单例,确保在容器中只有一个共享的实例,这有助于节省资源和提高性能。
- 适配器模式:
-
- SpringMVC中的HandlerAdapter:SpringMVC的HandlerAdapter允许不同类型的处理器适配到处理器接口,以实现统一的处理器调用。这是适配器模式的应用。
- 装饰器模式:
-
- BeanWrapper:Spring的BeanWrapper允许在不修改原始Bean类的情况下添加额外的功能,这是装饰器模式的实际应用。
- 代理模式:
-
- AOP底层:Spring的AOP(面向切面编程)底层通过代理模式来实现切面功能,包括JDK动态代理和CGLIB代理。
- 观察者模式:
-
- Spring的事件监听:Spring的事件监听机制是观察者模式的应用,它允许组件监听和响应特定类型的事件,实现了松耦合的组件通信。
- 策略模式:
-
- excludeFilters、includeFilters:Spring允许使用策略模式来定义包扫描时的过滤策略,如在**@ComponentScan注解中使用的excludeFilters和includeFilters**。
- 模板方法模式:
-
- Spring几乎所有的外接扩展:Spring框架的许多模块和外部扩展都采用模板方法模式,例如JdbcTemplate、HibernateTemplate等。
- 责任链模式:
-
- AOP的方法调用:Spring AOP通过责任链模式实现通知(Advice)的调用,确保通知按顺序执行。
Spring框架的设计哲学是通过这些设计模式来提供强大的功能和可定制性。它的模块化、松耦合的设计使得开发人员能够更轻松地构建可维护、可扩展和灵活的应用程序。这些设计模式的应用有助于实现代码重用、降低开发成本,是Spring框架广受欢迎的原因之一。
17.Spring事件监听的核心机制是什么?
Spring事件监听的核心机制围绕观察者模式展开:
观察者模式: 它允许一个对象(称为主题或被观察者)维护一组依赖于它的对象(称为观察者),并在主题状态发生变化时通知观察者。
它包含三个核心:
- 事件: 事件是观察者模式中的主题状态变化的具体表示,它封装了事件发生时的信息。在Spring中,事件通常是普通的Java对象,用于传递数据或上下文信息。
- 事件发布者: 在Spring中,事件发布者充当主题的角色,负责触发并发布事件。它通常实现了ApplicationEventPublisher接口或使用注解**@Autowired**来获得事件发布功能。
- 事件监听器: 事件监听器充当观察者的角色,负责监听并响应事件的发生。它实现了ApplicationListener接口,通过**onApplicationEvent()**方法来处理事件。
总之,Spring事件监听机制的核心机制是观察者模式,通过事件、事件发布者和事件监听器的协作,实现了松耦合的组件通信,使得应用程序更加灵活和可维护。
18.Spring事务的失效原因?
大部分失效是由于:
- 方法是private也会失效,解决:改成public: Spring的事务代理通常是通过Java动态代理或CGLIB动态代理生成的,这些代理要求目标方法是公开可访问的(public)。私有方法无法被代理,因此事务将无效。解决方法是将目标方法改为public或protected。
- 目标类没有配置为Bean也会失效,解决:配置为Bean: Spring的事务管理需要在Spring容器中配置的Bean上才能生效。如果目标类没有被配置为Spring Bean,那么事务将无法被应用。解决方法是确保目标类被正确配置为Spring Bean。
- 自己捕获了异常,解决:不要捕获处理: Spring事务管理通常依赖于抛出未捕获的运行时异常来触发事务回滚。如果您在方法内部捕获了异常并处理了它,事务将不会回滚。解决方法是让异常在方法内部被抛出,以触发事务回滚。
- 使用CGLIB动态代理,但@Transactional声明在接口上: 默认情况下,Spring的事务代理使用基于接口的JDK动态代理。如果您将**@Transactional注解声明在接口上,而目标类是使用CGLIB代理的,事务将不会生效。解决方法是将@Transactional**注解移到目标类的方法上,或者配置Spring以使用CGLIB代理接口。
- 跨越多个线程的事务管理,解决:使用编程式事务或分布式事务: 如果您的应用程序在多个线程之间共享数据库连接和事务上下文,事务可能会失效,除非适当地配置事务传播属性。
- 事务传播属性或捕获异常等熟悉设置不正确: 事务传播属性定义了事务如何传播到嵌套方法或外部方法。如果事务传播属性设置不正确,可能会导致事务失效或不符合预期的行为。
19.Spring多线程事务 能否保证事务的一致性
在多线程环境下,Spring事务管理默认情况下无法保证全局事务的一致性。这是因为Spring的本地事务管理是基于线程的,每个线程都有自己的独立事务。
- Spring的事务管理通常将事务信息存储在ThreadLocal中,这意味着每个线程只能拥有一个事务。这确保了在单个线程内的数据库操作处于同一个事务中,保证了原子性。
- 可以通过如下方案进行解决:
-
- 编程式事务: 为了在多线程环境中实现事务一致性,您可以使用编程式事务管理。这意味着您需要在代码中显式控制事务的边界和操作,确保在适当的时机提交或回滚事务。
- 分布式事务: 如果您的应用程序需要跨多个资源(例如多个数据库)的全局事务一致性,那么您可能需要使用分布式事务管理(如2PC/3PC TCC等)来管理全局事务。这将确保所有参与的资源都处于相同的全局事务中,以保证一致性。
总之,在多线程环境中,Spring的本地事务管理需要额外的协调和管理才能实现事务一致性。这可以通过编程式事务、分布式事务管理器或二阶段提交等方式来实现,具体取决于您的应用程序需求和复杂性。
20.什么情况下AOP会失效,怎么解决?
大部分失效是由于:
- 内部方法调用: 如果在同一个类中的一个方法调用另一个方法,AOP通知可能不会触发,因为AOP通常是通过代理对象拦截外部方法调用的。解决方式是注入本类对象进行调用, 或者设置暴露当前代理对象到本地线程, 可以通过
AopContext.currentProxy()
拿到当前正在调用的动态代理对象。 - 静态方法: AOP通常无法拦截静态方法的调用,因为静态方法不是通过对象调用的。解决方法是将静态方法调用替换为实例方法调用,或者考虑其他技术来实现横切关注点。
- AOP配置问题: 错误的AOP配置可能导致通知不正确地应用于目标方法,或者在不希望的情况下应用。解决方法是仔细检查AOP配置,确保切点表达式和通知类型正确配置。
- 代理问题: 如果代理对象不正确地创建或配置,AOP通知可能无法生效。解决方法是调试底层源码确保代理对象正确创建,并且AOP通知能够拦截代理对象的方法调用。
21.JDK动态代理和CGLIB动态代理的区别
从性能上特性对比:
JDK动态代理要求目标对象必须实现至少一个接口,因为它基于接口生成代理类。而CGLIB动态代理不依赖于目标对象是否实现接口,可以代理没有实现接口的类,它通过继承或者代理目标对象的父类来实现代理。
从创建代理时的性能对比:
JDK动态代理通常比CGLIB动态代理创建速度更快,因为它不需要生成字节码文件。而CGLIB动态代理的创建速度通常比较慢,因为它需要生成字节码文件。另外,JDK代理生成的代理类较小,占用较少的内存,而CGLIB生成的代理类通常较大,占用更多的内存。
从调用时的性能对比:
JDK动态代理在方法调用时需要通过反射机制来调用目标方法,因此性能略低于CGLIB,尽管JDK动态代理在Java 8中有了性能改进,但CGLIB动态代理仍然具有更高的方法调用性能。CGLIB动态代理在方法调用时不需要通过反射,直接调用目标方法,通常具有更高的方法调用性能,同时无需类型转换。
选择使用JDK动态代理还是CGLIB动态代理取决于具体需求。如果目标对象已经实现了接口,并且您更关注创建性能和内存占用,那么JDK动态代理可能是一个不错的选择。如果目标对象没有实现接口,或者您更关注方法调用性能,那么CGLIB动态代理可能更合适。综上所述,这两种代理方式各有优势,根据实际情况进行选择是明智的, Spring默认情况如果目标类实现了接口用JDK代理否则用CGLIB。 而SpringBoot默认用CGLIB,所以用哪个问题都不大。
22.介绍下SpringAop的底层实现
Spring AOP是Spring框架的一个重要组成部分,用于实现面向切面编程。它通过在方法调用前、调用后或异常抛出时插入通知,允许开发者在核心业务逻辑之外执行横切关注点的代码。
底层实现主要分两部分:创建AOP动态代理和调用代理
在启动Spring会创建AOP动态代理:
首先通过AspectJ解析切点表达式: 在创建代理对象时,Spring AOP使用AspectJ来解析切点表达式。它会根据定义的条件匹配目标Bean的方法。如果Bean不符合切点的条件,将跳过,否则将会**通动态代理包装Bean对象:**具体会根据目标对象是否实现接口来选择使用JDK动态代理或CGLIB代理。这使得AOP可以适用于各种类型的目标对象。
在调用阶段:
- Spring AOP使用责任链模式来管理通知的执行顺序。通知拦截链包括前置通知、后置通知、异常通知、最终通知和环绕通知,它们按照配置的顺序形成链式结构。
- 通知的有序执行: 责任链确保通知按照预期顺序执行。前置通知在目标方法执行前执行,后置通知在目标方法成功执行后执行,异常通知在方法抛出异常时执行,最终通知无论如何都会执行,而环绕通知包裹目标方法,允许在方法执行前后添加额外的行为。
综上所述,Spring AOP在创建启动阶段使用AspectJ解析切点表达式如果匹配使用动态代理,而在调用阶段使用责任链模式确保通知的有序执行。这些机制共同构成了Spring AOP的底层实现。
23.Spring是如何解决Bean的循环依赖?
Spring是如何解决的循环依赖: 采用三级缓存解决的 就是三个Map ; 关键: 一定要有一个缓存保存它的早期对象作为死循环的出口
- 1、一级缓存singletonObjects存放可以使用的单例。
2、二级缓存earlySingletonObjects存放的是早期的bean,即半成品,此时还无法使用。
3、三级缓存singletonFactories是一个对象工厂,用于创建对象并放入二级缓存中。同时,如果对象有Aop代理,则对象工厂返回代理对象。
面试官还可能问:
-
二级缓存能不能解决循环依赖?
-
- 如果只是循环依赖导致的死循环的问题: 一级缓存就可以解决 ,但是解决在并发下获取不完整的Bean。
- 二级缓存完全解决循环依赖: 只是需要在实例化后就创建动态代理,不优化也不符合spring生命周期规范。
-
Spring有没有解决多例Bean的循环依赖?
-
- 多例不会使用缓存进行存储(多例Bean每次使用都需要重新创建)
- 不缓存早期对象就无法解决循环
-
Spring有没有解决构造函数参数Bean的循环依赖?
-
- 构造函数的循环依赖也是会报错
- 可以通过人工进行解决:@Lazy
-
-
- 就不会立即创建依赖的bean了
- 而是等到用到才通过动态代理进行创建
-
24.解释Spring中bean的生命周期
Bean生命周期:指定的就是Bean从创建到销毁的整个过程: 分4大步:
-
实例化
-
- 通过反射去推断构造函数进行实例化
- 实例工厂、 静态工厂
-
依赖注入(DI)
-
- 解析自动装配(byname bytype constractor none @Autowired)
-
初始化
-
- 调用很多Aware回调方法
- 调用BeanPostProcessor.postProcessBeforeInitialization
- 调用生命周期回调初始化方法
- 调用BeanPostProcessor.postProcessAfterInitialization, 如果bean实现aop则会在这里创建动态代理
-
销毁
-
- 在spring容器关闭的时候进行调用
- 调用生命周期回调销毁方法
25.单例bean和单例模式有什么区别
- 定义和用途:
-
- 单例Bean:在Spring框架中,单例Bean是指在整个应用程序中只存在一个实例的Bean对象。单例Bean的作用是共享和复用对象实例,以提高性能和减少资源消耗。
- 单例模式:单例模式是一种设计模式,用于确保一个类只有一个实例,并提供一个全局访问点来获取该实例。单例模式的目的是限制类的实例化次数,以保证全局唯一性和避免资源浪费。
- 实现方式:
-
- 单例Bean:在Spring中,单例Bean的创建和管理由Spring容器负责。Spring容器在启动时会创建单例Bean的实例,并在整个应用程序的生命周期中共享该实例。
- 单例模式:单例模式的实现方式可以有多种,常见的方式包括饿汉式(在类加载时就创建实例)、懒汉式(在第一次使用时创建实例)等。
- 适用范围:
-
- 单例Bean:单例Bean适用于需要共享和复用对象实例的场景,例如数据库连接池、线程池等。
- 单例模式:单例模式适用于需要确保全局唯一性和避免资源浪费的场景,例如配置信息管理、日志记录器等。
总结起来,单例Bean是Spring框架中的概念,用于共享和复用对象实例;
而单例模式是一种设计模式,用于确保一个类只有一个实例。虽然单例Bean使用了单例设计模式,但是它们的实现方式和适用范围有所不同。也不能简单的将单例Bean等同于单例设计模式,因为同一个class可以在容器中存在2个不同name的实例, 这一点不符合单例设计模式的原则。
26.Spring IoC 的实现机制是什么?
Spring的IoC底层实现机制主要依赖于以下几个关键组件和技术:
- 反射:Spring使用Java的反射机制来实现动态创建和管理Bean对象。通过反射,Spring可以在运行时动态地实例化Bean对象、调用Bean的方法和设置属性值。
- 配置元数据:Spring使用配置元数据来描述Bean的定义和依赖关系。配置元数据可以通过XML配置文件、注解和Java代码等方式进行定义。Spring在启动时会解析配置元数据,根据配置信息创建和管理Bean对象。
- Bean定义:Spring使用Bean定义来描述Bean的属性、依赖关系和生命周期等信息。Bean定义可以通过XML配置文件中的元素、注解和Java代码中的@Bean注解等方式进行定义。Bean定义包含了Bean的类名、作用域、构造函数参数、属性值等信息。
- Bean工厂:Spring的Bean工厂负责创建和管理Bean对象。Bean工厂可以是BeanFactory接口的实现,如DefaultListableBeanFactory。Bean工厂负责解析配置元数据,根据Bean定义创建Bean对象,并将其放入容器中进行管理。
- 依赖注入:Spring使用依赖注入来解决Bean之间的依赖关系。通过依赖注入,Spring容器负责将Bean所依赖的其他Bean实例注入到它们之中。Spring使用反射和配置元数据来确定依赖关系,并在运行时进行注入。
总结起来,Spring的IoC底层实现机制主要依赖于反射、配置元数据、Bean定义、Bean工厂和依赖注入等技术和组件。通过这些机制,Spring实现了Bean的创建、配置和管理,以及Bean之间的解耦和依赖注入
27.Bean有哪几种配置方式?
在Spring框架中,有以下几种常见的Bean配置方式:
- XML配置:使用XML文件来配置Bean,通过元素定义Bean的属性和依赖关系。可以使用Spring的XML命名空间和标签来简化配置。
- 注解配置:使用注解来配置Bean,通过在Bean类上添加注解,如@Component、@Service、@Repository等,来标识Bean的角色和作用。
- JavaConfig方式:使用Java类来配置Bean,通过编写一个配置类,使用@Configuration注解标识,然后在方法上使用@Bean注解来定义Bean。
- @Import:@Import注解可以用于导入其他配置类,也可以用于导入其他普通类。当导入的是配置类时,被导入的配置类中定义的Bean会被纳入到当前配置类的上下文中;当导入的是普通类时,被导入的类本身会被当作一个Bean进行注册。
这4种是最常用的,另外还有两种一些冷门的:
- Groovy配置:使用Groovy脚本来配置Bean,通过编写一个Groovy脚本文件,使用Spring的DSL(Domain Specific Language)来定义Bean。
- JSR-330:Spring提供了对JSR-330标准注释(依赖注入)的支持,可以使用@Named 或 @ManagedBean替代@Component,但是需要
javax.inject
依赖。
这些配置方式可以单独使用,也可以结合使用,根据项目需求和个人偏好选择适合的配置方式。
28.BeanFactory 和 ApplicationContext有什么区别?
BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。
BeanFactory:是Spring框架的核心接口之一, 我们可以称之为 “低级容器”。为什么叫低级容器呢?因为Bean的生产过程分为【配置的解析】和【Bean的创建】,而BeanFactory只有Bean的创建功能,但也说明它内存占用更小,在早期会在一些内存受限的可穿戴设备中作为spring容器使用。
ApplicationContext 可以称之为 “高级容器”。因为他比 BeanFactory 多了更多的功能。他继承了多个接口,因此具备了更多的功能。例如配置的读取、解析、扫描等,还加入了 如 事件事件监听机制,及后置处理器让Spring提升了扩展性。所以你看他的名字,已经不是 BeanFactory 之类的工厂了,而是 “应用上下文”, 代表着整个大容器的所有功能。该接口定义了一个 refresh 方法,此方法是所有阅读 Spring 源码的人的最熟悉的方法,用于刷新整个容器,即重新加载/刷新所有的 bean。
29.BeanFactory 和FactoryBean有什么区别?
BeanFactory
是Spring框架的核心接口之一,用于管理和获取Bean对象亦陈为bean的容器。使用了简单工厂模式,提供getBean方法用来获取bean。FactoryBean
是一个bean,但是它是一个特殊的bean。
它是一个接口,他必须被一个bean去实现。
FactoryBean接口定义了两个方法:getObject()
和getObjectType()
。
getObjectType()方法用于返回创建的Bean对象的类型。
getObject()方法用于返回创建的Bean对象,最终该Bean对象会进行注入,MyBatis集成Spring时的那个SqlSessionFactoryBean就实现了FactoryBean, 最终通过getObject()
将SqlSessionFactory注入到Ioc容器中。
所以FactoryBean不是一个普通的Bean,它会表现出工厂模式的样子,可以自定义创建Bean对象的逻辑,可以在创建Bean对象之前进行一些额外的处理。
如果要获得FactoryBean类本身而非getObject()返回的Bean可以通过在BeanName前加“&”进行获取。
所以他们直接没有什么关系, 实在要扯上点关系那就是BeanFactory管理了FactoryBean,
30.Spring-Ioc容器的加载过程
Spring 的 IOC 容器工作的过程,其实可以划分为两个阶段:配置解析阶段和Bean 的创建段。
其中
-
配置解析阶段主要做的工作是加载和解析配置文件,将配置的bean解析成 BeanDefinition。
-
- 整个过程是:
- 读取配置:通过BeanDefinitionReader读取配置文件或配置类
- 解析配置信息:如ComonentScan、Bean配置等
- 扫描类注解:根据ComonentScan扫描@Component、@Bean、@Configuration、@Import等注解…
- 将符合的bean注册为BeanDefinition
-
Bean的创建过程主要做的工作是根据 BeanDefinition创建Bean。
-
- 大概过程:
- 实例化Bean:容器根据配置文件中的Bean定义,实例化Bean对象。可以通过构造函数实例化、工厂方法实例化、静态工厂方法实例化等方式来创建Bean对象。
- 注入Bean属性:容器会为实例化的Bean对象设置属性值,可以通过setter方法注入属性值,也可以通过构造函数注入属性值。
- 处理依赖关系:容器会处理Bean之间的依赖关系,将依赖的Bean注入到需要的地方 。
- 执行初始化方法:容器会调用Bean的初始化方法,可以通过实现InitializingBean接口或在配置文件中指定初始化方法来定义Bean的初始化逻辑。
- 注册Bean:容器会将实例化、属性设置和初始化完成的Bean对象注册到容器中,以便后续的使用和管理。
- 完成加载:容器完成所有Bean的加载和初始化后,即完成了IoC容器的加载过程。此时,可以通过容器调用getBean获取Bean对象。
31.将一个类声明为 Bean 的注解有哪些?
@Component
:通用的注解,可标注任意类为 Spring
组件。如果一个 Bean 不知道属于哪个层,可以使用@Component
注解标注。
@Repository
: 对应持久层即 Dao 层,主要用于数据库相关操作。
@Service
: 对应服务层,主要涉及一些复杂的逻辑,需要用到 Dao 层。
@Controller
: 对应 Spring MVC 控制层,主要用于接受用户请求并调用 Service
层返回数据给前端页面。
32.@Component 和 @Bean 的区别是什么?
@Component
注解作用于类,而@Bean
注解作用于方法。
@Component
通常是通过类路径扫描来自动侦测以及自动装配到 Spring 容器中(我们可以使用 @ComponentScan
注解定义要扫描的路径从中找出标识了需要装配的类自动装配到 Spring 的 bean 容器中)。@Bean
注解通常是我们在标有该注解的方法中定义产生这个 bean,@Bean
告诉了 Spring 这是某个类的实例,当我需要用它的时候还给我。
@Bean
注解比 @Component
注解的自定义性更强,而且很多地方我们只能通过 @Bean
注解来注册 bean。比如当我们引用第三方库中的类需要装配到 Spring
容器时,则只能通过 @Bean
来实现。
33.什么是AOP?
面向切面编程,作为面向对象的一种补充,将公共逻辑(事务管理、日志、缓存等)封装成切面,跟业务代码进行分离,可以减少系统的重复代码和降低模块之间的耦合度。切面就是那些与业务无关,但所有业务模块都会调用的公共逻辑。
34.@Autowired和@Resource的区别?
Autowire是spring的注解。默认情况下@Autowired是按类型匹配的(byType)。如果需要按名称(byName)匹配的话,可以使用@Qualifier注解与@Autowired结合。@Autowired 可以传递一个required=false
的属性,false指明当userDao实例存在就注入不存就忽略,如果为true,就必须注入,若userDao实例不存在,就抛出异常。
Resource是j2ee的注解,默认按 byName模式自动注入。@Resource有两个中重要的属性:name和type。name属性指定bean的名字,type属性则指定bean的类型。因此使用name属性,则按byName模式的自动注入策略,如果使用type属性,则按 byType模式自动注入策略。倘若既不指定name也不指定type属性,Spring容器将通过反射技术默认按byName模式注入。
35.@Component、@Controller、@Repositor和@Service 的区别?
@Component:最普通的组件,可以被注入到spring容器进行管理。
@Controller:将类标记为 Spring Web MVC 控制器。
@Service:将类标记为业务层组件。
@Repository:将类标记为数据访问组件,即DAO组件。
36.@Async注解的原理
当我们调用第三方接口或者方法的时候,我们不需要等待方法返回才去执行其它逻辑,这时如果响应时间过长,就会极大的影响程序的执行效率。所以这时就需要使用异步方法来并行执行我们的逻辑。在springboot中可以使用@Async注解实现异步操作。
37.Spring常用的注入方式有:属性注入, 构造方法注入, set 方法注入
- 构造器注入:利用构造方法的参数注入依赖
- set方法注入:调用setter的方法注入依赖
- 属性注入:在字段上使用@Autowired/Resource注解
38.为什么 Spring和IDEA 都不推荐使用 @Autowired 注解?
@Autowired是属性注入,而且@Autowired默认是按照类型匹配(ByType),因此有可能会出现两个相同的类型bean,进而导致Spring 装配失败。
如果要使用属性注入的话,可以使用 @Resource
代替 @Autowired
注解。@Resource默认是按照名称匹配(ByName),如果找不到则是ByType注入。另外,@Autowired是Spring提供的,@Resource是JSR-250提供的,是Java标准,我们使用的IoC容器会去兼容它,这样即使更换容器,也可以正常工作。