Spring容器学习小抄
在 Spring 应用中,所有组件都被 以 Bean 的方式管理,Spring 负责创建 Bean 实例,并管理他们的生命周期。Bean 在 Spring 容器中运行,无须感受 Spring 容器的存在,一样可以接受 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 实例,所以需要知道每个 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 的 Bean
Class <?> getType(String name)
:返回容器中指定 Bean 实例的类型。
调用者只需使用 getBean()
方法即可获得指定 Bean 的引用,无须关心 Bean 的实例化过程,即 Bean 实例的创建过程完全透明。
这些方法可以参考之前的笔记https://chanshiyu.gitbook.io/blog/hou-duan/spring/15springboot-guan-li-bean
创建 Spring 容器实例时,必须提供 Spring 容器管理的 Bean 的详细配置信息。Spring 的配置信息通常采用 xml 配置文件来设置,因此,创建 BeanFactory
实例时,应该提供 XML 配置文件作为参数。
XML 配置文件通常使用 Resource 对象传入。Resource 接口是 Spring 提供的资源访问接口,通过使用该接口,Spring 能够以简单、透明的方式访问磁盘、类路径以及网络上的资源。一般使用如下方式实例化 BeanFactory
:
1 | // 搜索当前文件路径下的bean.xml文件创建Resource对象 |
在使用 BeanFactory
接口时,我们一般都是使用这个实现类:org.springframework.beans.factory.xml.XmlBeanFactory
。然而 ApplicationContext
作为 BeanFactory
的子接口,使用它作为 Spring 容器会更加方便。它的实现类有:
-
FileSystemXmlApplicationContext
:以基于文件系统的 XML 配置文件创建 ApplicationContext 实例。 -
ClassPathXmlApplicationContext
:以类加载路径下的 XML 配置文件创建ApplicationContext
实例。 -
XmlWebApplicationContext
:以 web 应用下的 XML 配置文件创建ApplicationContext
实例。 -
AnnotationConfigApplicationContext
:以 java 的配置类创建ApplicationContext
实例。
食用方式:
1 | // 搜索CLASSPATH路径,以classpath路径下的bean.xml、service.xml文件创建applicationContext |
组件注册
我们举个栗子,比较传统的 xml 配置文件注册 bean 和注解方式注册 bean 两种方式。
给容器中注册组件有以下几种方式:
- @Bean:导入第三方包里面的组件
- @ComponentScan 包扫描 + 组件标注注解 @Controller/@Service/@Repository/@Component:导入自己写的类
- @Import:快速给容器中导入一个组件
- 使用 Spring 提供的 FactoryBean(工厂 Bean)
@bean
1**.使用 xml 配置文件**
1 |
|
然后通过 ClassPathXmlApplicationContext
引入类路径下的配置文件来注册 bean:
1 | ApplicationContext applicationContext = new ClassPathXmlApplicationContext("beans.xml"); |
2.使用注解方式
1 | // 配置类 == 配置文件 |
然后通过 AnnotationConfigApplicationContext
引入注解配置文件来注册 bean:
1 | ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig.class); |
需要注意:@Bean
注册 Bean,类型为返回值类型,id 默认为方法名,@Bean(name)
可以指定 bean id。
@componentscan
@ComponentScan
包扫描:只要标注了 @Controller
、@Service
、@Repository
、@Component
注解的类都可以被自动注册为 bean。
1.使用 xml 配置文件
在 beans.xml
中加入:
1 | <context:component-scan base-package="com.chanshiyu"></context:component-scan> |
2.使用注解方式
1 |
|
自定义过滤规则:
1 | /** |
这里重点介绍一下 FilterType.CUSTOM
自定义过滤规则,先自定义自己的规则类:
1 | public class MyTypeFilter implements TypeFilter { |
食用自定义规则:
1 |
使用包扫描的两个注意点:
- 注意
includeFilters
需要配合useDefaultFilters
一起使用,禁用默认的过滤规则,因为默认的规则就是扫描所有的组件。
1 | <context:component-scan base-package="com.chanshiyu" use-default-filters="false"/> |
1 |
- 如果使用的 jdk8 版本以上,
@ComponentScan
可以重复使用,即可以多次使用该注解定义规则。如果版本在 jdk8 以下,可以使用@ComponentScans
注解定义多个扫描规则。
1 |
时雨:@Configuration
只是配置文件,不会自动扫描包,需要配合 @ComponentScan
指定路径才会自动扫描注册。
https://github.com/scope
@Scope
调整组件注册作用域,默认情况下,组件注册是单实例的,注册后每次从容器中获取的实例都是同一个。
1 | /** |
1 | ApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfig2.class); |
@lazy
@Lazy
懒加载,针对上节提到的单实例 bean,单实例 bean 默认在容器启动的时候创建对象,通过懒加载让容器启动时不创建对象。第一次使用 Bean 时创建对象,并初始化。
1 |
|
@conditional
@Conditional
按照一定的条件进行判断,满足条件给容器中注册 bean,它既可以作用在方法上,也可以作用在类上,当作用于类上时,满足当前条件时,这个类中配置的所有 bean 注册才能生效。
类中组件统一设置:
1 | // 如果系统是windows,给容器中注册 bill |
判断是否 windows 系统:
1 | public class WindowsCondition implements Condition { |
在上下文环境中可以获取很多有用信息:
1 | //1、能获取到 ioc 使用的 beanfactory |
https://github.com/import
@Import
导入组件,id 默认是组件的全类名。
@Import
:容器中就会自动注册这个组件,id 默认是全类名ImportSelector
:返回需要导入的组件的全类名数组;ImportBeanDefinitionRegistrar
:手动注册 bean 到容器中
1 |
MyImportSelector
:
1 | public class MyImportSelector implements ImportSelector { |
MyImportBeanDefinitionRegistrar
:
1 | public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar { |
FactoryBean
1 | public class ColorFactoryBean implements FactoryBean<Color> { |
注册 Bean:
1 |
|
1 | //工厂Bean获取的是调用getObject创建的对象 |
需要注意:使用 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,容器不会调用销毁方法。
1 | public class Car { |
1 |
|
1 | AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(MainConfigOfLifeCycle.class); |
打印日志:
car … constructor …
car … init …
容器创建成功…
car … destroy …
容器关闭…
1 | **InitializingBean 和 DisposableBean** |
1 | cat ... constructor ... |