51工具盒子

依楼听风雨
笑看云卷云舒,淡观潮起潮落

Spring常见面试问题整理

1.spring是什么?它的优点是什么?

  1. Spring是一个轻量级的ioc和aop容器框架。目的是为了解决企业级应用开发的业务逻辑层和其他各层的耦合问题以及应用开发的复杂性,简化java开发

  2. 优点:

  • Spring属于低侵入设计,代码的污染极低

  • spring的DI机制将对象之间的依赖关系交由框架处理,减低组件的耦合性

  • Spring提供了AOP技术,支持将一些通用的任务,如安全,事务,日志,权限等进行集中式管理,从而提供更好的复用

  • Spring对于主流的应用框架提供了集成支持

2.spring有哪些模块?

  1. spring Context: 继承BeanFactory,提供上下文信息,扩展出JNDI,EJB,电子邮件,国际化等功能

  2. Spring Core: 框架的最基础部分,提供IOC容器,对bean 进行管理,它的主要的组件就是BeanFactory,是工厂模式的实现

  3. Spring AOP:集成了所有AOP功能,减弱代码的功能耦 合,清晰的被分离开

  4. Spring web:提供了基本的面向web的综合特性,提供对常见框架的支持,spring能管理这些框架,将spring对资源注入给框架,也能在这些框架的前后插入拦截器

  5. Spring MVC: 提供面向web应用的Model-View-Controller

  6. Spring DAO:提供了JDBC的抽象层,还提供了声明性事务管理方法

  7. Spring ORM:提供了JPA,JDO,Hibernate,Mybatis等ORM映射层

3.spring用到哪些设计模式?

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

  2. 单例模式:Bean默认采用单例方式,减少了对象的创建,从而减少了内存的消耗

  3. 策略模式:Resource的实现类,针对不同的资源文件, 实现了不同方式的资源获取策略

  4. 代理模式:Spring的AOP功能用到了JAVA的动态代理 以及CGlib动态代理

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

  • java动态代理:解决代码重复、混杂。基于接口实现,无法实现非接口方法

  • Cglib动态代理:代理者是被代理者的子类。优点:解决代码重复、混杂,由于是基于继承实现,可实现非接口方法。缺点:需要导包,相对java代理性能较低

4.springIOC的理解?-------------------PS:面试被问 +2

  1. IOC就是控制反转,把创建对象的控制权转交给spring框架进行管理,并由spring根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松耦合,也利于功能的复用,DI依赖注入,和控制反转是同一个概念的不同角度的描述,即应用程序在运行时依赖ioc容器来动态注入对象需要的外部依赖

  2. 最直观的表达就是,以前创建对象的主动权和时机都是自己把控的,ioc让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法

  3. Spring的ioc有三种方式注入:构造器注入,setter方法注入,根据注解注入

5.springAOP的理解?-----------------PS:面试被问 +3

  1. AOP一般被称为面向切面编程,作为面向对象的一种补充,用于将那些与业务无关,但却对多个对象产生影响的公共行为和逻辑,抽取并封装为一个可重用的模块,这个模块被命名为切面(Aspect),减少系统中的重复代码,降低了模块间的耦合度,提高系统的可维护性,可用于权限认证,日志,事务处理

  2. AOP的实现关键在于代理模式,AOP代理主要分为静态代理和动态代理,静态代理的代表为AspectJ,动态代理则以spring AOP为代表

  3. springAOP中的动态代理有两种情况:JDK代理和CGLIB代理,有接口的情况使用JDK动态代理,没有接口的情况,使用CGLIB动态代理

  4. 总结:将程序中的交叉业务逻辑(比如:事务,安全,日志等),封装成一个切面,然后注入到目标对象即具体业务中,AOP可以对某个对象或者某些对象的功能进行增强,比如对象中的方法进行增强,可以在执行某个方法之前或之后做一些额外的事情。

PS:简单来说,AspectJ是实现AOP的一种技术。AspectJ是一个java实现的AOP框架,它能够对java代码进行AOP编译(一般在编译期进行),让java代码具有AspectJ的AOP功能(当然需要特殊的编译器),可以这样说AspectJ是目前实现AOP框架中最成熟,功能最丰富的语言,更幸运的是,AspectJ与java程序完全兼容,几乎是无缝关联,因此对于有java编程基础的工程师,上手和使用都非常容易

6.SpringAOP里面几个名词的概念?

  1. 切面(Aspect):共有功能的实现,如权限切面,日志切面等。通俗理解:在实际开发中通常用一个存放共有功能实现的标准java类,当java类使用了@Aspect注解修饰时,就能被AOP容器识别为切面

  2. 通知(Advice):属于切面的具体实现,就是要给目标对象织入事情。通俗理解:在实际开发中通常是切面类中的一个方法,具体属于哪类通知,通过方法上的注解区分

  3. 连接点(JoinPoint):程序在运行过程中能够插入切面的点。通俗理解:一个类的所有方法前,后,抛出异常时等都是连接点

  4. 切入点(Pointcut):用于定义通知应该切入到哪些点上,不同的通知通常需要切入到不同的连接点上,这种精准的匹配是由切入点的正则表达式来定义的。通俗理解:切入点就是来定义哪些类里面的哪些方法会得到通知

  5. 目标对象(Target):那些即将切入切面的对象,也就是那些被通知的对象。这些对象专注业务本身的逻辑,所有的共有功能等待AOP容器的切入

  6. 织入(Weaving):将切面应用到目标对象从而创建一个新的代理对象的过程。这个过程可以发生在编译时、类加载时、运行时。Spring是在运行时完成织入,运行时织入通过Java语言的反射机制与动态代理机制来动态实现

  7. 代理对象(Proxy):将通知应用到目标对象之后被动态创建的对象。可以简单地理解为,代理对象的功能等于目标对象本身业务逻辑加上共有功能。代理对象对于使用者而言是透明的,是程序运行过程中的产物。目标对象被织入共有功能后产生的对象


8.Spring通知(Advice)有哪些类型?------------PS:面试被问 +1

  1. 前置通知(Before Advice):在目标方法执行之前执行 的通知

  2. 后置通知(After Advice):在目标方法执行之后执行 的通知

  3. 环绕通知(Around Advice):在目标方法执行之前和之 后都可以执行额外代码的通知

  4. 返回后通知(AfterReturning Advice):是在目标方法 执行之后执行的通知

  5. 抛出异常后通知(AfterThrowing advice):在目标方法 抛出异常时执行的通知

9.Spring容器的启动流程?

  1. 初始化Spring容器,调用工厂后处理器,然后注册

  2. 然后初始化消息源,初始化应用上下文事件广播器

  3. 初始化其他特殊的Bean,注册事件监听器,初始化所有单实例的 Bean

  4. 调用refresh()方法刷新容器

PS:大致就是:初始化Spring容器,注册内置的BeanPostProcessor的BeanDefinition到容器中,将配置类的BeanDefinition注册到容器中,调用refresh()方法刷新容器

10.Beanfactory和applicationcontext有什么区别?

  1. BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器(看底层源码可以知道,ApplicationContext是BeanFactory的子类)

  2. 当我们使用ApplicationContext去获取bean的时候,在加载XXX.xml的时候,会创建所有的配置单实例bean

  3. 当我们使用BeanFactory去获取Bean的时候,我们只是实例化了该容器,而该容器中的bean并没有被实例化。当我们getBean的时候,才会实时实例化该bean对象

  4. BeanFactory和ApplicationContext都支持BeanPostProcessor、BeanFactoryPostProcessor的使用,但两者之间的区别是:BeanFactory需要手动注册,而 ApplicationContext则是自动注册

  5. 区别总结:ApplicationContext启动后预载入所有的单实例Bean,所以在运行的时候速度比较快,因为它们已经创建好了。相对于BeanFactory,ApplicationContext 唯一的不足是占用内存空间,当应用程序配置Bean较多时,程序启动较慢

11.Spring Bean的生命周期?------------PS:面试被问 +1

  1. 简单来说spring bean只有四个阶段:实例化-属性赋值-初始化-销毁

  2. 但是具体来说,spring bean的生命周期包含如下:PS:上图为bean的完全生命周期

  • 实例化bean:

    • 对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean时,或初始化bean的时候需要注入另一个尚未初始化的依赖时,容器就会调用createBean进行实例化。

    • 对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean

  • 设置对象属性(依赖注入):

    • 实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息 以及 通过BeanWrapper提供的设置属性的接口完成属性设置与依赖注入
  • 处理Aware接口:

    • Spring会检测该对象是否实现了xxxAware接口,通过Aware类型的接口,可以让我们拿到Spring容器的一些资源:

    • 如果这个Bean实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,传入Bean的名字

    • 如果这个Bean实现了BeanClassLoaderAware接口,调用setBeanClassLoader()方法,传入ClassLoader对象的实例

    • 如果这个Bean实现了BeanFactoryAware接口,会调用它实现的setBeanFactory()方法,传递的是Spring工厂自身

    • 如果这个Bean实现了ApplicationContextAware接口,会调用setApplicationContext(ApplicationContext)传入Spring上下文

  • BeanPostProcessor前置处理:

    • 如果想对Bean进行一些自定义的前置处理,那么可以让Bean实现了BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj, String s)方法,去执行一些在实例化这个Bean之前的操作。
  • InitializingBean:

    • 如果Bean实现了InitializingBean接口,执行afeterPropertiesSet()方法。就是在实例化Bean的过程中的一些操作。可以用来定制初始化逻辑!
  • init-method:

    • 如果Bean在Spring配置文件中配置了 init-method 属性,则会自动调用其配置的初始化方法。注解配置:@PostConstruct
  • BeanPostProcessor后置处理:

    • 如果这个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj, String s)方法;由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;PS:到这里bean就已经正式创建好了
  • DisposableBean:

    • 当Bean不再需要时,会经过清理阶段,如果Bean实现了DisposableBean这个接口,会调用其实现的destroy()方法
  • destroy-method:

    • 最后,如果这个Bean的Spring配置中配置了destroy-method属性,会自动调用其配置的销毁方法。注解配置:@PreDestroy

12.Spring中bean的作用域?------------------PS:面试被问 +1

  1. Singleton:当一个bean的作用域为Singleton,那么Spring IoC容器中只会存在一个共享的bean实例,并且所有对 bean的请求,只要id与该bean定义相匹配,则只会返回 bean的同一实例。Singleton是单例类型,就是在创建起容 器时就同时自动创建了一个bean的对象,不管你是否使 用,他都存在了,每次获取到的对象都是同一个对象。注 意,Singleton作用域是Spring中的缺省作用域

  2. Prototype:当一个bean的作用域为Prototype,表示一个bean定义对应多个对象实例。Prototype作用域的bean会导致在每次对该bean请求(将其注入到另一个bean中,或者以程序的方式调用容器的getBean()方法)时都会创建一个新的bean实例。Prototype是原型类型,它在我们创建容器的时候并没有实例化,而是当我们获取bean的时候才会去创建一个对象,而且我们每次获取到的对象都不是同一个对象。根据经验,对有状态的bean应该使用prototype作用域,而对无状态的bean则应该使用singleton作用域

PS:主要的两个就是上面的两个,其它三个可看图了解,其实还有几个,用的不多。所以也就不归纳了。可以百度了解

13.Spring框架中的Bean是线程安全的么?如果线程不安全,那么如何处理?

  1. Spring容器本身并没有提供Bean的线程安全策略,因此可以说Spring容器中的Bean本身不具备线程安全的特性,但是具体情况还是要结合Bean的作用域来讨论:
  • 对于prototype作用域的Bean,每次都创建一个新对象,也就是线程之间不存在Bean共享,因此不会有线程安全问题

  • 对于singleton作用域的Bean,所有的线程都共享一个单例实例的Bean,因此是存在线程安全问题的。但是如果单例Bean是一个无状态Bean,也就是线程中的操作不会对Bean的成员执行查询以外的操作,那么这个单例Bean是线程安全的

    • 有状态Bean(Stateful Bean) :就是有实例变量的对象,可以保存数据,是非线程安全的

    • 无状态Bean(Stateless Bean):就是没有实例变量的对象,不能保存数据,是不变类,是线程安全的

  • 现在使用的MVC开发中,一般不会存在线程安全的问题,因为把有状态bean和无状态bean区分开来了。

    • 但是如果非要去弄这么一个问题,我们可以采用将Bean的作用域改为prototype,这样就做到了线程之间不共享对象了,即没有线程安全的问题了。还可以使用ThreadLocal来解决问题,ThreadLocal为每个线程保存了一个副本变量,每个线程操作的都是这个副本变量。

14.Spring基于xml注入bean的几种方式?

  1. 有三种

  2. set注入--构造方法注入--工厂注入(静态工厂注入和实例工厂注入) PS:本身我这里有这三个注入方法的代码解释图,因为不能复制粘贴图片所以我也就不贴了,因为也不是什么难题

15.Spring如何解决循环依赖问题?

1.先了解一下spring中循环依赖的三种情况

  • 构造器注入形成的循环依赖:使用互相谦让的方式解决,在构造器上加入@Lazy注解。(网上都说不饿能解决,我也不太确定我这个算不算一个解决办法)

  • 通过setter方法进行依赖注入且是在单例模式下产生的循环依赖问题:使用二级缓存和三级缓存解决

  • 通过setter方法进行依赖注入且是在多例(原型)模式下产生的循环依赖问题:目前无法解决

  1. 详解怎么解决setter方法循环依赖问题(单例模式)
  • BeanA的setter依赖了BeanB的实例对象,BeanB的setter又依赖了BeanA的实例对象,此时循环依赖就产生了

  • 解决:BeanA首先完成了初始化的第一步(实例化),并且将自己放到了三级缓存中(singletonFactory),此时进行初始化的第二步(属性填充),发现自己依赖对象BeanB,此时就尝试去get BeanB 但是发现BeanB还没有被create,所以走create流程

  • BeanB在初始化第一步的时候发现自己依赖了对象BeanA,于是尝试get BeanA,先尝试从一级缓存singletonObject获取,但是这时候肯定没有,因为BeanA还没初始化完,然后尝试去二级缓存earlySingletonObjects中获取,很明显,也没有,最后尝试从三级缓存singletonFactories中获取。由于BeanA通过ObjectFactory将自己提前曝光了。所以BeanB能够通过ObjectFactory.getObject()拿到BeanA对象

  • BeanB拿到BeanA对象后顺利的完成了初始化阶段。完全初始化之后将自己放入一级缓存singletonObject中,并且将BeanA放到二级缓存中,移除三级缓存中的BeanA

  • 此时返回BeanA中,BeanA此时拿到了BeanB的对像,所以顺利完成初始化。最终BeanA也完成了初始化,将BeanA也添加到一级缓存singletonObject中 PS:当使用@AutoWired注入依赖的时候,spring底层会帮我们解决循环依赖的问题,但是我们需要保证循环依赖的Bean是有无参构造函数的。例子:BeanA和BeanB循环依赖,当BeanA初始化的时候,发现依赖了B,它会去池中寻找,但是肯定是没有的。但是spring会帮我们在二级缓存里面创建两个空的Bean对象,即BeanA和BeanB。然后BeanA就创建完成了。后续BeanB就可以成功创建了。

16.为什么Spring无法解决构造器注入的循环依赖和原型(prototype)对象的循环依赖?

  1. 我们在上面已经分析过了,Spring解决循环依赖就是通过先缓存一个原生对象,然后发现存在依赖时,先去创建依赖的对象,当依赖的对象发现循环依赖时,就去二级、三级缓冲池去找,而这个时候发现存在,就用原生对象完成属性填充,当被依赖的对象完成最终的实例化后,再完成自己的最终实例化(属性填充)。但是如果我们使用构造器注入的话,在初始化的时候就需要填充依赖,如在创建BeanA类时,构造器需要BeanB类,那将去创建BeanB,在创建BeanB类时又发现需要BeanA类,则又去创建BeanB,从而形成一个环,没办法创建(网上都说不能解决,所以就总结一个问题吧!我上面说的那个方法不知道算不算!)

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

17.spring自动装配Bean的方式?

  1. no:默认的方式是不进行自动装配的,通过手工设置ref属性来进行装配bean

  2. byName:会自动在容器上下文中查找和自己set方法后面对应的bean id

  3. byType:通过参数的数据类型进行自动装配

  4. constructor:利用构造函数进行装配,并且构造函数的参数通过byType进行装配

  5. default:表示默认采用上一级标签的自动装配的取值,如果存在多个配置文件的话,那么每一个配置文件的自动装配方式都是独立的。PS:这个不演示了,主要是这个不能粘贴图片实在是不方便,希望后续能增加这个功能吧

PS:上面的是基于xml文件实现自动装配

18.spring事务什么时候会失效?

  1. 根本原因:Spring事务的原理是AOP,进行了切面增强,那么失效的根本原因是这个AOP不起作用了

  2. 常见失效情况:

  • 发生自调用,类里面使用this调用本类的方法,此时这个this对象不是代理类,而是本身。解决办法:就是使用代理类去调用即可。

  • 方法不是public的。@Transaction只能用于public的方法上,否则事务会失效。如果要用在非public方法,可以开启AspectJ代理模式

  • 数据库不支持事务,比如开启的数据库引擎本身不支持事务,如:myisam

  • 没有被spring管理

  • 异常被捕获了(catch),事务就不会回滚

19.spring事务传播机制?

  1. 分析一下这个事情:所谓事务传播机制,也就是事务在多个方法的调用中是如何传递的。打个比方:方法A是一个事务方法,方法A在执行过程中调用了方法B,那么方法B有没有事务或者方法B对事务的要求不同都会对方法A的事务具体执行造成影响。反之也是,这种影响具体是什么就由来两个方法定义的事务传播方式来决定

  2. 事务的传播机制:

  • REQUIRED:spring默认事务传播类型,如果当前没有事务,则自己新建一个事务,如果当前存在事务,那么就加入这个事务。

  • REQUIRES_NEW:不管是否存在事务,都创建一个新的事务,如果存在当前的事务,则挂起事务,新的方法执行完毕后,继续执行老的事务

  • NOT_SUPPORTED:Spring不为当前方法开启事务,相当于没有事务;即以非事务方式执行,如果当前存在事务,会挂起事务。

  • SUPPORTS:如果存在事务,则加入当前事务,如果没有声明事务,那就不用事务

  • NEVER:不适用事务,如果当前存在事务,那么抛出异常

  • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作

  • MANDATORY:如果存在事务,那么加入事务,如果没有事务,那么抛出异常

PS:推荐查看

20.spring怎么开启事务 -----------------被问+1

1.Spring中开启事务的方式主要有两种:编程式事务和声明式事务。PS:编程式事务这里就不讲了,可以翻阅一下资料。主要是我用的多的是声明式。。。 2.声明式开启的方式有两种

  • 第一种可以在xml文件里配置
<!-- 定义事务通知 -->  
<tx:advice id="txAdvice" transaction-manager="transactionManager">  
    <!-- 定义方法的过滤规则 -->  
    <tx:attributes>  
        <!-- 所有方法都使用事务 -->  
        <tx:method name="*" propagation="REQUIRED"/>  
        <!-- 定义所有get开头的方法都是只读的 -->  
        <tx:method name="get*" read-only="true"/>  
    </tx:attributes>  
</tx:advice>  
  
<!-- 定义AOP配置 -->  
<aop:config>  
    <!-- 定义一个切入点 -->  
    <aop:pointcut expression="execution (* com.demo.shallow.services.impl.*.*(..))" id="services"/>  
    <!-- 对切入点和事务的通知,进行适配 -->  
    <aop:advisor advice-ref="txAdvice" pointcut-ref="services"/>  
</aop:config>
  • 第二中配置方法:基于@Transactional的事务管理
<!-- 在xml文件里配置======声明使用注解式事务 -->  
<tx:annotation-driven transaction-manager="transactionManager"/>

PS:然后再去java代码里实现,具体可以翻阅资料。

21.spring如何处理事务?

22.spring中后置处理器的作用?

赞(6)
未经允许不得转载:工具盒子 » Spring常见面试问题整理