面试-Spring面试题

一、Bean的生命周期?
Spring有四个Bean级生命周期接口和2个容器级生命周期接口,分别是

  • BeanNameAware
  • BeanFactoryAware
  • InitializingBean
  • DisposableBean
  • InstantiationAwareBeanPostProcessorAdapter
  • BeanPostProcessor

1、 BeanNameAware
setBeanName(String beanName)
该方法将在bean实例化完成之后调用,设置beanName
2、BeanFactoryAware
setBeanFactory(BeanFactory beanFactory)
该方法在调用了setBeanName方法之后调用
3、InitializingBean
afterPropertiesSet()
该方法在调用set方法设置属性的时候会被调用
4、DisposableBean
destory()
该方法在容器关闭的时候被调用
5、InstantiationAwareBeanPostProcessorAdapter
postProcessBeforeInstantiation(Class beanClass, String beanName)
该方法在bean被实例化之前调用

PostProcessAfterInstantiation(Object bean, String beanName)
该方法在实例化之后被调用

postProcessPropertyValues(PropertyValues pvs, PropertyDescriptor[] pds, Object bean, String beanName)
该方法在bean被set赋值之前被调用(注意,这里的赋值是指在xml里面通过属性赋值)
6、BeanPostProcessor
postProcessBeforeInitialization
该方法在初始化之前被调用
postProcessAfterInitialization
该方法在bean对象初始化之后被调用

Bean的生命周期,如下所示:
1、检测有没有实现InstantiationAwareBeanPostProcessorAdapter接口,若实现了,则执行postProcessBeforeInstantiation();
2、调用无参构造函数实例化对象
3、检测是否实现了InstantiationAwareBeanPostProcessorAdapter接口 ,若实现了,则执行postProcessAfterInstantiation();
4、若是通过xml的方式配置的bean,则检测有没有设置属性,如果有,则检测有没有实现了InstantiationAwareBeanPostProcessorAdapter,若有,则执行postProcessPropertyValues(),然后设置属性;
5、检测有没有实现了BeanNameAware接口,若有,则调用setBeanName()
6、检测有没有实现BeanFactoryAware接口,若有,则调用setBeanFactory()
7、检测有没有实现了ApplicationContextAware接口,若有,则调用setApplicationContext,将上下文的引用传入到bean中
8、检测有没有实现BeanPostProcessor接口,如有则调用postProcessBeforeInitialization(),接着调用set方法设置属性值
9、检测有没有实现了InitializingBean接口,若有,则调用afterPropertiesSet()
10、检测有没有设置init-method()方法,若有,则调用
11、检测有没有实现了BeanPostProcessor,若有则调用postProcessAfterInitialization();
12、若当前bean是非单例的,则返回bean给调用者,spring容器后续不在管理;若是单例的,则将bean缓存在容器中,并继续管理;
13、若实现了DisposableBean接口,则在容器关闭的时候调用destory();
14、检测有没有设置destory-method方法,若有则调用该方法;

二、Bean的作用域(scope)有几种类型?
有五种类型,分别是:
singleton:单例的,spring容器中的bean以单例的方式运行,默认的作用域
prototype:原型的,每次从容器中调用bean时,都新建了一个Bean;
request:请求,每次Http请求都会新建一个Bean;
session:相同的session共享bean,不同的session使用不同的bean
application:限定一个bean的生命周期为整个servletContext上下文;

三、介绍下IOC?
IOC也就是控制反转,他是通过DI来实现控制反转的,它主要包括以下三个作用?
1:动态的创建和注入依赖的对象;
2:管理对象的生命周期;
3:映射依赖关系
IOC将依赖性的配置与实际的代码分开,减少代码的耦合性,使用者只需要暴露出对象之间的依赖关系,IOC容器会全权负责,并在容器启动的时候组装对象之间的依赖关系;DI是一种机制,将需要依赖对象的引用传递给被依赖对象;
依赖注入的主要有一下3种方式:
1:set属性注入
2:接口注入
3:构造器注入

对比一下这三种注入方式各有什么优缺点?
构造器注入:IOC会检查被注入对象的构造方法,取得他所需要的被依赖对象的列表,进而给其注入被依赖的对象;这种注入方式的优点是:注入方法比较直观,对象在被构造完成之后,无需等待,直接进入就绪状态;缺点是当依赖对象比较多时,构造方法的参数列表会比较长,而通过反射构造对象的时候,对相同类型的参数的处理比比较困难,维护和使用上也比较麻烦;而且构造方法无法被继承,无法设置默认值;

set属性注入:因为set方法可以命名,所以set方法注入在描述性上要比构造器方法好一些;set方法可以被继承,允许设置默认值;缺点是对象在构造完成之后无法直接进入就绪状态;

接口注入:相比较于构造器注入和set方法注入,接口注入比较繁琐,被注入对象如果想要IOC为其注入被依赖的对象,就必须实现某一个接口,这个接口提供了注入对象的方法;这种方式强制被注入对象实现某一个接口,具有很强的侵入性,因此不推荐使用;

四:Spring提供了几种Bean的配置方式
一、通过xml的配置
二、通过注解的方式创建bean,比如@Controller、@Service、@Repository、@Component,通过注解的方式可以申明当前对象是一个通过spring容器管理的Bean对象,一般在控制层用@Controller注解,在业务层用@Service注解,在DAO层使用@Repository注解,当我们无法确定当前对象属于那一层级时,使用@Component注解
三、通过java类配置Bean,也是通过注解的方式实现的,先使用@Configuration注解声明当前类是配置类,在类中编写方法来定义bean,并在方法上通过@Bean注解来声明,beanName默认是方法名首字母小写,可通过@Bean(name = “”)来指定bean的名称;
四、基于Groovy DSL配置,在Groovy中通过DSL来定义Bean;

五:springBean的配置方式使用在哪些应用场景?
1、当bean的实现类来源于第三方类库,比如说DataSource,无法直接在类中标注的一般通过xml配置的方式;
2、bean的实现类在当前项目中开发,可以直接在代码里面使用注解标注的一般通过注解的方式配置;
3、基于java类的配置的优势在于通过代码方式控制bean的整体逻辑,如果bean的逻辑比较复杂,则可以通过java类的方式
4、基于Groovy DSL的配置的优势在于可以通过Groovy脚本灵活控制bean的初始化过程,如果bean的实例化过程比较复杂,则比较适合基于Groovy DSL的方式配置

六、使用懒加载有什么好处?
在默认情况下,spring容器启动的时候会自动实例化所有singleton的实例并且缓存在容器中;
提前加载的好处就是:
1:可以在spring启动的时候就发现潜在的问题;
2:bean会缓存在容器里面,当我们再次使用的时候,会直接从容器中取,提高了运行速度;
懒加载的好处是:
1、实例在调用的时候才会初始化,节省了资源空间;

七、singleton和prototrye的区别?
singleton修饰的bean是单例的bean,在IOC容器中只有一个实例,每次调用该实例的时候就会从spring容器中获取,spring的缺省作用域就是singleton的;
protorype修饰的bean,在每次被调用的时候,都会重新产生一个对象,类似于new,而且使用prototype修饰的bean当spring将实例返回给调用者的时候就不在维护bean的生命周期了,有调用者自行维护,负责销毁和资源回收操作;
在spring中,一般有状态的bean设置成prototype,无状态的bean设置成singleton,但是spring对此作了一定的优化;由于dao类包含了Connection这个非线程安全的变量,所以一般往往不建议设置成singleton的,但是在spring环境下,dao类可以设置成singleton的,因为spring使用了ThreadLocal和AOP功能对非线程安全的变量作了处理,使这些非线程安全的类变成了线程安全的;
但是在structs中我们可以发现,Action一般被设置成prototype的,因为Structs中设置了一些私有属性等状态参数;

八、ApplicationContext和BeanFactory的区别?
BeanFactory是spring最底层的接口,他提供了包括管理bean的生命周期、维护依赖关系、读取bean的配置文档等功能,ApplicationContext作为BeanFactory的派生,他提供了BeanFactory具有的所有功能;而且他继承了MessageSource,提供了国际化访问的功能(不修改代码,通过不同的语言和地区展示不同的语言界面);资源访问,如文件和url;载入多个有继承关系的上下文,可以让每一个上下文都专注于特定的层次;BeanFactory采用的是延迟加载的方式来实例化bean对象的,只有在调用bean的时候才会实例化,而ApplicationContext在容器启动的时候就会实例化singleton的bean,这样有利于我们在启动的时候就可以发现一些隐性的配置错误,而且提供了系统的运行速度,唯一的缺点是一开始就加载了大量的bean,消耗了系统的内存资源;

九、springAOP原理?
AOP具有特定的使用场景,它适用于那些具有横切逻辑的应用场合,比如性能测试、日志记录、事务管理、权限控制等;
AOP相关术语:
连接点:连接点由2个信息确定,一是用方法表示的程序执行点、二是用相对位置表示的方位,如在Test.foo方法执行前的连接点,执行点为Test.foo(),方位为该方法执行前的位置;spring使用切点对执行点进行定位,而方位则在增强类型中定义;

切点:每个程序类中可以拥有多个连接点,比如一个拥有2个方法的类,这两个方法都可以作为连接点,但是如何定位到需要的连接点,AOP是通过切点来实现的,在spring中,切点通过Pointcur接口进行描述,他使用类和方法作为连接点的查询条件,定位到具体的执行点;

增强:织入到目标类连接点上的一段程序代码。在spring里面,增强除用于描述一段程序代码外,还拥有另一个和连接点相关的信息,这边是执行点的方位,结合执行点的方位信息和切点信息,就可以找到特定的连接;因为增强包含执行点的方位信息,又包含执行逻辑,所以spring提供的增强接口都是带有方位名的,比如:BeforeAdvice、AfterReturnningAdvice、ThrowsAdvice等,BeforeAdvice表示方法执行前的位置,AfterReturnningAdvice表示方法返回后的位置,ThrowsAdvice表示抛出异常时执行;

SpringAOP使用动态代理技术在运行期间向目标类织入增强的代码;
SpringAOP使用了2种代理技术:
1、基于JDK的动态代理
2、基于CGLIB的动态代理
之所以需要两种动态代理技术,是因为JDK只提供了基于接口的代理,而不支持类的代理;
为什么JDK只提供了动态代理技术呢?因为java是单继承的,而我们的代理类需要继承Proxy的并且实现传入的接口;
JDK的动态代理主要涉及到了java.lang.reflect中的两个类:Proxy和InvocationHandler,其中InvocationHandler是一个接口,可以通过实现该接口定义横切逻辑,并通过反射机制调用目标类的代码,动态的将横切逻辑和业务逻辑组装在一起;而Proxy利用InvocationHandler创建一个符合某一接口的实例,生成目标类的代理对象。

CGLIB采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑;

jdk代理和cglib代理性能上的对比?
CGLIB创建的动态代理在运行速度上较与JDK动态代理要快10倍;
CGLIB创建对象的时间较与JDK要多不少,大概8倍;
因此对于singleton的代理对象或者是对于有实例池的代理,一般用CGLIB的比较合适,反之比较适用于JDK的代理;
经过个人实验,在jdk1.7及1.8上,jdk创建的代理在运行速度上已经较cglib差不多了;

十、spring提供的几种类型的增强?

  • 前置增强(BeforeAdvice):表示在目标类执行方法前执行的
  • 后置增强(AfterReturnningAdvice):表示在目标类执行方法后执行的
  • 环绕增强(MethodInterceptor):表示在目标方法执行前后执行
  • 异常抛出增强(ThrowsAdvice):表示方法在抛出异常之后实施增强
  • 引介增强(IntroductionInterceptor):表示在目标类中添加一些新的方法和属性;

十一、spring事务的传播类型?

  • PROPAGATION_REQUIRED:如果当前没有事务,则新建一个事务;如果已经存在一个事务,则加入到当前事务中;这是最常见的选择;
  • PROPAGATION_SUPPORTS:支持当前事务,如果当前没有事务则以非事务的方式执行;
  • PROPAGATION_MANDATORY:使用当前的事务,如果当前没有事务则抛出异常;
  • PROPAGATION_REQUEST_NEW:新建事务,如果当前存在事务,则将当前事务挂起;
  • PROPAGATION_NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务则将当前事务挂起;
  • PROPAGATION_NEVER:以非事务方式执行操作,如果当前存在事务则抛出异常;
  • PROPAGATION_NESTED:如果当前存在事务,则在嵌套事务内执行,如果当前不存在事务,则执行与PROPAGATION_REQUEST类似的操作;

PROPAGATION_REQUEST_NEW:启动一个新的、不依赖于环境的内部事务,这个事务完全被commited和rolled back,完全不依赖与外部事务,它拥有自己的隔离范围,自己的锁等,当内部事务执行时,外部事物被挂起,内部事务执行完成之后外部事物才继续执行;

PROPAGATION_NESTED:开启一个嵌套的事务,他是一个已经存在事务的真正的子事务,嵌套事务真正执行的时候,他会取得一个savepoint,如果这个嵌套事务失败,我们将回滚到savepoint,嵌套事务是外部事务的一部分,只有外部事务结束后他才会提交;

十二、嵌套事务中,如果父事务回滚,子事务会发生什么?事务的提交,是父事务先提交,子事务在提交还是子事务先提交,父事务在提交?
如果父事务回滚,子事务会跟着回滚,因为父事务提交之前,子事务是不会跟着提交的;
子事务先提交,父事务在提交,子事务是父事务的一部分,由父事务统一管理;

十三、在spring中开启事务的方法?
1、编程式事务
在spring的XML文件中配置基于TransactionalTemplate模板的bean,然后将bean注入到业务层中,在业务层代码中通过编程的方式来使用事务;
具体使用如下:


    
    
    
    
        
        
        
        
    
    
    
    
        
        
        
    
    
    
        
        
    
    
    
    
        
    
    
    
        
    
public void transfer(String out, String in, Double money) {
        atTemplate.execute(new TransactionCallbackWithoutResult() {
            
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                //调用DAO层接口修改数据库
            	accountDao.xxxx();
            }
        });
        
    }

2、声明式事务
声明式事务是建立在AOP之上的,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务,声明式事务优点是不需要通过编程的方式管理事务,这样就不需要在业务代码里面编写其他的非业务的代码,减少了代码的侵略性,只需要通过在配置文件中配置事务的切入规则或者基于@Transactional注解的方式声明事务;

	
    
    
        
    
     
    

基于AspectJ的XML声明式事务其实就是AOP在xml中的配置,通过AOP将事务织入到切入点;

 
 
	

 
  
	  
		 
		 
	 
   
  
   
	  
	   
	  
	 
 

十四、spring如何解决循环嵌套问题的?
当我们使用的是构造器注入的时候,如果出现循环嵌套的问题就会直接报错;如果使用的是set方式注入的话,会通过以下方式来解决;
spring通过三级缓存来解决循环嵌套的问题的,三级缓存分别是singletonObjects、earlySingletonObjects、singletonFactories;
当我们创建对象A,B的时候,其中A依赖B,B依赖A,此时spring通过调用ApplicationContext获取A对象,当时还没有A对象则创建A对象,此时发现A对象依赖B对象,则尝试通过递归的调用B对象的实例,然而spring中还没有B对象,则会创建B对象的实例,这个时候A对象和B对象都已经创建了,只是都是半成品,没有设置属性;在创建B对象之后,发现B对象依赖A对象,则尝试递归的获取A对象的实例,因为已经有了A对象的实例,虽然是半成品,就把A对象的实例返回给B对象了,此时B对象的属性就设置进去了,然后还是调用递归的返回,将B对象设置到A对象的属性中了;

我们创建bean的时候会先从cache中获取bean额,也就是singletonObjects,此时若获取不到并且对象正在创建中,则从二级缓存earlySingletonObjects中获取,如果还是获取不到singletonFactories通过getObjects获取,则从三级缓存singletonFactories中获取,若获取到了则从singletonFactories中移除,加入到earlySingletonObjects中;

十五、BeanFactory与FactoryBean的区别?
BeanFactory是spring中最底层的接口,他负责管理bean的生命周期,管理bean的依赖关系,读取bean的xml文件等;

FactoryBean是一个工厂bean,用户可以通过实现FactoryBean接口来定制化生成一些复杂逻辑的 bean,是一个能生产或者修饰对象生成的工厂bean;

十六、spring事务失效的原因?
1、非public修饰的方法上使用事务会失效;
2、static修饰的方法使用事务会失效;
3、final修饰的方法使用事务会失效;
4、如果被调用方法是会产生新的事务的话,如果使用的是this调用非spring代理方式调用,则回滚不会生效;这是因为只有被动态代理对象直接调用时才会生效,因为spring是监听模式,监听的是代理对象;
PROPAGATION_REQUEST:当前存在事务则使用当前事务,不存在则创建新的;
PROPAGATION_REQUEST_NEW:新建事务,若当前存在事务,则挂起当前事务;

你可能感兴趣的:(面试)