文章目錄
夯實Spring系列|第六章:Spring Bean 註冊、實例化、初始化、銷燬
本章說明
在上一章,我們知道如何來定義一個 Bean ,如何來構建 BeanDefinition;本章主要簡單演示 Spring Bean 剩下的生命週期,Bean 生命週期後續有專門的章節來討論,本章知識簡單的代碼演示,討論一些相關的細節。
由於本章內容聯繫比較緊密,所有並沒有分爲幾章,導致內容較多,讀者可以通過目錄跳轉到有興趣的議題即可。
1.項目環境
- jdk 1.8
- spring 5.2.2.RELEASE
- github 地址:https://github.com/huajiexiewenfeng/thinking-in-spring
- 本章模塊:spring-bean
2.註冊 Spring Bean
BeanDefinition 註冊有以下方式
- XML 配置元信息
- <bean name="" …/>
- Java 註解配置元信息
- @Bean
- @Component
- @Import
- Java API 配置元信息
- 命名方式:BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)
- 非命名方式:BeanDefinitionReaderUtils#registerWithGeneratedName
- 配置類方式:AnnotatedBeanDefinitionReader#register(Class<?>… componentClasses)
2.1 註冊示例
之前的章節都是 xml 的方式進行註冊,下面的例子演示剩下幾種註冊方式。
2.1.1 Java 註解配置元信息
- @Bean 方式定義
- @Component 方式定義
//2.通過 @Component 方式定義
@Component //定義當前類最爲 Spring Bean 組件
public static class Config {
//1.通過 @Bean 方式定義
@Bean(name = {"user", "xwf-user"})
public User user() {
User user = new User();
user.setAge(18);
user.setName("xwf");
user.setId(123L);
return user;
}
}
- 通過 @Import 來進行導入
@Import(AnnotationBeanDefinitionDemo.Config.class)
public class AnnotationBeanDefinitionDemo {
...
2.2.2 Java API 配置元信息
完整示例
@Import(AnnotationBeanDefinitionDemo.Config.class)
public class AnnotationBeanDefinitionDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(AnnotationBeanDefinitionDemo.class);
//通過 BeanDefinition 註冊 API 實現
// 1.通過命名方式註冊
registerUserBeanDefinition(applicationContext,"definition-user");
// 2.非命名方式註冊
registerUserBeanDefinition(applicationContext);
//啓動 Spring 應用上下文
applicationContext.refresh();
Map<String,Config> beansOfType = applicationContext.getBeansOfType(Config.class);
printEach(beansOfType);
Map<String, User> users = applicationContext.getBeansOfType(User.class);
printEach(users);
applicationContext.close();
}
private static void printEach(Map<String, ?> beansOfType) {
System.out.println("=====");
beansOfType.entrySet().stream().forEach(System.out::println);
System.out.println("=====");
}
/**
* 命名 Bean 的註冊方式
*
* @param registry
* @param beanName
*/
public static void registerUserBeanDefinition(BeanDefinitionRegistry registry, String beanName) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
beanDefinitionBuilder.addPropertyValue("age", 18)
.addPropertyValue("id", 1L)
.addPropertyValue("name", "xwf");
if (StringUtils.hasText(beanName)) {
registry.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
} else {
//非命名方式
BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinitionBuilder.getBeanDefinition(), registry);
}
}
public static void registerUserBeanDefinition(BeanDefinitionRegistry registry) {
registerUserBeanDefinition(registry, null);
}
@Component //定義當前類最爲 Spring Bean 組件
public static class Config {
@Bean(name = {"user", "xwf-user"})
public User user() {
User user = new User();
user.setAge(18);
user.setName("xwf");
user.setId(123L);
return user;
}
}
}
控制檯打印結果
=====
com.huajie.thinking.in.spring.bean.definition.AnnotationBeanDefinitionDemo$Config=com.huajie.thinking.in.spring.bean.definition.AnnotationBeanDefinitionDemo$Config@3bf7ca37
=====
=====
definition-user=User{id=1, name='xwf', age=18, configFileReource=null, city=null, cities=null}
com.huajie.thinking.in.spring.ioc.overview.domain.User#0=User{id=1, name='xwf', age=18, configFileReource=null, city=null, cities=null}
user=User{id=123, name='xwf', age=18, configFileReource=null, city=null, cities=null}
=====
Config.class 類型的有一個對象,通過@Component
註冊的Config
類
User.class 類型的有三個對象
- 通過命名方式註冊:
definition-user
- 通過非命名的方式註冊:
com.huajie.thinking.in.spring.ioc.overview.domain.User#0
- 通過
@Bean
註冊:user
疑問:爲啥之前配置在 xml 中的 User 類型相關定義沒有獲取到?
因爲本例子中的 Application 爲 AnnotationConfigApplicationContext
,只關注註解相關的內容。
3.實例化 Spring Bean
Bean 實例化(Instantiation)
- 常規方式
- 通過構造器(配置元信息:XML、Java 註解和 Java API)
- 通過靜態工廠方法(配置元信息:XML和 Java API)
- 通過 Bean 工廠方法(配置元信息:XML和 Java API)
- 通過 FactoryBean (配置元信息:XML、Java 註解和 Java API)
- 特殊方式
- 通過 ServiceLoaderFactoryBean(配置元信息:XML、Java 註解和 Java API)
- 通過 AutowireCapableBeanFactory#createBean(java.lang.Class,int,boolean)
- 通過 BeanDefinitionRegistry#registerBeanDefinition(String,Beandefinition)
3.1 常規方式
3.1.1 通過構造器
- xml:<bean id=“user” …> 默認是通過構造器方式
- @Bean 在上一章已經展示過了
3.1.2 通過靜態工廠方法
我們再新建一個bean-instantiation-context.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 靜態方法實例化 Bean -->
<bean id="user-static-method" class="com.huajie.thinking.in.spring.ioc.overview.domain.User"
factory-method="createUser"></bean>
</beans>
User 類中增加 createUser 靜態方法,爲了演示方便多加一個參數帶 name 的方法。
/**
* 用戶類
*/
public class User {
...
public static User createUser() {
User user = new User();
user.setName("static-user");
return user;
}
//爲了演示方便,我們多加一個方法
public static User createUser(String name) {
User user = new User();
user.setName(name);
return user;
}
...
}
3.1.3 通過 Bean 工廠方法
bean-instantiation-context.xml
增加
<!-- 實例(Bean) 方法實例化 Bean -->
<bean id="user-instance-method" factory-method="createUser" factory-bean="user-factory-bean"></bean>
<bean id="user-factory-bean" class="com.huajie.thinking.in.spring.bean.factory.DefaultUserFactory"></bean>
</beans>
新建 UserFactory 接口
/**
* {@link User} 工廠類
*/
public interface UserFactory {
default User createUser(){
return User.createUser("工廠Bean-user");
}
}
新建 DefaultUserFactory 實現類,這裏我們用 default 實現,就不需要覆蓋了
public class DefaultUserFactory implements UserFactory {
}
3.1.4 通過 FactoryBean
bean-instantiation-context.xml
增加
<!-- FactoryBean實例化 Bean -->
<bean id="user-by-factory-bean" class="com.huajie.thinking.in.spring.bean.factory.UserFactoryBean"></bean>
新建 UserFactoryBean
public class UserFactoryBean implements FactoryBean {
@Override
public Object getObject() throws Exception {
return User.createUser("FactoryBean-user");
}
@Override
public Class<?> getObjectType() {
return User.class;
}
}
3.1.5 演示示例
/**
* Bean 實例化示例
*/
public class BeanInstantiationDemo {
public static void main(String[] args) {
BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/bean-instantiation-context.xml");
User xwfUser = (User) beanFactory.getBean("user-static-method");
User instanceUser = (User) beanFactory.getBean("user-instance-method");
User factoryUser = (User) beanFactory.getBean("user-by-factory-bean");
System.out.println(xwfUser);
System.out.println(instanceUser);
System.out.println(factoryUser);
}
}
控制檯打印結果
User{id=null, name='static-user', age=null, configFileReource=null, city=null, cities=null}
User{id=null, name='工廠Bean-user', age=null, configFileReource=null, city=null, cities=null}
User{id=null, name='FactoryBean-user', age=null, configFileReource=null, city=null, cities=null}
結果符合預期,爲了方便,三種方式底層都是複用了 User#createUser() 方法進行實例化,當然可以自行修改每種實現。
3.2 特殊方式
3.2.1 BeanDefinitionRegistry#registerBeanDefinition
在 本章 2.2.2 Java API 配置元信息 已經展示過了
3.2.2 ServiceLoaderFactoryBean
-
我們需要在
resources/META-INF/
下面建立services
目錄-
原因需要看 java.util.ServiceLoader 部分源碼
-
public final class ServiceLoader<S> implements Iterable<S> { private static final String PREFIX = "META-INF/services/"; ...
-
-
然後再建一個實現類全類名的文件
-
文件中寫入實現類的全類名,可以寫多個實現類。
com.huajie.thinking.in.spring.bean.factory.DefaultUserFactory
- 新建
special-bean-instantiation-context.xml
文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="userFactoryServiceLoader" class="org.springframework.beans.factory.serviceloader.ServiceLoaderFactoryBean">
<property name="serviceType" value="com.huajie.thinking.in.spring.bean.factory.UserFactory"></property>
</bean>
</beans>
爲什麼要這麼配置呢?
這裏需要看一下ServiceLoaderFactoryBean
源碼
org.springframework.beans.factory.serviceloader.AbstractServiceLoaderBasedFactoryBean#createInstance
創建實例的方法
/**
* Delegates to {@link #getObjectToExpose(java.util.ServiceLoader)}.
* @return the object to expose
*/
@Override
protected Object createInstance() {
Assert.notNull(getServiceType(), "Property 'serviceType' is required");
return getObjectToExpose(ServiceLoader.load(getServiceType(), this.beanClassLoader));
}
這裏調用了getServiceType()
方法獲取 serviceType
屬性,我們通過 xml 的方法來設置這個屬性。
- 調用示例
/**
* Bean 特殊實例化示例
*/
public class SpecialBeanInstantiationDemo {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:/META-INF/special-bean-instantiation-context.xml");
// 第一種方式:這種直接通過 ClassLoader 得到 ServiceLoader
demoServiceLoader();
// 第二種方式:通過 ServiceLoaderFactoryBean
ServiceLoader<UserFactory> load = beanFactory.getBean("userFactoryServiceLoader", ServiceLoader.class);
displayServiceLoader(load);
}
/**
* jdk中的反轉控制
*/
public static void demoServiceLoader() {
ServiceLoader<UserFactory> load = ServiceLoader.load(UserFactory.class, Thread.currentThread().getContextClassLoader());
displayServiceLoader(load);
}
public static void displayServiceLoader(ServiceLoader<UserFactory> load) {
Iterator<UserFactory> iterator = load.iterator();
while (iterator.hasNext()) {
UserFactory next = iterator.next();
System.out.println(next.createUser());
}
}
}
3.2.3 AutowireCapableBeanFactory#createBean
public class SpecialBeanInstantiationDemo {
public static void main(String[] args) {
ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:/META-INF/special-bean-instantiation-context.xml");
//通過 ApplicationContext 獲取 AutowireCapableBeanFactory
AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();
UserFactory bean = beanFactory.createBean(DefaultUserFactory.class);
System.out.println(bean.createUser());
}
}
4.初始化 Spring Bean
Bean 初始化(Initialization)
- @PostConstruct 標註方法
- 實現 InitializingBean 接口的 afterPropertiesSet() 方法
- 自定義初始化方法
- XML 配置:<bean init-method=“init” …/>
- Java 註解:@Bean(initMethod=“init”)
- Java API:AbstractBeanDefinition#setInitMethodName(String)
- @Bean(initMethod=“init”) 這種方式,最後方法執行的調用鏈也會進到這個 API 中
- 如果上述方法在同一個Bean初始化,順序如下
4.1 @PostConstruct 標註方法
public class DefaultUserFactory implements UserFactory {
//1.基於 @PostConstruct 註解
@PostConstruct
public void init() {
System.out.println("@PostConstruct : UserFactory 初始化....");
}
}
4.2 @Bean(initMethod=“init”)
DefaultUserFactory 加入初始化的方法initUserFactory()
public class DefaultUserFactory implements UserFactory {
//1.基於 @PostConstruct 註解
@PostConstruct
public void init() {
System.out.println("@PostConstruct : UserFactory 初始化....");
}
public void initUserFactory() {
System.out.println("自定義初始化 : UserFactory 初始化....");
}
}
初始化示例 BeanInitializationDemo
/**
* Bean 初始化示例
*/
@Configuration
public class BeanInitializationDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(BeanInitializationDemo.class);
//啓動應用上下文
applicationContext.refresh();
//依賴查找
UserFactory bean = applicationContext.getBean(UserFactory.class);
System.out.println(bean.createUser());
//關閉
applicationContext.close();
}
@Bean(initMethod = "initUserFactory")
public UserFactory userFactory(){
return new DefaultUserFactory();
}
}
4.3 實現 InitializingBean 接口的 afterPropertiesSet() 方法
public class DefaultUserFactory implements UserFactory, InitializingBean {
//1.基於 @PostConstruct 註解
@PostConstruct
public void init() {
System.out.println("@PostConstruct : UserFactory 初始化....");
}
public void initUserFactory() {
System.out.println("自定義初始化 : UserFactory 初始化....");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean : UserFactory 初始化....");
}
}
執行結果:
@PostConstruct : UserFactory 初始化....
InitializingBean : UserFactory 初始化....
自定義初始化 : UserFactory 初始化....
User{id=null, name='工廠Bean-user', age=null, configFileReource=null, city=null, cities=null}
5.延遲初始化 Spring Bean
Bean 延遲初始化(Lazy Initialization)
- XML 配置:<bean lazy-init=“true” …/>
- Java 註解:@Lazy(true)
加上一個@Lazy
註解
@Bean(initMethod = "initUserFactory")
@Lazy
public UserFactory userFactory(){
return new DefaultUserFactory();
}
問題:當某個 Bean 定義爲延遲初始化,那麼 Spring 容器返回的對象與非延遲的對象存在怎樣的差異?
非延遲初始化在 Spring 應用上下文啓動完成後,已經被初始化。
如果不加@Lazy
執行效果:
加上之後
結論:
當 Bean 定義爲延遲初始化,初始化的過程是在 Spring 應用上下文啓動之後,在本例子中通過applicationContext.getBean(UserFactory.class)
觸發 Bean 的初始化。延遲初始化和非延遲初始化在 Bean 的定義上面沒有區別,但是在依賴查找或者依賴注入有所不同。
源碼分析:
在 AbstractApplicationContext 的 refresh() 方法中,會初始化所有非延遲初始化的 singletons
- org.springframework.context.support.AbstractApplicationContext#finishBeanFactoryInitialization
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
6.銷燬 Spring Bean
Bean 銷燬(Destroy)
- @PreDestroy 標註方法
- 實現 DisposableBean 接口的 destory 方法
- 自定義銷燬方法
- XML 配置:<bean destroy=“destroy” …/>
- Java 註解:@Bean(destroy=“init”)
- Java API:AbstractBeanDefinition#setDestroyMethodName(String)
6.1 @PreDestroy 標註方法
public class DefaultUserFactory implements UserFactory, InitializingBean {
//1.基於 @PostConstruct 註解
@PostConstruct
public void init() {
System.out.println("@PostConstruct : UserFactory 初始化....");
}
public void initUserFactory() {
System.out.println("自定義初始化 : UserFactory 初始化....");
}
public void destroyUserFactory() {
System.out.println("自定義銷燬 : UserFactory 銷燬....");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean : UserFactory 初始化....");
}
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy : UserFactory 銷燬....");
}
}
6.2 @Bean(destroy=“init”)
@Bean(initMethod = "initUserFactory",destroyMethod = "destroyUserFactory")
@Lazy
public UserFactory userFactory(){
return new DefaultUserFactory();
}
6.3 實現 DisposableBean 接口的 destory 方法
public class DefaultUserFactory implements UserFactory, InitializingBean, DisposableBean {
//1.基於 @PostConstruct 註解
@PostConstruct
public void init() {
System.out.println("@PostConstruct : UserFactory 初始化....");
}
public void initUserFactory() {
System.out.println("自定義初始化 : UserFactory 初始化....");
}
public void destroyUserFactory() {
System.out.println("自定義銷燬 : UserFactory 銷燬....");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean : UserFactory 初始化....");
}
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy : UserFactory 銷燬....");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean : UserFactory 銷燬....");
}
}
示例
/**
* Bean 初始化示例
*/
@Configuration
public class BeanInitializationDemo {
public static void main(String[] args) throws InterruptedException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(BeanInitializationDemo.class);
//啓動應用上下文
applicationContext.refresh();
System.out.println("Spring 應用上下文已啓動");
//依賴查找
UserFactory bean = applicationContext.getBean(UserFactory.class);
System.out.println(bean.createUser());
//關閉
System.out.println("Spring 應用上下文準備關閉");
applicationContext.close();
System.out.println("Spring 應用上下文已經關閉");
}
@Bean(initMethod = "initUserFactory",destroyMethod = "destroyUserFactory")
@Lazy
public UserFactory userFactory(){
return new DefaultUserFactory();
}
}
控制執行結果:
Spring 應用上下文已啓動
@PostConstruct : UserFactory 初始化....
InitializingBean : UserFactory 初始化....
自定義初始化 : UserFactory 初始化....
User{id=null, name='工廠Bean-user', age=null, configFileReource=null, city=null, cities=null}
Spring 應用上下文準備關閉
@PreDestroy : UserFactory 銷燬....
DisposableBean : UserFactory 銷燬....
自定義銷燬 : UserFactory 銷燬....
Spring 應用上下文已經關閉
說明applicationContext.close()
應用上下文關閉的時候進行 Bean 的銷燬。
源碼實際調用的位置:
- org.springframework.context.support.AbstractApplicationContext#destroyBeans
7.垃圾回收
Bean 垃圾回收(GC)
- 關閉 Spirng 容器
- 執行 GC
- Spring Bean 覆蓋 finalize() 方法被回調
- 垃圾回收時,此方法會被回調
7.1 DefaultUserFactory 重寫 finalize()
方法
public class DefaultUserFactory implements UserFactory, InitializingBean, DisposableBean {
//1.基於 @PostConstruct 註解
@PostConstruct
public void init() {
System.out.println("@PostConstruct : UserFactory 初始化....");
}
public void initUserFactory() {
System.out.println("自定義初始化 : UserFactory 初始化....");
}
public void destroyUserFactory() {
System.out.println("自定義銷燬 : UserFactory 銷燬....");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("InitializingBean : UserFactory 初始化....");
}
@PreDestroy
public void preDestroy() {
System.out.println("@PreDestroy : UserFactory 銷燬....");
}
@Override
public void destroy() throws Exception {
System.out.println("DisposableBean : UserFactory 銷燬....");
}
@Override
protected void finalize() throws Throwable {
System.out.println("垃圾回收");
}
}
7.2 將 bean 對象設置爲 null,手動調用System.gc()
進行垃圾回收
/**
* Bean 初始化示例
*/
@Configuration
public class BeanInitializationDemo {
public static void main(String[] args) throws InterruptedException {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
applicationContext.register(BeanInitializationDemo.class);
//啓動應用上下文
applicationContext.refresh();
System.out.println("Spring 應用上下文已啓動");
//依賴查找
UserFactory bean = applicationContext.getBean(UserFactory.class);
System.out.println(bean.createUser());
//關閉
applicationContext.close();
//觸發垃圾回收
bean=null;
System.gc();
Thread.sleep(5000);
}
@Bean(initMethod = "initUserFactory",destroyMethod = "destroyUserFactory")
@Lazy
public UserFactory userFactory(){
return new DefaultUserFactory();
}
}
執行結果:
Spring 應用上下文已啓動
@PostConstruct : UserFactory 初始化....
InitializingBean : UserFactory 初始化....
自定義初始化 : UserFactory 初始化....
User{id=null, name='工廠Bean-user', age=null, configFileReource=null, city=null, cities=null}
@PreDestroy : UserFactory 銷燬....
DisposableBean : UserFactory 銷燬....
自定義銷燬 : UserFactory 銷燬....
垃圾回收
8.面試
1.如何註冊一個 Spring Bean ?
通過 BeanDefinition 和外部單體對象來註冊
- 外部單體對象註冊
/**
* 單體 Bean 註冊實例
*/
public class SingletonBeanRegistrationDemo {
public static void main(String[] args) {
AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
//註冊外部單例對象
UserFactory userFactory = new DefaultUserFactory();
ConfigurableListableBeanFactory beanFactory = applicationContext.getBeanFactory();
//註冊外部單例對象
beanFactory.registerSingleton("singleton-user-factory",userFactory);
//啓動應用上下文
applicationContext.refresh();
//通過依賴查找的方式獲取 UserFactory
UserFactory bean = beanFactory.getBean("singleton-user-factory", UserFactory.class);
System.out.println(bean.createUser());
System.out.println(userFactory==bean);
//關閉
applicationContext.close();
}
}
2.什麼是 Spring BeanDefinition?
上一章:夯實Spring系列|第五章:Spring Bean 定義
什麼是 BeanDefinition? 和 元信息 兩個章節
簡單來說,BeanDefinition 是 Spring 中用來定義 Bean 的配置元信息接口,比如 xml 中定義 相關的信息和配置屬性,在該接口中都能找到對應的 set/get 方法。
3.Spring 容器是怎樣管理註冊 Bean?
答案在後續的章節進行詳細的討論,IoC 配置元信息的讀取和解析,依賴查找和注入,Bean生命週期管理等等。
9.參考
- 極客時間-小馬哥《小馬哥講Spring核心編程思想》