Spring的常见面试题

63 阅读43分钟

Spring

1.什么是spring?

spring是个java企业级应用的开源开发框架,框架目标是简化Java企业级应用开发,通过POJO为基础的编程模型促进良好的编程习惯。有些扩展是针对构建J2EE平台的web应用。

2.为什么使用Spring框架?

  • 轻量:Spring是轻量的,基本版本大约2MB。
  • 控制反转(IOC):Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
  • 面向切面编程(AOP):Spring支持面向切面编程,并且把应用业务逻辑和系统服务分开。
  • 容器:Spring包含并管理应用中对象的生命周期和配置。
  • MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。
  • 事务管理:Spring提供一个持续的事物管理接口,可以扩展到上至本地事物下至全局事务(JTA)。
  • 异常处理:Spring提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked异常。

3. Autowired和Resource关键字的区别?

@Resource和@Autowired都是注入bean时使用的注解,但@Resource不是Spring的注解,它是java自己的注解,属于javax.annotation.Resource包,是JSR-250标准,被绝大部分框架所支持,包括Spring。

  1. 共同点

    两者都可以写在字段和setter方法上。两者如果都写在字段上,则不需要再写setter方法上。

  2. 不同点

    @Autowired

    是spring提供的注解,注入bean时只按type注入。默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果想使用按名称(byName)来装配,可以结合@Qualifier注解一起使用。如下:

    public class TestServiceImpl{
     	@Autowired
     	@Qualifier("userDao")
     	private UserDao userDao;
    }
    

    ​ @Resource

    ​ 默认按照ByName自动注入。它含有两个重要的属性:name和type,而Spring将@Resrouce注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果都不指定,则通过反射机制使用byName自动注入策略。

    public class TestServiceImpl{
    	//下面两种方式只使用一种即可
    	@Resource(name="userDao")
    	private UserDao userDao; //用于字段上
    	
    	@Resrouce(name="userDao") //用于属性的setter方法
    	public void setUserDao(UserDao userDao){
    		this.userDao = userDao; 
    	}
    }
    

    注:最好将@Resource放在setter方法上,因为这样更符合面向对象的思想,通过set、get去操作属性,而不是直接去操作属性。

    @Resource装配顺序:

    1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常;
    2. 如果指定name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
    3. 如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
    4. 如果既没有指定name也没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自定装配。

    二者作用相当,只不过@Autowired按照byType自动注入。

4.依赖注入的方式有几种,各是什么?

  1. 构造器注入 将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注入。

​ 优点:对象初始化完成后便可获得可使用的对象;

​ 缺点:当需要注入的对象很多时。构造器参数列表将会很长,若有多种注入方式,每种方式只需注入指定几个依赖,那么就需要提供多个重载的构造函数,不够灵活。

​ 2.setter方法注入 IOC Service Provider通过调用成员变量提供的setter函数将被依赖对象注入给依赖类。

​ 优点:灵活,可以选择性的注入需要的对象。

​ 缺点: 依赖对象初始化完成后由于尚未注入被依赖对象,因此还不能使用。

  1. 接口注入 依赖类必须要实现指定的接口,然后实现该接口中的一个函数,该函数就是用于依赖注入。该函数的参数就是要注入的对象。

​ 优点:接口注入中,接口的名字、函数的名字都不重要,只要保证函数的参数是要注入的对象类型即可。

​ 缺点:侵入性太强(如果类A要使用别人提供的一个功能,为了使用此功能需要在自己的类中增加额外的代码,就叫侵入性),不建议使用。

5. 什么是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实现。

6. 如何理解Spring的AOP?

AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。

Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。

当然也可以使用AspectJ,Spring AOP中已经集成了AspectJ,AspectJ算是Java生态系统中最完整的AOP框架。使用AOP之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使 用即可,这样可以大大简化代码量。我们需要增加新功能也方便,提高了系统的扩展性。日志功能、事务管理和权限管理等场景都用到了AOP。

7.Spring AOP和AspectJ AOP有什么区别?

Spring AOP属于运行时增强,而AspectJ是编译时增强。Spring AOP基于代理(Proxying),而AspectJ基于字节码操作。

Spring AOP已经集成了AspectJ,AspectJ相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单。

如果切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择AspectJ,它比SpringAOP快很多。

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

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

**什么是连接点?**连接点代表一个应用程序的某个位置,这个位置可以插入一个AOP切面,它实际上是应用程序执行Spring AOP的位置。

**什么是切入点?**切入点是一个或一组连接点,通知将在这些位置执行,可通过表达式或匹配的方式指明切入点。

**什么是通知?有哪些类型?**通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码段。

Spring切面可以应用五种类型的通知:

  • before: 前置通知,在一个方法执行前被调用
  • after: 在方法执行之后调用的通知,无论方法执行是否成功
  • after-returing:仅当方法成功完成后执行的通知
  • after-throwing:在方法抛出异常退出时执行的通知
  • around:在方法执行之前和之后调用的通知

9. 如何理解Spring的IOC?

  1. IOC就是控制反转,是指创建对象的控制权的转移。以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系。对象之间松散耦合,便于功能复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即应用程序在运行时依赖IOC容器来动态注入对象需要的外部资源。
  2. 最直观的表达就是,IOC让对象的创建不用去new,可以由Spring自动生产,使用Java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法
  3. Spring的IOC有三种注入方式:构造器注入、setter方法注入、根据注解注入。

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

10.Spring bean的生命周期

Servlet的生命周期:实例化、初始化init、接收请求service、销毁destroy

Spring上下文中的Bean生命周期也类似,如下:

  1. 实例化Bean 对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean或初始化bean的时候需要注入另一个尚未初始化的依赖时,BeanFactory会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。

  2. 设置对象属性(依赖注入) 实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成依赖注入。

  3. 处理Aware接口: Spring检测该对象是否实现xxxAware接口,并将相关的xxxAware实例注入给Bean:

    ​ ①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值;

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

    ​ ③如果这个Bean已经实现了ApplicationContextAware接口,会调用 setApplicationContext(ApplicationContext)方法,传入Spring上下文

  4. BeanPostProcessor:如果要对Bean进行自定义的处理,可以让Bean实现BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj,String s)方法。

  5. InitializingBean与init-methid: 如果Bean在Spring配置文件中配置了init-method属性,则会自动调用其配置的初始化方法。

  6. 如果整个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj,String s)方法,由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;

    以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了

  7. DisposableBean: 当Bean不在需要时,会经过清理阶段,如果Bean实现DisposableBean这个接口,会调用其实现的destory()方法

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

11. Spring bean的作用域有哪些?

  1. singleton: 默认,每个容器中只有一个bean的实例,单例模式由BeanFactory自身来维护
  2. prototype:为每个bean请求提供一个实例
  3. request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收
  4. session: 与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
  5. global-session: 全局作用域,和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。

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

  1. set方法注入
  2. 构造器注入: 通过index设置参数的位置.;通过type设置参数类型
  3. 静态工厂注入;
  4. 实例工厂注入。

通常回答前面两种即可,后面两种很多人都不太会,到时问到就尴尬了

13.Spring框架中都用到了哪些设计模式?

不仅要回答设计模式,还要回答每个设计模式在Spring中如何使用的

简单工厂模式: Spring的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是在传入参数后创建还是传入参数前创建,要根据情况来定。

工厂模式: FactoryBean就是典型的工厂方法模式。其特点是,Spring在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是getObject()方法的返回值。

单例模式: 单例模式体现在scope="singleton",注册式单例模式,bean存放于map中,bean name为key,bean当作value。

原型模式: 原型模式体现在scope="prototype",每次获取的是通过克隆生成的新实例,对其进行修改对原有实例对象不造成影响。

迭代器模式: 在Spring中有个Compositelterator实现了Iterator,实现此接口或Iterable接口则表示某个对象是可被迭代的

代理模式: Spring中经典的AOP,使用了动态代理,分JDK和CGlib动态代理。

适配器模式: Spring中以Adapter结尾的类,大多数都是适配器模式。如AOP中AdvisorAdapter类,它有三个实现:MethodBeforAdviceAdapter、AfterReturnningAdviceAdapter、ThrowsAdviceAdapter。根据不同的AOP配置来使用对应的Advice,与策略模式不同的是,一个方法可以同时拥有多个Advice。

观察者模式: Spring中的Event和Listener。Spring事件:ApplicationEvent,该抽象类继承了EventObject类,JDK建议所有的事件都应该继承自EventObject。Spring事件监听器:ApplicationListener,该接口继承了EventListener接口,JDK建议所有的事件监听器都应该继承EventListener。

**模板模式:**Spring中的JdbcTemplate就是非常经典的模板模式的应用,里面的execute方法,把整个算法步骤都定义好了。

责任链模式: DispatcherServlet中的doDispatch()方法中获取与请求匹配的处理器HandlerExecutionChain,this.getHandler()方法的处理使用了责任链模式。

注意:这里只是列举了部分设计模式,比如还有享元模式、建造者模式等。可选择性回答,避免再问出现三不知就很尴尬了。

14.Spring中ApplicationContext和BeanFactory的区别

包目录不同

  • spring-beans.jar 中org.springframework.beans.factory.BeanFactory
  • spring-context.jar 中 org.springframework.context.ApplicationContext

国际化

BeanFactory是不支持国际化功能的,因为BeanFactory没有扩展Spring中MessageResource接口。相反,由于ApplicationContext扩展了MessageResource接口,因而具有消息处理的能力(i18N)。

强大的事件机制(Event)

基本上牵涉到事件方面的设计,就离不开观察者模式,ApplicationContext的事件机制主要通过ApplicationEvent和ApplicationListener这两个接口提供的,和Java swing中的事件机制一样。即当ApplicationContext中发布一个事件时,所有扩展了ApplicationListener的Bean都会接收到这个事件,进行响应的处理。

底层资源的访问

ApplicationContext扩展了ResourceLoader(资源加载器)接口,从而可用来加载多个Resource,而BeanFactory没有扩展ResourceLoader。

对Web应用的支持

与BeanFactory通常以编程的方式被创建,ApplicationContext能以声明的方式创建,如使用ContextLoader。当然也可以使用ApplicationContext的实现方式之一,以编程的方式创建ApplicationContext实例。

延迟加载

  1. BeanFactory采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对改Bean进行加载实例化。这样,我们就不能发现一些存在的spring配置问题。而ApplicationContext相反,它时在容器启动时,一次性创建了所有的Bean。这样在容器启动时就可以发现Spring中存在的配置错误。
  2. BeanFactory和ApplicationContext都支持BeanPostProccessor、BeanFactoryPostProcessor的使用。两者的区别是BeanFactory需要手动注册,而ApplicationContext是自动注册。

可以看到,ApplicationContext继承了BeanFactory,BeanFactory是Spring中比较原始的Factory,它不支持AOP、Web等Spring插件。而ApplicationContext不仅包含了BeanFactory的所有功能,还支持Spring的各种插件,以一种面向框架的方式工作以及对上下文进行分层和实现继承。

BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;而 ApplicationContext 面向使用 Spring 的开发者,相比 BeanFactory 提供了更多面向实际应用的功能,几乎所有场合都可以直接使 用 ApplicationContext,而不是底层的 BeanFactory。

常用容器

BeanFactory 类型的有 XmlBeanFactory,它可以根据 XML 文件中定义的内容,创建相应的 Bean。

ApplicationContext 类型的常用容器有: 1. ClassPathXmlApplicationContext:从 ClassPath 的 XML 配置文件中读取上下文,并生成上 下文定义。应用程序上下文从程序环境变量中取得。 2. FileSystemXmlApplicationContext:由文件系统中的 XML 配置文件读取上下文。 3. XmlWebApplicationContext:由 Web 应用的 XML 文件读取上下文。例如我们在 Spring MVC 使用的情况。

15.Spring框架中的单例Bean是线程安全的吗?

Spring没有对单例Bean做任何多线程的封装处理。

  • 关于单例Bean的线程安全和并发问题,需要开发者自行处理
  • 单例的线程安全问题,并不是Spring应当关心的。Spring做的是根据配置创建单例Bean或者多例Bean的功能。

当然,大部分的Spring Bean并没有可变的状态,所有某种程度上说Spring的单例Bean是线程安全的。如果你的Bean有多种状态的化,就需要自行保证线程安全,最浅显的解决办法就是将多态Bean的作用域由Singleton变更为Prototype

16.Spring如何处理循环依赖的?

循环依赖

流程大致如下:

  1. 首先A完成初始化第一步并同股票ObjectFactory将自己提前曝光出来,在初始化的时候,发现自己依赖对象B,此时回去尝试get(B),这个时候发现B还没被创建出来;
  2. 然后B就走创建流程,在B初始化的时候,同样发现自己依赖C,C也没有被创建出来;
  3. 此时C也走创建流程,但是在初始化的过程中发现自己依赖A,于是尝试get(A)。这个时候由于A已经添加到缓存中(一般都是添加至三级缓存singletonFactories),通过ObjectFactory提前曝光,所以可通过ObjectFactory#getObject()方法来拿到A对象。C拿到A对象后顺利完成初始化,将自己添加到一级缓存中;
  4. 回到B,B也拿到C对象,完成初始化,A可以顺利拿到B完成初始化。此时整个链路就已经完成了初始化过程。

关键字: 三级缓存、提前曝光。

17.说说事务的隔离级别

未提交读(Read Uncommitted): 允许脏读,也就是可能读取到其他会话中未提交事物修改的数据

提交读(Read Committed): 只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别(不重复读)

**可重复读(Repeated Read): ** 在同一个事务内的查询都是事务开始时刻一致的,MySQL的InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是仍存在幻读(多个事务同时修改同一条记录,事务之间不知道彼此存在,当事务提交之后,后面的事务修改的数据会覆盖前事务,前一个事务就像发生幻觉一样)

**可串行化(Serializable): ** 完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。

事务隔离级别脏读不可重复读幻读
读未提交(Read Uncommitted)允许允许允许
读已提交(Read Committed)禁止允许允许
可重复读(Repeated Read)禁止禁止允许
顺序读(Serializable)禁止禁止禁止

不可重复读和幻读的区别主要是:解决不可重复读需要锁定了当前满足条件的记录,而解决幻读需要锁定当前满足条件的记录及相近的记录。比如查询某个商品的信息,可重复读事务隔离级别可以保证当前商品信息被锁定,解决不可重复读;但是如果统计商品个数,中途有记录插入,可重复读事务隔离级别就不能保证两个事务统计的个数相同。

18.Spring事务的传播级别?

Spring事务定义了7种传播机制:

  1. PROPAGETION_REQUIRED: 默认的Spring事务传播级别,若当前存在事务,则加入该事务,若不存在则新建一个事务。
  2. PROPAGETION——REQUIRE_NEW: 若当前没有事务,则新建一个事务。若当前存在事务,则新建一个事务,新老事务相互独立,外部事物抛出异常回滚不会影响内部事务的正常提交。
  3. PROPAGETION_NESTED: 如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,类似于REQUIRE_NEW。
  4. PROPAGETION_SUPPORTS: 支持当前事务,若当前不存在事务,以非事务的方式执行。
  5. PROPAGETION_NOT_SUPPORTS: 以非事务的方式执行,若当前存在事务,则把当前事务挂起。
  6. PROPAGETION_MANDATORY: 强制事务执行,若当前不存在事务,则抛出异常。
  7. PROPAGETION_NEVER: 以非事务方式执行,如果当前存在事务,则抛出异常

Spring事务传播级别一般不需要定义,默认就是PROPAGETION_REQUIRED,除非在嵌套事务的情况下需要重点了解。

19.Spring事务实现方式

编程式事务管理: 这意味着可以通过编程的方式管理事务,具有很大的灵活性,但难以维护。

声明式事务管理: 这种方式意味着可以将事务管理和业务代码分离。只需通过注解或者XML配置管理事务。

20. Spring框架的事务管理优点?

它为不同的事务API(JTA、JDBC、Hibernate、JPA、JDO)提供了统一的编程模型。它为编程式事务管理提供了一个简单的API而非一系列复杂的事物API(JTA)。它支持声明式事务管理,可以和Spring的多种数据访问技术很好的融合。

21.事务三要素是什么?

数据源: 表示具体的事务性资源,是事务的真正处理者,如MYSQL等

事务管理器: 像一个大管家,从整体上管理事务的处理过程,如打开、提交、回滚等

事务应用和属性配置: 像一个标识符,表明哪些方法要参与事务,如何参与事务,以及一些相关属性如隔离级别、超时时间等

22. 事务注解的本质是什么?

@Transactional 这个注解仅仅是一些(和事务相关的)元数据,在运行时被事务基础设施读取消费,并使用这些元数据来配置bean的事务行为。大致来说具有两方面功能,一是表明该方法要参与事务,二是配置相关属性来定制事务的参与方式和运行行为

声明式事务主要是得益于Spring AOP。使用一个事务拦截器,在方法调用的前后/周围进行事务性增强(advice),来驱动事务完成。

@Transactional注解既可以标注在类上,也可以标注在方法上。当在类上时,默认应用到类里的所有方法,如果此时方法上也标注了,则方法上的优先级高,另外注意,方法一定要是public的。

Spring

1.什么是spring?

spring是个java企业级应用的开源开发框架,框架目标是简化Java企业级应用开发,通过POJO为基础的编程模型促进良好的编程习惯。有些扩展是针对构建J2EE平台的web应用。

2.为什么使用Spring框架?

  • 轻量:Spring是轻量的,基本版本大约2MB。
  • 控制反转(IOC):Spring通过控制反转实现了松散耦合,对象们给出它们的依赖,而不是创建或查找依赖的对象们。
  • 面向切面编程(AOP):Spring支持面向切面编程,并且把应用业务逻辑和系统服务分开。
  • 容器:Spring包含并管理应用中对象的生命周期和配置。
  • MVC框架:Spring的WEB框架是个精心设计的框架,是Web框架的一个很好的替代品。
  • 事务管理:Spring提供一个持续的事物管理接口,可以扩展到上至本地事物下至全局事务(JTA)。
  • 异常处理:Spring提供方便的API把具体技术相关的异常(比如由JDBC,Hibernate or JDO抛出的)转化为一致的unchecked异常。

3. Autowired和Resource关键字的区别?

@Resource和@Autowired都是注入bean时使用的注解,但@Resource不是Spring的注解,它是java自己的注解,属于javax.annotation.Resource包,是JSR-250标准,被绝大部分框架所支持,包括Spring。

  1. 共同点

    两者都可以写在字段和setter方法上。两者如果都写在字段上,则不需要再写setter方法上。

  2. 不同点

    @Autowired

    是spring提供的注解,注入bean时只按type注入。默认情况下它要求依赖对象必须存在,如果允许null值,可以设置它的required属性为false。如果想使用按名称(byName)来装配,可以结合@Qualifier注解一起使用。如下:

    public class TestServiceImpl{
     	@Autowired
     	@Qualifier("userDao")
     	private UserDao userDao;
    }
    

    ​ @Resource

    ​ 默认按照ByName自动注入。它含有两个重要的属性:name和type,而Spring将@Resrouce注解的name属性解析为bean的名字,而type属性则解析为bean的类型。所以,如果使用name属性,则使用byName的自动注入策略,而使用type属性时则使用byType自动注入策略。如果都不指定,则通过反射机制使用byName自动注入策略。

    public class TestServiceImpl{
    	//下面两种方式只使用一种即可
    	@Resource(name="userDao")
    	private UserDao userDao; //用于字段上
    	
    	@Resrouce(name="userDao") //用于属性的setter方法
    	public void setUserDao(UserDao userDao){
    		this.userDao = userDao; 
    	}
    }
    

    注:最好将@Resource放在setter方法上,因为这样更符合面向对象的思想,通过set、get去操作属性,而不是直接去操作属性。

    @Resource装配顺序:

    1. 如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常;
    2. 如果指定name,则从上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常。
    3. 如果指定了type,则从上下文中找到类似匹配的唯一bean进行装配,找不到或是找到多个,都会抛出异常。
    4. 如果既没有指定name也没有指定type,则自动按照byName方式进行装配;如果没有匹配,则回退为一个原始类型进行匹配,如果匹配则自定装配。

    二者作用相当,只不过@Autowired按照byType自动注入。

4.依赖注入的方式有几种,各是什么?

  1. 构造器注入 将被依赖对象通过构造函数的参数注入给依赖对象,并且在初始化对象的时候注入。

​ 优点:对象初始化完成后便可获得可使用的对象;

​ 缺点:当需要注入的对象很多时。构造器参数列表将会很长,若有多种注入方式,每种方式只需注入指定几个依赖,那么就需要提供多个重载的构造函数,不够灵活。

​ 2.setter方法注入 IOC Service Provider通过调用成员变量提供的setter函数将被依赖对象注入给依赖类。

​ 优点:灵活,可以选择性的注入需要的对象。

​ 缺点: 依赖对象初始化完成后由于尚未注入被依赖对象,因此还不能使用。

  1. 接口注入 依赖类必须要实现指定的接口,然后实现该接口中的一个函数,该函数就是用于依赖注入。该函数的参数就是要注入的对象。

​ 优点:接口注入中,接口的名字、函数的名字都不重要,只要保证函数的参数是要注入的对象类型即可。

​ 缺点:侵入性太强(如果类A要使用别人提供的一个功能,为了使用此功能需要在自己的类中增加额外的代码,就叫侵入性),不建议使用。

5. 什么是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实现。

6. 如何理解Spring的AOP?

AOP(Aspect-Oriented Programming,面向切面编程)能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,并有利于未来的可扩展性和可维护性。

Spring AOP是基于动态代理的,如果要代理的对象实现了某个接口,那么Spring AOP就会使用JDK动态代理去创建代理对象;而对于没有实现接口的对象,就无法使用JDK动态代理,转而使用CGlib动态代理生成一个被代理对象的子类来作为代理。

当然也可以使用AspectJ,Spring AOP中已经集成了AspectJ,AspectJ算是Java生态系统中最完整的AOP框架。使用AOP之后我们可以把一些通用功能抽象出来,在需要用到的地方直接使 用即可,这样可以大大简化代码量。我们需要增加新功能也方便,提高了系统的扩展性。日志功能、事务管理和权限管理等场景都用到了AOP。

7.Spring AOP和AspectJ AOP有什么区别?

Spring AOP属于运行时增强,而AspectJ是编译时增强。Spring AOP基于代理(Proxying),而AspectJ基于字节码操作。

Spring AOP已经集成了AspectJ,AspectJ相比于Spring AOP功能更加强大,但是Spring AOP相对来说更简单。

如果切面比较少,那么两者性能差异不大。但是,当切面太多的话,最好选择AspectJ,它比SpringAOP快很多。

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

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

**什么是连接点?**连接点代表一个应用程序的某个位置,这个位置可以插入一个AOP切面,它实际上是应用程序执行Spring AOP的位置。

**什么是切入点?**切入点是一个或一组连接点,通知将在这些位置执行,可通过表达式或匹配的方式指明切入点。

**什么是通知?有哪些类型?**通知是个在方法执行前或执行后要做的动作,实际上是程序执行时要通过SpringAOP框架触发的代码段。

Spring切面可以应用五种类型的通知:

  • before: 前置通知,在一个方法执行前被调用
  • after: 在方法执行之后调用的通知,无论方法执行是否成功
  • after-returing:仅当方法成功完成后执行的通知
  • after-throwing:在方法抛出异常退出时执行的通知
  • around:在方法执行之前和之后调用的通知

9. 如何理解Spring的IOC?

  1. IOC就是控制反转,是指创建对象的控制权的转移。以前创建对象的主动权和时机是由自己把控的,而现在这种权力转移到Spring容器中,并由容器根据配置文件去创建实例和管理各个实例之间的依赖关系。对象之间松散耦合,便于功能复用。DI依赖注入,和控制反转是同一个概念的不同角度的描述,即应用程序在运行时依赖IOC容器来动态注入对象需要的外部资源。
  2. 最直观的表达就是,IOC让对象的创建不用去new,可以由Spring自动生产,使用Java的反射机制,根据配置文件在运行时动态的去创建对象以及管理对象,并调用对象的方法
  3. Spring的IOC有三种注入方式:构造器注入、setter方法注入、根据注解注入。

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

10.Spring bean的生命周期

Servlet的生命周期:实例化、初始化init、接收请求service、销毁destroy

Spring上下文中的Bean生命周期也类似,如下:

  1. 实例化Bean 对于BeanFactory容器,当客户向容器请求一个尚未初始化的bean或初始化bean的时候需要注入另一个尚未初始化的依赖时,BeanFactory会调用createBean进行实例化。对于ApplicationContext容器,当容器启动结束后,通过获取BeanDefinition对象中的信息,实例化所有的bean。

  2. 设置对象属性(依赖注入) 实例化后的对象被封装在BeanWrapper对象中,紧接着,Spring根据BeanDefinition中的信息以及通过BeanWrapper提供的设置属性的接口完成依赖注入。

  3. 处理Aware接口: Spring检测该对象是否实现xxxAware接口,并将相关的xxxAware实例注入给Bean:

    ​ ①如果这个Bean已经实现了BeanNameAware接口,会调用它实现的setBeanName(String beanId)方法,此处传递的就是Spring配置文件中Bean的id值;

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

    ​ ③如果这个Bean已经实现了ApplicationContextAware接口,会调用 setApplicationContext(ApplicationContext)方法,传入Spring上下文

  4. BeanPostProcessor:如果要对Bean进行自定义的处理,可以让Bean实现BeanPostProcessor接口,那将会调用postProcessBeforeInitialization(Object obj,String s)方法。

  5. InitializingBean与init-methid: 如果Bean在Spring配置文件中配置了init-method属性,则会自动调用其配置的初始化方法。

  6. 如果整个Bean实现了BeanPostProcessor接口,将会调用postProcessAfterInitialization(Object obj,String s)方法,由于这个方法是在Bean初始化结束时调用的,所以可以被应用于内存或缓存技术;

    以上几个步骤完成后,Bean就已经被正确创建了,之后就可以使用这个Bean了

  7. DisposableBean: 当Bean不在需要时,会经过清理阶段,如果Bean实现DisposableBean这个接口,会调用其实现的destory()方法

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

11. Spring bean的作用域有哪些?

  1. singleton: 默认,每个容器中只有一个bean的实例,单例模式由BeanFactory自身来维护
  2. prototype:为每个bean请求提供一个实例
  3. request:为每一个网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收
  4. session: 与request范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。
  5. global-session: 全局作用域,和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果你想要声明让所有的portlet共用全局的存储变量的话,那么全局变量需要存储在global-session中。全局作用域与Servlet中的session作用域效果相同。

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

  1. set方法注入
  2. 构造器注入: 通过index设置参数的位置.;通过type设置参数类型
  3. 静态工厂注入;
  4. 实例工厂注入。

通常回答前面两种即可,后面两种很多人都不太会,到时问到就尴尬了

13.Spring框架中都用到了哪些设计模式?

不仅要回答设计模式,还要回答每个设计模式在Spring中如何使用的

简单工厂模式: Spring的BeanFactory就是简单工厂模式的体现,根据传入一个唯一的标识来获得Bean对象,但是在传入参数后创建还是传入参数前创建,要根据情况来定。

工厂模式: FactoryBean就是典型的工厂方法模式。其特点是,Spring在使用getBean()调用获得该bean时,会自动调用该bean的getObject()方法,所以返回的不是factory这个bean,而是getObject()方法的返回值。

单例模式: 单例模式体现在scope="singleton",注册式单例模式,bean存放于map中,bean name为key,bean当作value。

原型模式: 原型模式体现在scope="prototype",每次获取的是通过克隆生成的新实例,对其进行修改对原有实例对象不造成影响。

迭代器模式: 在Spring中有个Compositelterator实现了Iterator,实现此接口或Iterable接口则表示某个对象是可被迭代的

代理模式: Spring中经典的AOP,使用了动态代理,分JDK和CGlib动态代理。

适配器模式: Spring中以Adapter结尾的类,大多数都是适配器模式。如AOP中AdvisorAdapter类,它有三个实现:MethodBeforAdviceAdapter、AfterReturnningAdviceAdapter、ThrowsAdviceAdapter。根据不同的AOP配置来使用对应的Advice,与策略模式不同的是,一个方法可以同时拥有多个Advice。

观察者模式: Spring中的Event和Listener。Spring事件:ApplicationEvent,该抽象类继承了EventObject类,JDK建议所有的事件都应该继承自EventObject。Spring事件监听器:ApplicationListener,该接口继承了EventListener接口,JDK建议所有的事件监听器都应该继承EventListener。

**模板模式:**Spring中的JdbcTemplate就是非常经典的模板模式的应用,里面的execute方法,把整个算法步骤都定义好了。

责任链模式: DispatcherServlet中的doDispatch()方法中获取与请求匹配的处理器HandlerExecutionChain,this.getHandler()方法的处理使用了责任链模式。

注意:这里只是列举了部分设计模式,比如还有享元模式、建造者模式等。可选择性回答,避免再问出现三不知就很尴尬了。

14.Spring中ApplicationContext和BeanFactory的区别

包目录不同

  • spring-beans.jar 中org.springframework.beans.factory.BeanFactory
  • spring-context.jar 中 org.springframework.context.ApplicationContext

国际化

BeanFactory是不支持国际化功能的,因为BeanFactory没有扩展Spring中MessageResource接口。相反,由于ApplicationContext扩展了MessageResource接口,因而具有消息处理的能力(i18N)。

强大的事件机制(Event)

基本上牵涉到事件方面的设计,就离不开观察者模式,ApplicationContext的事件机制主要通过ApplicationEvent和ApplicationListener这两个接口提供的,和Java swing中的事件机制一样。即当ApplicationContext中发布一个事件时,所有扩展了ApplicationListener的Bean都会接收到这个事件,进行响应的处理。

底层资源的访问

ApplicationContext扩展了ResourceLoader(资源加载器)接口,从而可用来加载多个Resource,而BeanFactory没有扩展ResourceLoader。

对Web应用的支持

与BeanFactory通常以编程的方式被创建,ApplicationContext能以声明的方式创建,如使用ContextLoader。当然也可以使用ApplicationContext的实现方式之一,以编程的方式创建ApplicationContext实例。

延迟加载

  1. BeanFactory采用的是延迟加载形式来注入Bean的,即只有在使用到某个Bean时(调用getBean()),才对改Bean进行加载实例化。这样,我们就不能发现一些存在的spring配置问题。而ApplicationContext相反,它时在容器启动时,一次性创建了所有的Bean。这样在容器启动时就可以发现Spring中存在的配置错误。
  2. BeanFactory和ApplicationContext都支持BeanPostProccessor、BeanFactoryPostProcessor的使用。两者的区别是BeanFactory需要手动注册,而ApplicationContext是自动注册。

可以看到,ApplicationContext继承了BeanFactory,BeanFactory是Spring中比较原始的Factory,它不支持AOP、Web等Spring插件。而ApplicationContext不仅包含了BeanFactory的所有功能,还支持Spring的各种插件,以一种面向框架的方式工作以及对上下文进行分层和实现继承。

BeanFactory 是 Spring 框架的基础设施,面向 Spring 本身;而 ApplicationContext 面向使用 Spring 的开发者,相比 BeanFactory 提供了更多面向实际应用的功能,几乎所有场合都可以直接使 用 ApplicationContext,而不是底层的 BeanFactory。

常用容器

BeanFactory 类型的有 XmlBeanFactory,它可以根据 XML 文件中定义的内容,创建相应的 Bean。

ApplicationContext 类型的常用容器有: 1. ClassPathXmlApplicationContext:从 ClassPath 的 XML 配置文件中读取上下文,并生成上 下文定义。应用程序上下文从程序环境变量中取得。 2. FileSystemXmlApplicationContext:由文件系统中的 XML 配置文件读取上下文。 3. XmlWebApplicationContext:由 Web 应用的 XML 文件读取上下文。例如我们在 Spring MVC 使用的情况。

15.Spring框架中的单例Bean是线程安全的吗?

Spring没有对单例Bean做任何多线程的封装处理。

  • 关于单例Bean的线程安全和并发问题,需要开发者自行处理
  • 单例的线程安全问题,并不是Spring应当关心的。Spring做的是根据配置创建单例Bean或者多例Bean的功能。

当然,大部分的Spring Bean并没有可变的状态,所有某种程度上说Spring的单例Bean是线程安全的。如果你的Bean有多种状态的化,就需要自行保证线程安全,最浅显的解决办法就是将多态Bean的作用域由Singleton变更为Prototype

16.Spring如何处理循环依赖的?

循环依赖.png 流程大致如下:

  1. 首先A完成初始化第一步并同股票ObjectFactory将自己提前曝光出来,在初始化的时候,发现自己依赖对象B,此时回去尝试get(B),这个时候发现B还没被创建出来;
  2. 然后B就走创建流程,在B初始化的时候,同样发现自己依赖C,C也没有被创建出来;
  3. 此时C也走创建流程,但是在初始化的过程中发现自己依赖A,于是尝试get(A)。这个时候由于A已经添加到缓存中(一般都是添加至三级缓存singletonFactories),通过ObjectFactory提前曝光,所以可通过ObjectFactory#getObject()方法来拿到A对象。C拿到A对象后顺利完成初始化,将自己添加到一级缓存中;
  4. 回到B,B也拿到C对象,完成初始化,A可以顺利拿到B完成初始化。此时整个链路就已经完成了初始化过程。

关键字: 三级缓存、提前曝光。

17.说说事务的隔离级别

未提交读(Read Uncommitted): 允许脏读,也就是可能读取到其他会话中未提交事物修改的数据

提交读(Read Committed): 只能读取到已经提交的数据。Oracle等多数数据库默认都是该级别(不重复读)

**可重复读(Repeated Read): ** 在同一个事务内的查询都是事务开始时刻一致的,MySQL的InnoDB默认级别。在SQL标准中,该隔离级别消除了不可重复读,但是仍存在幻读(多个事务同时修改同一条记录,事务之间不知道彼此存在,当事务提交之后,后面的事务修改的数据会覆盖前事务,前一个事务就像发生幻觉一样)

**可串行化(Serializable): ** 完全串行化的读,每次读都需要获得表级共享锁,读写相互都会阻塞。

事务隔离级别脏读不可重复读幻读
读未提交(Read Uncommitted)允许允许允许
读已提交(Read Committed)禁止允许允许
可重复读(Repeated Read)禁止禁止允许
顺序读(Serializable)禁止禁止禁止

不可重复读和幻读的区别主要是:解决不可重复读需要锁定了当前满足条件的记录,而解决幻读需要锁定当前满足条件的记录及相近的记录。比如查询某个商品的信息,可重复读事务隔离级别可以保证当前商品信息被锁定,解决不可重复读;但是如果统计商品个数,中途有记录插入,可重复读事务隔离级别就不能保证两个事务统计的个数相同。

18.Spring事务的传播级别?

Spring事务定义了7种传播机制:

  1. PROPAGETION_REQUIRED: 默认的Spring事务传播级别,若当前存在事务,则加入该事务,若不存在则新建一个事务。
  2. PROPAGETION——REQUIRE_NEW: 若当前没有事务,则新建一个事务。若当前存在事务,则新建一个事务,新老事务相互独立,外部事物抛出异常回滚不会影响内部事务的正常提交。
  3. PROPAGETION_NESTED: 如果当前存在事务,则嵌套在当前事务中执行。如果当前没有事务,则新建一个事务,类似于REQUIRE_NEW。
  4. PROPAGETION_SUPPORTS: 支持当前事务,若当前不存在事务,以非事务的方式执行。
  5. PROPAGETION_NOT_SUPPORTS: 以非事务的方式执行,若当前存在事务,则把当前事务挂起。
  6. PROPAGETION_MANDATORY: 强制事务执行,若当前不存在事务,则抛出异常。
  7. PROPAGETION_NEVER: 以非事务方式执行,如果当前存在事务,则抛出异常

Spring事务传播级别一般不需要定义,默认就是PROPAGETION_REQUIRED,除非在嵌套事务的情况下需要重点了解。

19.Spring事务实现方式

编程式事务管理: 这意味着可以通过编程的方式管理事务,具有很大的灵活性,但难以维护。

声明式事务管理: 这种方式意味着可以将事务管理和业务代码分离。只需通过注解或者XML配置管理事务。

20. Spring框架的事务管理优点?

它为不同的事务API(JTA、JDBC、Hibernate、JPA、JDO)提供了统一的编程模型。它为编程式事务管理提供了一个简单的API而非一系列复杂的事物API(JTA)。它支持声明式事务管理,可以和Spring的多种数据访问技术很好的融合。

21.事务三要素是什么?

数据源: 表示具体的事务性资源,是事务的真正处理者,如MYSQL等

事务管理器: 像一个大管家,从整体上管理事务的处理过程,如打开、提交、回滚等

事务应用和属性配置: 像一个标识符,表明哪些方法要参与事务,如何参与事务,以及一些相关属性如隔离级别、超时时间等

22. 事务注解的本质是什么?

@Transactional 这个注解仅仅是一些(和事务相关的)元数据,在运行时被事务基础设施读取消费,并使用这些元数据来配置bean的事务行为。大致来说具有两方面功能,一是表明该方法要参与事务,二是配置相关属性来定制事务的参与方式和运行行为

声明式事务主要是得益于Spring AOP。使用一个事务拦截器,在方法调用的前后/周围进行事务性增强(advice),来驱动事务完成。

@Transactional注解既可以标注在类上,也可以标注在方法上。当在类上时,默认应用到类里的所有方法,如果此时方法上也标注了,则方法上的优先级高,另外注意,方法一定要是public的。