Spring面试题

为了应付面试,搞了一些spring的面试题

1、Spring是什么?

        Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求。常见的配置方式有三种:基于XML的配置、基于注解的配置、基于Java的配置。

主要由以下几个模块组成:

Spring Core:核心类库,提供IOC服务;

Spring Context:提供框架式的Bean访问方式,以及企业级功能(JNDI、定时任务等);

Spring AOP:AOP服务;

Spring DAO:对JDBC的抽象,简化了数据访问异常的处理;

Spring ORM:对现有的ORM框架的支持;

Spring Web:提供了基本的面向Web的综合特性,例如多方文件上传;

Spring MVC:提供面向Web应用的Model-View-Controller实现。

2、Spring 的优点?

(1)spring属于低侵入式设计,代码的污染极低;

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

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

(4)spring对于主流的应用框架提供了集成支持。

 

3、Spring的AOP理解:

OOP面向对象,允许开发者定义纵向的关系,但并适用于定义横向的关系,导致了大量代码的重复,而不利于各个模块的重用。

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

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

(1)AspectJ是静态代理的增强,所谓静态代理,就是AOP框架会在编译阶段生成AOP代理类,因此也称为编译时增强,他会在编译阶段将AspectJ(切面)织入到Java字节码中,运行的时候就是增强之后的AOP对象。

(2)Spring AOP使用的动态代理,所谓的动态代理就是说AOP框架不会去修改字节码,而是每次运行时在内存中临时为方法生成一个AOP对象,这个AOP对象包含了目标对象的全部方法,并且在特定的切点做了增强处理,并回调原对象的方法。

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

        ①JDK动态代理只提供接口的代理,不支持类的代理。核心InvocationHandler接口和Proxy类,InvocationHandler 通过invoke()方法反射来调用目标类中的代码,动态地将横切逻辑和业务编织在一起;接着,Proxy利用 InvocationHandler动态创建一个符合某一接口的的实例,  生成目标类的代理对象。

        ②如果代理类没有实现 InvocationHandler 接口,那么Spring AOP会选择使用CGLIB来动态代理目标类。CGLIB(Code Generation Library),是一个代码生成的类库,可以在运行时动态的生成指定类的一个子类对象,并覆盖其中特定方法并添加增强代码,从而实现AOP。CGLIB是通过继承的方式做的动态代理,因此如果某个类被标记为final,那么它是无法使用CGLIB做动态代理的。

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

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

 

4、Spring的IoC理解:

(1)IOC就是控制反转,是指创建对象的控制权的转移,以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系,对象与对象之间松散耦合,也利于功能的复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即 应用程序在运行时依赖IoC容器来动态注入对象需要的外部资源。

(2)最直观的表达就是,IOC让对象的创建不用去new了,可以由spring自动生产,使用java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法的。

(3)Spring的IOC有三种注入方式 :构造器注入、setter方法注入、根据注解注入。

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

5、BeanFactory和ApplicationContext有什么区别?

        BeanFactory和ApplicationContext是Spring的两大核心接口,都可以当做Spring的容器。其中ApplicationContext是BeanFactory的子接口。

(1)BeanFactory:是Spring里面最底层的接口,包含了各种Bean的定义,读取bean配置文档,管理bean的加载、实例化,控制bean的生命周期,维护bean之间的依赖关系。ApplicationContext接口作为BeanFactory的派生,除了提供BeanFactory所具有的功能外,还提供了更完整的框架功能:

①继承MessageSource,因此支持国际化。

②统一的资源文件访问方式。

③提供在监听器中注册bean的事件。

④同时加载多个配置文件。

⑤载入多个(有继承关系)上下文 ,使得每一个上下文都专注于一个特定的层次,比如应用的web层。

(2)①BeanFactroy采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对该Bean进行加载实例化。这样,我们就不能发现一些存在的Spring的配置问题。如果Bean的某一个属性没有注入,BeanFacotry加载后,直至第一次使用调用getBean方法才会抛出异常。

        ②ApplicationContext,它是在容器启动时,一次性创建了所有的Bean。这样,在容器启动时,我们就可以发现Spring中存在的配置错误,这样有利于检查所依赖属性是否注入。 ApplicationContext启动后预载入所有的单实例Bean,通过预载入单实例bean ,确保当你需要的时候,你就不用等待,因为它们已经创建好了。

        ③相对于基本的BeanFactory,ApplicationContext 唯一的不足是占用内存空间。当应用程序配置Bean较多时,程序启动较慢。

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

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

 

6、请解释Spring Bean的生命周期?

上面是一些基本的流程图:

实例化bean------------为bean设置值---------------调用各个xxxAware接口(获取各个引用)-----------------

bean后置处理器的预处理方法(初始化之前)---------------初始化方法(也会调用自定义的)-------------

bean后置处理器的后初始化方法(初始化之后)---------------将bean放到单例池---------------bean的销毁

说说xxxAware接口和beanPostProcessor的主要作用:

 

7、 解释Spring支持的几种bean的作用域。

Spring容器中的bean可以分为5个范围:

(1)singleton:默认,每个容器中只有一个bean的实例,单例的模式由BeanFactory自身来维护。

(2)prototype:为每一个bean请求提供一个实例。

(3)request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。

(4)session:与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。

(5)global-session:全局作用域,global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。

8、Spring框架中的单例Beans是线程安全的么?

        首先spring的单例bean并不是安全的,Spring框架并没有对单例bean进行任何多线程的封装处理。关于单例bean的线程安全和并发问题需要开发者自行去搞定。但实际上,大部分的Spring bean并没有可变的状态(比如Serview类和DAO类),所以在某种程度上说Spring的单例bean是线程安全的。如果你的bean有多种状态的话(比如 View Model 对象),就需要自行保证线程安全。最浅显的解决办法就是将多态bean的作用域由“singleton”变更为“prototype”(这样拿到的不是同一个bean,就不存在安全问题)。

9、Spring如何处理线程并发问题?

在一般情况下,只有无状态的Bean才可以在多线程环境下共享(没有实例变量等),在Spring中,绝大部分Bean都可以声明为singleton作用域,因为Spring对一些Bean中非线程安全状态采用ThreadLocal进行处理(官方自己提供的bean),解决线程安全问题。

ThreadLocal和线程同步机制都是为了解决多线程中相同变量的访问冲突问题。同步机制采用了“时间换空间”的方式,仅提供一份变量,不同的线程在访问前需要获取锁,没获得锁的线程则需要排队。而ThreadLocal采用了“空间换时间”的方式。

ThreadLocal会为每一个线程提供一个独立的变量副本,从而隔离了多个线程对数据的访问冲突。因为每一个线程都拥有自己的变量副本,从而也就没有必要对该变量进行同步了。ThreadLocal提供了线程安全的共享对象,在编写多线程代码时,可以把不安全的变量封装进ThreadLocal。

10-1、Spring基于xml注入bean的几种方式:

(1)Set方法注入;

(2)构造器注入:①通过index设置参数的位置;②通过type设置参数类型;

(3)静态工厂注入;

(4)实例工厂;

详细内容可以阅读:https://blog.csdn.net/a745233700/article/details/89307518

10-2、Spring的自动装配:

在spring中,对象无需自己查找或创建与其关联的其他对象,由容器负责把需要相互协作的对象引用赋予各个对象,使用autowire来配置自动装载模式。

在Spring框架xml配置中共有5种自动装配:

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

(2)byName:通过bean的名称进行自动装配,如果一个bean的 property 与另一bean 的name 相同,就进行自动装配。 

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

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

(5)autodetect:自动探测,如果有构造方法,通过 construct的方式自动装配,否则使用 byType的方式自动装配。

基于注解的方式:

使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在Spring配置文件进行配置,<context:annotation-config />。在启动spring IoC时,容器自动装载了一个AutowiredAnnotationBeanPostProcessor后置处理器,当容器扫描到@Autowied、@Resource或@Inject时,就会在IoC容器自动查找需要的bean,并装配给该对象的属性。在使用@Autowired时,首先在容器中查询对应类型的bean:

如果查询结果刚好为一个,就将该bean装配给@Autowired指定的数据;

如果查询的结果不止一个,那么@Autowired会根据名称来查找;

如果上述查找的结果为空,那么会抛出异常。解决方法时,使用required=false。

@Autowired可用于:构造函数、成员变量、Setter方法

注:@Autowired和@Resource之间的区别

(1) @Autowired默认是按照类型装配注入的,默认情况下它要求依赖对象必须存在(可以设置它required属性为false)。(spring提供的)

(2) @Resource默认是按照名称来装配注入的,只有当找不到与名称匹配的bean才会按照类型来装配注入。(jdk提供的)

11、Spring 框架中都用到了哪些设计模式?

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

(2)单例模式:Bean默认为单例模式。

(3)代理模式:Spring的AOP功能用到了JDK的动态代理和CGLIB字节码生成技术;

(4)模板方法:用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。

(5)观察者模式:定义对象键一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖于它的对象都会得到通知被制动更新,如Spring中listener的实现--ApplicationListener。

12、Spring事务的实现方式和实现原理:

Spring事务的本质其实就是数据库对事务的支持,没有数据库的事务支持,spring是无法提供事务功能的。真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。

(1)Spring事务的种类:

spring支持编程式事务管理和声明式事务管理两种方式:

①编程式事务管理使用TransactionTemplate。

②声明式事务管理建立在AOP之上的。其本质是通过AOP功能,对方法前后进行拦截,将事务处理的功能编织到拦截的方法中,也就是在目标方法开始之前加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

声明式事务最大的优点就是不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明或通过@Transactional注解的方式,便可以将事务规则应用到业务逻辑中。

声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式,使业务代码不受污染,只要加上注解就可以获得完全的事务支持。唯一不足地方是,最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。

(2)spring的事务传播行为:

spring事务的传播行为说的是,当多个事务同时存在的时候,spring如何处理这些事务的行为。

① PROPAGATION_REQUIRED:如果当前没有事务,就创建一个新事务,如果当前存在事务,就加入该事务,该设置是最常用的设置。

② PROPAGATION_SUPPORTS:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就以非事务执行。‘

③ PROPAGATION_MANDATORY:支持当前事务,如果当前存在事务,就加入该事务,如果当前不存在事务,就抛出异常。

④ PROPAGATION_REQUIRES_NEW:创建新事务,无论当前存不存在事务,都创建新事务。

⑤ PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。

⑥ PROPAGATION_NEVER:以非事务方式执行,如果当前存在事务,则抛出异常。

⑦ PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则按REQUIRED属性执行。

(3)Spring中的隔离级别:

① ISOLATION_DEFAULT:这是个 PlatfromTransactionManager 默认的隔离级别,使用数据库默认的事务隔离级别。

② ISOLATION_READ_UNCOMMITTED:读未提交,允许另外一个事务可以看到这个事务未提交的数据。

③ ISOLATION_READ_COMMITTED:读已提交,保证一个事务修改的数据提交后才能被另一事务读取,而且能看到该事务对已有记录的更新。

④ ISOLATION_REPEATABLE_READ:可重复读,保证一个事务修改的数据提交后才能被另一事务读取,但是不能看到该事务对已有记录的更新。

⑤ ISOLATION_SERIALIZABLE:一个事务在执行的过程中完全看不到其他事务对数据库所做的更新

13、Spring框架中有哪些不同类型的事件?

Spring 提供了以下5种标准的事件:

(1)上下文更新事件(ContextRefreshedEvent):在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。

(2)上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。

(3)上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。

(4)上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。

(5)请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。

如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。

 

14、解释一下Spring AOP里面的几个名词:

(1)切面(Aspect):被抽取的公共模块,可能会横切多个对象。 在Spring AOP中,切面可以使用通用类(基于模式的风格) 或者在普通类中以 @AspectJ 注解来实现。

(2)连接点(Join point):指方法,在Spring AOP中,一个连接点 总是 代表一个方法的执行。 

(3)通知(Advice):在切面的某个特定的连接点(Join point)上执行的动作。通知有各种类型,其中包括“around”、“before”和“after”等通知。许多AOP框架,包括Spring,都是以拦截器做通知模型, 并维护一个以连接点为中心的拦截器链。

(4)切入点(Pointcut):切入点是指 我们要对哪些Join point进行拦截的定义。通过切入点表达式,指定拦截的方法,比如指定拦截add*、search*。

(5)引入(Introduction):(也被称为内部类型声明(inter-type declaration))。声明额外的方法或者某个类型的字段。Spring允许引入新的接口(以及一个对应的实现)到任何被代理的对象。例如,你可以使用一个引入来使bean实现 IsModified 接口,以便简化缓存机制。

(6)目标对象(Target Object): 被一个或者多个切面(aspect)所通知(advise)的对象。也有人把它叫做 被通知(adviced) 对象。 既然Spring AOP是通过运行时代理实现的,这个对象永远是一个 被代理(proxied) 对象。

(7)织入(Weaving):指把增强应用到目标对象来创建新的代理对象的过程。Spring是在运行时完成织入。

切入点(pointcut)和连接点(join point)匹配的概念是AOP的关键,这使得AOP不同于其它仅仅提供拦截功能的旧技术。 切入点使得定位通知(advice)可独立于OO层次。 例如,一个提供声明式事务管理的around通知可以被应用到一组横跨多个对象中的方法上(例如服务层的所有业务操作)。

15、Spring通知有哪些类型?

https://blog.csdn.net/qq_32331073/article/details/80596084

(1)前置通知(Before advice):在某连接点(join point)之前执行的通知,但这个通知不能阻止连接点前的执行(除非它抛出一个异常)。

(2)返回后通知(After returning advice):在某连接点(join point)正常完成后执行的通知:例如,一个方法没有抛出任何异常,正常返回。 

(3)抛出异常后通知(After throwing advice):在方法抛出异常退出时执行的通知。 

(4)后通知(After (finally) advice):当某连接点退出的时候执行的通知(不论是正常返回还是异常退出)。 

(5)环绕通知(Around Advice):包围一个连接点(join point)的通知,如方法调用。这是最强大的一种通知类型。 环绕通知可以在方法调用前后完成自定义的行为。它也会选择是否继续执行连接点或直接返回它们自己的返回值或抛出异常来结束执行。 环绕通知是最常用的一种通知类型。大部分基于拦截的AOP框架,例如Nanning和JBoss4,都只提供环绕通知。 

同一个aspect,不同advice的执行顺序:

①没有异常情况下的执行顺序:

around before advice
before advice
target method 执行
around after advice
after advice
afterReturning

②有异常情况下的执行顺序:

around before advice
before advice
target method 执行
around after advice
after advice
afterThrowing:异常发生
java.lang.RuntimeException: 异常发生

 

16、beanfactory和factorybean的区别?

BeanFactory可以看出是一个生成bean的工厂,用来管理和获取很多Bean对象,例如:加载applicationContext.xml文件。

而FactoryBean是一个Bean生成工具,是用来获取一种类型对象的Bean,它是构造Bean实例的一种方式。他本身也是一个bean,只不过是一个特殊的bean而是一个能生产或者修饰对象生成的工厂Bean,它的实现与设计模式中的工厂模式和修饰器模式类似

(一般情况下,Spring通过反射机制利用<bean>的class属性指定实现类实例化Bean,在某些情况下,实例化Bean过程比较复杂,如果按照传统的方式,则需要在<bean>中提供大量的配置信息。配置方式的灵活性是受限的,这时采用编码的方式可能会得到一个简单的方案。Spring为此提供了一个org.springframework.bean.factory.FactoryBean的工厂类接口,用户可以通过实现该接口定制实例化Bean的逻辑。
FactoryBean接口对于Spring框架来说占用重要的地位,Spring自身就提供了70多个FactoryBean的实现。它们隐藏了实例化一些复杂Bean的细节,给上层应用带来了便利。从Spring3.0开始,FactoryBean开始支持泛型,即接口声明改为FactoryBean<T>的形式)也就是说FactoryBean是为了简化生产复制定义的bean而出现的。

17、什么是bean的后置处理器?什么是bean工厂的后置处理器?他们的执行时机是什么时候?

bean的后置处理器:即实现了beanPostProcesssor接口的。一般后置处理器的作用是获取到bean然后对其进行干预改变的操作。执行时机:不管内置的还是程序员提供)————Ⅰ:如果是直接实现BeanFactoryPostProcessor的类是在spring完成扫描类之后(所谓的扫描包括把类变成beanDefinition然后put到map之中),在实例化bean之前执行;Ⅱ如果是实现BeanDefinitionRegistryPostProcessor接口的类;诚然这种也叫bean工厂后置处理器他的执行时机是在执行直接实现BeanFactoryPostProcessor的类之前,和扫描是同期执行。(实现RegistryPostProcessor的大部分是spring内部的后置处理器,实现beanPostProcesssor的大部分是程序员提供的,所以前者快于后者执行)

bean工厂的后置处理器:即实现了beanFactoryPostProcesssor接口的。主要是对bean工厂进行干预改变。

可以让程序员干预bean工厂的初始化过程(重点会考);这句话最重要的几个字是初始化过程,注意不是实例化过程 ;初始化和实例化有很大的区别的,特别是在读spring源码的时候一定要注意这两个名词;翻开spring源码你会发现整个容器初始化过程就是spring各种后置处理器调用过程;而各种后置处理器当中大体分为两种;一种关于实例化的后置处理器一种是关于初始化的后置处理器,这里不是笔者臆想出来的,如果读者熟悉spring的后置处理器体系就可以从spring的后置处理器命名看出来spring对初始化和实例化是有非常大的区分的。

说白了就是——beanFactory怎么new出来的(实例化)BeanFactoryPostProcessor是干预不了的,但是beanFactory new出来之后各种属性的填充或者修改(初始化)是可以通过BeanFactoryPostProcessor来干预;可以看到BeanFactoryPostProcessor里唯一的方法postProcessBeanFactory中唯一的参数就是一个标准的beanFactory对象——ConfigurableListableBeanFactory;既然spring在调用postProcessBeanFactory方法的时候把已经实例化好的beanFactory对象传过来了,那么自然而然我们可以对这个beanFactory肆意妄为了;

18、spring是怎么解决循环依赖的问题?也能解决原生bean的循环依赖?注解@dependon也是属于循环依赖问题?

https://blog.csdn.net/qq_36381855/article/details/79752689

什么是循环依赖:

A依赖B,B依赖A。所以A对象的创建需要先获取到B对象,而B对象的创建需要先获取到A对象。从而形成了循环依赖的问题。

spring主要有三种循环依赖:

1、构造器的循环依赖。【这个Spring解决不了】

2、【setter循环依赖】(setter方式单例,默认方式。我们一般所说的就是解决这个循环依赖)

3、setter方式原型,prototype(原型也解决不了,会报错)

下面主要讲讲第二种循环依赖的解决方法:
setter循环依赖:field属性的循环依赖【setter方式 单例,默认方式-->通过递归方法找出当前Bean所依赖的Bean,然后提前缓存【会放入Cach中】起来。通过提前暴露 -->暴露一个exposedObject用于返回提前暴露的Bean。】

 

setter方式注入:

图中前两步骤得知:Spring是先将Bean对象实例化【依赖无参构造函数】--->再设置对象属性的

这样出现循环依赖问题则不会报错:

原因:Spring先用构造器实例化Bean对象----->将实例化结束的对象放到一个Map中,并且Spring提供获取这个未设置属性的实例化对象的引用方法。结合我们的实例来看,,当Spring实例化了StudentA、StudentB、StudentC后,紧接着会去设置对象的属性,此时StudentA依赖StudentB,就会去Map中取出存在里面的单例StudentB对象,以此类推,不会出来循环的问题喽。
————————————————

如何检测是否有循环依赖?how to  find?

   可以 Bean在创建的时候给其打个标记,如果递归调用回来发现正在创建中的话--->即可说明循环依赖。

怎么解决的?

Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或zh属性是可以延后设置的(但是构造器必须是在获取引用之前)。

   Spring的单例对象的初始化主要分为三步:

 ①:createBeanInstance:实例化,其实也就是 调用对象的构造方法实例化对象(构造方法依赖的bean就要再此进行填充)

    ②:populateBean:填充属性,这一步主要是多bean的依赖属性进行填充(setter方法的循环依赖的bean就是在这里要进行填充)

    ③:initializeBean:调用spring xml中的init() 方法。

从上面讲述的单例bean初始化步骤我们可以知道,循环依赖主要发生在第一、第二步。也就是构造器循环依赖和field循环依赖。

那么我们要解决循环引用也应该从初始化过程着手,对于单例来说,在Spring容器整个生命周期内,有且只有一个对象,所以很容易想到这个对象应该存在Cache中,Spring为了解决单例的循环依赖问题,使用了三级缓存。

  
调整配置文件,将构造函数注入方式改为 属性注入方式 即可
————————————————

源码怎么实现的? 

三级缓存源码主要 指:

/** Cache of singleton objects: bean name --> bean instance */
//一级缓存  单例对象的缓存
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);

 
/** Cache of early singleton objects: bean name --> bean instance */
//二级缓存   提前暴光的单例对象的Cache (用于解决循环依赖)
private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16); 

/** Cache of singleton factories: bean name --> ObjectFactory */
//三级缓存   单例对象工厂的缓存
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<String, ObjectFactory<?>>(16);

我们在创建bean的时候,首先想到的是从cache中获取这个单例的bean,这个缓存就是singletonObjects。主要调用方法就就是:

protected Object getSingleton(String beanName, boolean allowEarlyReference) {
    //现在一级缓存即单例池中获取单例对象
    Object singletonObject = this.singletonObjects.get(beanName);

    if (singletonObject == null && isSingletonCurrentlyInCreation(beanName)) {
        synchronized (this.singletonObjects) {
            singletonObject = this.earlySingletonObjects.get(beanName);
            if (singletonObject == null && allowEarlyReference) {
                ObjectFactory<?> singletonFactory = this.singletonFactories.get(beanName);
                if (singletonFactory != null) {
                    singletonObject = singletonFactory.getObject();
                    this.earlySingletonObjects.put(beanName, singletonObject);
                    this.singletonFactories.remove(beanName);
                }
            }
        }
    }
    return (singletonObject != NULL_OBJECT ? singletonObject : null);
}

上面的代码需要解释两个参数:

isSingletonCurrentlyInCreation():判断当前单例bean是否正在创建中,也就是没有初始化完成(比如A的构造器依赖了B对象所以得先去创建B对象, 或则在A的populateBean过程中依赖了B对象,得先去创建B对象,这时的A就是处于创建中的状态。主要是对该对象进行了正在创建的标记)
allowEarlyReference :是否允许从singletonFactories中通过getObject拿到对象
            分析getSingleton()的整个过程,Spring首先从一级缓存singletonObjects中获取。如果获取不到,并且对象正在创建中,就再从二级缓存earlySingletonObjects中获取。如果还是获取不到且允许singletonFactories通过getObject()获取,就从三级缓存singletonFactory.getObject()(三级缓存)获取,如果获取到了则:

将该对象移动到二级缓存中
this.earlySingletonObjects.put(beanName, singletonObject);
                        this.singletonFactories.remove(beanName);

从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义如下:

public interface ObjectFactory<T> {
    T getObject() throws BeansException;
}

这个接口在下面被引用

protected void addSingletonFactory(String beanName, ObjectFactory<?> singletonFactory) {
    Assert.notNull(singletonFactory, "Singleton factory must not be null");
    synchronized (this.singletonObjects) {
        if (!this.singletonObjects.containsKey(beanName)) {
            this.singletonFactories.put(beanName, singletonFactory);
            this.earlySingletonObjects.remove(beanName);
            this.registeredSingletons.add(beanName);
        }
    }
}

这里就是解决循环依赖的关键,这段代码发生在createBeanInstance之后,也就是说单例对象此时已经被创建出来(调用了构造器)。这个对象已经被生产出来了,虽然还不完美(还没有进行初始化的第二步和第三步),但是已经能被人认出来了(根据对象引用能定位到堆中的对象),所以Spring此时将这个对象提前曝光出来让大家认识,让大家使用。

这样做有什么好处呢?让我们来分析一下“A的某个field或者setter依赖了B的实例对象,同时B的某个field或者setter依赖了A的实例对象”这种循环依赖的情况。A首先完成了初始化的第一步,并且将自己提前曝光到singletonFactories(三级缓存)中,此时进行初始化的第二步,发现自己依赖对象B,此时就尝试去get(B),发现B还没有被create,所以走create流程,B在初始化第一步的时候发现自己依赖了对象A,于是尝试get(A),尝试一级缓存singletonObjects(肯定没有,因为A还没初始化完全),尝试二级缓存earlySingletonObjects(也没有),尝试三级缓存singletonFactories,由于A通过ObjectFactory将自己提前曝光了,所以B能够通过ObjectFactory.getObject拿到A对象(虽然A还没有初始化完全,但是总比没有好呀),B拿到A对象后顺利完成了初始化阶段1、2、3,完全初始化之后将自己放入到一级缓存singletonObjects(单例池)中。此时返回A中,A此时能拿到B的对象顺利完成自己的初始化阶段2、3,最终A也完成了初始化,进去了一级缓存singletonObjects中,而且更加幸运的是,由于B拿到了A的对象引用,所以B现在hold住的A对象完成了初始化。

知道了这个原理时候,肯定就知道为啥Spring不能解决“A的构造方法中依赖了B的实例对象,同时B的构造方法中依赖了A的实例对象”这类问题了!因为加入singletonFactories三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

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

————————————————

下面简单的总结下:

1.先加载A,依次判断(一级缓存)、(二级缓存)、(三级缓存)中是否有A,没有就将A加入(三级缓存)

2. A依赖B,先加载B
2.1 依次判断(一级缓存)、(二级缓存)、(三级缓存)中是否有B,没有就将B加入(三级缓存)
2.2 加载B的依赖,发现依赖A,依次从(一级缓存)、(二级缓存)、(三级缓存)中查找A,发现(三级缓存)有A,将A上升到(二级缓存)中(曝光对象A的缓存)
2.3 将A注入B的引用,完成B的加载,将B从(三级缓存)升级至(一级缓存)中(B放入单例池)

3.A依赖的B加载完了,继续加载A完成。将A从(二级缓存)上升到(一级缓存)。(B放入单例池)

(A依赖B,B依赖A。先创建A并将A放入三级缓存,发现依赖B则去创建B,发现依赖A去缓存中找到A并将其升至二级缓存,完成创建进入单例池,A继续完成创建进入单例池)

 

19、bd是什么?有几种bd?

bd:即beanDefintion,是将类解析出来然后存放起来的一个对象。即一个类的描述信息等都存放在bd中。

bd有多种类型的:如genericbd、rootbd、childrenbd(当然还有一些实现了这些bd的其他实现类)。

genericbd:名字可以看出是属于通用类型的。可做为父、子bd。

rootbd:可以作为父bd(提供模板的作用),而不能作为子bd。

childrenbd:只可作为子bd。(基本不用了)

为什么有genericbd可以替换rootbd的功能还用rootbd?  因为在bd合并的时候spring是将所有bd合并返回成一个rootbd的。

20、你建议使用哪种依赖注入方法? 构造方法注入和setter注入之间的区别吗?

两种注入方式有和区别呢?下面做简单比较:
  在过去的开发过程中,这两种注入方式都是非常常用的。Spring也同时支持两种依赖注入方式:设值注入和构造注入。 这两种依赖注入的方式,并没有绝对的好坏,只是适应的场景有所不同。相比之下,设值注入有如下优点:

1、设值注入需要该Bean包含这些属性的setter方法
2、与传统的JavaBean的写法更相似,程序开发人员更容易理解、接收。通过setter方法设定依赖关系显得更加直观。
3、对于复杂的依赖关系,如果采用构造注入,会导致构造器过于臃肿,难以阅读。Spring在创建Bean实例时,需要同时实例化器依赖的全部实例,因而导致性能下降。而使用设值注入,则能避免这些问题
4、尤其是在某些属性可选的情况况下,多参数的构造器显得更加笨重
  构造注入也不是绝对不如设值注入,在某些特定的场景下,构造注入比设值注入更加优秀。构造注入有以下优势:

1、构造注入需要该Bean包含带有这些属性的构造器
2、构造注入可以在构造器中决定依赖关系的注入顺序,优先依赖的优先注入。例如,组件中其他依赖关系的注入,常常要依赖于DataSrouce的注入。采用构造注入,可以在代码中清晰的决定注入顺序。
3、对于依赖关系无需变化的Bean,构造注入更有用处。因为没有Setter方法,所有的依赖关系全部在构造器内设定。因此,无需担心后续的代码对依赖关系产生破坏。
4、依赖关系只能在构造器中设定,则只有组件的创建者才能改变组件的依赖关系。对组件的调用者而言,组件内部的依赖关系完全透明,更符合高内聚的原则。
  建议:采用以设值注入为主,构造注入为辅的注入策略。对于依赖关系无需变化的注入,尽量采用构造注入;而其他的依赖关系的注入,则考虑采用设值注入。

21、简单的说下一个类的java文件到称为单例池中的springbean的过程?

一个类先被解析转换为bd,然后将bd put到bdmap中,之后再进行实例化化初始化过程。

22、哪些是重要的bean生命周期方法? 你能重载它们吗?

有两个重要的bean 生命周期方法,第一个是setup , 它是在容器加载bean的时候被调用。第二个方法是 teardown 它是在容器卸载类的时候被调用。

The bean 标签有两个重要的属性(init-method和destroy-method)。用它们你可以自己定制初始化和注销方法。它们也有相应的注解(@PostConstruct和@PreDestroy)。

23、自动装配有哪些局限性 ?

重写: 你仍需用 和 配置来定义依赖,意味着总要重写自动装配。

基本数据类型:你不能自动装配简单的属性,如基本数据类型,String字符串,和类。

模糊特性:自动装配不如显式装配精确,如果有可能,建议使用显式装配。

24、你可以在Spring中注入一个null 和一个空字符串吗?

可以

25、什么是基于Java的Spring注解配置? 给一些注解的例子.?

基于Java的配置,允许你在少量的Java注解的帮助下,进行你的大部分Spring配置而非通过XML文件。

以@Configuration 注解为例,它用来标记类可以当做一个bean的定义,被Spring IOC容器使用。另一个例子是@Bean注解,它表示此方法将要返回一个对象,作为一个bean注册进Spring应用上下文。

26、Spring支持的事务管理类型

Spring支持两种类型的事务管理:

  • 编程式事务管理:这意味你通过编程的方式管理事务,给你带来极大的灵活性,但是难维护。
  • 声明式事务管理:这意味着你可以将业务代码和事务管理分离,你只需用注解和XML配置来管理事务。

27、 Spring框架的事务管理有哪些优点?你更倾向用那种事务管理类型?

  • 它为不同的事务API  如 JTA,JDBC,Hibernate,JPA 和JDO,提供一个不变的编程模式。
  • 它为编程式事务管理提供了一套简单的API而不是一些复杂的事务API如
  • 它支持声明式事务管理。
  • 它和Spring各种数据访问抽象层很好得集成。

大多数Spring框架的用户选择声明式事务管理,因为它对应用代码的影响最小,因此更符合一个无侵入的轻量级容器的思想。声明式事务管理要优于编程式事务管理,虽然比编程式事务管理(这种方式允许你通过代码控制事务)少了一点灵活性。

28、在Spring AOP 中,关注点和横切关注的区别是什么?

关注点是应用中一个模块的行为,一个关注点可能会被定义成一个我们想实现的一个功能。
横切关注点是一个关注点,此关注点是整个应用都会使用的功能,并影响整个应用,比如日志,安全和数据传输,几乎应用的每个模块都需要的功能。因此这些都属于横切关注点。

29、什么是织入。什么是织入应用的不同点?

织入是将切面和到其他应用类型或对象连接或创建一个被通知对象的过程。

织入可以在编译时,加载时,或运行时完成。

30、什么是引入?

引入允许我们在已存在的类中增加新的方法和属性。

31、@Component和@Bean的区别是什么?还有@Component和@service、@controller的区别?

    @Component主要用于注册类到容器中,而@Bean主要用于注册对象到容器中

    spring 2.5 中除了提供 @Component 注释外,还定义了几个拥有特殊语义的注释,它们分别是:@Repository、@Service 和 @Controller。在目前的 Spring 版本中,这 3 个注释和 @Component 是等效的,但是从注释类的命名上,很容易看出这 3 个注释分别和持久层、业务层和控制层(Web 层)相对应。虽然目前这 3 个注释和 @Component 相比没有什么新意,但 Spring 将在以后的版本中为它们添加特殊的功能。所以,如果Web 应用程序采用了经典的三层分层结构的话,最好在持久层、业务层和控制层分别采用@Repository、@Service 和 @Controller 对分层中的类进行注释,而用 @Component 对那些比较中立的类进行注释。 (@service等的注解也是用了@Component注解的

32、将一个对象注入spring容器有几种方法?

1 将类注册到到Spring容器
该类的实例化和初始化过程由spring控制。

方法一:xml注册Bean
xml注册Bean,如果spring配置元数据的方式是xml时,可以手动在xml注册第三方jar包中的类。常用来配置本项目中的Bean。

<bean id="service1" class="stu.spring.services.Service1"></bean>
方法二:@Componen
扫描注解,如果spring配置元数据的方式是注解时,指定扫描包然后给类上加@Component注解就可以。常用来扫描本项目中的Bean。

xml指定扫描包:

<context:component-scan base-package="stu.spring.lifecycle"></context:component-scan>
Java Configuration指定扫描包:

@Configuration
@ComponentScan("stu.spring.services")
public class AppConfig {
}

方法三:@Configuration
spring配置元数据的方式使用Java Configuration时,指定配置类的注解,spring该注解spring在实例化该类的对象时是先通过cglib代理生成代理类,再实例化对象。

@Configuration
public class ConfigA {
}

方法四:@Import
@Import注解,用于java配置类上才有效,常用来导入一个或者多个配置类,导入非配置类也可以。

@Configuration
public class ConfigA {
    @Bean
    public A a() {
        return new A();
    }
}
@Configuration
@Import(ConfigA.class)
public class ConfigB {
    @Bean
    public B b() {
        return new B();
    }
}

2 将一个对象注册到spring容器
该对象的实例化和初始化过程由程序员控制。

方法一:@Bean
@Bean注册Bean,如果spring配置元数据的方式是Java Configuration时,可以使用@Bean注解注册第三方jar包中的类。一般用来注册一些配置信息。

@Bean
public A1Service getA2Service(){
   return new A1Service() ;
}

方法二:spring容器直接注册
获取到使用容器,使用spring容器直接注册bean。获取spring容器的方法有很多不仅仅下面示例展示的方式。

AnnotationConfigApplicationContext ann = new AnnotationConfigApplicationContext(AppConfig.class);
ann.getBeanFactory().registerSingleton("testService",new A1Service());

方法三:FactoryBean
使用自定义的FactoryBean,一般和第三方jar继承或者扩展spring使用该方式

@Component
public class CustomerFactoryBean implements FactoryBean<A1Service> {
   @Override
   public A1Service getObject() throws Exception {
      return new A1Service();
   }

   @Override
   public Class<?> getObjectType() {
      return A1Service.class;
   }

}

33、 如何将一个java.util.属性注入到Spring Bean?如何在Spring中注入Java集合?请给个例子好吗?

第一种方法是使用如下面代码所示的<props> 标签:

<bean id="adminUser" class="com.howtodoinjava.common.Customer">

    <!-- java.util.Properties -->
    <property name="emails">
        <props>
            <prop key="admin">admin@nospam.com</prop>
            <prop key="support">support@nospam.com</prop>
        </props>
    </property>

</bean>

也可用”util:”命名空间来从properties文件中创建出一个propertiesbean,然后利用setter方法注入bean的引用。
————————————————

具体代码看看:https://www.cnblogs.com/lihuibin/p/7928893.html

<list>类型用于注入一列值,允许有相同的值。
<set>类型用于注入一组值,不允许有相同的值。
<map>类型用于注入一组键值对,键和值都可以为任意类型。
<props>类型用于注入一组键值对,键和值都只能为 String 类型。

34、解释一下JDBC抽象和DAO模块?解释一下对象/关系映射集成模块?

使用JDBC抽象和DAO模块,我们可以确保我们使数据库代码保持干净和简单,并防止因无法关闭数据库资源而导致的问题。它在几个数据库服务器给出的错误消息之上提供了一层有意义的异常。它还利用Spring的AOP模块为Spring应用程序中的对象提供事务管理服务。

Spring虽然集成了几个ORM产品,但也可以不选择这几款产品,因为Spring提供了JDBC和DAO模块。该模块对现有的JDBC技术进行了优化。你可以保持你的数据库访问代码干净简洁,并且可以防止因关闭数据库资源失败而引起的问题。

[JDBC DAO 抽象层提供了有意义的异常层次结构,可用该结构管管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。SpringDAO的面向JDBC的异常遵从从通用的DAO异常层次结构]

在Spring框架中如何更有效地使用JDBC? 

使用Spring JDBC框架,资源管理和错误处理的代价都会被减轻。所以开发者只需statements和queries从数据存取数据,JDBC也可以在Spring框架提供的模板类的帮助下更有效地被使用,这个模板叫JDBC Template 。可以使用这些模板类有效简洁的操作。

对象关系映射,简单讲就是Java对象 Object 和关系型数据库 Relationship 之间的映射Mapping, 即ORM。

为什么要使用ORM?
因为面向对象的概念,使得操作关系型数据库也可以用操作对象那样处理。操作关系型数据库,就像操作Java对象一样,更容易以面向对象的方式理解。

ORM框架
常见的ORM框架有:Hibernate、TopLink、Castor JDO、Apache OJB等

ORM实现原理
先说ORM的实现原理,其实,要实现JavaBean的属性到数据库表的字段的映射,任何ORM框架不外乎是读某个配置文件把JavaBean的属 性和数据库表的字段自动关联起来,当从数据库Query时,自动把字段的值塞进JavaBean的对应属性里,当做INSERT或UPDATE时,自动把 JavaBean的属性值绑定到SQL语句中。

ORM在实际中使用
通常写Java系统,用Spring + Hibernate /MyBatis 一起实现Web项目。

35、@Required 注解有什么作用

@Required 注释应用于 bean 属性的 setter 方法,是用于检查一个Bean的属性在配置期间是否被赋值。

也就是用确保属性值是否已经设置,检查配置在XML中的Bean的相关属性,是否被注入依赖值。

如果没有注入相关值,那么就会抛出一个BeanInitializationException 异常。

36、JdbcTemplate是什么?

使用jdbc时,每次都需要自己获取PreparedStatement,输入sql语句参数,关闭连接等操作。造成操作冗余。影响我们打代码的效率。有了JDBCTemplate以后就可以只写SQL语句就可以了。

JdbcTemplate是Spring的一部分,是对数据库的操作在jdbc的封装,处理了资源的建立和释放(不需要我们管理连接了),我们只需要提供SQL语句(不需要我们设置参数了)和提取结果(查询时候可以直接返回对应的实体类),使JDBC更加易于使用。
JdbcTemplate使用spring的注入功能,把DataSource注册到JdbcTemplate之中。

37、什么是代理?静态代理?动态代理?

38、介绍下jdk动态代理?cglib动态代理?区别?SpringAop什么时候用jdk动态代理什么时候用cglib动态代理?

 

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值