spring官網筆記1

2020-09-24 11:01:56
Spring容器
    容器是什麼?
    容器如何工作?

Spring容器
容器是什麼?
我們先看官網中的一句話:

在這裡插入圖片描述

翻譯如下:
org.springframework.context。ApplicationContext介面表示Spring IoC容器,並負責範例化、設定和組裝bean。
那麼我們就可以說:
	從程式碼層次來看:Spring容器就是一個實現了ApplicationContext的介面的物件。
    從功能上來看:SPring容器是Spring框架的核心,是用來管理物件的。容器將建立物件,把它們連線在一起設定他們,並管理它們的整個生命週期從建立到銷燬。
容器如何工作?
我們直接看官網上的一張圖片,如下

在這裡插入圖片描述

Spring容器通過我們提交的pojo類以及設定後設資料產生一個充分設定的可使用的系統。
這裡說的設定原資料,實際上就是我們提供的XML組態檔,或者通過註解方式提供的一些設定資訊


Spring Bean
如何範例化一個Bean?
從官網上來看,主要有三種方式
``
在這裡插入圖片描述

1、構造方法
2、通過靜態工廠方法
3、通過範例工廠方法

這三種例子,官網都有例子,這裡就不在貼了,我們通過自己查閱部分原始碼,來驗證我們在官網得出的結論,然後通過debug等方式驗證。

我們再從程式碼的角度分析一下,我們直接定位到
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance 具體定位後面再來分析,大家可以通過形如下面的這段程式碼

在這裡插入圖片描述

<?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="myServiceImpl" class="com.phr.tx.MyServiceImpl">
		</bean>
</beans>
public static void main(String[] args) {
		ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("classpath:application.xml");
		MyServiceImpl myService = (MyServiceImpl) cc.getBean("myServiceImpl");
	}

在這裡插入圖片描述

然後打個斷點打在上圖如是位置。直接執行main方法。然後在org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory#createBeanInstance打個斷點,debug它

在這裡插入圖片描述

此時的beanName為我們需要的。接下來我們對這個方法進行分析,程式碼如下

protected BeanWrapper createBeanInstance(String beanName, RootBeanDefinition mbd, @Nullable Object[] args) {
		// Make sure bean class is actually resolved at this point.
		//獲取這個bean的class屬性,確保BeanDefinition中的beanClass已經完成解析
		// 我們通過xml從<bean>標籤中解析出來的class屬性在剛剛開始的時候必定是個字串
		Class<?> beanClass = resolveBeanClass(mbd, beanName);
		if (beanClass != null && !Modifier.isPublic(beanClass.getModifiers()) && !mbd.isNonPublicAccessAllowed()) {
			throw new BeanCreationException(mbd.getResourceDescription(), beanName,
					"Bean class isn't public, and non-public access not allowed: " + beanClass.getName());
		}
		//2.通過beanDefinition中的supplier範例化這個bean
		Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
		if (instanceSupplier != null) {
			return obtainFromSupplier(instanceSupplier, beanName);
		}
		// 3.通過FactoryMethod範例化這個bean
		if (mbd.getFactoryMethodName() != null) {
			return instantiateUsingFactoryMethod(beanName, mbd, args);
		}
		// Shortcut when re-creating the same bean...
		// 4.下面這段程式碼都是在通過建構函式範例化這個Bean,分兩種情況,一種是通過預設的無參構造,一種是通過推斷出來的建構函式
		boolean resolved = false;
		boolean autowireNecessary = false;
		if (args == null) {
			synchronized (mbd.constructorArgumentLock) {
				if (mbd.resolvedConstructorOrFactoryMethod != null) {
					resolved = true;
					autowireNecessary = mbd.constructorArgumentsResolved;
				}
			}
		}
		if (resolved) {
			if (autowireNecessary) {
				return autowireConstructor(beanName, mbd, null, null);
			}
			else {
				return instantiateBean(beanName, mbd);
			}
		}
		// Candidate constructors for autowiring?
		//第二次呼叫後置處理器 作用:推斷建立這個bean的構造方法
		//後置處理器和方法:
		//SmartInstantiationAwareBeanPostProcessor#determineCandidateConstructors
		Constructor<?>[] ctors = determineConstructorsFromBeanPostProcessors(beanClass, beanName);
		if (ctors != null || mbd.getResolvedAutowireMode() == AUTOWIRE_CONSTRUCTOR ||
				mbd.hasConstructorArgumentValues() || !ObjectUtils.isEmpty(args)) {
			return autowireConstructor(beanName, mbd, ctors, args);
		}
		// Preferred constructors for default construction?
		ctors = mbd.getPreferredConstructors();
		if (ctors != null) {
			return autowireConstructor(beanName, mbd, ctors, null);
		}
		// No special handling: simply use no-arg constructor.
		return instantiateBean(beanName, mbd);
	}
我們主要關注進行範例化的幾個方法:
1、通過BeanDefinitionz中的instanceSupplier直接獲取一個範例物件。這個instanceSupplier在org.springframework.context.support.GenericApplicationContext這個類裡面

在這裡插入圖片描述

經過斷點偵錯,在範例化物件時會進入上面的方法。下面是測試程式碼。

public static void main(String[] args) {
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext();
		ac.registerBean("service", Service.class,Service::new);
		ac.refresh();
		System.out.println(ac.getBean("service"));
	}

在這裡插入圖片描述

這個方法一般不常用,筆者認為這是spring提供給外部的一種範例化方式。這裡不過多討論。

1、接下來,我們通過不同的方式建立bean,來分別驗證物件的範例化方法。
    通過@compent @service 等方式建立
@Component
public class ServiceTest {
}
@Configuration
@ComponentScan(value = {"com.phr.tx"})
public class Config {
}
public static void main(String[] args) {
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
		System.out.println(ac.getBean(ServiceTest.class));
}

​ 觀測debug

在這裡插入圖片描述

看javadoc就知道,預設使用無參構造方法進行範例化。
通過普通xml方式和@component類似,這裡就不贅述了
通過@configuration
	public static void main(String[] args) {
//		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
//		System.out.println(ac.getBean(ServiceTest.class));
		// 通過設定類掃描
		AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
		// 這裡將測試物件換為config即可,同時記得將條件斷點更改為beanName.equlas("config")
		System.out.println(ac.getBean(Config.class));
	}

檢視debug效果圖

在這裡插入圖片描述

同樣也是通過無參構造,不過看beanClass就知道這個是走了cglib代理。對於這種現象,筆者後面再來分析。

通過@Bean的方式

	@Bean
	public Service service(){
		return new Service();
	}

	AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(Config.class);
	System.out.println(ac.getBean(Service.class));	

在這裡插入圖片描述

可以發現我們通過@Bean方法建立物件時,Spring底層是通過factoryMethod的方法進行範例化物件的,Spring會在我們需要範例化的這個對應的Beandefinition中記錄factoryBeanName是什麼 如圖所示,這個factoryBeanName是config。最後通過factoryBeanName獲取一個Bean然後反射呼叫factoryMethod範例化一個物件。

<?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="myServiceImpl" class="com.dmz.official.service.Service"/>-->

	<!-- the factory bean, which contains a method called get() -->
	<bean id="myFactoryBean" class="com.phr.tx.MyFactoryBean">
		<!-- inject any dependencies required by this locator bean -->
	</bean>

	<!-- 測試範例工廠方法建立物件-->
<!--	<bean id="clientService"-->
<!--		  factory-bean="myFactoryBean"-->
<!--		  factory-method="get"/>-->

	<!--測試靜態工廠方法建立物件-->
	<bean id="service"
		  class="com.phr.tx.MyFactoryBean"
		  factory-method="staticGet"/>
</beans>

public static void main(String[] args) {
		ClassPathXmlApplicationContext cc = new ClassPathXmlApplicationContext("classpath:application.xml");
		System.out.println(cc.getBean(Service.class));
	}

在這裡插入圖片描述

可以發現,這種情況也進入了instantiateUsingFactoryMethod方法中。通過靜態工廠方法這種方式特殊之處在於,包含這個靜態方法的類,不需要範例化,不需要被Spring管理。Spring的呼叫邏輯大概是:

  1. 通過<bean>標籤中的class屬性得到一個Class物件
  2. 通過Class物件獲取到對應的方法名稱的Method物件
  3. 最後反射呼叫Method.invoke(null,args)

因為是靜態方法,方法在執行時,不需要一個物件。

  • 通過範例工廠方法的方式

測試程式碼(組態檔不變)這種方式和@Bean的方式一樣,就不多贅述了。

d`方法中。通過靜態工廠方法這種方式特殊之處在於,包含這個靜態方法的類,不需要範例化,不需要被Spring管理。Spring的呼叫邏輯大概是:

  1. 通過<bean>標籤中的class屬性得到一個Class物件
  2. 通過Class物件獲取到對應的方法名稱的Method物件
  3. 最後反射呼叫Method.invoke(null,args)

因為是靜態方法,方法在執行時,不需要一個物件。

  • 通過範例工廠方法的方式

測試程式碼(組態檔不變)這種方式和@Bean的方式一樣,就不多贅述了。

這樣只是產生了一個Bean物件,還沒走完bean的生命週期,接下來筆者會按照官網的節奏分析Bean的生命週期。