Spring IOC

本文深入探讨了Spring的IOC容器和Bean的概念,包括容器的元数据配置(XML方式)、实例化和使用,以及Bean的命名、实例化方式、依赖注入、生命周期管理和范围。重点介绍了构造函数注入、setter注入、延迟初始化、自动装配以及基于注解的配置。此外,还讨论了Bean的扩展,如生命周期回调、方法注入、不同作用域的Bean,以及如何优雅关闭Spring IOC容器。
摘要由CSDN通过智能技术生成

目录

1.Spring IOC容器和Bean简介

2.容器概述

2.1 元数据配置(XML方式)

2.2 实例化一个容器

2.3 容器的使用

3.Bean的概述

3.1 Bean的命名

3.2 实例化bean的三种方式

3.2.1 构造函数实例化

3.2.2 静态工厂实例化

3.2.3 实例工厂实例化

4.Bean的依赖

4.1 依赖注入

4.1.1 构造函数注入

4.1.2 Setter注入

4.1.3 到底是选择基于构造函数DI还是基于setter的DI?

4.1.4 循环依赖

4.2 如何配置详细依赖关系

4.3 延迟初始化

4.4 自动装配

4.5 方法注入

4.5.1 查找方法注入

4.5.2 任意方法替换

5.Bean的范围

5.1 singleton

5.2 prototype

5.3 依赖原型bean的单例bean

5.4 request、session、application、websocket

6.扩展容器中的bean

6.1 生命周期回调

6.1.1 初始化回调

6.1.2 销毁回调

6.1.3 xml配置默认初始化和销毁方法

6.1.4 生命周期配置的优先级

6.1.5 启动和关闭回调

6.1.6 非web应用如何优雅关闭Spring IOC容器

6.2 ApplicationContextAware和BeanNameAware

6.3 其他感知接口

7.扩展IOC容器

7.1 使用BeanPostProcessor自定义bean

7.2 使用BeanFactoryPostProcessor自定义配置元数据

7.3 使用FactoryBean自定义实例化逻辑

8.基于注解的IOC配置

8.1 使用@Autowired

8.1.1 作用于构造器

8.1.2 作用于setter方法

8.1.3 作用于成员变量

8.2 使用@Primary进行精细装配

8.3 使用@Qualifier进行精细装配

9.基于java的容器配置

9.1 @Bean和@Configuration

9.2 使用@Bean

9.3 使用@Configuration


1.Spring IOC容器和Bean简介

        在 Spring 中,构成应用程序主干并由 Spring IOC 容器管理的对象称为 bean。bean 是由 Spring IoC 容器实例化、组装和管理的对象。bean 只是应用程序中的众多对象之一。Bean 以及它们之间的依赖关系反映在容器使用的配置元数据中。

        IOC也称为依赖注入 (DI),对象仅通过构造函数参数、工厂方法的参数或在对象实例被构造或从工厂方法返回后设置的属性来定义它们的依赖关系, 然后容器在创建 bean 时注入这些依赖项。这个过程基本上是 bean 本身通过使用类的直接构造或诸如服务定位器模式之类的机制来控制其依赖关系的实例化或位置的逆过程(因此称为控制反转)。

2.容器概述

        该org.springframework.context.ApplicationContext接口代表 Spring IOC 容器,负责实例化、配置和组装 bean。容器通过读取配置元数据来获取关于要实例化、配置和组装哪些对象的指令。此功能可以通过XML、注解或代码来实现。

2.1 元数据配置(XML方式)

        id:标识单个bean定义的字符串

        class:定义bean类型并使用全限定类名

2.2 实例化一个容器

        提供一个或多个位置路径的ClassPathXmlApplicationContext,允许容器从各种外部资源(例如本地文件系统、Java 等)加载配置元数据CLASSPATH。

2.3 容器的使用

        ApplicationContext是一个高级工厂接口,能够维护不同 bean 及其依赖项的注册表。通过使用 method T getBean(String name, Class<T> requiredType),可以检索 bean 实例。

3.Bean的概述

        Spring IOC 容器管理一个或多个 bean。这些 bean 是使用您提供给容器的配置元数据创建的(例如,以 XML <bean/>定义的形式)。在容器本身中,这些 bean 定义表示为BeanDefinition 对象,其中包含(以及其他信息)以下元数据:

  • 一个包的全限定类名:通常是被定义的 bean 的实际实现类。
  • 范围、生命周期回调:Bean行为配置元素,说明 bean 在容器中的行为方式。
  • 依赖项:对 bean 完成工作所需的其他 bean 的引用。
  • 要在新创建的对象中设置的其他配置设置——例如,池的大小限制或在管理连接池的 bean 中使用的连接数。

        一个bean可以配置如下参数:class、name、作用域、构造函数参数、自动装配模式、惰性初始化模式、初始化方法、销毁方法。

3.1 Bean的命名

        每个 bean 都有一个或多个标识符。这些标识符在承载 bean 的容器中必须是唯一的。一个 bean 通常只有一个标识符。但是如果它需要多个,则可以将多余的视为别名。

        在基于 XML 的配置元数据中,您可以使用id属性、name属性或两者来指定 bean 标识符。该id属性可让您准确指定一个 id。如果要为 bean 引入其他别名,也可以在name 属性中指定,用逗号 ( ,)、分号 ( ;) 或空格分隔。

        如果不显式提供id或name,则容器会为该 bean 生成一个唯一名称,为该类的全限类名。 

        注:bean的默认名称,注解和XML有不同的生成规则,XML方式生成规则是:类名+“#”+数字;而注解方式生成规则使用的是java.beans.Introspector.decapitalize方法(默认将其初始字符转换为小写,但是当有多个字符并且第一个和第二个字符都是大写时,会保留原始大小写)。

        XML方式:

//Bean类
public class UserDao {

}
//XML文件配置
<bean class="com.zoo.dao.UserDao"/>

        注解方式:bean类名为UserDao

         注解方式的特殊情况:bean类名为USerDao

为bean起别名

<alias name="fromName" alias="toName"/>

3.2 实例化bean的三种方式

3.2.1 构造函数实例化

        Spring可以根据bean类默认提供的无参构造函数,来对bean进行实例化,只需要在XML配置bean对象即可。

3.2.2 静态工厂实例化

        创建自定义静态工厂MyFactory:

        XML文件中配置自定义静态工厂:

        getBean获取实例化的Demo对象:

3.2.3 实例工厂实例化

        创建自定义实例工厂MyFactory:

        XML文件中配置自定实例工厂(先将工厂实例化):

        getBean获取实例化的Demo对象:

4.Bean的依赖

4.1 依赖注入

4.1.1 构造函数注入

        构造函数注入这种注入值的方式,就是通过调用bean所属类的带参构造器为bean的属性注入值。也就是说,我们如果需要使用构造器注入,就得为类提供包含参数的构造方法。可能会使用到<constructor-arg>标签的type、value、index、name等属性来标识构造函数内的形参。

4.1.2 Setter注入

        setter注入使用类中的setter方法来完成依赖注入。

4.1.3 到底是选择基于构造函数DI还是基于setter的DI?

        Spring 团队提倡构造函数注入,因为它允许您将应用程序组件实现为不可变对象,并确保所需的依赖项不是null。由于您可以混合使用基于构造函数的DI和基于setter的DI,因此将构造函数用于强制依赖项并将 setter 方法或配置方法用于可选依赖项是一个很好的经验法则。

4.1.4 循环依赖

        Spring中的循环依赖是一个复杂的问题,我会单起一篇博文分析。

4.2 如何配置详细依赖关系

给bean属性赋值

        <property>标签可以给Bean属性赋值

        <ref>标签负责配置依赖bean

        还有一些为bean里list、map赋值的标签(如<list/>, <set/>, <map/>)就省略了,这种标签也不用急,需要的时候查官方文档就好。

4.3 延迟初始化

        延迟初始化(懒加载):告诉Spring IOC容器在第一次请求时创建一个bean实例,而不是启动时。

        <bean>标签里lazy-init属性可以控制bean是否延迟初始化:

        也可以在容器级别控制延迟初始化,属性值为default-lazy-init:

4.4 自动装配

        Spring有四种装配模式,分别为no、byName、byType、constructor。

        no:不自动装配。

        byName:按属性名称自动装配,Spring寻找与需要自动装配的属性同名的 bean。例如一个bean内部有一个master属性,并被设置为自动装配,那么Spring会去查找一个名为master的bean来进行装配。

        byType:如果容器中恰好存在一个属性类型的 bean,则让属性自动装配。如果存在多个同名bean,则会抛出一个致命异常。如果没有匹配的bean,则不会发生任何事情。

        constructor:类似于byType但适用于构造函数参数。如果容器中没有一个构造函数参数类型的 bean,则会引发致命错误。        

自动装配的优缺点

        优点:

        1.自动装配能显著减少装配的数量,因此在配置数量相当多时采用自动装配,可以减少工作量。

        2.自动装配可以使配置与Java代码同步更新。例如:如果需要给一个Java类增加一个依赖,那么该依赖将自动实现而不需要修改配置。因此强烈在开发过程中采用自动装配,而在系统趋于稳定的时候改为显式装配的方式。

        缺点:

        1.Spring会尽量避免在装配不明确时进行猜测,因为装配不明确可能出现难以预料的结果,而Spring所管理的对象之间的关联关系也不再能清晰地进行文档化。

        2.对于那些根据Spring配置文件生成文档的工具来说,自动装配将会使这些工具无法生成依赖信息。

4.5 方法注入

4.5.1 查找方法注入

        LookUp方法查找注入是Spring容器覆盖Bean指定方法返回容器内另一个Bean的功能。这个功能在一个Scope
为singleton的Bean需要返回Scope为protocol的Bean时很实用。 

        User类:scope为原型的bean

        UserManage:scope为单例的bean,内部依赖原型bean

        XML配置:

        Application:

        可以看出来User是两个不同的对象,但是UserManage对象一直是单例的。 

4.5.2 任意方法替换

        Spring官网说这种方式不太有用,就不写了。

5.Bean的范围

5.1 singleton

        只有一个单例bean的共享实例会被Spring管理,并且所有对具有与该 bean 定义匹配的一个或多个 ID 的 bean 的请求都会导致 Spring 容器返回一个特定的 bean 实例。

        换句话说,当您定义 bean 定义并将其限定为单例时,Spring IoC 容器会创建该 bean 定义所定义的对象的一个​​实例。此单个实例存储在此类单例 bean 的缓存中,并且该命名 bean 的所有后续请求和引用都返回缓存的对象。下图显示了单例作用域的工作原理:

5.2 prototype

        bean 部署的非单例原型范围导致每次对特定 bean 发出请求时都会创建一个新的 bean 实例。也就是说,将 bean 注入到另一个 bean 中,或者您通过getBean()容器上的方法调用来请求它。通常,您应该对所有有状态 bean 使用原型范围,对无状态 bean 使用单例范围。
下图说明了 Spring 原型范围:

        与其他范围相比,Spring 不管理原型 bean 的完整生命周期。容器实例化、配置和以其他方式组装原型对象并将其传递给客户端,而没有该原型实例的进一步记录。因此,尽管在所有对象上调用初始化生命周期回调方法而不考虑范围,但在原型的情况下,不会调用配置的销毁生命周期回调。客户端代码必须清理原型范围的对象并释放原型 bean 拥有的昂贵资源。要让 Spring 容器释放原型范围 bean 持有的资源,请尝试使用自定义bean 后处理器,它包含对需要清理的 bean 的引用。
        在某些方面,Spring 容器在原型范围 bean 方面的角色是 Javanew运算符的替代品。此后的所有生命周期管理都必须由客户处理。

5.3 依赖原型bean的单例bean

        如果将原型范围的 bean 依赖注入到单例范围的 bean 中,则会实例化一个新的原型 bean,然后将依赖注入到单例 bean 中。原型实例是提供给单例范围 bean 的唯一实例。
        但是如果希望单例 bean 在运行时重复获取原型 bean 的新实例。您不能将原型范围的 bean 依赖注入到单例 bean 中,因为该注入仅发生一次,为当 Spring 容器实例化单例 bean 并解析并注入其依赖项时。如果您在运行时多次需要原型 bean 的新实例,请参阅4.5 方法注入。

5.4 request、session、application、websocket

        上述四种作用域与web相关。为了支持request、session、application和 websocket级别的 bean 范围,在定义 bean 之前需要进行一些小的初始配置。如何完成此初始设置取决于您的特定 Servlet 环境。如果您在 Spring Web MVC 中访问作用域 bean,实际上,在 Spring 处理的请求中DispatcherServlet,不需要特殊设置。 DispatcherServlet已经暴露了所有相关状态。

        request: 只Web应用有效,每次Http请求,创建一个Bean的实例
        session: 只Web应用有效,每个session会话过程中有效
        application:只Web应用有效,整个web应用中唯一
        webSocket: 只Web应用有效,webSocket声明周期内唯一

6.扩展容器中的bean

6.1 生命周期回调

6.1.1 初始化回调

实现bean的初始化回调有三种方式

  1. xml配置 init-method
  2. 实现接口 InitializingBean
  3. 注解 @PostConstruct

xml配置 init-method

实现接口 InitializingBean

注解 @PostConstruct 

6.1.2 销毁回调

实现bean的销毁回调有三种方式

  1. xml配置 destroy-method
  2. 实现接口 DisposableBean
  3. 注解 @PreDestroy

xml配置 destroy-method

实现接口 DisposableBean

注解 @PreDestroy

6.1.3 xml配置默认初始化和销毁方法

        如果想设置全局bean的初始化和销毁方法,可以在XML文件顶级标签<beans>中进行统一配置。

6.1.4 生命周期配置的优先级

为同一个bean配置的多个生命周期机制,不同的初始化方法,调用顺序如下:

  1. 用注释的方法@PostConstruct
  2. afterPropertiesSet()由InitializingBean回调接口定义
  3. 自定义配置init()方法

销毁方法的调用顺序相同:

  1. 用注释的方法@PreDestroy
  2. destroy()由DisposableBean回调接口定义
  3. 自定义配置destroy()方法

6.1.5 启动和关闭回调

Lifecycle

        Lifecycle是一个接口,它的作用是让开发者可以在所有的bean都创建完成(getBean)之后执行自己的初始化工作,或者在退出时执行资源销毁工作。

        Lifecycle定义了三个方法,任何Bean实现了Lifecycle方法,当ApplicationContext收到start、stop和restart等信号时,就会调用对应的方法。因此可以通过实现Lifecycle接口获得容器生命周期的回调,实现业务扩展。

        单纯的将上述代码添加的Spring Boot项目当中,你会发现启动时并没有打印出任何相关的日志,只有在关闭应用时会打印出:

        这是因为,在SpringBoot或Spring应用中如果没有调用AbstractApplicationContext#start方法,只是实现了Lifecycle接口,是不会执行Lifecycle接口中的启动方法和isRunning方法的。但在应用退出时会执行Lifecycle#isRunning方法判断该Lifecycle是否已经启动,如果返回true则调用Lifecycle#stop()停止方法。

        这个实例有一个很明显的问题,那就是需要使用者显式的调用容器的start()和stop()方法,Lifecycle的接口方法才会被执行。

        而在一般的项目中,我们很少这样显式的去调用,所以就需要一个更“聪明”的类来处理,这就是SmartLifecycle。

SmartLifecycle

        SmartLifecycle继承自Lifecycle,提供了更丰富的功能:

  1. start()方法无需容器显式调用就可以被执行
  2. 借助于Phased接口可以控制多SmartLifecycle实例的执行顺序

可以看到SmartLifecycle接口内部共有三个方法

        可以看出该接口除了继承Lifecycle接口外,还继承了Phased。其中getPhase方法便是来自Phased。也正是基于Phased接口的这个方法来控制SmartLifecycle的执行顺序的。

User类源码:

/**
 * @author liuxiaobai
 */
@Component
public class User implements SmartLifecycle {

	private volatile boolean running = false;

	/**
	 * 如果该`SmartLifecycle`类所在的上下文在调用`refresh`时,希望能够自己自动进行回调,则返回`true`,
	 * false的值表明组件打算通过显式的start()调用来启动,类似于普通的Lifecycle实现。
	 */
	@Override
	public boolean isAutoStartup() {
		return true;
	}

	/**
	 * SmartLifecycle子类的才有的方法,当isRunning方法返回true时,该方法才会被调用。
	 * 很多框架中的源码中,都会把真正逻辑写在stop()方法内。
	 */
	@Override
	public void stop(Runnable callback) {
		System.out.println("User容器停止,执行回调函数");
		stop();
		// 如果你让isRunning返回true,需要执行stop这个方法,那么就不要忘记调用callback.run()。
		// 否则在程序退出时,Spring的DefaultLifecycleProcessor会认为这个User没有stop完成,程序会一直卡着结束不了,等待一定时间(默认超时时间30秒)后才会自动结束。
		callback.run();
	}

	/**
	 * 1. 主要在该方法中启动任务或者其他异步服务,比如开启MQ接收消息<br/>
	 * 2. 当上下文被刷新(所有对象已被实例化和初始化之后)时,将调用该方法,
	 * 默认生命周期处理器将检查每个SmartLifecycle对象的isAutoStartup()方法返回的布尔值。
	 * 如果为“true”,则该方法会被调用,而不是等待显式调用自己的start()方法。
	 */
	@Override
	public void start() {
		System.out.println("User容器启动完成");
		running = true;
	}

	/**
	 * 接口Lifecycle子类的方法,只有非SmartLifecycle的子类才会执行该方法。<br/>
	 * 1. 该方法只对直接实现接口Lifecycle的类才起作用,对实现SmartLifecycle接口的类无效。<br/>
	 * 2. 方法stop()和方法stop(Runnable callback)的区别只在于,后者是SmartLifecycle子类的专属。
	 */
	@Override
	public void stop() {
		System.out.println("User容器停止");
		running = false;
	}

	/**
	 * 1. 只有该方法返回false时,start方法才会被执行。<br/>
	 * 2. 只有该方法返回true时,stop(Runnable callback)或stop()方法才会被执行。
	 */
	@Override
	public boolean isRunning() {
		System.out.println("User检查运行状态");
		return running;
	}

	/**
	 * 如果有多个实现接口SmartLifecycle的类,则这些类的start的执行顺序按getPhase方法返回值从小到大执行。<br/>
	 * 例如:1比2先执行,-1比0先执行。stop方法的执行顺序则相反,getPhase返回值较大类的stop方法先被调用,小的后被调用。
	 */
	@Override
	public int getPhase() {
		return 0;
	}
}

        通过上述实例可以看出:如果一个Bean实现了SmartLifecycle接口,则会执行启动方法。SmartLifecycle#isRunning判断是否已经执行,返回false表示还未执行,则调用SmartLifecycle#start()执行。当容器关闭时,同样先检查运行状态,如果正在运行,则执行关闭操作。关闭时,还可以处理对应的回调函数。其中,Phased返回值越小,优先级越高。

6.1.6 非web应用如何优雅关闭Spring IOC容器

        如果您在非 Web 应用程序环境中使用 Spring 的 IOC 容器,请向 JVM 注册一个关闭挂钩。这样做可确保正常关闭并在单例 bean 上调用相关的销毁方法,以便释放所有资源。要注册关闭挂钩,请调用接口ConfigurableApplicationContext的registerShutdownHook()方法

6.2 ApplicationContextAware和BeanNameAware

        ApplicationContextAware:实现此接口的bean可以获取到配置它们的ApplicationContext实例的引用,访问到ApplicationContext。

        BeanNameAware:实现此接口的类可以获取自己在Spring容器中的beanName。

6.3 其他感知接口

        ApplicationContextAware、ApplicationEventPublisherAware、BeanClassLoaderAware、BeanFactoryAware、BeanNameAware、BootstrapContextAware、LoadTimeWeaverAware、MessageSourceAware、NotificationPublisherAware、ResourceLoaderAware、ServletConfigAware、ServletContextAware

        具体功能详见官方文档:Core Technologieshttps://docs.spring.io/spring-framework/docs/5.2.20.RELEASE/spring-framework-reference/core.html#aware-list

7.扩展IOC容器

7.1 使用BeanPostProcessor自定义bean

        BeanPostProcessor接口提供了两个方法:postProcessBeforeInitialization和postProcessAfterInitialization

        由方法名字也可以看出,前者在实例化及依赖注入完成后、在任何初始化代码(比如配置文件中的init-method)调用之前调用;后者在初始化代码调用之后调用。此处需要注意的是:接口中的两个方法都要将传入的 bean 返回,而不能返回 null,如果返回的是 null 那么我们通过 getBean() 方法将得不到目标。

7.2 使用BeanFactoryPostProcessor自定义配置元数据

        User类:

        XML配置:

        Application:

        新写个MyBeanFactoryPostProcessor实现BeanFactoryPostProcessor接口:

        Application可见User对象的属性值已被修改:

7.3 使用FactoryBean自定义实例化逻辑

        FactoryBean接口是Spring IOC容器Bean实例化逻辑的一个扩展点,如果有复杂的初始化Bean逻辑,则可以选择创建自定义FactoryBean,在该类中编写初始化逻辑,然后把自定义FactoryBean注入到容器中即可。

FactoryBean提供了三种方法:

  1. Object getObject():返回此工厂创建的对象的实例。实例可以共享,具体取决于该工厂是否返回单例或原型。
  2. boolean isSingleton():true表示返回单例对象。
  3. Class getObjectType():返回getObject方法返回的对象类型;如果类型null,则返回null。

        自定义MyFactoryBean:

        XML配置文件:

        Application:

8.基于注解的IOC配置

8.1 使用@Autowired

        我们都知道在Spring中@Autowired注解是用来自动装配对象的,默认情况下spring是按照类型装配的,也就是我们所说的byType方式。此外,@Autowired注解的required参数默认是true,表示开启自动装配,有些时候我们不想使用自动装配功能,可以将该参数设置成false。

        @Autowired可以作用在构造器、方法、成员变量上。

8.1.1 作用于构造器

        在Spring4.3之后,如果一个类中仅定义了一个构造函数,则不再需要对此类构造函数打@Autowired了。 

8.1.2 作用于setter方法

8.1.3 作用于成员变量

8.2 使用@Primary进行精细装配

        作用:当有多个相同类型的bean时,使用@Primary来赋予bean更高的优先级。

UserInterface:

User1、User2两个上述接口的实现类:

Application启动报错:

解决方案:在其中一个主要的实现类上打上@Primary标签

8.3 使用@Qualifier进行精细装配

@Qualifier有两个作用:

  1. 使用@Autowired自动注入的时候,可以在属性或参数加上@Qualifier("xxx")指定注入到对象;
  2. 可以作为筛选的限定符,我们在写自定义的注解时,可以在其定义上增加@Qualifier,来筛选需要的对象。

作用1:

作用2: 

        UserInterface:

        @DI:自定义注解

        User1:打了自定义@DI注解和扫描注解

        User2:没打自定义@DI注解,但打了扫描注解

        Demo:测试是否能按需注入

        Application:可以看到Spring只注入了打了@DI注解的User1

9.基于java的容器配置

9.1 @Bean和@Configuration

        @Configuration和@Bean是Spring中常用的用于配置的Bean的两个注解。

9.2 使用@Bean

        在@Configuration和@Component可以使用@Bean注解。作用:用@Bean注解的方法会实例化、配置并初始化返回一个新的对象,这个对象会由spring IoC 容器管理。

        相当于XML中以下配置

         bean默认使用注解方法名作为bean的id)

        可以给bean起别名(相当于XML配置中的name)

9.3 使用@Configuration

        @Configuration和@Component区别在于@Configuration可以在注入类内依赖的Bean,即上一个方法注入的bean可以被下一个方法获取使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值