天天看點

Spring Bean 是什麼?

本文内容如有錯誤、不足之處,歡迎技術愛好者們一同探讨,在本文下面讨論區留言,感謝。

文章目錄

        • 簡述
        • Spring Bean 基礎
          • 定義Spring Bean
          • BeanDefinition 元資訊
          • 命名 Spring Bean
          • Spring Bean 的别名
          • 注冊 Spring Bean
          • 執行個體化 Spring Bean
          • 初始化 Spring Bean
          • 延遲初始化 Spring Bean
          • 銷毀 Spring Bean
        • 總結
        • 參考資料

簡述

Spring 架構基礎核心之一是 Bean 的概念。Spring bean 是 Spring 架構在運作時管理的對象。Spring bean 是任何 Spring 應用程式的基本建構塊。如何去定義 Spring Bean ,這種 Bean 和傳統的 Java Bean 是有差別的。

Spring Framework 官方文檔對 bean 的定義:

In Spring, the objects that form the backbone of your application and that are managed by the Spring IoC container are called beans. A bean is an object that is instantiated, assembled, and otherwise managed by a Spring IoC container.

在 Spring 中,構成應用程式主幹并由 Spring IoC 容器管理的對象稱為 bean 。Bean 是由 Spring IoC 容器執行個體化,組裝和以其他方式管理的對象。

Spring Bean 基礎

Spring bean 隻是由 Spring 容器管理的執行個體對象,它們是由架構建立和關聯的,并放入 Spring 容器中,後續可以從那裡擷取它們。
定義Spring Bean

BeanDefinition 是 Spring Framework 中定義 Bean 的配置元資訊接口,包含:

  • Bean 的類名 全稱包含包名
  • Bean 行為配置元素,如作用域、自動綁定的模式、生命周期回調等
  • 其他 Bean 引用,又可稱作合作者(Collaboratiors) 或者依賴(Dependencies)
  • 配置設定,比如 Bean 屬性(Properties)
BeanDefinition 元資訊
屬性 說明
Class Bean 全類名,必須是具體類,不能用抽象類或接口
Name Bean 的名稱或者 ID
Scope Bean 的作用域(如:singleton、prototype等)
Constructor arguments Bean 構造器參數(用于依賴注入)
Properties Bean 屬性設定(用于依賴注入)
Autowiring mode Bean 自動綁定模式(如:通過名稱 byName)
Lazy initialization mode Bean 延遲初始化模式(延遲和非延遲)
Initialization method Bean 初始化回調方法名稱
Destruction method Bean 銷毀回調方法名稱
  • BeanDefinition 建構的兩種方式
    1. 通過 BeanDefinitonBuilder
    2. 通過 AbstractBeanDefinition 以及派生類

User POJO 類

public class User {
    private Long id;
    private String name;

    public void setId(Long id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }
}
           

BeanDefianitonDemo BeanDefiniton 兩種建構方式的代碼

public class BeanDefianitonDemo {

    public static void main(String[] args) {
        //1. 通過 BeanDefinitionBuilder
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
        // 通過屬性設定
        beanDefinitionBuilder.addPropertyValue("id", 1);
        beanDefinitionBuilder.addPropertyValue("name", "小明");
        BeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();

        // 2. 通過 AbstractBeanDefinition 以及派生類
        GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
        // 設定 Bean 類型
        genericBeanDefinition.setBeanClass(User.class);
        // 通過 MutablePropertyValues 批量操作屬性
        MutablePropertyValues propertyValues = new MutablePropertyValues();
        propertyValues.add("id", 1).add("name", "小紅");
        genericBeanDefinition.setPropertyValues(propertyValues);
        
    }
}
           
命名 Spring Bean

每個 Bean 擁有一個或多個辨別符(identifiers),這些辨別符在 Bean 所在的容器必須是唯一的。通常,一個 Bean 僅有一個辨別符,如果需要額外的,可考慮使用别名(Alias)來擴充。

通常 Bean 的 辨別符由字母組成,允許出現特殊字元。如果要想引入 Bean 的别名的話,可在 name 屬性使用半形逗號(“,”)或分号(“;”) 來間隔。

Bean 的 id 或 name 屬性并非必須制定,如果留白的話,容器會為 Bean 自動生成一個唯一的名稱。Bean 的命名盡管沒有限制,官方建議采用駝峰的方式,更符合 Java 的命名約定。

Bean 名稱生成器(BeanNameGenerator)由 Spring Framework 2.0.3 引入,架構內建兩種實作:

  • DefaultBeanNameGenerator:預設通用 BeanNameGenerator 實作。
  • AnnotationBeanNameGenerator:基于注解掃描的 BeanNameGenerator 實作。

BeanNameGenerator 源碼

public interface BeanNameGenerator {

	/**
	 * Generate a bean name for the given bean definition.
	 * @param definition the bean definition to generate a name for
	 * @param registry the bean definition registry that the given definition
	 * is supposed to be registered with
	 * @return the generated bean name
	 */
	String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry);

}
           

DefaultBeanNameGenerator 源碼

public class DefaultBeanNameGenerator implements BeanNameGenerator {

	@Override
	public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
		return BeanDefinitionReaderUtils.generateBeanName(definition, registry);
	}

}
           

AnnotationBeanNameGenerator 源碼

public class AnnotationBeanNameGenerator implements BeanNameGenerator {

	private static final String COMPONENT_ANNOTATION_CLASSNAME = "org.springframework.stereotype.Component";


	@Override
	public String generateBeanName(BeanDefinition definition, BeanDefinitionRegistry registry) {
		// 判斷是否為 注解 bean 定義
		if (definition instanceof AnnotatedBeanDefinition) {
			String beanName = determineBeanNameFromAnnotation((AnnotatedBeanDefinition) definition);
			if (StringUtils.hasText(beanName)) {
				// Explicit bean name found.
				return beanName;
			}
		}
		// Fallback: generate a unique default bean name.
		// 回調,生成預設的bean 名稱
		return buildDefaultBeanName(definition, registry);
	}
}
           

BeanDefinitionReaderUtils#generateBeanName()

if (isInnerBean) {
			// Inner bean: generate identity hashcode suffix.
			// 嵌套bean,bean裡面生成bean :生成 定義的 hash編碼 作為字尾,使用 # 來連接配接字首和字尾
			id = generatedBeanName + GENERATED_BEAN_NAME_SEPARATOR + ObjectUtils.getIdentityHexString(definition);
		}
else {
	// Top-level bean: use plain class name with unique suffix if necessary.
	// 頂層 bean 
	return uniqueBeanName(generatedBeanName, registry);
}
           

BeanDefinitionReaderUtils#uniqueBeanName()

public static String uniqueBeanName(String beanName, BeanDefinitionRegistry registry) {
	String id = beanName;
	int counter = -1;

	// Increase counter until the id is unique.
	// counter 計數器 從0開始
	while (counter == -1 || registry.containsBeanDefinition(id)) {
		counter++;
		// GENERATED_BEAN_NAME_SEPARATOR # 來連接配接 bean名稱和計數器
		id = beanName + GENERATED_BEAN_NAME_SEPARATOR + counter;
	}
	return id;
}
           
Spring Bean 的别名

Bean 别名使開發人員可以覆寫已配置的 Bean,并用不同的對象定義替換它們。當 bean 定義是無法控制的外部資源繼承時,這個時候别名非常有用。

Bean 别名(Alias)的價值

  • 複用現有的 BeanDefinition,不能無中生有
  • 更具有場景化的命名方法,比如:
<alias name="myApp-dataSource" alias="subsystemA-dataSource"/>
<alias name="myApp-dataSource" alias="subsystemB-dataSource"/>
           

呼應上面更具場景化的命名方法,下面将列舉一個實際場景中出現的做法。

src/main/resources/spring/applicationContext-tx.xml

<bean id="dataSource" class="bitronix.tm.resource.jdbc.PoolingDataSource" init-method="init" destroy-method="close">
    <property name="className" value="${jdbc.driverClassName}"/>
    <property name="uniqueName" value="dataSource"/>
    <property name="minPoolSize" value="0"/>
    <property name="maxPoolSize" value="5"/>
    <property name="allowLocalTransactions" value="false" />
    <property name="driverProperties">
        <props>
            <prop key="user">${jdbc.username}</prop>
            <prop key="password">${jdbc.password}</prop>
            <prop key="url">${jdbc.url}</prop>
        </props>
    </property>
</bean>
 
<bean id="jtaTransactionManager" factory-method="getTransactionManager"
    class="bitronix.tm.TransactionManagerServices" depends-on="btmConfig, dataSource"
    destroy-method="shutdown"/>
           

dataSource bean 定義期望使用 XA 資料源,但是由于 HSQLDB 不提供 XA 資料源,是以必須依靠 LrcXADataSource 來解決此限制。但這意味着 DataSource 将更改為使用不同的 className 和 driverProperties ,因為上下文環境配置來自外部工件,是以實作起來比較複雜。

src/test/resources/spring/applicationContext-test.xml

<import resource="classpath:spring/applicationContext-tx.xml" />
 
<bean id="testDataSource" class="bitronix.tm.resource.jdbc.PoolingDataSource" init-method="init" destroy-method="close">
    <property name="className" value="bitronix.tm.resource.jdbc.lrc.LrcXADataSource"/>
    <property name="uniqueName" value="testDataSource"/>
    <property name="minPoolSize" value="0"/>
    <property name="maxPoolSize" value="5"/>
    <property name="allowLocalTransactions" value="false" />
    <property name="driverProperties">
        <props>
            <prop key="user">${jdbc.username}</prop>
            <prop key="password">${jdbc.password}</prop>
            <prop key="url">${jdbc.url}</prop>
            <prop key="driverClassName">${jdbc.driverClassName}</prop>
        </props>
    </property>
</bean>
 
<alias name="testDataSource" alias="dataSource"/>
           

testDataSource 與繼承的 dataSource 具有相同的 Class 類型,但它可以具有不同的對象配置。每次開發人員希望在需要 dataSource 依賴項時都将使用 dataSource 資料源,而不是原始資料源 testDataSource。這可以通過别名關鍵字來實作,該關鍵字訓示依賴項注入容器将原始資料源定義替換為新版本。

注冊 Spring Bean

BeanDefinition 注冊的三種方式

  1. XML 配置元資訊
<bean name=”…” … />
  1. Java 注解配置元資訊
  • @Bean
  • @Component
  • @Import
  1. Java API 配置元資訊
  • 命名方式:
BeanDefinitionRegistry#registerBeanDefinition(String,BeanDefinition)
  • 非命名方式:
BeanDefinitionReaderUtils#registerWithGeneratedName(AbstractBeanDefinition,BeanDefinitionRegistry)
  • 配置類方式:
AnnotatedBeanDefinitionReader#register(Class…)

這裡介紹第二種和第三種注冊方式。

@Bean 注冊 Bean

import com.feng.spring.bean.pojo.User;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;

public class AnnotationBeanDefinitionDemo {

    public static void main(String[] args) {
        // 建立 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注冊 Configuration Class (配置類)
        applicationContext.register(Config.class);
        // 重新整理引用上下文
        applicationContext.refresh();
        System.out.println(" User Beans :"+ applicationContext.getBeansOfType(User.class));
        // 顯示地關閉 Spring 應用上下文
        applicationContext.close();
    }

    public static class Config{

        /**
         * 通過 Java 注解的方式,定義一個 Bean
         */
        @Bean(name = "user")
        public User user(){
            User user = new User();
            user.setId(1L);
            user.setName("小明");
            return user;
        }
    }
}
           

@Component 注冊 Bean

import com.feng.spring.bean.pojo.User;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

public class AnnotationBeanDefinitionDemo {

    public static void main(String[] args) {
        // 建立 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注冊 Configuration Class (配置類)
        applicationContext.register(Config.class);
        // 重新整理引用上下文
        applicationContext.refresh();
        System.out.println(" User Beans :"+ applicationContext.getBeansOfType(User.class));
        // 顯示地關閉 Spring 應用上下文
        applicationContext.close();
    }

	// 使用 @Component 注冊 Bean
    @Component
    public static class Config{

        /**
         * 通過 Java 注解的方式,定義一個 Bean
         */
        @Bean(name = "user")
        public User user(){
            User user = new User();
            user.setId(1L);
            user.setName("小明");
            return user;
        }
    }
}
           

@Import 注冊 Bean

@Import(AnnotationBeanDefinitionDemo.Config.class)
public class AnnotationBeanDefinitionDemo {
...
// 此處代碼同上
...
}
           

Java API 注冊 Bean , 命名方式和非命名方式

public class AnnotationBeanDefinitionDemo {

    public static void main(String[] args) {
        // 建立 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        registerBeanDefinition(applicationContext, "xiaoming-user");
        registerBeanDefinition(applicationContext);
        // 重新整理引用上下文
        applicationContext.refresh();
        System.out.println(" User Beans :"+ applicationContext.getBeansOfType(User.class));
        // 顯示地關閉 Spring 應用上下文
        applicationContext.close();
    }

    public static void registerBeanDefinition(BeanDefinitionRegistry registry,String beanName){
        BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(User.class);
        beanDefinitionBuilder
                .addPropertyValue("id", 1L)
                .addPropertyValue("name", "小明");
        if (StringUtils.hasText(beanName)) {
            // 命名方式 注冊 BeanDefinition
            registry.registerBeanDefinition(beanName, beanDefinitionBuilder.getBeanDefinition());
        }else{
            BeanDefinitionReaderUtils.registerWithGeneratedName(beanDefinitionBuilder.getBeanDefinition(), registry);
        }
    }
}
           
執行個體化 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)

正常方式

通過靜态工廠方法

建立 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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user-by-static-method" class="com.feng.spring.bean.pojo.User"
        factory-method="createUser"/>
</beans>
           

建立 User#createUser 靜态方法

public class User {
    private Long id;
    private String name;

    public void setId(Long id) {
        this.id = id;
    }

    public void setName(String name) {
        this.name = name;
    }

    public static User createUser() {
        User user = new User();
        user.setName("小明");
        user.setId(1L);
        return user;
    }

    @Override
    public String toString(){
        return "User{" +
                "id = " + id +
                ", name = " + name +
                "}";
    }
}
           

BeanInstantiationDemo bean 執行個體化示例

import com.feng.spring.bean.pojo.User;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * bean 執行個體化 示例
 */
public class BeanInstantiationDemo {
    public static void main(String[] args) {
        // 配置 XML 配置檔案
        // 啟動 Spring 應用上下文
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/bean-instantiation-context.xml");

        User userByStaticMethod = beanFactory.getBean("user-by-static-method", User.class);
        System.out.println(userByStaticMethod);
    }
}
           

通過 Bean 工廠方法

建立 User 的工廠方法:

UserFactory.class

public interface UserFactory {
	// 采用 Java 1.8 實作方式 
    default User createUser(){
        return new User();
    }
}
           

DefaultUserFactory.class

public class DefaultUserFacoty implements  UserFactory {
}
           

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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 工廠方法執行個體化 Bean-->
    <bean id="user-by-instance-method" factory-bean="userFacoty" factory-method="createUser"/>
    <bean id="userFacoty" class="com.feng.spring.bean.factory.DefaultUserFacoty"/>
</beans>
           
/**
 * bean 執行個體化 示例
 */
public class BeanInstantiationDemo {
    public static void main(String[] args) {
        // 配置 XML 配置檔案
        // 啟動 Spring 應用上下文
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/bean-instantiation-context.xml");
        User userByInstanceMethod = beanFactory.getBean("user-by-instance-method", User.class);
        System.out.println(userByInstanceMethod);
    }
}
           

注意:

上面使用了 Java 1.8 的新特性 接口預設實作,需要修改編譯級别,在 pom.xml 檔案中添加如下代碼

<build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
           

通過 FactoryBean,實作 UserFactoryBean

public class UserFactoryBean implements FactoryBean<User> {
    @Override
    public User getObject() throws Exception {
        return User.createUser();
    }

    @Override
    public Class<?> getObjectType() {
        return User.class;
    }
}
           

配置 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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- FactoryBean 執行個體化 Bean-->
    <bean id="user-by-factory-bean" class="com.feng.spring.bean.factory.UserFactoryBean"/>
</beans>
           

這種實作方式比較特殊,這裡并不是直接去定義一個 User Bean ,而是定義一個 FactoryBean 去連結 User 對應的 實作方法和實作類型。

public class BeanInstantiationDemo {
    public static void main(String[] args) {
        // 配置 XML 配置檔案
        // 啟動 Spring 應用上下文
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/bean-instantiation-context.xml");

        User userByFactoryBean = beanFactory.getBean("user-by-factory-bean", User.class);
		System.out.println(userByFactoryBean);
    }
}
           

特殊方式

通過 ServiceLoaderFactoryBean

在介紹這個方法前,先看 Java 1.6 中的 ServiceLoader 類

public final class ServiceLoader<S>
    implements Iterable<S>
{

    private static final String PREFIX = "META-INF/services/";
    ...
}
           

META-INF/services/ 是資源管理的目錄,Spring 中的 ServiceLoaderFactoryBean 整合了 ServiceLoader 這個對應的資源目錄,下面看下 ServiceLoader 是如何進行操作的。

建立一個全類名作為名稱的檔案沒有字尾,内容填寫對應的實作類 com.feng.spring.bean.factory.DefaultUserFacoty

Spring Bean 是什麼?
Spring Bean 是什麼?

ServiceLoaderDemo.class

import java.util.Iterator;
import java.util.ServiceLoader;

public class ServiceLoaderDemo {

    public static void main(String[] args) {
        demoServiceLoader();
        
    }
    public static void demoServiceLoader(){
        // service 加載
        ServiceLoader<UserFactory> serviceLoader = ServiceLoader.load(UserFactory.class, Thread.currentThread()
                .getContextClassLoader());
        // 使用疊代器 進行疊代
        Iterator<UserFactory> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            UserFactory userFactory = iterator.next();
            System.out.println(userFactory.createUser());
        }
    }
}
           

如何使用 ServiceLoaderFactoryBean 進行初始化 Bean?

建立 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 http://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="userFactoryServiceLoader" class="org.springframework.beans.factory.serviceloader.ServiceLoaderFactoryBean">
        <property name="serviceType" value="com.feng.spring.bean.factory.UserFactory"/>
    </bean>
</beans>
           

注冊一個 ServiceLoaderFactoryBean 其中 serviceType 類型為 UserFactory ,可以根據 AbstractServiceLoaderBasedFactoryBean$serviceType 這個類屬性進行判斷,serviceType 是 ServiceLoader 所關注的對象,對 serviceType 屬性進行指派後,将影響 createInstance() 進行初始化建立。

public abstract class AbstractServiceLoaderBasedFactoryBean extends AbstractFactoryBean<Object>
		implements BeanClassLoaderAware {

	@Nullable
	private Class<?> serviceType;

	@Nullable
	private ClassLoader beanClassLoader = ClassUtils.getDefaultClassLoader();

	public void setServiceType(@Nullable Class<?> serviceType) {
		this.serviceType = serviceType;
	}

	@Nullable
	public Class<?> getServiceType() {
		return this.serviceType;
	}

	@Override
	public void setBeanClassLoader(@Nullable ClassLoader beanClassLoader) {
		this.beanClassLoader = beanClassLoader;
	}

	@Override
	protected Object createInstance() {
		Assert.notNull(getServiceType(), "Property 'serviceType' is required");
		return getObjectToExpose(ServiceLoader.load(getServiceType(), this.beanClassLoader));
	}
	...
}
           
return getObjectToExpose(ServiceLoader.load(getServiceType(), this.beanClassLoader));

初始化場景将執行 getObjectToExpose() 方法

public class ServiceFactoryBean extends AbstractServiceLoaderBasedFactoryBean implements BeanClassLoaderAware {

	/**
	* 對 ServiceLoader 進行疊代 并傳回一個對象
	*/
	@Override
	protected Object getObjectToExpose(ServiceLoader<?> serviceLoader) {
		Iterator<?> it = serviceLoader.iterator();
		if (!it.hasNext()) {
			throw new IllegalStateException(
					"ServiceLoader could not find service for type [" + getServiceType() + "]");
		}
		return it.next();
	}

	@Override
	@Nullable
	public Class<?> getObjectType() {
		return getServiceType();
	}
}	
           

由此可以寫出執行方法:

public class SpecialBeanInstantiationDemo {

    public static void main(String[] args) {

        // 配置 xml 配置檔案
        // 啟動 Spring 應用上下文
        BeanFactory beanFactory = new ClassPathXmlApplicationContext("classpath:/META-INF/special-bean-instantiation-context.xml");

        ServiceLoader<UserFactory> serviceLoader = beanFactory.getBean("userFactoryServiceLoader", ServiceLoader.class);

        // 使用疊代器 進行疊代
        Iterator<UserFactory> iterator = serviceLoader.iterator();
        while (iterator.hasNext()) {
            UserFactory userFactory = iterator.next();
            System.out.println(userFactory.createUser());
        }
    }
}
           

通過 AutowireCapableBeanFactory

public class AutowireCapableBeanFactoryDemo {

    public static void main(String[] args) {
        // 配置 xml 配置檔案,啟動 Spring 應用上下文
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:/META-INF/special-bean-instantiation-context.xml");

        AutowireCapableBeanFactory beanFactory = applicationContext.getAutowireCapableBeanFactory();

        UserFactory userFactory = beanFactory.createBean(DefaultUserFacoty.class);
        System.out.println(userFactory.createUser());
    }
}
           
初始化 Spring Bean

Bean 初始化(Initialization) 的三種方式

  1. @PostConstruct 标注方法
  2. 實作 InitializingBean#afterPropertiesSet() 方法
  3. 自定義初始化方法
    • XML 配置:<bean init-method=”init” … />
    • Java 注解:@Bean$initMethod
    • Java API:AbstractBeanDefinition#setInitMethodName(String)

基于 @PostConstruct 标注方法:

建立 DefaultUserFacoty#init 方法,并标注 @PostConstruct 注解

public class DefaultUserFacoty implements  UserFactory {

    // 1. 基于 @PostConstruct 注解
    @PostConstruct
    public void init(){
        System.out.println(" @PostConstruct 初始化。。。 ");
    }
}
           

建立 BeanInitializationDemo 類,并通過 @Bean 将 DefaultUserFacoty 進行注冊,此時将會回調 DefaultUserFacoty 類中被 @PostConstruct 注解的方法。

/**
 * Bran 初始化 示例
 */
@Configuration  // 配置 類
public class BeanInitializationDemo {

    public static void main(String[] args) {
        // 建立 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注冊 Configuration 類
        applicationContext.register(BeanInitializationDemo.class);
        // 啟動 applicationContext
        applicationContext.refresh();

        UserFactory userFactory = applicationContext.getBean(UserFactory.class);
        // 關閉 applicationContext
        applicationContext.close();
    }

    @Bean
    public UserFactory initUserFactory(){
        return new DefaultUserFacoty();
    }
}
           

實作 InitializingBean#afterPropertiesSet() 方法:

DefaultUserFacoty 實作 InitializingBean 接口,并實作 afterPropertiesSet 方法。

public class DefaultUserFacoty implements  UserFactory ,InitializingBean {

    @Override
    public void afterPropertiesSet() throws Exception {
        System.out.println(" InitializingBean#afterPropertiesSet 初始化..  ");
    }
}
           

自定義初始化方法(這裡隻介紹一下 @Bean$initMehtod 如何實作)

Java 注解:@Bean$initMethod

建立 DefaultUserFacoty#initMethod 方法

public class DefaultUserFacoty implements  UserFactory {

    // 3.2 基于 @Bean initMethod 方法
    public void initMethod(){
        System.out.println(" @Bean$initMethod 初始化...");
    }
}
           
public interface UserFactory {
    default User createUser(){
        return new User();
    }

    void initMethod();
}
           

BeanInitializationDemo 類中對 @Bean$initMethod 屬性進行指派,賦予 DefaultUserFacoty 中初始化的方法名。

/**
 * Bran 初始化 示例
 */
@Configuration  // 配置 類
public class BeanInitializationDemo {

    public static void main(String[] args) {
        // 建立 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注冊 Configuration 類
        applicationContext.register(BeanInitializationDemo.class);
        // 啟動 applicationContext
        applicationContext.refresh();

        UserFactory userFactory = applicationContext.getBean(UserFactory.class);
        // 關閉 applicationContext
        applicationContext.close();
    }

    @Bean(initMethod = "initMethod")
    public UserFactory initUserFactory(){
        return new DefaultUserFacoty();
    }
}
           

以上三種方法在同一個方法中同時出現,其執行順序是:

延遲初始化 Spring Bean

Spring 官方API:

By default, ApplicationContext implementations eagerly create and configure all singleton beans as part of the initialization process. Generally, this pre-instantiation is desirable, because errors in the configuration or surrounding environment are discovered immediately, as opposed to hours or even days later. When this behavior is not desirable, you can prevent pre-instantiation of a singleton bean by marking the bean definition as lazy-initialized. A lazy-initialized bean tells the IoC container to create a bean instance when it is first requested, rather than at startup.

譯文:

預設情況下,Spring 容器在初始化過程中會建立和配置所有單例的bean。這種提前執行個體化是可取的,因為配置環境錯誤會被立即發現而不需要過多的時間。如果不采取這種行為,可以将單例的bean标記為延遲初始化。一個延遲初始化的bean告訴Spring IoC容器去建立這個bean執行個體化對象當它第一次被調用時而不是在容器啟動時立即建立。

設計模式中有一種延遲加載的模式和延遲初始化的效果類似。

Bean 延遲初始化(Lazy Initialization) 的兩種方式:

  • XML 配置:<bean lazy-init=”true” … />
  • Java 注解:@Lazy(true)

預設情況下是非延遲加載,基于 XML 配置和 Java 注解所産生的效果是類似的,隻要在被加載的 Bean 上注解 @Lazy ,那麼這個 Bean 就是延遲加載。

@Bean(initMethod = "initMethod")
@Lazy
public UserFactory initUserFactory(){
    return new DefaultUserFacoty();
}
           

下面例子通過對容器啟動完成後,列印一句話,之後對 UserFactory 進行依賴查找,通過列印的順序看下,延遲加載的特性。

非延遲加載例子,列印結果。

/**
 * Bran 初始化 示例
 */
@Configuration  // 配置 類
public class BeanInitializationDemo {

    public static void main(String[] args) {
        // 建立 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注冊 Configuration 類
        applicationContext.register(BeanInitializationDemo.class);
        // 啟動 applicationContext
        applicationContext.refresh();
        // 非延遲初始化在 Spring 應用上下文啟動完成後,被初始化
        System.out.println("Spring 應用上下文已啟動...");
        UserFactory userFactory = applicationContext.getBean(UserFactory.class);
        // 關閉 applicationContext
        applicationContext.close();
    }

    @Bean(initMethod = "initMethod")
    public UserFactory initUserFactory(){
        return new DefaultUserFacoty();
    }
}
           

控制台輸出:

@PostConstruct 初始化。。。 
InitializingBean#afterPropertiesSet 初始化..  
@Bean$initMethod 初始化...
Spring 應用上下文已啟動..
           

延遲加載例子,列印結果。

/**
 * Bran 初始化 示例
 */
@Configuration  // 配置 類
public class BeanInitializationDemo {

    public static void main(String[] args) {
        // 建立 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注冊 Configuration 類
        applicationContext.register(BeanInitializationDemo.class);
        // 啟動 applicationContext
        applicationContext.refresh();
        // 非延遲初始化在 Spring 應用上下文啟動完成後,被初始化
        System.out.println("Spring 應用上下文已啟動...");
        UserFactory userFactory = applicationContext.getBean(UserFactory.class);
        // 關閉 applicationContext
        applicationContext.close();
    }

    @Bean(initMethod = "initMethod")
    @Lazy
    public UserFactory initUserFactory(){
        return new DefaultUserFacoty();
    }
}
           

列印結果:

Spring 應用上下文已啟動...
@PostConstruct 初始化。。。 
InitializingBean#afterPropertiesSet 初始化..  
@Bean$initMethod 初始化...
           

從結果對比可以看出,延遲初始化是在容器啟動完成後,依賴查找對應的 Bean 時,才會觸發 Bean 的初始化。

延遲初始化是一種按需初始化。

Spring 容器傳回的對象與非延遲的對象存在怎樣的差異?

差異在于依賴查找和依賴注入時,是否在容器啟動前後進行初始化。

相同在于注冊 Bean 時,容器都是無差别進行注冊。

銷毀 Spring Bean

Bean 銷毀(Destroy) 的三種方式:

  1. @PreDestroy 标注方法
  2. 實作 DisposableBean 接口的 destroy() 方法
  3. 自定義銷毀方法
    • XML 配置:<bean destroyMethod=”destroyMethod” … />
    • Java 注解:@Bean(destroyMethod=”destoryMethod”)
    • Java API:AbstractBeanDefinition#setDestroyMethodName(String)

銷毀 Bean 和初始化 Bean 相似,下面通過代碼來講述一下:

DefaultUserFacoty 類

/**
 * 預設 {@link UserFactory} 實作
 */
public class DefaultUserFacoty implements  UserFactory,DisposableBean {

    // 1. 基于 @PreDestroy 注解
    @PreDestroy
    public void perDestroy(){
        System.out.println(" @PreDestroy 銷毀... ");
    }

    // 4.2 基于 @Bean initMethod 方法
    public void destoryMethod(){
        System.out.println(" @Bean$destoryMethod 初始化...");
    }

    @Override
    public void destroy() throws Exception {
        System.out.println(" DisposableBean#destroy 銷毀... ");
    }
}
           
@Configuration  // 配置 類
public class BeanInitializationDemo {

    public static void main(String[] args) {
        // 建立 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注冊 Configuration 類
        applicationContext.register(BeanInitializationDemo.class);
        // 啟動 applicationContext
        applicationContext.refresh();
        // 非延遲初始化在 Spring 應用上下文啟動完成後,被初始化
        System.out.println("Spring 應用上下文已啟動...");
        UserFactory userFactory = applicationContext.getBean(UserFactory.class);
        // 關閉 applicationContext
        applicationContext.close();
    }

    @Bean(destroyMethod = "destoryMethod")
    public UserFactory initUserFactory(){
        return new DefaultUserFacoty();
    }
}
           

控制台輸出:

Spring 應用上下文已啟動...
@PreDestroy 銷毀... 
DisposableBean#destroy 銷毀... 
@Bean$destoryMethod 初始化...
           

由控制台輸出順序得出,以上三種方法在同一個方法中同時出現,其執行順序是:

銷毀觸發的條件是什麼?

通過在 關閉 applicationContext

applicationContext.close();

語句上下列印兩條語句可以進行判斷:

/**
 * Bran 初始化 示例
 */
@Configuration  // 配置 類
public class BeanInitializationDemo {

    public static void main(String[] args) {
        // 建立 BeanFactory 容器
        AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext();
        // 注冊 Configuration 類
        applicationContext.register(BeanInitializationDemo.class);
        // 啟動 applicationContext
        applicationContext.refresh();
        // 非延遲初始化在 Spring 應用上下文啟動完成後,被初始化
        System.out.println("Spring 應用上下文已啟動...");
        UserFactory userFactory = applicationContext.getBean(UserFactory.class);
        System.out.println("Spring 應用準備關閉...");
        // 關閉 applicationContext
        applicationContext.close();
        System.out.println("Spring 應用已關閉...");
    }

    @Bean(initMethod = "initMethod",destroyMethod = "destoryMethod")
    public UserFactory initUserFactory(){
        return new DefaultUserFacoty();
    }
}
           

控制台輸出:

Spring 應用上下文已啟動...
Spring 應用準備關閉...
@PreDestroy 銷毀... 
DisposableBean#destroy 銷毀... 
@Bean$destoryMethod 初始化...
Spring 應用已關閉...
           

通過對比控制台輸出語句可以得出,Spring Bean 銷毀是由

applicationContext.close();

觸發的。

總結

本文主要介紹 Spring Bean 的生命周期過程中的階段,讨論 Spring 容器如何針對各個階段編寫代碼。

參考資料

Spring Bean

  • https://www.baeldung.com/spring-bean

how is Spring bean

  • http://dolszewski.com/spring/spring-bean/

what in the world are spring beans

  • https://stackoverflow.com/questions/17193365/what-in-the-world-are-spring-beans

Spring 核心

  • https://time.geekbang.org/course/detail/265-187472

Spring中Bean命名源碼分析

  • https://www.cnblogs.com/heliusKing/p/11741403.html

1.3.1.Bean的命名

  • https://www.kancloud.cn/liuqingyu/spring-doc/654226

How to create an alias for a bean and inner bean in Spring Framework

  • http://mrbool.com/how-to-create-an-alias-for-a-bean-and-inner-bean-in-spring-framework/28549

為什麼我喜歡Spring bean别名(Why I like Spring bean aliasing)

  • https://vladmihalcea.com/why-i-like-spring-bean-aliasing/

SpringBoot基礎篇Bean之動态注冊

  • https://juejin.im/post/5bcc54a9f265da0aaa054b3a

spring之Bean執行個體化過程

  • https://www.jianshu.com/p/1d0fb520e5c0