spring常见面试问题

142 阅读47分钟

以下为spring常见面试问题:

  1、什么是Spring框架?Spring框架有哪些主要模块?

Spring框架是一个为Java应用程序的开发提供了综合、广泛的基础性支持的Java平台。 Spring帮助开发者解决了开发中基础性的问题,使得开发人员可以专注于应用程序的开发。 Spring框架本身亦是按照设计模式精心打造,这使得我们可以在开发环境中安心的集成Spring框架,不必担心Spring是如何在后台进行工作的。 Spring框架至今已集成了20多个模块。这些模块主要被分如下图所示的核心容器、数据访问/集成,、Web、AOP(面向切面编程)、工具、消息和测试模块。

2,IOC容器的初始化分为三个过程实现:

第一个过程是Resource资源定位。这个Resouce指的是BeanDefinition的资源定位。这个过程就是容器找数据的过程,就像水桶装水需要先找到水一样。 第二个过程是BeanDefinition的载入过程。这个载入过程是把用户定义好的Bean表示成Ioc容器内部的数据结构,而这个容器内部的数据结构就是BeanDefition。 第三个过程是向IOC容器注册这些BeanDefinition的过程,这个过程就是将前面的BeanDefition保存到HashMap中的过程。 第四步如果单例有lazy-init 且值为false进行相关bean预加载 (lazy-init=”true”),在getBean时才会实例化对象。 如果scope=”prototype”时,无论lazy-init的值是什么都只会在使用时才会创建

3、使用Spring框架能带来哪些好处?  

下面列举了一些使用Spring框架带来的主要好处:

Dependency Injection(DI) 方法使得构造器和JavaBean properties文件中的依赖关系一目了然。

与EJB容器相比较,IoC容器更加趋向于轻量级。这样一来IoC容器在有限的内存和CPU资源的情况下进行应用程序的开发和发布就变得十分有利。

Spring并没有闭门造车,Spring利用了已有的技术比如ORM框架、logging框架、J2EE、Quartz和JDK Timer,以及其他视图技术。

Spring框架是按照模块的形式来组织的。由包和类的编号就可以看出其所属的模块,开发者仅仅需要选用他们需要的模块即可。

要测试一项用Spring开发的应用程序十分简单,因为测试相关的环境代码都已经囊括在框架中了。更加简单的是,利用JavaBean形式的POJO类,可以很方便的利用依赖注入来写入测试数据。

Spring的Web框架亦是一个精心设计的Web MVC框架,为开发者们在web框架的选择上提供了一个除了主流框架比如Struts、过度设计的、不流行web框架的以外的有力选项。

Spring提供了一个便捷的事务管理接口,适用于小型的本地事物处理(比如在单DB的环境下)和复杂的共同事物处理(比如利用JTA的复杂DB环境)。

4、什么是控制反转(IOC)?什么是依赖注入?

控制反转是应用于软件工程领域中的,在运行时被装配器对象来绑定耦合对象的一种编程技巧,对象之间耦合关系在编译时通常是未知的。在传统的编程方式中,业 务逻辑的流程是由应用程序中的早已被设定好关联关系的对象来决定的。在使用控制反转的情况下,业务逻辑的流程是由对象关系图来决定的,该对象关系图由装配 器负责实例化 +++++++++++++++++++++++++++++++++++++ 依赖注入是在编译阶段尚未知所需的功能是来自哪个的类的情况下,将其他对象所依赖的功能对象实例化的模式。这就需要一种机制用来激活相应的组件以提供特定的功能,所以依赖注入是控制反转的基础。否则如果在组件不受框架控制的情况下,框架又怎么知道要创建哪个组件? 在Java中依然注入有以下三种实现方式: 1.构造器注入 2.Setter方法注入 3.接口注入

5、请解释下Spring框架中的IoC?

Spring中的 org.springframework.beans 包和 org.springframework.context包构成了Spring框架IoC容器的基础。 BeanFactory 接口提供了一个先进的配置机制,使得任何类型的对象的配置成为可能。ApplicationContex接口对BeanFactory(是一个子接口)进行了扩展,在BeanFactory的基础上添加了其他功能,比如与Spring的AOP更容易集成,也提供了处理message resource的机制(用于国际化)、事件传播以及应用层的特别配置,比如针对Web应用的WebApplicationContext。 org.springframework.beans.factory.BeanFactory 是Spring IoC容器的具体实现,用来包装和管理前面提到的各种bean。BeanFactory接口是Spring IoC 容器的核心接口。 IOC:把对象的创建、初始化、销毁交给spring来管理,而不是由开发者控制,实现控制反转。

  6、BeanFactory和ApplicationContext有什么区别?   Bean Factory 可以理解为含有bean集合的工厂类。BeanFactory 包含了种bean的定义,以便在接收到客户端请求时将对应的bean实例化。 BeanFactory还能在实例化对象的时生成协作类之间的关系。此举将bean自身与bean客户端的配置中解放出来。BeanFactory还包含 了bean生命周期的控制,调用客户端的初始化方法(initialization methods)和销毁方法(destruction methods)。 从表面上看,application context如同bean factory一样具有bean定义、bean关联关系的设置,根据请求分发bean的功能。但applicationcontext在此基础上还提供了其他的功能。 1.提供了支持国际化的文本消息 2.统一的资源文件读取方式 3.已在监听器中注册的bean的事件 以下是三种较常见的 ApplicationContext 实现方式: 1、ClassPathXmlApplicationContext:从classpath的XML配置文件中读取上下文,并生成上下文定义。应用程序上下文从程序环境变量中 ApplicationContext context = new ClassPathXmlApplicationContext(“bean.xml”);
2、FileSystemXmlApplicationContext :由文件系统中的XML配置文件读取上下文。   ApplicationContext context = new FileSystemXmlApplicationContext(“bean.xml”);     3、XmlWebApplicationContext:由Web应用的XML文件读取上下文。   4.AnnotationConfigApplicationContext(基于Java配置启动容器)  

 

  7、Spring有几种配置方式?

  将Spring配置到应用开发中有以下三种方式: 1.基于XML的配置 2.基于注解的配置 3.基于Java的配置

8、如何用基于XML配置的方式配置Spring?

在Spring框架中,依赖和服务需要在专门的配置文件来实现,我常用的XML格式的配置文件。这些配置文件的格式通常用开头,然后一系列的bean定义和专门的应用配置选项组成。 SpringXML配置的主要目的时候是使所有的Spring组件都可以用xml文件的形式来进行配置。这意味着不会出现其他的Spring配置类型(比如声明的方式或基于Java Class的配置方式) Spring的XML配置方式是使用被Spring命名空间的所支持的一系列的XML标签来实现的。Spring有以下主要的命名空间:context、beans、jdbc、tx、aop、mvc和aso。 如:  

  下面这个web.xml仅仅配置了DispatcherServlet,这件最简单的配置便能满足应用程序配置运行时组件的需求。

Archetype Created Web Application spring org.springframework.web.servlet.DispatcherServlet 1 spring /

      9、如何用基于Java配置的方式配置Spring?   Spring对Java配置的支持是由@Configuration注解和@Bean注解来实现的。由@Bean注解的方法将会实例化、配置和初始化一个 新对象,这个对象将由Spring的IoC容器来管理。@Bean声明所起到的作用与 元素类似。被 @Configuration所注解的类则表示这个类的主要目的是作为bean定义的资源。被@Configuration声明的类可以通过在同一个类的 内部调用@bean方法来设置嵌入bean的依赖关系。 最简单的@Configuration 声明类请参考下面的代码:

@Configuration
public class AppConfig{
@Bean
public MyService myService() {
return new MyServiceImpl();
}
}

  对于上面的@Beans配置文件相同的XML配置文件如下:

  上述配置方式的实例化方式如下:利用AnnotationConfigApplicationContext 类进行实例化   public static void main(String[] args) {
ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
MyService myService = ctx.getBean(MyService.class);
myService.doStuff();
}   要使用组件组建扫描,仅需用@Configuration进行注解即可: @Configuration
@ComponentScan(basePackages = "com.somnus")
public class AppConfig {
...
}
在上面的例子中,com.acme包首先会被扫到,然后再容器内查找被@Component 声明的类,找到后将这些类按照Sring bean定义进行注册。 如果你要在你的web应用开发中选用上述的配置的方式的话,需要用AnnotationConfigWebApplicationContext 类来读 取配置文件,可以用来配置Spring的Servlet监听器ContextLoaderListener或者Spring MVC的DispatcherServlet。

contextClass org.springframework.web.context.support.AnnotationConfigWebApplicationContext
<!-- Configuration locations must consist of one or more comma- or space-delimited    
    fully-qualified @Configuration classes. Fully-qualified packages may also be    
    specified for component-scanning -->    
<context-param>    
    <param-name>contextConfigLocation</param-name>    
    <param-value>com.howtodoinjava.AppConfig</param-value>    
</context-param>    
 
<!-- Bootstrap the root application context as usual using ContextLoaderListener -->    
<listener>    
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>    
</listener>    
 
<!-- Declare a Spring MVC DispatcherServlet as usual -->    
<servlet>    
    <servlet-name>dispatcher</servlet-name>    
    <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>    
    <!-- Configure DispatcherServlet to use AnnotationConfigWebApplicationContext    
        instead of the default XmlWebApplicationContext -->    
    <init-param>    
        <param-name>contextClass</param-name>    
        <param-value>    
            org.springframework.web.context.support.AnnotationConfigWebApplicationContext    
        </param-value>    
    </init-param>    
    <!-- Again, config locations must consist of one or more comma- or space-delimited    
        and fully-qualified @Configuration classes -->    
    <init-param>    
        <param-name>contextConfigLocation</param-name>    
        <param-value>com.howtodoinjava.web.MvcConfig</param-value>    
    </init-param>    
</servlet>    
 
<!-- map all requests for /app/* to the dispatcher servlet -->    
<servlet-mapping>    
    <servlet-name>dispatcher</servlet-name>    
    <url-pattern>/app/*</url-pattern>    
</servlet-mapping>    

</web-app

        10、怎样用注解的方式配置Spring? Spring在2.5版本以后开始支持用注解的方式来配置依赖注入。可以用注解的方式来替代XML方式的bean描述,可以将bean描述转移到组件类的 内部,只需要在相关类上、方法上或者字段声明上使用注解即可。注解注入将会被容器在XML注入之前被处理,所以后者会覆盖掉前者对于同一个属性的处理结 果。 注解装配在Spring中是默认关闭的。所以需要在Spring文件中配置一下才能使用基于注解的装配模式。如果你想要在你的应用程序中使用关于注解的方法的话,请参考如下的配置。
context:annotation-config/

    在 标签配置完成以后,就可以用注解的方式在Spring中向属性、方法和构造方法中自动装配变量。 下面是几种比较重要的注解类型: 1.@Required:该注解应用于设值方法。 2.@Autowired:该注解应用于有值设值方法、非设值方法、构造方法和变量。 3.@Qualifier:该注解和@Autowired注解搭配使用,用于消除特定bean自动装配的歧义。 4.JSR-250 Annotations:Spring支持基于JSR-250 注解的以下注解,@Resource、@PostConstruct 和 @PreDestroy。

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

Spring Bean的生命周期简单易懂。在一个bean实例被初始化时,需要执行一系列的初始化操作以达到可用的状态。同样的,当一个bean不在被调用时需要进行相关的析构操作,并从bean容器中移除。 Spring bean factory 负责管理在spring容器中被创建的bean的生命周期。Bean的生命周期由两组回调(call back)方法组成。 1.初始化之后调用的回调方法。 2.销毁之前调用的回调方法。 Spring框架提供了以下四种方式来管理bean的生命周期事件: InitializingBean和DisposableBean回调接口 针对特殊行为的其他Aware接口 Bean配置文件中的Custom init()方法和destroy()方法 @PostConstruct和@PreDestroy注解方式 使用customInit()和 customDestroy()方法管理bean生命周期的代码样例如下:


12、Spring Bean的作用域之间有什么区别?

Spring容器中的bean可以分为5个范围。所有范围的名称都是自说明的,但是为了避免混淆,还是让我们来解释一下: 1.singleton:这种bean范围是默认的,这种范围确保不管接受到多少个请求,每个容器中只有一个bean的实例,单例的模式由bean factory自身来维护。 2.prototype:原形范围与单例范围相反,为每一个bean请求提供一个实例。 3.request:在请求bean范围内会每一个来自客户端的网络请求创建一个实例,在请求完成以后,bean会失效并被垃圾回收器回收。 4.Session:与请求范围类似,确保每个session中有一个bean的实例,在session过期后,bean会随之失效。 5.global- session:global-session和Portlet应用相关。当你的应用部署在Portlet容器中工作时,它包含很多portlet。如果 你想要声明让所有的portlet共用全局的存储变量的话,那么这全局变量需要存储在global-session中。 全局作用域与Servlet中的session作用域效果相同。

  13、什么是Spring inner beans?  

在Spring框架中,无论何时bean被使用时,当仅被调用了一个属性。一个明智的做法是将这个bean声明为内部bean。内部bean可以用setter注入“属性”和构造方法注入“构造参数”的方式来实现。 比如,在我们的应用程序中,一个Customer类引用了一个Person类,我们的要做的是创建一个Person的实例,然后在Customer内部使用。 public class Customer{
private Person person;
//Setters and Getters
}

public class Person{
private String name;
private String address;
private int age;
//Setters and Getters
}

        内部bean的声明方式如下:

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

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

  15、请举例说明如何在Spring中注入一个Java Collection?

  Spring提供了以下四种集合类的配置元素:  :   该标签用来装配可重复的list值。  :    该标签用来装配没有重复的set值。 :   该标签可用来注入键和值可以为任何类型的键值对。  : 该标签支持注入键和值都是字符串类型的键值对。 下面看一下具体的例子:

INDIA Pakistan USA UK
 <!-- java.util.Set -->    
 <property name="customSet">    
    <set>    
       <value>INDIA</value>    
       <value>Pakistan</value>    
       <value>USA</value>    
       <value>UK</value>    
    </set>    
  </property>    
 
 <!-- java.util.Map -->    
 <property name="customMap">    
    <map>    
       <entry key="1" value="INDIA"/>    
       <entry key="2" value="Pakistan"/>    
       <entry key="3" value="USA"/>    
       <entry key="4" value="UK"/>    
    </map>    
  </property>    
 
<!-- java.util.Properties -->    
<property name="customProperies">    
    <props>    
        <prop key="admin">admin@nospam.com</prop>    
        <prop key="support">support@nospam.com</prop>    
    </props>    
</property>    
 

        16、如何向Spring Bean中注入一个Java.util.Properties? 第一种方法是使用如下面代码所示的 标签:

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

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

  17、请解释Spring Bean的自动装配?  

在Spring框架中,在配置文件中设定bean的依赖关系是一个很好的机制,Spring容器还可以自动装配合作关系bean之间的关联关系。这意味着 Spring可以通过向Bean Factory中注入的方式自动搞定bean之间的依赖关系。自动装配可以设置在每个bean上,也可以设定在特定的bean上。 下面的XML配置文件表明了如何根据名称将一个bean设置为自动装配:  
除了bean配置文件中提供的自动装配模式,还可以使用@Autowired注解来自动装配指定的bean。在使用@Autowired注解之前需要在按照如下的配置方式在Spring配置文件进行配置才可以使用。 <context:annotation-config />
也可以通过在配置文件中配置AutowiredAnnotationBeanPostProcessor 达到相同的效果。  
配置好以后就可以使用@Autowired来标注了。 @Autowired
public EmployeeDAOImpl ( EmployeeManager manager ) {
this.manager = manager;
}

  18、请解释自动装配模式的区别?  

在Spring框架中共有5种自动装配,让我们逐一分析。 1.no:这是Spring框架的默认设置,在该设置下自动装配是关闭的,开发者需要自行在bean定义中用标签明确的设置依赖关系。 2.byName:该选项可以根据bean名称设置依赖关系。当向一个bean中自动装配一个属性时,容器将根据bean的名称自动在在配置文件中查询一个匹配的bean。如果找到的话,就装配这个属性,如果没找到的话就报错。 3.byType:该选项可以根据bean类型设置依赖关系。当向一个bean中自动装配一个属性时,容器将根据bean的类型自动在在配置文件中查询一个匹配的bean。如果找到的话,就装配这个属性,如果没找到的话就报错。 4.constructor:造器的自动装配和byType模式类似,但是仅仅适用于与有构造器相同参数的bean,如果在容器中没有找到与构造器参数类型一致的bean,那么将会抛出异常。 5.autodetect:该模式自动探测使用构造器自动装配或者byType自动装配。首先,首先会尝试找合适的带参数的构造器,如果找到的话就是用构造器自动装配,如果在bean内部没有找到相应的构造器或者是无参构造器,容器就会自动选择byTpe的自动装配方式。

19、如何开启基于注解的自动装配?

要使用 @Autowired,需要注册 AutowiredAnnotationBeanPostProcessor,可以有以下两种方式来实现: 1、引入配置文件中的下引入 context:annotation-config
<context:annotation-config />

2、在bean配置文件中直接引入AutowiredAnnotationBeanPostProcessor

        

20、请举例解释@Required注解?

在产品级别的应用中,IoC容器可能声明了数十万了bean,bean与bean之间有着复杂的依赖关系。设值注解方法的短板之一就是验证所有的属性是否被注解是一项十分困难的操作。可以通过在中设置“dependency-check”来解决这个问题。 在应用程序的生命周期中,你可能不大愿意花时间在验证所有bean的属性是否按照上下文文件正确配置。或者你宁可验证某个bean的特定属性是否被正确的设置。即使是用“dependency-check”属性也不能很好的解决这个问题,在这种情况下,你需要使用@Required 注解。 需要用如下的方式使用来标明bean的设值方法。  

public class EmployeeFactoryBean extends AbstractFactoryBean{
private String designation;
public String getDesignation() {
return designation;
}
@Required
public void setDesignation(String designation) {
this.designation = designation;
}
//more code here
}

      RequiredAnnotationBeanPostProcessor是Spring中的后置处理用来验证被@Required 注解的bean属性是否被正确的设置了。在使用RequiredAnnotationBeanPostProcesso来验证bean属性之前,首先要在IoC容器中对其进行注册:    
但是如果没有属性被用 @Required 注解过的话,后置处理器会抛出一个BeanInitializationException 异常。  

21、请举例解释@Autowired注解?  

@Autowired注解对自动装配何时何处被实现提供了更多细粒度的控制。@Autowired注解可以像@Required注解、构造器一样被用于在bean的设值方法上自动装配bean的属性,一个参数或者带有任意名称或带有多个参数的方法。 比如,可以在设值方法上使用@Autowired注解来替代配置文件中的 元素。当Spring容器在setter方法上找到@Autowired注解时,会尝试用byType 自动装配。 当然我们也可以在构造方法上使用@Autowired 注解。带有@Autowired 注解的构造方法意味着在创建一个bean时将会被自动装配,即便在配置文件中使用 元素。

public class TextEditor {
private SpellChecker spellChecker;
@Autowired
public TextEditor(SpellChecker spellChecker){
System.out.println("Inside TextEditor constructor." );
this.spellChecker = spellChecker;
}
public void spellCheck(){
spellChecker.checkSpelling();
}
}

    下面是没有构造参数的配置方式:  

context:annotation-config/

     

  22、请举例说明@Qualifier注解?

  @Qualifier注解意味着可以在被标注bean的字段上可以自动装配。Qualifier注解可以用来取消Spring不能取消的bean应用。 下面的示例将会在Customer的person属性中自动装配person的值。 public class Customer{
@Autowired
private Person person;
}
下面我们要在配置文件中来配置Person类。  

         Spring会知道要自动装配哪个person bean么?不会的,但是运行上面的示例时,会抛出下面的异常:   Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException:
No unique bean of type [com.howtodoinjava.common.Person] is defined:
expected single matching bean but found 2: [personA, personB]
要解决上面的问题,需要使用 @Quanlifier注解来告诉Spring容器要装配哪个bean:   public class Customer{
@Autowired
@Qualifier("personA")
private Person person;
}

23、构造方法注入和设值注入有什么区别?

请注意以下明显的区别: 1.在设值注入方法支持大部分的依赖注入,如果我们仅需 要注入int、string和long型的变量,我们不要用设值的方法注入。对于基本类型,如果我们没有注入的话,可以为基本类型设置默认值。在构造方法 注入不支持大部分的依赖注入,因为在调用构造方法中必须传入正确的构造参数,否则的话为报错。 2.设值注入不会重写构造方法的值。如果我们对同一个变量同时使用了构造方法注入又使用了设置方法注入的话,那么构造方法将不能覆盖由设值方法注入的值。很明显,因为构造方法尽在对象被创建时调用。 3.在使用设值注入时有可能还不能保证某种依赖是否已经被注入,也就是说这时对象的依赖关系有可能是不完整的。而在另一种情况下,构造器注入则不允许生成依赖关系不完整的对象。 4.在设值注入时如果对象A和对象B互相依赖,在创建对象A时Spring会抛出sObjectCurrentlyInCreationException异常,因为在B对象被创建之前A对象是不能被创建的,反之亦然。所以Spring用设值注入的方法解决了循环依赖的问题,因对象的设值方法是在对象被创建之前被调用的。

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

Spring的ApplicationContext 提供了支持事件和代码中监听器的功能。 我们可以创建bean用来监听在ApplicationContext 中发布的事件。ApplicationEvent类和在ApplicationContext接口中处理的事件,如果一个bean实现了ApplicationListener接口,当一个ApplicationEvent 被发布以后,bean会自动被通知。

public class AllApplicationEventListener implements ApplicationListener < ApplicationEvent >{
@Override
public void onApplicationEvent(ApplicationEvent applicationEvent)
{
//process event
}
}

     

Spring 提供了以下5中标准的事件: 1.上下文更新事件(ContextRefreshedEvent):该事件会在ApplicationContext被初始化或者更新时发布。也可以在调用ConfigurableApplicationContext 接口中的refresh()方法时被触发。 2.上下文开始事件(ContextStartedEvent):当容器调用ConfigurableApplicationContext的Start()方法开始/重新开始容器时触发该事件。 3.上下文停止事件(ContextStoppedEvent):当容器调用ConfigurableApplicationContext的Stop()方法停止容器时触发该事件。 4.上下文关闭事件(ContextClosedEvent):当ApplicationContext被关闭时触发该事件。容器被关闭时,其管理的所有单例Bean都被销毁。 5.请求处理事件(RequestHandledEvent):在Web应用中,当一个http请求(request)结束触发该事件。 除了上面介绍的事件以外,还可以通过扩展ApplicationEvent 类来开发自定义的事件。 public class CustomApplicationEvent extends ApplicationEvent{
public CustomApplicationEvent ( Object source, final String msg ){
super(source);
System.out.println("Created a Custom event");
}
}
为了监听这个事件,还需要创建一个监听器: public class CustomEventListener implements ApplicationListener < CustomApplicationEvent >{
@Override
public void onApplicationEvent(CustomApplicationEvent applicationEvent) {
//handle event
}
}

之后通过applicationContext接口的publishEvent()方法来发布自定义事件。 CustomApplicationEvent customEvent = new CustomApplicationEvent(applicationContext, "Test message");
applicationContext.publishEvent(customEvent);

24、FileSystemResource和ClassPathResource有何区别? 在FileSystemResource 中需要给出spring-config.xml文件在你项目中的相对路径或者绝对路径。在ClassPathResource中spring会在ClassPath中自动搜寻配置文件,所以要把ClassPathResource 文件放在ClassPath下。 如果将spring-config.xml保存在了src文件夹下的话,只需给出配置文件的名称即可,因为src文件夹是默认。 简而言之,ClassPathResource在环境变量中读取配置文件,FileSystemResource在配置文件中读取配置文件。 25、Spring 框架中都用到了哪些设计模式? Spring框架中使用到了大量的设计模式,下面列举了比较有代表性的: o代理模式—在AOP和remoting中被用的比较多。 o单例模式—在spring配置文件中定义的bean默认为单例模式。 o模板方法—用来解决代码重复的问题。比如. RestTemplate, JmsTemplate, JpaTemplate。 o前端控制器—Spring提供了DispatcherServlet来对请求进行分发。 o视图帮助(View Helper )—Spring提供了一系列的JSP标签,高效宏来辅助将分散的代码整合在视图里。 o依赖注入—贯穿于BeanFactory / ApplicationContext接口的核心理念。 o工厂模式—BeanFactory用来创建对象的实例      

  1. 开发中主要使用 Spring 的什么技术 ? ①. IOC 容器管理各层的组件 ②. 使用 AOP 配置声明式事务 ③. 整合其他框架.  
  2. 简述 AOP 和 IOC 概念 AOP: Aspect Oriented Program, 面向(方面)切面的编程;Filter(过滤器) 也是一种 AOP. AOP 是一种新的方法论, 是对传统 OOP(Object-Oriented Programming, 面向对象编程) 的补充. AOP 的主要编程对象是切面(aspect), 而切面模块化横切关注点.可以举例通过事务说明.   IOC: Invert Of Control, 控制反转. 也成为 DI(依赖注入)其思想是反转 资源获取的方向. 传统的资源查找方式要求组件向容器发起请求查找资源.作为 回应, 容器适时的返回资源. 而应用了 IOC 之后, 则是容器主动地将资源推送 给它所管理的组件,组件所要做的仅是选择一种合适的方式来接受资源. 这种行 为也被称为查找的被动形式  
  3. 在 Spring 中如何配置 Bean ? Bean 的配置方式: 通过全类名(反射)、通过工厂方法(静态工厂方法 & 实 例工厂方法)、FactoryBean  
  4. IOC 容器对 Bean 的生命周期: ①. 通过构造器或工厂方法创建 Bean 实例 ②. 为 Bean 的属性设置值和对其他 Bean 的引用 ③ . 将 Bean 实 例 传 递 给 Bean 后 置 处 理 器 的 postProcessBeforeInitialization 方法 ④. 调用 Bean 的初始化方法(init-method) ⑤ . 将 Bean 实 例 传 递 给 Bean 后 置 处 理 器 的 postProcessAfterInitialization 方法 ⑦. Bean 可以使用了 ⑧. 当容器关闭时, 调用 Bean 的销毁方法(destroy-method)

Spring怎么解决循环依赖 Spring的循环依赖的理论依据其实是基于Java的引用传递,当我们获取到对象的引用时,对象的field或则属性是可以延后设置的(但是构造器必须是在获取引用之前)。 Spring的单例对象的初始化主要分为三步:

(1)createBeanInstance:实例化,其实也就是调用对象的构造方法实例化对象 (2)populateBean:填充属性,这一步主要是多bean的依赖属性进行填充 (3)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 singleton factories: bean name --> ObjectFactory /private final Map<String, ObjectFactory> singletonFactories = new HashMap>(16); /* Cache of early singleton objects: bean name --> bean instance */private final Map<String, Object> earlySingletonObjects = new HashMap<String, Object>(16); 1 2 3 4 5 6 7 8 这三级缓存分别指: singletonFactories : 单例对象工厂的cache earlySingletonObjects :提前暴光的单例对象的Cache singletonObjects:单例对象的cache 我们在创建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); 1 2 从singletonFactories中移除,并放入earlySingletonObjects中。其实也就是从三级缓存移动到了二级缓存。 从上面三级缓存的分析,我们可以知道,Spring解决循环依赖的诀窍就在于singletonFactories这个三级cache。这个cache的类型是ObjectFactory,定义如下: public interface ObjectFactory { T getObject() throws BeansException; } 1 2 3 这个接口在下面被引用 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); } } } 1 2 3 4 5 6 7 8 9 10 这里就是解决循环依赖的关键,这段代码发生在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三级缓存的前提是执行了构造器,所以构造器的循环依赖没法解决。

2 Spring 为何需要三级缓存解决循环依赖,而不是二级缓存 前言     在使用spring框架的日常开发中,bean之间的循环依赖太频繁了,spring已经帮我们去解决循环依赖问题,对我们开发者来说是无感知的,下面具体分析一下spring是如何解决bean之间循环依赖,为什么要使用到三级缓存,而不是二级缓存

bean生命周期     首先大家需要了解一下bean在spring中的生命周期,bean在spring的加载流程,才能够更加清晰知道spring是如何解决循环依赖的

    我们在spring的BeanFactory工厂列举了很多接口,代表着bean的生命周期,我们主要记住的是我圈红线圈出来的接口, 再结合spring的源码来看这些接口主要是在哪里调用的

    AbstractAutowireCapableBeanFactory类的doCreateBean方法是创建bean的开始,我们可以看到首先需要实例化这个bean,也就是在堆中开辟一块内存空间给这个对象,createBeanInstance方法里面逻辑大概就是采用反射生成实例对象,进行到这里表示对象还并未进行属性的填充,也就是@Autowired注解的属性还未得到注入

    我们可以看到第二步就是填充bean的成员属性,populateBean方法里面的逻辑大致就是对使用到了注入属性的注解就会进行注入,如果在注入的过程发现注入的对象还没生成,则会跑去生产要注入的对象,第三步就是调用initializeBean方法初始化bean,也就是调用我们上述所提到的接口

    可以看到initializeBean方法中,首先调用的是使用的Aware接口的方法,我们具体看一下invokeAwareMethods方法中会调用Aware接口的那些方法

    我们可以知道如果我们实现了BeanNameAware,BeanClassLoaderAware,BeanFactoryAware三个Aware接口的话,会依次调用setBeanName(), setBeanClassLoader(), setBeanFactory()方法,再看applyBeanPostProcessorsBeforeInitialization源码

    发现会如果有类实现了BeanPostProcessor接口,就会执行postProcessBeforeInitialization方法,这里需要注意的是:如果多个类实现BeanPostProcessor接口,那么多个实现类都会执行postProcessBeforeInitialization方法,可以看到是for循环依次执行的,还有一个注意的点就是如果加载A类到spring容器中,A类也重写了BeanPostProcessor接口的postProcessBeforeInitialization方法,这时要注意A类的postProcessBeforeInitialization方法并不会得到执行,因为A类还未加载完成,还未完全放到spring的singletonObjects一级缓存中。     再看一个注意的点

    可以看到ApplicationContextAwareProcessor也实现了BeanPostProcessor接口,重写了postProcessBeforeInitialization方法,方法里面并调用了invokeAwareInterfaces方法,而invokeAwareInterfaces方法也写着如果实现了众多的Aware接口,则会依次执行相应的方法,值得注意的是ApplicationContextAware接口的setApplicationContext方法,再看一下invokeInitMethods源码

    发现如果实现了InitializingBean接口,重写了afterPropertiesSet方法,则会调用afterPropertiesSet方法,最后还会调用是否指定了init-method,可以通过标签,或者@Bean注解的initMethod指定,最后再看一张applyBeanPostProcessorsAfterInitialization源码图

    发现跟之前的postProcessBeforeInitialization方法类似,也是循环遍历实现了BeanPostProcessor的接口实现类,执行postProcessAfterInitialization方法。整个bean的生命执行流程就如上面截图所示,哪个接口的方法在哪里被调用,方法的执行流程     最后,对bean的生命流程进行一个流程图的总结

三级缓存解决循环依赖     上一小节对bean的生命周期做了一个整体的流程分析,对spring如何去解决循环依赖的很有帮助。前面我们分析到填充属性时,如果发现属性还未在spring中生成,则会跑去生成属性对象实例

    我们可以看到填充属性的时候,spring会提前将已经实例化的bean通过ObjectFactory半成品暴露出去,为什么称为半成品是因为这时候的bean对象实例化,但是未进行属性填充,是一个不完整的bean实例对象

    spring利用singletonObjects, earlySingletonObjects, singletonFactories三级缓存去解决的,所说的缓存其实也就是三个Map

    可以看到三级缓存各自保存的对象,这里重点关注二级缓存earlySingletonObjects和三级缓存singletonFactory,一级缓存可以进行忽略。前面我们讲过先实例化的bean会通过ObjectFactory半成品提前暴露在三级缓存中

    singletonFactory是传入的一个匿名内部类,调用ObjectFactory.getObject()最终会调用getEarlyBeanReference方法。再来看看循环依赖中是怎么拿其它半成品的实例对象的。     我们假设现在有这样的场景AService依赖BService,BService依赖AService         1. AService首先实例化,实例化通过ObjectFactory半成品暴露在三级缓存中         2. 填充属性BService,发现BService还未进行过加载,就会先去加载BService         3. 再加载BService的过程中,实例化,也通过ObjectFactory半成品暴露在三级缓存         4. 填充属性AService的时候, 这时候能够从三级缓存中拿到半成品的ObjectFactory

    拿到ObjectFactory对象后,调用ObjectFactory.getObject()方法最终会调用getEarlyBeanReference()方法,getEarlyBeanReference这个方法主要逻辑大概描述下如果bean被AOP切面代理则返回的是beanProxy对象,如果未被代理则返回的是原bean实例,这时我们会发现能够拿到bean实例(属性未填充),然后从三级缓存移除,放到二级缓存earlySingletonObjects中,而此时B注入的是一个半成品的实例A对象,不过随着B初始化完成后,A会继续进行后续的初始化操作,最终B会注入的是一个完整的A实例,因为在内存中它们是同一个对象。下面是重点,我们发现这个二级缓存好像显得有点多余,好像可以去掉,只需要一级和三级缓存也可以做到解决循环依赖的问题???

    只要两个缓存确实可以做到解决循环依赖的问题,但是有一个前提这个bean没被AOP进行切面代理,如果这个bean被AOP进行了切面代理,那么只使用两个缓存是无法解决问题,下面来看一下bean被AOP进行了切面代理的场景

    我们发现AService的testAopProxy被AOP代理了,看看传入的匿名内部类的getEarlyBeanReference返回的是什么对象

    发现singletonFactory.getObject()返回的是一个AService的代理对象,还是被CGLIB代理的。再看一张再执行一遍singletonFactory.getObject()返回的是否是同一个AService的代理对象

    我们会发现再执行一遍singleFactory.getObject()方法又是一个新的代理对象,这就会有问题了,因为AService是单例的,每次执行singleFactory.getObject()方法又会产生新的代理对象,假设这里只有一级和三级缓存的话,我每次从三级缓存中拿到singleFactory对象,执行getObject()方法又会产生新的代理对象,这是不行的,因为AService是单例的,所有这里我们要借助二级缓存来解决这个问题,将执行了singleFactory.getObject()产生的对象放到二级缓存中去,后面去二级缓存中拿,没必要再执行一遍singletonFactory.getObject()方法再产生一个新的代理对象,保证始终只有一个代理对象。还有一个注意的点

    既然singleFactory.getObject()返回的是代理对象,那么注入的也应该是代理对象,我们可以看到注入的确实是经过CGLIB代理的AService对象。所以如果没有AOP的话确实可以两级缓存就可以解决循环依赖的问题,如果加上AOP,两级缓存是无法解决的,不可能每次执行singleFactory.getObject()方法都给我产生一个新的代理对象,所以还要借助另外一个缓存来保存产生的代理对象

总结     前面先讲到bean的加载流程,了解了bean加载流程对spring如何解决循环依赖的问题很有帮助,后面再分析到spring为什么需要利用到三级缓存解决循环依赖问题,而不是二级缓存。网上可以试试AOP的情形,实践一下就能明白二级缓存为什么解决不了AOP代理的场景了 在工作中,一直认为编程代码不是最重要的,重要的是在工作中所养成的编程思维。

spring为什么使用三级缓存来解决循环依赖?用一级或者二级缓存行不行?

你全家都是程序员 2020-12-14 18:18:10 865 收藏 4 文章标签: spring java 版权 一级缓存:Map<String, Object> singletonObjects 

二级缓存:Map<String, Object> earlySingletonObjects

三级缓存:Map<String, ObjectFactory<?>> singletonFactories

其中一级缓存存放已经初始化完成的单例对象,二级缓存存放半成品对象(已经实例化,但是还没有初始化,也就是还没有填充属性),三级缓存存放对象工厂。

现在假设A依赖B,B依赖A,也就是循环依赖的情况。 1.如果只使用一级缓存能不能解决循环依赖? 如果只是用一级缓存,那么成品跟半成品(属性都为空)都放在singletonObjects。

那么整个流程可以这样进行:实例化A -> 将半成品的A放入singletonObjects中->填充A的属性时发现取不到B->实例化B->从singletonObjects中取出A填充B的属性->将成品B放入singletonObjects->将B填充到A的属性中->将成品A放入singletonObjects。

流程是通的,不过这只能满足最普通的IOC情况。

假设整个流程进行中,有另一个线程要来取A,那么有可能拿到的只是一个属性都为null的半成品A,这样就会有问题。

还有一种无法满足的情况是需要注入的是代理对象的时候,这种情况下面再说。

2.如果使用二级缓存能不能解决循环依赖? 使用二级缓存也可以分为两种:使用singletonObjects和earlySingletonObjects,或者使用singletonObjects和singletonFactories。

①使用singletonObjects和earlySingletonObjects 成品放在singletonObjects中,半成品放在earlySingletonObjects中

流程可以这样走:实例化A ->将半成品的A放入earlySingletonObjects中 ->填充A的属性时发现取不到B->实例化B->将半成品的A放入earlySingletonObjects中->从earlySingletonObjects中取出A填充B的属性->将成品B放入singletonObjects,并从earlySingletonObjects中删除B->将B填充到A的属性中->将成品A放入singletonObjects并删除earlySingletonObjects。

这样的流程是线程安全的,不过如果A上加个切面(AOP),这种做法就没法满足需求了,因为earlySingletonObjects中存放的都是原始对象,而我们需要注入的其实是A的代理对象。

②使用singletonObjects和singletonFactories 成品放在singletonObjects中,半成品通过singletonFactories来获取

流程是这样的:实例化A ->创建A的对象工厂并放入singletonFactories中 ->填充A的属性时发现取不到B->实例化B->创建B的对象工厂并放入singletonFactories中->从singletonFactories中获取A的对象工厂并获取A填充到B中->将成品B放入singletonObjects,并从singletonFactories中删除B的对象工厂->将B填充到A的属性中->将成品A放入singletonObjects并删除A的对象工厂。

同样,这样的流程也适用于普通的IOS已经有并发的场景,但如果A上加个切面(AOP)的话,这种情况也无法满足需求。

根据源码可知从singletonFactories获取对象时候会对对象进行增强处理,动态代理也是在这一步完成的。

现在假设不只A需要动态代理,B也需要动态代理,参照上述的流程可以发现,没有从singletonFactories中获取过B,也就是说B始终是原始对象,不符合需求。

 

以下是个人对三级缓存意义的理解 1.一级缓存就是普通的ioc的容器。

2.二级缓存是为了解决循环依赖。

3.三级缓存是spring提供给开发者一个拓展的入口(比如AOP) ————————————————

请别再问Spring Bean的生命周期了!

sunshujie1990关注 162019.05.30 23:22:09字数 2,609阅读 154,245 Spring Bean的生命周期是Spring面试热点问题。这个问题即考察对Spring的微观了解,又考察对Spring的宏观认识,想要答好并不容易!本文希望能够从源码角度入手,帮助面试者彻底搞定Spring Bean的生命周期。 只有四个! 是的,Spring Bean的生命周期只有这四个阶段。把这四个阶段和每个阶段对应的扩展点糅合在一起虽然没有问题,但是这样非常凌乱,难以记忆。要彻底搞清楚Spring的生命周期,首先要把这四个阶段牢牢记住。实例化和属性赋值对应构造方法和setter方法的注入,初始化和销毁是用户能自定义扩展的两个阶段。在这四步之间穿插的各种扩展点,稍后会讲。 1.实例化 Instantiation 2.属性赋值 Populate 3.初始化 Initialization 4.销毁 Destruction 实例化 -> 属性赋值 -> 初始化 -> 销毁 主要逻辑都在doCreate()方法中,逻辑很清晰,就是顺序调用以下三个方法,这三个方法与三个生命周期阶段一一对应,非常重要,在后续扩展接口分析中也会涉及。 1.createBeanInstance() -> 实例化 2.populateBean() -> 属性赋值 3.initializeBean() -> 初始化 源码如下,能证明实例化,属性赋值和初始化这三个生命周期的存在。关于本文的Spring源码都将忽略无关部分,便于理解: // 忽略了无关代码 protected Object doCreateBean(final String beanName, final RootBeanDefinition mbd, final @Nullable Object[] args) throws BeanCreationException {

// Instantiate the bean. BeanWrapper instanceWrapper = null; if (instanceWrapper == null) { // 实例化阶段! instanceWrapper = createBeanInstance(beanName, mbd, args); }

// Initialize the bean instance. Object exposedObject = bean; try { // 属性赋值阶段! populateBean(beanName, mbd, instanceWrapper); // 初始化阶段! exposedObject = initializeBean(beanName, exposedObject, mbd); }

} 至于销毁,是在容器关闭时调用的,详见ConfigurableApplicationContext#close() 常用扩展点 Spring生命周期相关的常用扩展点非常多,所以问题不是不知道,而是记不住或者记不牢。其实记不住的根本原因还是不够了解,这里通过源码+分类的方式帮大家记忆。 第一大类:影响多个Bean的接口 实现了这些接口的Bean会切入到多个Bean的生命周期中。正因为如此,这些接口的功能非常强大,Spring内部扩展也经常使用这些接口,例如自动注入以及AOP的实现都和他们有关。 BeanPostProcessor InstantiationAwareBeanPostProcessor 这两兄弟可能是Spring扩展中最重要的两个接口!InstantiationAwareBeanPostProcessor作用于实例化阶段的前后,BeanPostProcessor作用于初始化阶段的前后。正好和第一、第三个生命周期阶段对应。通过图能更好理解:

未命名文件 (1).png

InstantiationAwareBeanPostProcessor实际上继承了BeanPostProcessor接口,严格意义上来看他们不是两兄弟,而是两父子。但是从生命周期角度我们重点关注其特有的对实例化阶段的影响,图中省略了从BeanPostProcessor继承的方法。 InstantiationAwareBeanPostProcessor extends BeanPostProcessor InstantiationAwareBeanPostProcessor源码分析: postProcessBeforeInstantiation调用点,忽略无关代码: @Override protected Object createBean(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) throws BeanCreationException {

    try {
        // Give BeanPostProcessors a chance to return a proxy instead of the target bean instance.
        // postProcessBeforeInstantiation方法调用点,这里就不跟进了,
        // 有兴趣的同学可以自己看下,就是for循环调用所有的InstantiationAwareBeanPostProcessor
        Object bean = resolveBeforeInstantiation(beanName, mbdToUse);
        if (bean != null) {
            return bean;
        }
    }
    
    try {   
        // 上文提到的doCreateBean方法,可以看到
        // postProcessBeforeInstantiation方法在创建Bean之前调用
        Object beanInstance = doCreateBean(beanName, mbdToUse, args);
        if (logger.isTraceEnabled()) {
            logger.trace("Finished creating instance of bean '" + beanName + "'");
        }
        return beanInstance;
    }
    
}

可以看到,postProcessBeforeInstantiation在doCreateBean之前调用,也就是在bean实例化之前调用的,英文源码注释解释道该方法的返回值会替换原本的Bean作为代理,这也是Aop等功能实现的关键点。 postProcessAfterInstantiation调用点,忽略无关代码: protected void populateBean(String beanName, RootBeanDefinition mbd, @Nullable BeanWrapper bw) {

// Give any InstantiationAwareBeanPostProcessors the opportunity to modify the // state of the bean before properties are set. This can be used, for example, // to support styles of field injection. boolean continueWithPropertyPopulation = true; // InstantiationAwareBeanPostProcessor#postProcessAfterInstantiation() // 方法作为属性赋值的前置检查条件,在属性赋值之前执行,能够影响是否进行属性赋值! if (!mbd.isSynthetic() && hasInstantiationAwareBeanPostProcessors()) { for (BeanPostProcessor bp : getBeanPostProcessors()) { if (bp instanceof InstantiationAwareBeanPostProcessor) { InstantiationAwareBeanPostProcessor ibp = (InstantiationAwareBeanPostProcessor) bp; if (!ibp.postProcessAfterInstantiation(bw.getWrappedInstance(), beanName)) { continueWithPropertyPopulation = false; break; } } } }

// 忽略后续的属性赋值操作代码} 可以看到该方法在属性赋值方法内,但是在真正执行赋值操作之前。其返回值为boolean,返回false时可以阻断属性赋值阶段(continueWithPropertyPopulation = false;)。 关于BeanPostProcessor执行阶段的源码穿插在下文Aware接口的调用时机分析中,因为部分Aware功能的就是通过他实现的!只需要先记住BeanPostProcessor在初始化前后调用就可以了。 第二大类:只调用一次的接口 这一大类接口的特点是功能丰富,常用于用户自定义扩展。 第二大类中又可以分为两类: 1.Aware类型的接口 2.生命周期接口 无所不知的Aware Aware类型的接口的作用就是让我们能够拿到Spring容器中的一些资源。基本都能够见名知意,Aware之前的名字就是可以拿到什么资源,例如BeanNameAware可以拿到BeanName,以此类推。调用时机需要注意:所有的Aware方法都是在初始化阶段之前调用的! Aware接口众多,这里同样通过分类的方式帮助大家记忆。 Aware接口具体可以分为两组,至于为什么这么分,详见下面的源码分析。如下排列顺序同样也是Aware接口的执行顺序,能够见名知意的接口不再解释。 Aware Group1 1.BeanNameAware 2.BeanClassLoaderAware 3.BeanFactoryAware Aware Group2 1.EnvironmentAware 2.EmbeddedValueResolverAware 这个知道的人可能不多,实现该接口能够获取Spring EL解析器,用户的自定义注解需要支持spel表达式的时候可以使用,非常方便。 3.ApplicationContextAware(ResourceLoaderAware\ApplicationEventPublisherAware\MessageSourceAware) 这几个接口可能让人有点懵,实际上这几个接口可以一起记,其返回值实质上都是当前的ApplicationContext对象,因为ApplicationContext是一个复合接口,如下: public interface ApplicationContext extends EnvironmentCapable, ListableBeanFactory, HierarchicalBeanFactory, MessageSource, ApplicationEventPublisher, ResourcePatternResolver {} 这里涉及到另一道面试题,ApplicationContext和BeanFactory的区别,可以从ApplicationContext继承的这几个接口入手,除去BeanFactory相关的两个接口就是ApplicationContext独有的功能,这里不详细说明。 Aware调用时机源码分析 详情如下,忽略了部分无关代码。代码位置就是我们上文提到的initializeBean方法详情,这也说明了Aware都是在初始化阶段之前调用的! // 见名知意,初始化阶段调用的方法 protected Object initializeBean(final String beanName, final Object bean, @Nullable RootBeanDefinition mbd) {

    // 这里调用的是Group1中的三个Bean开头的Aware
    invokeAwareMethods(beanName, bean);

    Object wrappedBean = bean;
    
    // 这里调用的是Group2中的几个Aware,
    // 而实质上这里就是前面所说的BeanPostProcessor的调用点!
    // 也就是说与Group1中的Aware不同,这里是通过BeanPostProcessor(ApplicationContextAwareProcessor)实现的。
    wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName);
    // 下文即将介绍的InitializingBean调用点
    invokeInitMethods(beanName, wrappedBean, mbd);
    // BeanPostProcessor的另一个调用点
    wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName);

    return wrappedBean;
}

可以看到并不是所有的Aware接口都使用同样的方式调用。Bean××Aware都是在代码中直接调用的,而ApplicationContext相关的Aware都是通过BeanPostProcessor#postProcessBeforeInitialization()实现的。感兴趣的可以自己看一下ApplicationContextAwareProcessor这个类的源码,就是判断当前创建的Bean是否实现了相关的Aware方法,如果实现了会调用回调方法将资源传递给Bean。 至于Spring为什么这么实现,应该没什么特殊的考量。也许和Spring的版本升级有关。基于对修改关闭,对扩展开放的原则,Spring对一些新的Aware采用了扩展的方式添加。 BeanPostProcessor的调用时机也能在这里体现,包围住invokeInitMethods方法,也就说明了在初始化阶段的前后执行。 关于Aware接口的执行顺序,其实只需要记住第一组在第二组执行之前就行了。每组中各个Aware方法的调用顺序其实没有必要记,有需要的时候点进源码一看便知。 简单的两个生命周期接口 至于剩下的两个生命周期接口就很简单了,实例化和属性赋值都是Spring帮助我们做的,能够自己实现的有初始化和销毁两个生命周期阶段。 1.InitializingBean 对应生命周期的初始化阶段,在上面源码的invokeInitMethods(beanName, wrappedBean, mbd);方法中调用。 有一点需要注意,因为Aware方法都是执行在初始化方法之前,所以可以在初始化方法中放心大胆的使用Aware接口获取的资源,这也是我们自定义扩展Spring的常用方式。 除了实现InitializingBean接口之外还能通过注解或者xml配置的方式指定初始化方法,至于这几种定义方式的调用顺序其实没有必要记。因为这几个方法对应的都是同一个生命周期,只是实现方式不同,我们一般只采用其中一种方式。 2.DisposableBean 类似于InitializingBean,对应生命周期的销毁阶段,以ConfigurableApplicationContext#close()方法作为入口,实现是通过循环取所有实现了DisposableBean接口的Bean然后调用其destroy()方法 。感兴趣的可以自行跟一下源码。 扩展阅读: BeanPostProcessor 注册时机与执行顺序 注册时机 我们知道BeanPostProcessor也会注册为Bean,那么Spring是如何保证BeanPostProcessor在我们的业务Bean之前初始化完成呢? 请看我们熟悉的refresh()方法的源码,省略部分无关代码: @Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) {

        try {
            // Allows post-processing of the bean factory in context subclasses.
            postProcessBeanFactory(beanFactory);

            // Invoke factory processors registered as beans in the context.
            invokeBeanFactoryPostProcessors(beanFactory);

            // Register bean processors that intercept bean creation.
            // 所有BeanPostProcesser初始化的调用点
            registerBeanPostProcessors(beanFactory);

            // Initialize message source for this context.
            initMessageSource();

            // Initialize event multicaster for this context.
            initApplicationEventMulticaster();

            // Initialize other special beans in specific context subclasses.
            onRefresh();

            // Check for listener beans and register them.
            registerListeners();

            // Instantiate all remaining (non-lazy-init) singletons.
            // 所有单例非懒加载Bean的调用点
            finishBeanFactoryInitialization(beanFactory);

            // Last step: publish corresponding event.
            finishRefresh();
        }

}

可以看出,Spring是先执行registerBeanPostProcessors()进行BeanPostProcessors的注册,然后再执行finishBeanFactoryInitialization初始化我们的单例非懒加载的Bean。 执行顺序 BeanPostProcessor有很多个,而且每个BeanPostProcessor都影响多个Bean,其执行顺序至关重要,必须能够控制其执行顺序才行。关于执行顺序这里需要引入两个排序相关的接口:PriorityOrdered、Ordered  PriorityOrdered是一等公民,首先被执行,PriorityOrdered公民之间通过接口返回值排序   Ordered是二等公民,然后执行,Ordered公民之间通过接口返回值排序   都没有实现是三等公民,最后执行  在以下源码中,可以很清晰的看到Spring注册各种类型BeanPostProcessor的逻辑,根据实现不同排序接口进行分组。优先级高的先加入,优先级低的后加入。 // First, invoke the BeanDefinitionRegistryPostProcessors that implement PriorityOrdered.// 首先,加入实现了PriorityOrdered接口的BeanPostProcessors,顺便根据PriorityOrdered排了序 String[] postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false); for (String ppName : postProcessorNames) { if (beanFactory.isTypeMatch(ppName, PriorityOrdered.class)) { currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class)); processedBeans.add(ppName); } } sortPostProcessors(currentRegistryProcessors, beanFactory); registryProcessors.addAll(currentRegistryProcessors); invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry); currentRegistryProcessors.clear();

        // Next, invoke the BeanDefinitionRegistryPostProcessors that implement Ordered.// 然后,加入实现了Ordered接口的BeanPostProcessors,顺便根据Ordered排了序
        postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
        for (String ppName : postProcessorNames) {
            if (!processedBeans.contains(ppName) && beanFactory.isTypeMatch(ppName, Ordered.class)) {
                currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                processedBeans.add(ppName);
            }
        }
        sortPostProcessors(currentRegistryProcessors, beanFactory);
        registryProcessors.addAll(currentRegistryProcessors);
        invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
        currentRegistryProcessors.clear();

        // Finally, invoke all other BeanDefinitionRegistryPostProcessors until no further ones appear.// 最后加入其他常规的BeanPostProcessors
        boolean reiterate = true;
        while (reiterate) {
            reiterate = false;
            postProcessorNames = beanFactory.getBeanNamesForType(BeanDefinitionRegistryPostProcessor.class, true, false);
            for (String ppName : postProcessorNames) {
                if (!processedBeans.contains(ppName)) {
                    currentRegistryProcessors.add(beanFactory.getBean(ppName, BeanDefinitionRegistryPostProcessor.class));
                    processedBeans.add(ppName);
                    reiterate = true;
                }
            }
            sortPostProcessors(currentRegistryProcessors, beanFactory);
            registryProcessors.addAll(currentRegistryProcessors);
            invokeBeanDefinitionRegistryPostProcessors(currentRegistryProcessors, registry);
            currentRegistryProcessors.clear();
        }

根据排序接口返回值排序,默认升序排序,返回值越低优先级越高。 /** * Useful constant for the highest precedence value. * @see java.lang.Integer#MIN_VALUE */ int HIGHEST_PRECEDENCE = Integer.MIN_VALUE;

/**
 * Useful constant for the lowest precedence value.
 * @see java.lang.Integer#MAX_VALUE
 */
int LOWEST_PRECEDENCE = Integer.MAX_VALUE;

PriorityOrdered、Ordered接口作为Spring整个框架通用的排序接口,在Spring中应用广泛,也是非常重要的接口。 总结 Spring Bean的生命周期分为四个阶段和多个扩展点。扩展点又可以分为影响多个Bean和影响单个Bean。整理如下: 四个阶段 实例化 Instantiation 属性赋值 Populate 初始化 Initialization 销毁 Destruction 多个扩展点 影响多个Bean BeanPostProcessor InstantiationAwareBeanPostProcessor 影响单个Bean Aware Aware Group1 BeanNameAware BeanClassLoaderAware BeanFactoryAware Aware Group2 EnvironmentAware EmbeddedValueResolverAware ApplicationContextAware(ResourceLoaderAware\ApplicationEventPublisherAware\MessageSourceAware) 生命周期 InitializingBean DisposableBean