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的呼叫邏輯大概是:
<bean>
標籤中的class屬性得到一個Class物件Method.invoke(null,args)
因為是靜態方法,方法在執行時,不需要一個物件。
測試程式碼(組態檔不變)這種方式和@Bean的方式一樣,就不多贅述了。
d`方法中。通過靜態工廠方法這種方式特殊之處在於,包含這個靜態方法的類,不需要範例化,不需要被Spring管理。Spring的呼叫邏輯大概是:
<bean>
標籤中的class屬性得到一個Class物件Method.invoke(null,args)
因為是靜態方法,方法在執行時,不需要一個物件。
測試程式碼(組態檔不變)這種方式和@Bean的方式一樣,就不多贅述了。
這樣只是產生了一個Bean物件,還沒走完bean的生命週期,接下來筆者會按照官網的節奏分析Bean的生命週期。