Spring 常见面试题
Spring 八股文
说说你对Spring的理解?
Spring 使创建 Java 企业应用程序变得更加容易。
它提供了在企业环境中接受 Java 语言所需的一切,并支持 Groovy 和 Kotlin 作为 JVM 上的替代语言,并可根据应用程序的需要灵活地创建多种体系结构。 从 Spring Framework 5.0 开始,Spring 需要 JDK8 (Java SE 8+),并且已经为 JDK9 提供了现成的支持。
Spring支持各种应用场景,在大型企业中, 应用程序通常需要运行很长时间,而且必须运行在 jdk 和应用服务器上,这种场景开发人员无法控制其升级周期。 其他可能作为一个单独的 jar 嵌入到服务器去运行,也有可能在云环境中。还有一些可能是不需要服务器的独立应用程序(如批处理或集成的工作任务)。
Spring 是开源的。它拥有一个庞大而且活跃的社区,提供不同范围的,真实用户的持续反馈。这也帮助 Spring 不断地改进,不断发展。
你觉得Spring的核心是什么?
Spring 是一个 IOC 和 AOP 的 容器 框架。
- IoC:控制反转
- AOP:面向切面编程
- 容器:包含并管理应用对象的生命周期
说一下Spring的优势?
- Spring通过DI、AOP和消除样板式代码来简化企业级Java开发。
- Spring框架之外还存在一个构建在核心框架之上的庞大生态圈,它将Spring 扩展到不同的领域,如Web服务、REST、移动开发以及NoSQL。
- 低侵入式设计,代码的污染极低。
- 独立于各种应用服务器,基于Spring框架的应用,可以真正实现Write Once,Run Anywhere的承诺。
- Spring的IoC容器降低了业务对象替换的复杂性,提高了组件之间的解耦。
- Spring的AOP支持允许将一些通用任务如安全、事务、日志等进行集中式处理,从而提供了更好的复用。
- Spring的ORM和DAO提供了与第三方持久层框架的的良好整合,并简化了底层的数据库访问。
- Spring的高度开放性,并不强制应用完全依赖于Spring,开发者可自由选用Spring框架的部分或全部。
Spring是如何简化开发的?
- 基于POJO的轻量级和最小侵入性编程。
- 通过依赖注入和面向接口实现松耦合。
- 基于切面和惯例进行声明式编程。
- 通过切面和模板减少样板式代码。
Spring 核心
Spring支持的bean作用域有哪些?
- singleton:使用该属性定义Bean时,IOC容器仅创建一个Bean实例,IOC容器每次返回的是同一个Bean实例。
- prototype:使用该属性定义Bean时,IOC容器可以创建多个Bean实例,每次返回的都是一个新的实例。
- request:该属性仅对HTTP请求产生作用,使用该属性定义Bean时,每次HTTP请求都会创建一个新的Bean,适用于WebApplicationContext环境。
- session:该属性仅用于HTTP Session,同一个Session共享一个Bean实例,不同Session使用不同的实例。
- global-session:该属性仅用于HTTP Session,同session作用域不同的是,所有的Session共享一个Bean实例。
Spring中的单例bean是线程安全的么?
Spring中的Bean对象默认是单例的,框架并没有对bean进行多线程的封装处理。
如果Bean是有状态的,那么就需要开发人员自己来保证线程安全的保证,最简单的办法就是改变bean的作用域把singleton改成prototype,这样每次请求bean对象就相当于是创建新的对象来保证线程的安全。
Bean有状态就是有数据存储的功能,无状态就是不会存储数据。你想一下,我们的controller,service和dao本身并不是线程安全的,只是调用里面的方法,而且多线程调用一个实例的方法,会在内存中复制遍历,这是自己线程的工作内存,是最安全的。
因此在进行使用的时候,不要在bean中声明任何有状态的实例变量或者类变量,如果必须如此,也推荐大家使用ThreadLocal把变量变成线程私有,如果bean的实例变量或者类变量需要在多个线程之间共享,那么就只能使用synchronized,lock,cas等这些实现线程同步的方法了。
Spring中bean的自动装配有哪些方式?
bean的自动装配指的是,bean的属性值在进行注入的时候,通过某种特定的规则和方式去容器中查找,并设置到具体的对象属性中,主要有五种方式:
- no:缺省情况下,自动配置是通过“ref”属性手动设定,在项目中最常用
- byName:根据属性名称自动装配。如果一个bean的名称和其他bean属性的名称是一样的,将会自装配它。
- byType:按数据类型自动装配,如果bean的数据类型是用其它bean属性的数据类型,兼容并自动装配它。
- constructor:在构造函数参数的byType方式。
- autodetect:如果找到默认的构造函数,使用“自动装配用构造”; 否则,使用“按类型自动装配”。
说说你对Spring IOC的理解?
总:
控制反转:原来的对象是由使用者来进行控制,有了Spring之后,可以把整个对象交给Spring来帮我们进行管理。
DI:依赖注入,把对应的属性的值注入到具体的对象中,@Autowired,populateBean完成属性值的注入。
容器:存储对象,使用map结构来存储,在Spring中一般存在三级缓存,singletonObjects存放完整的bean对象,整个bean的生命周期,从创建到使用到注销的过程全部都是由容器来管理(bean的生命周期)。
分:
- 一般聊IOC容器的时候要涉及到容器的创建过程(BeanFactory、DefaultListableBeanFactory),向bean工厂中设置一些参数(BeanPostProcessor,Aware接口的子类)等等属性。
- 加载并解析bean对象,准备要创建的bean对象的定义对象BeanDefinition(xml或者注解的解析过程)。
- BeanFactoryPostProcessor的处理,此处是扩展点,PlaceholderConfigurerSupport,ConfigurationClassPostProcessor。
- BeanPostProcessor的注册功能,方便后续对bean对象完成具体的扩展功能。
- 通过反射的方式将BeanDefinition对象实例化成具体的bean对象。
- bean对象的初始化过程(填充属性,调用aware子类的方法,调用BeanPostProcessor前置处理方法,调用init-method方法,调用BeanPostProcessor的后置处理方法)。
- 生成完整的bean对象,通过getBean方法可以直接获取。
- 销毁过程。
面试官,这是我对ioc的整体理解,包含了一些详细的处理过程,您看一下有什么问题,可以指点我一下,您有什么想问的?
具体的细节我记不太清了,但是Spring中的bean都是通过反射的方式生成的,同时其中包含了很多的扩展点,比如最常用的对BeanFactory的扩展,对bean的扩展(对占位符的处理),我们在公司对这方面的使用是比较多的,除此之外,ioc中最核心的也就是填充具体bean的属性和生命周期。
谈一下Spring IOC的底层实现?
底层实现:工作原理、实现过程、数据结构、流程、设计思想、设计模式
你对他的理解和你了解的实现过程
反射、工厂、设计模式、关键的几个方法。。。
createBeanFactory,getBean,doGetBean,createBean,doCreateBean,createBeanInstance (getDeclaredConstructor,newInstance),populateBean,initializingBean
- 先通过createBeanFactory创建出一个Bean工厂(DefaultListableBeanFactory)。
- 开始循环创建对象,因为容器中的bean默认都是单例的,所以优先通过getBean,doGetBean从容器中查找。
- 找不到的话,通过createBean,doCreateBean方法,以反射的方式创建对象。一般情况下使用的是无参的构造方法(getDeclaredConstructor,newInstance)。
- 进行对象的属性填充populateBean。
- 进行其他的初始化操作(initializingBean)。
描述一下bean的生命周期?
背图:记住图中的流程。在表述的时候不要只说图中有的关键点,要学会扩展描述。
- 实例化bean:通过反射的方式进行对象的创建,此时创建的只是在堆空间中申请空间,属性都是默认值。
- 设置对象属性:给对象中的属性进行值的设置工作,populateBean(),循环依赖的问题(三级缓存)。
- 检查Aware相关接口并设置相关依赖:如果对象中需要引用容器内部的对象,那么需要调用aware接口的子类方法来进行统一设置。invokeAwareMethod(完成BeanName、BeanFactory、BeanClassLoader对象的属性设置)。
- BeanPostProcessor的前置处理:对生成的bean对象进行前置的处理工作,使用比较多的有(ApplicationContextPostProcessor,设置ApplicationContext,Environment,ResourceLoader,EmbeddValueResolver等对象)。
- 检查是否是InitializingBean的子类来决定是否调用afterPropertiesSet方法:判断当前bean对象是否设置了InitializingBean接口,然后进行属性的设置等基本工作。
- 检查是否配置有自定义的init-method方法:如果当前bean对象定义了初始化方法,那么在此处调用初始化方法。
- BeanPostProcessor的后置处理:对生成的bean对象进行后置的处理工作,spring的aop就是在此处实现的。
- 注册必要的Destruction相关的回调接口:为了方便对象的销毁,在此处调用注销的回调接口,方便对象进行销毁操作。
- 获取并使用bean对象:通过容器来获取对象并进行使用。
- 是否实现DisposableBean接口:判断是否实现了DisposableBean接口,并调用具体的方法来进行对象的销毁工作。
- 是否配置有自定义的destroy方法:如果当前bean对象定义了销毁方法,那么在此处调用销毁方法。
Spring是如何解决循环依赖问题的?
三级缓存,提前暴露对象,aop。总:什么是循环依赖问题,A依赖B,B依赖A。分:先说明bean的创建过程,实例化、初始化(填充属性)。
- 先创建A对象,实例化A对象,此时A对象中的b属性为空,填充属性b。
- 从容器中查找B对象,如果找到了,直接赋值不存在循环依赖的问题(不通),找不到直接创建B对象。
- 实例化B对象,此时B对象中a的属性为空,填充属性a。
- 从容器中查找A对象,找不到,直接创建。
形成闭环的原因
此时,如果仔细琢磨的话,会发现A对象是存在的,只不过此时的A对象不是一个完成的状态,只完成了实例化但是未完成初始化,如果程序在调用过程中,拥有了某个对象的引用,能否在后期给他完成赋值操作,可以优先把非完整状态的对象优先赋值,等待后续操作来完成赋值,相当于提前暴露了某个不完整对象的引用,所以解决问题的核心在于实例化和初始化分开操作,这也是解决循环依赖问题的关键。
当所有的对象都完成实例化和初始化操作之后,还要把完整对象放到容器中,此时在容器中存在对象的几个状态:完成实例化-但未完成初始化、完整状态,因为都在容器中,所以要使用不同的map结构来进行存储,此时就有了一级缓存和二级缓存,如果一级缓存中有了,那么二级缓存中就不会存在同名的对象,因为他们的查找顺序是1,2,3这样的方式来查找的。一级缓存中放的是完整对象,二级缓存中放的是非完整对象。
为什么需要三级缓存?
三级缓存的value类型是ObjectFactory (Map<String, ObjectFactory<?>>),是一个函数式接口,存在的意义是保证在整个容器的运行过程中同名的bean对象只能有一个。
如果一个对象需要被代理,或者说需要生成代理对象,那么要不要优先生成一个普通对象?要
普通对象和代理对象是不能同时出现在容器中的,因此当一个对象需要被代理的时候,就要使用代理对象覆盖掉之前的普通对象,在实际的调用过程中,是没有办法确定什么时候对象被使用的,所以就要求当某个对象被调用的时候,优先判断此对象是否需要被代理,类似于一种回调机制的实现,因此传入lambda表达式的时候,可以通过lambda表达式来执行对象的覆盖过程,具体方法为getEarlyBeanReference(Object bean, String beanName);。
因此,所有的bean对象在创建的时候都要优先放到三级缓存中,在后续的使用过程中,如果需要被代理则返回代理对象,如果不需要被代理,则直接返回普通对象。
缓存的放置时间和删除时间?
- 三级缓存:createBeanInstance之后,核心方法 addSingletonFactory。
- 二级缓存:第一次从三级缓存确认对象是代理对象还是普通对象的时候,同时删除三级缓存,核心方法 getSingleton。
- 一级缓存:生成完整对象之后放到一级缓存,删除二三级缓存,核心方法 addSingleton。
Spring的AOP的底层原理实现?
动态代理aop是ioc的一个扩展功能,现有的ioc,再有的aop,只是在ioc整个流程中新增的一个扩展点而已:BeanPostProcessor后置处理。
- 总:aop概念、应用场景、动态代理
- 分:bean的创建过程中有一个步骤可以对bean进行扩展实现,aop本身就是一个扩展功能,所以在BeanPostProcessor的后置处理方法中来进行实现:
- 代理对象的创建过程(advice,切面,切点)。
- 通过jdk或者cglib的方式来生成代理对象。
- 在执行方法调用的时候,会调用到生成的字节码文件中,直接会找到DynamicAdvisoredInterceptor类中的intercept方法,从此方法开始执行。
- 根据之前定义好的通知来生成拦截器链。
- 从拦截器链中依次获取每一个通知开始进行执行,在执行过程中,为了方便找到下一个通知是哪个,会有一个CglibMethodInvocation的对象,找的时候是从-1的位置以此开始查找并执行的。
八股文
AOP全称叫做 Aspect Oriented Programming 面向切面编程。它是为解耦而生的,解耦是程序员编码开发过程中一直追求的境界,AOP在业务类的隔离上,绝对是做到了解耦,在这里面有几个核心的概念:
-
切面(Aspect): 指关注点模块化,这个关注点可能会横切多个对象。事务管理是企业级Java应用中有关横切关注点的例子。 在Spring AOP中,切面可以使用通用类基于模式的方式(schema-based approach)或者在普通类中以
@Aspect
注解(@AspectJ 注解方式)来实现。
-
连接点(Join point): 在程序执行过程中某个特定的点,例如某个方法调用的时间点或者处理异常的时间点。在Spring AOP中,一个连接点总是代表一个方法的执行。
-
通知(Advice): 在切面的某个特定的连接点上执行的动作。通知有多种类型,包括“around”, “before” and “after”等等。通知的类型将在后面的章节进行讨论。 许多AOP框架,包括Spring在内,都是以拦截器做通知模型的,并维护着一个以连接点为中心的拦截器链。
-
切点(Pointcut): 匹配连接点的断言。通知和切点表达式相关联,并在满足这个切点的连接点上运行(例如,当执行某个特定名称的方法时)。切点表达式如何和连接点匹配是AOP的核心:Spring默认使用AspectJ切点语义。
-
引入(Introduction): 声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被通知的对象上。例如,可以使用引入来使bean实现
IsModified
接口, 以便简化缓存机制(在AspectJ社区,引入也被称为内部类型声明(inter))。
-
目标对象(Target object): 被一个或者多个切面所通知的对象。也被称作被通知(advised)对象。既然Spring AOP是通过运行时代理实现的,那么这个对象永远是一个被代理(proxied)的对象。
-
AOP代理(AOP proxy):AOP框架创建的对象,用来实现切面契约(aspect contract)(包括通知方法执行等功能)。在Spring中,AOP代理可以是JDK动态代理或CGLIB代理。
-
织入(Weaving): 把切面连接到其它的应用程序类型或者对象上,并创建一个被被通知的对象的过程。这个过程可以在编译时(例如使用AspectJ编译器)、类加载时或运行时中完成。 Spring和其他纯Java AOP框架一样,是在运行时完成织入的。
这些概念都太学术了,如果更简单的解释呢,其实非常简单:
任何一个系统都是由不同的组件组成的,每个组件负责一块特定的功能,当然会存在很多组件是跟业务无关的,例如日志、事务、权限等核心服务组件,这些核心服务组件经常融入到具体的业务逻辑中,如果我们为每一个具体业务逻辑操作都添加这样的代码,很明显代码冗余太多,因此我们需要将这些公共的代码逻辑抽象出来变成一个切面,然后注入到目标对象(具体业务)中去,AOP正是基于这样的一个思路实现的,通过动态代理的方式,将需要注入切面的对象进行代理,在进行调用的时候,将公共的逻辑直接添加进去,而不需要修改原有业务的逻辑代码,只需要在原来的业务逻辑基础之上做一些增强功能即可。
BeanFactory与FactoryBean有什么区别?
- 相同点:都是用来创建bean对象的。
- 不同点:使用BeanFactory创建对象的时候,必须要严格遵循bean的生命周期的流程,太复杂了,如果想要简单的自定义某个对象的创建,同时创建完成的对象想交给Spring来管理,那么就需要实现FactoryBean接口了。
public interface FactoryBean<T> {...// 自定义创建对象的过程(new、反射、动态代理)@NullableT getObject() throws Exception;// 获取返回对象的类型@NullableClass<?> getObjectType();// 是否是单例对象default boolean isSingleton() {return true;}}
BeanFactory和ApplicationContext有什么区别?
相同点:
- Spring提供了两种不同的IOC容器,一个是BeanFactory,另外一个是ApplicationContext,它们都是Java interface,ApplicationContext继承于BeanFactory(ApplicationContext继承ListableBeanFactory。
- 它们都可以用来配置XML属性,也支持属性的自动注入。
- 而ListableBeanFactory继承BeanFactory,BeanFactory和ApplicationContext 都提供了一种方式,使用getBean("beanName")获取bean。
不同点:
- 当你调用getBean()方法时,BeanFactory仅实例化bean,而ApplicationContext 在启动容器的时候实例化单例bean,不会等待调用getBean()方法时再实例化。
- BeanFactory不支持国际化,即i18n,但ApplicationContext提供了对它的支持。
- BeanFactory与ApplicationContext之间的另一个区别是能够将事件发布到注册为监听器的bean。
- BeanFactory 的一个核心实现是XMLBeanFactory 而ApplicationContext 的一个核心实现是ClassPathXmlApplicationContext,Web容器的环境我们使用WebApplicationContext并且增加了getServletContext 方法。
- 如果使用自动注入并使用BeanFactory,则需要使用API注册AutoWiredBeanPostProcessor,如果使用ApplicationContext,则可以使用XML进行配置。
- 简而言之,BeanFactory提供基本的IOC和DI功能,而ApplicationContext提供高级功能,BeanFactory可用于测试和非生产使用,但ApplicationContext是功能更丰富的容器实现,应该优于BeanFactory。
Spring中使用了哪些设计模式及应用场景?
- 工厂模式,在各种BeanFactory以及ApplicationContext创建中都用到了。
- 模版模式,在各种BeanFactory以及ApplicationContext实现中也都用到了。
- 代理模式,Spring AOP 利用了 AspectJ AOP实现的! AspectJ AOP 的底层用了动态代理。
- 策略模式,加载资源文件的方式,使用了不同的方法,比如:ClassPathResourece,FileSystemResource,ServletContextResource,UrlResource但他们都有共同的借口Resource;在Aop的实现中,采用了两种不同的方式,JDK动态代理和CGLIB代理。
- 单例模式,比如在创建bean的时候
- 观察者模式,spring中的ApplicationEvent,ApplicationListener,ApplicationEventPublisher。
- 适配器模式,MethodBeforeAdviceAdapter,ThrowsAdviceAdapter,AfterReturningAdapter。
- 装饰者模式,源码中类型带Wrapper或者Decorator的都是。
Spring 事务
Spring事务的实现方式及原理是什么?
在使用Spring框架的时候,可以有两种事务的实现方式,一种是编程式事务,有用户自己通过代码来控制事务的处理逻辑;还有一种是声明式事务,即通过@Transactional注解来实现。
其实事务的操作本来应该是由数据库来进行控制,但是为了方便用户进行业务逻辑的操作,spring对事务功能进行了扩展实现,一般我们很少会用编程式事务,更多的是通过添加@Transactional注解来进行实现,当添加此注解之后事务的自动功能就会关闭,由spring框架来帮助进行控制。
其实事务操作是AOP的一个核心体现,当一个方法添加@Transactional注解之后,spring会基于这个类生成一个代理对象,会将这个代理对象作为bean,当使用这个代理对象的方法的时候,如果有事务处理,那么会先把事务的自动提交给关闭,然后去执行具体的业务逻辑,如果执行逻辑没有出现异常,那么代理逻辑就会直接提交,如果出现任何异常情况,那么直接进行回滚操作,当然用户可以控制对哪些异常进行回滚操作。
TransactionInterceptor
Spring事务的隔离级别有哪些?
Spring中的事务隔离级别就是数据库的隔离级别,有以下几种:
- read uncommitted:读未提交
- read committed:读已提交
- repeatable read:可重复读
- serializable:串行化
在进行配置的时候,如果数据库和Spring代码中的隔离级别不同,那么以Spring的配置为主。
Spring事务的传播机制是什么?
多个事务方法相互调用时,事务如何在这些方法之间进行传播,spring中提供了7中不同的传播特性,来保证事务的正常执行:
- REQUIRED:默认的传播特性,如果当前没有事务,则新建一个事务,如果当前存在事务,则加入这个事务。
- SUPPORTS:当前存在事务,则加入当前事务,如果当前没有事务,则以非事务的方式执行。
- MANDATORY:当前存在事务,则加入当前事务,如果当前事务不存在,则抛出异常。
- REQUIRED_NEW:创建一个新事务,如果存在当前事务,则挂起该事务。
- NOT_SUPPORTED:以非事务方式执行,如果存在当前事务,则挂起当前事务。
- NEVER:不使用事务,如果当前事务存在,则抛出异常。
- NESTED:如果当前事务存在,则在嵌套事务中执行,否则REQUIRED的操作一样。
NESTED和REQUIRED_NEW的区别:
REQUIRED_NEW是新建一个事务并且新开始的这个事务与原有事务无关,而NESTED则是当前存在事务时会开启一个嵌套事务,在NESTED情况下,父事务回滚时,子事务也会回滚,而REQUIRED_NEW情况下,原有事务回滚,不会影响新开启的事务。
NESTED和REQUIRED的区别:
REQUIRED情况下,调用方存在事务时,则被调用方和调用方使用同一个事务,那么被调用方出现异常时,由于共用一个事务,所以无论是否catch异常,事务都会回滚,而在NESTED情况下,被调用方发生异常时,调用方可以catch其异常,这样只有子事务回滚,父事务不会回滚。
某一个事务嵌套另一个事务的时候怎么办?A方法调用B方法,AB方法都有事务,并且传播特性不同,那么A如果有异常,B怎么办,B如果有异常,A怎么办?
总事务的传播特性指的是不同方法的嵌套调用过程中,事务应该如何进行处理,是用同一个事务还是不同的事务,当出现异常的时候会回滚还是提交,两个方法之间的相关影响,在日常工作中,使用的比较多的是 required,requires_new,nested
分
- 先说事务的不同分类,可以分为三类:支持当前事务、不支持当前事务、嵌套事务
- 如果外层方法是required,内层方法是
- 如果外层方法是requires_new,内层方法是,required,requires_new,nested
- 如果外层方法是nested,内层方法是,required,requires_new,nested
Spring事务是如何回滚的?
Spring的事务管理是如何实现的?
总:
Spring的事务管理是通过aop来实现的,首先要生成具体的代理对象,然后按照aop的整套流程来执行具体的操作逻辑,正常情况下要通过通知来完成核心功能,但是事务不是通过通知来实现的,而是通过一个TransactionInterceptor来实现的,然后调用invoke来实现具体的逻辑。
分:
- 先做准备工作,解析各个方法上事务相关的属性,根据具体的属性来判断是否开始新事务。
- 当需要开启的时候,获取数据库连接,关闭自动提交功能,开启事务。
- 执行具体的sql逻辑操作。
- 在操作过程中,如果执行失败了,那么会通过completeTransactionAfterThrowing来完成事务的回滚操作,回滚的具体逻辑是通过doRollback方法来实现的,实现的时候也是要先获取连接对象,通过连接对象来进行回滚。
- 如果在执行过程中,没有任何意外情况的发生,那么通过commitTransactionAfterReturning来完成事务的提交操作,提交的具体逻辑是通过doCommit方法来实现的,实现的时候也是要获取连接,通过连接对象来提交。
- 当事务执行完毕之后需要清除相关的事务信息cleanupTransactionInfo,如果要聊的更细致的话,需要知道TransactionInfo,TransactionStatus。
Spring事务什么时候会失效?
- bean对象没有被Spring容器管理。
- 方法的访问修饰符不是public。
- 自身调用的问题。
- 数据源没有配置事务管理器。
- 数据库不支持事务。
- 异常类型错误或者配置错误。
总结
- 面试之前一定要调整好心态,不管你会多少东西,干就完了,出去面试就一个心态,老子天下第一,让自己超常发挥。
- 得失心不要太重,全中国企业很多,好公司也有很多,没必要在一棵树上吊死,你可以有心仪的公司,留到最后,等你准备充分再去。
- 找工作永远不可能准备好,很多人怂,心态不好,不敢出去面试。先按照你的技术储备尝试一些公司(我就是来试水的),面试回来之后做总结,做好准备,不断总结,复盘,这样才能成长。