04 Spring 容器

Spring 注解驱动开发

Spring 有两个核心接口:BeanFactoryApplicationContext,其中 ApplicationContextBeanFactory 的子接口。他们都可代表 Spring 容器,Spring 容器是生成 Bean 实例的工厂,并且管理容器中的 Bean,包括整个的生命周期的管理——创建、装配、销毁。

Bean 是 Spring 管理的基本单位,在基于 Spring 的 Java EE 应用中,所有的组件都被当成 Bean 处理,包括数据源、Hibernate 的 SessionFactory、事务管理器等。在 Spring 中,Bean 的是一个非常广义的概念,任何的 Java 对象、Java 组件都被当成 Bean 处理。

应用中的所有组件,都被 Spring 以 Bean 的方式管理,Spring 负责创建 Bean 实例,并管理他们的生命周期。Bean 在 Spring 容器中运行,无须感受 Spring 容器的存在,一样可以接受 Spring 的依赖注入,包括 Bean 属性的注入、协作者的注入、依赖关系的注入等。

Spring 容器负责创建 Bean 实例,所以需要知道每个 Bean 的实现类,Java 程序面向接口编程,无须关心 Bean 实例的实现类;但是 Spring 容器必须能够精确知道每个 Bean 实例的实现类,因此 Spring 配置文件必须精确配置 Bean 实例的实现类。

BeanFactory

Spring 容器最基本的接口就是 BeanFactoryBeanFactory 负责配置、创建、管理 Bean,ApplicationContext 是它的子接口,因此也称之为 Spring 上下文。Spring 容器负责管理 Bean 与 Bean 之间的依赖关系。

BeanFactory 接口包含以下几个基本方法:

  • Boolean containBean(String name):判断 Spring 容器是否包含 id 为 name 的 Bean 实例。

  • Object getBean(String name):返回 Spring 容器中 id 为 name 的 Bean 实例。

  • <T> getBean(Class<T> requiredType):获取 Spring 容器中属于 requiredType 类型的唯一的 Bean 实例。

  • <T> T getBean(String name, Class requiredType):返回容器中 id 为 name,并且类型为 requiredType 的 Bean

  • Class <?> getType(String name):返回容器中指定 Bean 实例的类型。

调用者只需使用 getBean() 方法即可获得指定 Bean 的引用,无须关心 Bean 的实例化过程,即 Bean 实例的创建过程完全透明。

这些方法可以参考之前的笔记 15 Spring Boot 管理 beanarrow-up-right

创建 Spring 容器实例时,必须提供 Spring 容器管理的 Bean 的详细配置信息。Spring 的配置信息通常采用 xml 配置文件来设置,因此,创建 BeanFactory 实例时,应该提供 XML 配置文件作为参数。

XML 配置文件通常使用 Resource 对象传入。Resource 接口是 Spring 提供的资源访问接口,通过使用该接口,Spring 能够以简单、透明的方式访问磁盘、类路径以及网络上的资源。一般使用如下方式实例化 BeanFactory

在使用 BeanFactory 接口时,我们一般都是使用这个实现类:org.springframework.beans.factory.xml.XmlBeanFactory。然而 ApplicationContext 作为 BeanFactory 的子接口,使用它作为 Spring 容器会更加方便。它的实现类有:

  • FileSystemXmlApplicationContext:以基于文件系统的 XML 配置文件创建 ApplicationContext 实例。

  • ClassPathXmlApplicationContext:以类加载路径下的 XML 配置文件创建 ApplicationContext 实例。

  • XmlWebApplicationContext:以 web 应用下的 XML 配置文件创建 ApplicationContext 实例。

  • AnnotationConfigApplicationContext:以 java 的配置类创建 ApplicationContext 实例。

食用方式:

组件注册

我们举个栗子,比较传统的 xml 配置文件注册 bean 和注解方式注册 bean 两种方式。

Spring Context依赖

给容器中注册组件有以下几种方式:

  1. @Bean:导入第三方包里面的组件

  2. @ComponentScan 包扫描 + 组件标注注解 @Controller/@Service/@Repository/@Component:导入自己写的类

  3. @Import:快速给容器中导入一个组件

  4. 使用 Spring 提供的 FactoryBean(工厂 Bean)

@Bean

1.使用 xml 配置文件

然后通过 ClassPathXmlApplicationContext 引入类路径下的配置文件来注册 bean:

2.使用注解方式

然后通过 AnnotationConfigApplicationContext 引入注解配置文件来注册 bean:

需要注意:@Bean 注册 Bean,类型为返回值类型,id 默认为方法名,@Bean(name) 可以指定 bean id

@ComponentScan

@ComponentScan 包扫描:只要标注了 @Controller@Service@Repository@Component 注解的类都可以被自动注册为 bean。

1.使用 xml 配置文件

beans.xml 中加入:

2.使用注解方式

自定义过滤规则:

这里重点介绍一下 FilterType.CUSTOM 自定义过滤规则,先自定义自己的规则类:

食用自定义规则:

使用包扫描的两个注意点:

  • 注意 includeFilters 需要配合 useDefaultFilters 一起使用,禁用默认的过滤规则,因为默认的规则就是扫描所有的组件。

  • 如果使用的 jdk8 版本以上,@ComponentScan 可以重复使用,即可以多次使用该注解定义规则。如果版本在 jdk8 以下,可以使用 @ComponentScans 注解定义多个扫描规则。

时雨:@Configuration 只是配置文件,不会自动扫描包,需要配合 @ComponentScan 指定路径才会自动扫描注册。

@Scope

@Scope 调整组件注册作用域,默认情况下,组件注册是单实例的,注册后每次从容器中获取的实例都是同一个。

@Lazy

@Lazy 懒加载,针对上节提到的单实例 bean,单实例 bean 默认在容器启动的时候创建对象,通过懒加载让容器启动时不创建对象。第一次使用 Bean 时创建对象,并初始化。

@Conditional

@Conditional 按照一定的条件进行判断,满足条件给容器中注册 bean,它既可以作用在方法上,也可以作用在类上,当作用于类上时,满足当前条件时,这个类中配置的所有 bean 注册才能生效。

类中组件统一设置。;

判断是否 windows 系统:

在上下文环境中可以获取很多有用信息:

@Import

@Import 导入组件,id 默认是组件的全类名。

  1. @Import:容器中就会自动注册这个组件,id 默认是全类名

  2. ImportSelector:返回需要导入的组件的全类名数组;

  3. ImportBeanDefinitionRegistrar:手动注册 bean 到容器中

MyImportSelector

MyImportBeanDefinitionRegistrar

FactoryBean

注册 Bean:

需要注意:使用 Spring 提供的 FactoryBean,默认获取到的是工厂 bean 调用 getObject 创建的对象,要获取工厂 Bean 本身,我们需要给 id 前面加一个 &,如 &colorFactoryBean

如上栗子:@Bean 返回 ColorFactoryBean,默认获取 com.chanshiyu.bean.Color,需要加上 & 才返回 com.chanshiyu.bean.ColorFactoryBean

生命周期

bean 的生命周期:创建 --> 初始化 --> 销毁的过程。容器管理 bean 的生命周期。

  1. 指定初始化和销毁方法:通过 @Bean 指定 initMethoddestroyMethod

  2. 通过让 Bean 实现 InitializingBean(定义初始化逻辑)和 DisposableBean(定义销毁逻辑)接口;

  3. 可以使用 JSR250:

    • @PostConstruct:在 bean 创建完成并且属性赋值完成,来执行初始化方法

    • @PreDestroy:在容器销毁 bean 之前通知进行清理工作

  4. BeanPostProcessor【interface】:bean 的后置处理器,在 bean 初始化前后进行一些处理工作

    • postProcessBeforeInitialization:在初始化之前工作

    • postProcessAfterInitialization:在初始化之后工作

初始化和销毁

我们可以通过 @Bean 指定 initMethoddestroyMethod 自定义初始化和销毁方法,容器在 bean 进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法。

  • 初始化:对象创建完成,并赋值好,调用初始化方法,单实例在容器创建时创建对象,多实例在获取时候创建对象。

  • 销毁:单实例在容器关闭的时候销毁,多实例下容器不会管理这个 bean,容器不会调用销毁方法。

打印日志:

InitializingBean 和 DisposableBean

@PostConstruct 和 @PreDestroy

BeanPostProcessor

BeanPostProcessor 后置处理器:初始化前后进行处理工作,需要将后置处理器加入到容器中,将对每一个注册的 bean 都起作用。

遍历得到容器中所有的 BeanPostProcessor;挨个执行 beforeInitialization,一但返回 null,跳出 for 循环,不会执行后面的 beforeInitialization

BeanPostProcessor 执行顺序:

Spring 底层对 BeanPostProcessor 的使用:bean 赋值,注入其他组件,生命周期注解功能,@Autowired@Async 等等功能都是使用 BeanPostProcessor

属性赋值

使用 @Value赋值,赋值有三种形式:

  1. 基本数值

  2. 可以写#{},SpEL

  3. 可以写${},取出配置文件中的值(在运行环境变量里面的值)

在传统 xml 的方法中,需要装载配置文件:

使用注解方式依旧需要装载配置文件:

使用:

自动装配

自动装配:Spring 利用依赖注入(DI),完成对 IOC 容器中中各个组件的依赖关系赋值。

  1. 默认优先按照类型去容器中找对应的组件:applicationContext.getBean(BookDao.class),找到就赋值

  2. 如果找到多个相同类型的组件,再将属性的名称作为组件的 id 去容器中查找,applicationContext.getBean("bookDao")

  3. @Qualifier("bookDao"):使用 @Qualifier 指定需要装配的组件的 id,而不是使用属性名

  4. 自动装配默认一定要将属性赋值好,没有就会报错,@Autowired(required=false) 使容器中不必强制包含某种类型的 bean

  5. @Primary:让 Spring 进行自动装配的时候,默认使用首选的 bean;也可以继续使用 @Qualifier 指定需要装配的 bean 的名字,优先级 @Qualifier 大于 @Primary

@Autowired

栗子一:自动注入组件和容器中的组件是同一个实例

栗子二:存在多个同类型的组件,将属性的名称作为组件的 id 去容器中查找

接上面栗子,修改 BookDao,添加 label 属性,默认值为 "1":

再通过 @Bean 手动注入一个 BookDao,设置 label 为 "2":

@Autowired 自动注入 bookDao

此时自动注入的 bookDao 的 label 属性是 "1" 还是 "2" 呢:

根据规则一,默认优先按照类型去容器中找对应的组件,当找到多个相同类型的组件,再根据规则二,将属性的名称作为组件的 id 去容器中查找。这里自动注入的属性名为 bookDao,所以查找的组件 id 也是 bookDao,而我们通过 @Bean("bookDao2") 注入的组件 id 为 bookDao2,所以最终自动注入的组件 label 属性为 "1"。

通过 @Qualifier 指定注入的组件 id,而不是使用属性名:

通过 @Primary 让 Spring 进行自动装配的时候,默认使用首选的 bean,优先级 @Qualifier 大于 @Primary

@Resource/@Inject

spring 还支持 @Resource(JSR250)@Inject(JSR330) 自动注入。

@Autowired 属于 Spring 定义的注解,@Resource@Inject 都是 java 规范。

@Resource 可以和 @Autowired 一样实现自动装配功能;默认是按照组件名称进行装配的,但是没有能支持 @Primary 功能,也没有支持 @Autowired(reqiured=false),不过 @Resource 可以指定装配的组件 id,功能类似 @Qualifier

@Inject 需要导入 javax.inject 的包,和 @Autowired 的功能一样,支持 @Primary,但是没有支持@Autowired(reqiured=false)

自动装配位置

@Autowired 可以作用的位置:构造器,方法,参数,属性,都是从容器中获取参数组件的值。

  • 标注在构造器上:如果组件只有一个有参构造器,这个有参构造器的 @Autowired 可以省略,参数位置的组件还是可以自动从容器中获取

  • 标注在方法位置:@Bean + 方法参数,参数从容器中获取,@Autowired 可以省略,依旧可以自动装配

  • 标注在参数位置

下面四种情形的 @Autowired 都可以省略:

构造器

方法

参数

属性

再举栗一种常用的情形,@Bean + 方法参数,参数从容器中获取:

Aware

自定义组件想要使用 Spring 容器底层的一些组件,如ApplicationContextBeanFactory 等,可以实现接口 Aware,这样在创建对象的时候,会调用接口规定的方法注入 Spring 容器底层相关组件。

Aware 依旧通过 AwareProcessor 处理,如 ApplicationContextAware 会实现通过 ApplicationContextAwareProcessor

@Profile

Spring 通过环境标识注解 @Profile 为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能。加了环境标识的 bean,只有这个环境被激活的时候才能注册到容器中。默认是 default 环境。

@Profile 可以写在配置类上,只有指定的环境的时候,整个配置类里面的所有配置才能开始生效,没有标注环境标识的 bean 在,任何环境下都是加载的。

测试:

时雨:AnnotationConfigApplicationContext 有参构造器如下,使用无参构造器只多了设置环境标识的步骤。

Tips

获取运行环境信息

因为配置文件会装载进环境变量,所以 getProperty 可以获取配置文件中的值。

获取容器中所有 bean

获取容器中特定类型的 bean

最后更新于