04 Spring 容器

Spring 有两个核心接口:BeanFactory 和 ApplicationContext,其中 ApplicationContext 是 BeanFactory 的子接口。他们都可代表 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 容器最基本的接口就是 BeanFactory。BeanFactory 负责配置、创建、管理 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 的 BeanClass <?> getType(String name):返回容器中指定 Bean 实例的类型。
调用者只需使用 getBean() 方法即可获得指定 Bean 的引用,无须关心 Bean 的实例化过程,即 Bean 实例的创建过程完全透明。
这些方法可以参考之前的笔记 15 Spring Boot 管理 bean。
创建 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 两种方式。

给容器中注册组件有以下几种方式:
@Bean:导入第三方包里面的组件@ComponentScan包扫描 + 组件标注注解@Controller/@Service/@Repository/@Component:导入自己写的类@Import:快速给容器中导入一个组件使用 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 默认是组件的全类名。
@Import:容器中就会自动注册这个组件,id 默认是全类名ImportSelector:返回需要导入的组件的全类名数组;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 的生命周期。
指定初始化和销毁方法:通过
@Bean指定initMethod和destroyMethod;通过让 Bean 实现
InitializingBean(定义初始化逻辑)和DisposableBean(定义销毁逻辑)接口;可以使用 JSR250:
@PostConstruct:在 bean 创建完成并且属性赋值完成,来执行初始化方法@PreDestroy:在容器销毁 bean 之前通知进行清理工作
BeanPostProcessor【interface】:bean 的后置处理器,在 bean 初始化前后进行一些处理工作
postProcessBeforeInitialization:在初始化之前工作postProcessAfterInitialization:在初始化之后工作
初始化和销毁
我们可以通过 @Bean 指定 initMethod 和 destroyMethod 自定义初始化和销毁方法,容器在 bean 进行到当前生命周期的时候来调用我们自定义的初始化和销毁方法。
初始化:对象创建完成,并赋值好,调用初始化方法,单实例在容器创建时创建对象,多实例在获取时候创建对象。
销毁:单实例在容器关闭的时候销毁,多实例下容器不会管理这个 bean,容器不会调用销毁方法。
打印日志:
InitializingBean 和 DisposableBean
@PostConstruct 和 @PreDestroy
BeanPostProcessor
BeanPostProcessor 后置处理器:初始化前后进行处理工作,需要将后置处理器加入到容器中,将对每一个注册的 bean 都起作用。
遍历得到容器中所有的 BeanPostProcessor;挨个执行 beforeInitialization,一但返回 null,跳出 for 循环,不会执行后面的 beforeInitialization。
BeanPostProcessor 执行顺序:
Spring 底层对 BeanPostProcessor 的使用:bean 赋值,注入其他组件,生命周期注解功能,@Autowired,@Async 等等功能都是使用 BeanPostProcessor。
属性赋值
使用 @Value赋值,赋值有三种形式:
基本数值
可以写
#{},SpEL可以写
${},取出配置文件中的值(在运行环境变量里面的值)
在传统 xml 的方法中,需要装载配置文件:
使用注解方式依旧需要装载配置文件:
使用:
自动装配
自动装配:Spring 利用依赖注入(DI),完成对 IOC 容器中中各个组件的依赖关系赋值。
默认优先按照类型去容器中找对应的组件:
applicationContext.getBean(BookDao.class),找到就赋值如果找到多个相同类型的组件,再将属性的名称作为组件的 id 去容器中查找,
applicationContext.getBean("bookDao")@Qualifier("bookDao"):使用@Qualifier指定需要装配的组件的 id,而不是使用属性名自动装配默认一定要将属性赋值好,没有就会报错,
@Autowired(required=false)使容器中不必强制包含某种类型的 bean@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 容器底层的一些组件,如ApplicationContext,BeanFactory 等,可以实现接口 Aware,这样在创建对象的时候,会调用接口规定的方法注入 Spring 容器底层相关组件。
Aware 依旧通过 AwareProcessor 处理,如 ApplicationContextAware 会实现通过 ApplicationContextAwareProcessor。
@Profile
Spring 通过环境标识注解 @Profile 为我们提供的可以根据当前环境,动态的激活和切换一系列组件的功能。加了环境标识的 bean,只有这个环境被激活的时候才能注册到容器中。默认是 default 环境。
@Profile 可以写在配置类上,只有指定的环境的时候,整个配置类里面的所有配置才能开始生效,没有标注环境标识的 bean 在,任何环境下都是加载的。
测试:
时雨:AnnotationConfigApplicationContext 有参构造器如下,使用无参构造器只多了设置环境标识的步骤。
Tips
获取运行环境信息
因为配置文件会装载进环境变量,所以 getProperty 可以获取配置文件中的值。
获取容器中所有 bean
获取容器中特定类型的 bean
最后更新于
这有帮助吗?