天天看點

SpringIOC源碼學習DayOneBeanFactory和ApplicationContext

SpringIOC源碼閱讀一

  • BeanFactory和ApplicationContext
    • IOC容器加載過程源碼深度剖析
      • 注冊我們的配置類
      • IOC容器重新整理接口
    • BeanFactory和FactoryBean
    • Bean的生命周期源碼深度剖析
    • 總結

在源碼編譯的時候遇到缺少

import org.apache.commons.logging.Log;

我的解決辦法是

build.gradle 檔案的dependencies裡

org.apache.logging.log4j:log4j-jul後加:
           
SpringIOC源碼學習DayOneBeanFactory和ApplicationContext

BeanFactory和ApplicationContext

SpringIOC源碼學習DayOneBeanFactory和ApplicationContext

Bean工廠的存在就是為了生成bean。applicationContext繼承了BeanFactory

IOC容器加載過程源碼深度剖析

準備的類:

Bean

@Component
public class Car {
	private String name;

	public String getName() {
		return name;
	}

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

	public Car() {
		System.out.println("car加載....");
	}
}
           

MainConfig

@Configuration
@ComponentScan(basePackages = {"com.tuling.iocbeanlifecicle"})
public class MainConfig {
	
}
           

MainStarter

public class MainStarter {

	public static void main(String[] args) {

		AnnotationConfigApplicationContext context = new 
AnnotationConfigApplicationContext(MainConfig.class);

		Car car = context.getBean("car", Car.class);

		System.out.println(car.getName());
	}
}
           

第一步:

public AnnotationConfigApplicationContext(Class<?>... annotatedClasses) {
		//調用構造函數
		this();
		//注冊我們的配置類
		register(annotatedClasses);
		//IOC容器重新整理接口
		refresh();
	}
           

我們先來為構造方法做一個簡單的說明:

  1. 這是一個有參的構造方法,可以接收多個配置類,不過一般情況下,隻會傳入一個配置類。
  2. 這個配置類有兩種情況,一種是傳統意義上的帶上@Configuration注解的配置類,還有一種是沒有 帶上@Configuration,但是帶有@Component,@Import,@ImportResouce,@Service,

    @ComponentScan等注解的配置類,在Spring内部把前者稱為

    Full配置類

    ,把後者稱之為

    Lite配置 類

    。在本源碼分析中,有些地方也把Lite配置類稱為普通Bean。

調用無參構造

public AnnotationConfigApplicationContext() {
		/**
		 * 初始化注解模式下的bean定義掃描器
		 * 調用AnnotatedBeanDefinitionReader構造方法,傳入的是this(AnnotationConfigApplicationContext)對象
		 */

		this.reader = new AnnotatedBeanDefinitionReader(this);
		/**
		 * 初始化我們的classPath類型的bean定義掃描器
		 */
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}
           

首先會調用父類無參構造,執行個體化beanFactory

public GenericApplicationContext() {
		/**
		 * 調用父類的構造函數,為ApplicationContext spring上下文對象
		 * 初始beanFactory
		 * 
		 * 為啥是DefaultListableBeanFactory?我們去看BeanFactory接口
		 * 的時候
		 * 
		 * 發DefaultListableBeanFactory是最底層的實作,功能是最全的
		 */
		this.beanFactory = new DefaultListableBeanFactory();
}
           
SpringIOC源碼學習DayOneBeanFactory和ApplicationContext

其中實作了BeanDefinitionRegistry接口,有注冊bean定義的能力,接下來回到我們的子類的無參構造

public AnnotationConfigApplicationContext() {
		/**
		 * 初始化注解模式下的bean定義掃描器,讀取的是配置類
		 * 
		 * 調用AnnotatedBeanDefinitionReader構造方法,
		 * 傳入的是this(AnnotationConfigApplicationContext)對象
		 */

		this.reader = new AnnotatedBeanDefinitionReader(this);
		/**
		 * 初始化我們的classPath類型的bean定義掃描器
		 */
		this.scanner = new ClassPathBeanDefinitionScanner(this);
	}
           

在這裡

new AnnotatedBeanDefinitionReader

注冊了很多後置處理器,

SpringIOC源碼學習DayOneBeanFactory和ApplicationContext

注冊了很多創世紀的類

SpringIOC源碼學習DayOneBeanFactory和ApplicationContext

再說下

ClassPathBeanDefinitionScanner

。我們可以手動掃描。舉個例子

AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
	String name = Car.class.getPackage().getName();
	context.scan(name);
	context.refresh();
	Car car = (Car) context.getBean("car");
	System.out.println(car.getName());
           

總結一下

this()

,

  • 執行個體化bean工廠
  • 擷取bean定義的讀取器,注冊了很多創世紀的類,其中很重要的:

    ConfigurationClassPostProcessor

    。給大家看下類結構圖
    SpringIOC源碼學習DayOneBeanFactory和ApplicationContext
    如果不注冊這個,我們的配置類将無法解析。
  • 執行個體化了

    AnnotatedBeanDefinitionReader

    ,隻是讀到了bean定義,是由bean的後置處理器負責解析的,因為這種方式擴充性強,靈活度高。把創世紀的6個bean定義注冊到bean工廠的beanDefinitionMap裡
  • 執行個體化了

    ClassPathBeanDefinitionScanner

    ,這裡的scanner僅僅是為了程式員可以手動調用

    AnnotationConfigApplicationContext

    對象的scan方法, spring在執行工程後置處理器

    ConfigurationClassPostProcessor

    時,去掃描包時會new一個

    ClassPathBeanDefinitionScanner

注冊我們的配置類

把配置類

MainConfig

注冊到

BeanDefinitionMap

SpringIOC源碼學習DayOneBeanFactory和ApplicationContext

最終會調用

DefaultListableBeanFactory

中的

registerBeanDefinition

方法去注冊,

DefaultListableBeanFactory

維護着一系列資訊,比如

beanDefinitionNames

beanDefinitionMap

beanDefinitionNames

是一個List,用來儲存beanName

beanDefinitionMap

是一個Map,用來儲存beanName和beanDefinition

IOC容器重新整理接口

學spring就是學這裡面的十三個方法。這個是重點

先看這個方法

invokeBeanFactoryPostProcessors(beanFactory);

調用我們的bean工廠的後置處理器.會在此處将class掃描成beanDefinition。

SpringIOC源碼學習DayOneBeanFactory和ApplicationContext

這個方法,調用後置處理器将注解标記為@ComponentScan,@Component,@Service等,解析出來放到beanDefinitionMap裡面。如上圖。當執行完這句,bean就到了map裡

SpringIOC源碼學習DayOneBeanFactory和ApplicationContext

注冊我們bean的後置處理器, 給我們容器中注冊了我們bean的後置處理器

bean的後置處理器在什麼時候進行調用?在bean的各個生命周期中都會進行調用

再看這個方法,

finishBeanFactoryInitialization(beanFactory);

執行個體化我們剩餘的單執行個體bean。

BeanFactory和FactoryBean

實作FactoryBean接口的bean,會重寫getObject方法。是一種特殊的bean。

@Component
public class Car implements FactoryBean<Tank> {
	private String name;

	public String getName() {
		return name;
	}

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

	public Car() {
		System.out.println("car加載....");
	}

	@Override
	public Tank getObject() throws Exception {
		return new Tank();
	}

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

	@Override
	public boolean isSingleton() {
		return false;
	}
}
           

我的car實作了Factory接口,getObject方法傳回的是Tank;

AnnotationConfigApplicationContext context = new 
					AnnotationConfigApplicationContext(MainConfig.class);
Car car = (Car) context.getBean("car");
           

報錯:

SpringIOC源碼學習DayOneBeanFactory和ApplicationContext

那麼怎麼得到car呢?通過這種方式

這個有什麼作用?當我們去內建mybatis時候,mybaitis的Mapper接口以動态代理的形式傳回。

總結:

  1. BeanFactory是負責生産bean的,利用了工廠方法。
  2. 實作FactoryBean接口的bean,會重寫getObject方法。傳回的是getObject裡的傳回值

Bean的生命周期源碼深度剖析

我們看refresh方法裡的

finishBeanFactoryInitialization(beanFactory)

;執行個體化我們剩餘的單執行個體bean。然後主要看這個方法

beanFactory.preInstantiateSingletons();

執行個體化剩餘的單執行個體bean。進入

DefaultListableBeanFactory

類的

preInstantiateSingletons

方法。看下面的方法,打個斷點,beanName為car的,進入,發現不是工廠bean,則進入

getBean(bean)

方法。

//擷取我們容器中所有bean定義的名稱
		List<String> beanNames = new ArrayList<>(this.beanDefinitionNames);

		//循環我們所有的bean定義名稱
		for (String beanName : beanNames) {
			//合并我們的bean定義
			RootBeanDefinition bd = getMergedLocalBeanDefinition(beanName);
			/**
			 * 根據bean定義判斷是不是抽象的&& 不是單例的 &&不是懶加載的
			 */
			if (!bd.isAbstract() && bd.isSingleton() && !bd.isLazyInit()) {
				//是不是工廠bean
				if (isFactoryBean(beanName)) {
					//是的話 給beanName+字首&符号
					Object bean = getBean(FACTORY_BEAN_PREFIX + beanName);
					if (bean instanceof FactoryBean) {
						final FactoryBean<?> factory = (FactoryBean<?>) bean;
						boolean isEagerInit;
						if (System.getSecurityManager() != null && factory instanceof SmartFactoryBean) {
							isEagerInit = AccessController.doPrivileged((PrivilegedAction<Boolean>)
											((SmartFactoryBean<?>) factory)::isEagerInit,
									getAccessControlContext());
						}
						else {
							isEagerInit = (factory instanceof SmartFactoryBean &&
									((SmartFactoryBean<?>) factory).isEagerInit());
						}
						//調用真正的getBean的流程
						if (isEagerInit) {
							getBean(beanName);
						}
					}
				}
				else {//非工廠Bean 就是普通的bean
					getBean(beanName);
				}
			}
		}
           
/**
	 * 該方法是一個空殼方法,沒有任何的實作邏輯 真正的邏輯調用在doGetBean()中
	 * 該接口是實作了BeanFactory的getBean(String name)接口
	 * @param name bean的名稱 該名稱也有可能是bean的alies(别名)
	 * @return 我們的單例對象
	 * @throws BeansException
	 */
	@Override
	public Object getBean(String name) throws BeansException {
		//真正的擷取bean的邏輯
		return doGetBean(name, null, null, false);
	}
           

doCreateBean方法去建立bean,做了這幾步:

  1. 執行個體化反射

    createBeanInstance

  2. 填充屬性@Autowired @Value
  3. 初始化initMethod destoory
    SpringIOC源碼學習DayOneBeanFactory和ApplicationContext

    createBeanInstancef

    方法得到bean執行個體
    SpringIOC源碼學習DayOneBeanFactory和ApplicationContext
    此時還沒有經過屬性指派。此處用到了裝飾器設計模式,将bean的執行個體以及很多資訊都封裝到了

    instanceWrapper

    裡面。

    然後調用

    populateBean(beanName, mbd, instanceWrapper);

    我們的屬性進行指派(調用set方法進行指派)

總結

首先執行個體化

AnnotationConfigApplicationContext

,執行個體化

AnnotatedBeanDefinitionReader

,它會加載很多創世紀的類,這個創世紀的類裡面,包括很多解析我們後續類的處理,比如會解析加了

@Configuration

的配置類,還會解析

@ComponentScan

@ComponentScans

注解掃描的包,以及解析

@[email protected]

等注解。其中最關鍵的處理我們bean的配置類的後置處理器.然後執行個體化scan,最終再解析配置類的時候所初始化的掃描器才是真正去掃描配置類的,這裡

SpringIOC源碼學習DayOneBeanFactory和ApplicationContext

用到的僅是手動去掃描包,來提供的支援。

context.scan(name); context.refresh();

父類無參構造方法裡的

DefaultListableBeanFactory

會實作

BeanDefinitionRegistry

SpringIOC源碼學習DayOneBeanFactory和ApplicationContext
SpringIOC源碼學習DayOneBeanFactory和ApplicationContext

隻是把需要的後續處理的類準備好了。然後refresh方法裡

調用我們的bean工廠的後置處理器,會在此處将class掃描成beanDefinition

解析配置類,把配置類注冊成bean定義。注冊好後,執行個體化我們剩餘的單執行個體bean.

把我們所有的單例從bean定義注冊成最終的bean。

getBean(),判斷是否符合生産标準,及是不是

FactoryBean

,是的話通過

getObject()

放法傳回。普通bean則從一級緩存中拿,如果拿到了直接傳回,沒有拿到調用

createBean

。最終進入doCreateBean進行執行個體化,填充屬性,初始化,最終把bean放到1級緩存中。