第二章Spring源碼

第二章Spring

2.1 Spring基礎 2.2 Spring生命週期完整源碼流程 2.3 Spring實例化Bean源碼過程(及三級緩存如何處理循環依賴 2.4 AOP底層原理及應用 2.5 SpringMVC到SpringBoot源碼演變 2.6 Spring下mybatics原理 2.7 SpringMVC源碼運行流程



2.1 Spring基礎

想想spring有啥基礎好講的啊哈哈,直接源碼了

2.2 Spring生命週期完整源碼流程

第一步
Spring中從main方法開始
Main中調用了 XXXApplicationContext(config.class)作爲入口運行整個Spring

第二步
在這個ApplicationContext底層extend GeneericApplicationContext 並且實現了3個方法
3個方法:
1 this() : 會調用父類構造方法,父類的構造方法中實例化了Spring工廠
就是我們所知道的beanFactory,並會在後面給beanFactory加入信息,這裏只是單純先實例化

2 register(): 在這裏會讀取參數config.class,這個類就是配置文件,通過這個配置文件Spring纔會知道真正要掃描那些地方比如@component

3 refresh() 核心,在這裏synchronize了12個類並且依次執行

這裏講重點幾個
a InvokeBeanFactoryPostProcessor(beanFactory)
a:
通過前面register告訴了掃描哪裏,這個方法真正執行了所有類掃描。處理各種import(比如@ImportResource(“xxx.xml”,@Import(xxx.class),@MapperScan(…)等)
掃描類比如city.class 利用反射配置
RootBeanDefinition cityBeanDefinition = new ~;
cityBeanDefinition.setBeanClassName(“city”);
cityBeanDefinition.setBeanClass (city.class);
set是否abstract,是否byName/byType/@lazy/單例還是多例等等

所有 beanDefinition註冊完後,同時也配置完一個 votaile list用來存貯這寫beanDefinition的key比如該list中就存有key “city”。

然後通過遍歷這個list把這寫beanDefinition全部存入map(比如map.put(“city”,cityBeanDefinition))

這裏的map就是上文實例化beanFactory中的一個map。就此整個類註冊完畢

b BeanFactoryPostProcessor
b:
這裏就是我們開發者可以配置的接口
我們如何自己提供BeanFactoryPostProcessor?

@Component
public woaiyunyunProcessor implements BeanFactoryPostProcessor{
	@Override
	public ~ BeanFactoryPostProcessor(~ beanFactory){
		GenerBeanDefinition g = beanFactory.getBeanDefinition(“city”);
		System.out.println(“我愛肉肉”);
		c.setBeanClass(TestService.class);
	}
}

這個配置後最後spring單例池中city.class就”消失了”,真正執行的是TestService.class
動態代理的也是利用這種機制實現,把xxx.class消失了,用xxxProxy.class在這裏來代替他註冊到單例池中,比如mybatics就通過這樣實現

Spring中自身就有大量BeanFactoryPostProcessor,Spring會自身加上我們自己實現的一起參與構建整個BeanFactoryPostProcessor流程

c finishBeanFactoryInitialization(beanFactory)
c:
到了這裏就是真正實例化bean的流程了,通過beanFactory的所有註冊信息,調用這個方法實例化所有bean並且放入concurrentHashMap的單例池中

單例池中只有單例,原型和懶加載是不會實例化的

d finishRefresh() //bean實例化到此完全結束

2.3 Spring實例化Bean源碼過程(及三級緩存如何處理循環依賴)

回溯到上一章的方法finishBeanFactoryInitalization
這章講這個方法底層實現

首先要知道循環依賴問題 A中注入B,B中注入A。 當實例化A時候會實例化B,因爲B也注入了A再實例化A,死循環反覆。所以後面利用三級緩存,三個緩存來處理

幾個重要定義
三級緩存都是map
一級緩存:concurrentHashMap單例池,存放已經實例化完整的bean
二級緩存: singletonFactories 放ObjectFactory工廠,存能夠生產半成品對象的工廠
三級緩存:earlySingletonObjects 放Object,存半成品對象

半成品bean: 創建狀態下的bean,不是完整的bean,存放在三級緩存中。
通過半成品概念,還區別是否處於循環依賴中間的狀態

處理循環依賴真正執行流程
A getBean -> new A(此步驟會在二級緩存中存入A的工廠對象) -> 因爲注入了B,B getBean -> new B -> 因爲注入A,要找A,一級緩存中無A -> 三級緩存中也沒有A -> 步驟6下A前面已經創建過,是創建創建狀態。走下面第7步調用, 因爲前面new A時候二級緩存已經有A工廠對象,用該工廠對象生產A半成品bean,A半成品bean放入三級緩存,二級緩存中的這個A工廠對象刪除。死循環解除

實例化Bean完整執行流程
1 先判斷是否是單例(Spring中百分之90以上是單例)
2 是單例走getBean(beanName)
3 先判斷BeanName是否符合規則,符合然後走getSingleton(beanName)
4 走singletonObjects從上一章所說concurrentHashMap的單例池,這裏單例池是一級緩存,一級緩存中找是否已創建過bean,找到則獲取並直接返回,流程結束,找不到走5
5 判斷是否能在三級緩存中拿到,拿到則返回,拿不到走6
6 判斷是否是正在創建狀態isSingletonCurrentlyInCreation
7 如果是創建狀態,則從二級緩存工廠中拿出該生產對象,並通過二級緩存生產半成品狀態bean。將該半成品bean存入三級緩存用並刪除二級緩存中這個生產生產對象,返回該bean,bean實例化該流程結束
8 如果不是創建狀態,則開始調用createBean方法
9 開始調用多個BeanPostProcessor(核心)

BeanPostProcessor是一個接口,兩個方法 postProcessBeforeInitialization和 postProcessAfterInitialization.

所有後置處理器都繼承了該接口,以策略設計模式,用幾十個實現類共同實現了該接口
如自動注入,callback,AOP等都是不同獨立的BeanPostProcessor實現類實現這些功能

第二個bean後置處理器利用反射才真正實例化bean

10 然後判斷是否允許循環依賴,默認是true,我們可以改成false不允許循環依賴
11 允許循環依賴下,二級緩存中put該工廠緩存,this.singletonFactories.put(beanName,singletonFactory) 這個工廠緩存singletonFactory可以用來實例化半成品bean,但不是bean。也就是第7步,循環依賴下利用這個二級緩存產生bean保存到三級緩存中。

12 之後再經歷n次bean後置處理器,bean實例化完全結束並加入一級緩存單例池中

相關問題

爲什麼需要三級緩存,直接二級緩存中拿不好嗎?
因爲二級緩存中存放的是能生產bean的工廠,工廠本身很複雜,代價高,不適合多次調用。每次調用相當於就是重新創建一次新的半成品bean。三級緩存只存半成品bean對象,取出性能高,並且保存後就會直接刪除二級緩存中這個工廠對象。

爲什麼需要二級緩存?
主要因爲要處理循環依賴問題,其次工廠能判斷是否需要需要動態代理等等,利用策略模式+工廠設計模式生成合格的bean

AOP實現方式及幾種實現方式執行順序及爲什麼?
3種方式

  1. Implement接口,重寫intit post方法
  2. Xml配置 init post
  3. 加註解@
    3中執行順序 方法3先於方法2先於方法1
    Why? BeanPostProcessor處理執行順序導致

2.4 AOP原理及應用

AOP大體過程
Spring AOP{
	A a = new A();
	Aop(a){
		Return proxy.newInstance(a.getImpl(),InnovationHandler);
	}

AOP也是重寫 BeanPostProcessor接口下postProcessAfterInitialization干預bean初始化實現的。真實實現類不在單例池中,而是代理類註冊在單例池中。2.2章節中重寫BeanFactoryPostProcessor也提到過類似方法

AOP應用
@AspectJ註解 是一種第三方專門處理aop的技術,但是底層實現和Spring AOP毫無關係。那麼爲什麼Spring註解也用Aspect呢。因爲一開始Spring AOP使用極其複雜,後來借鑑了AspectJ的實用風格。

AOP是一種標準
Spring AOP動態織入(藉助AspectJ語法風格)
AspectJ 靜態織入

1 Config.class中加註解@EnableAspectJAutoProxy開啓AOP,或者xml中加AspectJ
2 應用

	@Component
	@Aspect //定義切面
	public class Aspect@Point cut(within(com.xx.xx.xx))//定義切點
		Public void pointCut(){
}
}

	@Before(“pointCut()”)
	public void advice(){
		//業務邏輯

3 場景-橫向切面
Controller層面 日誌記錄,
service層面 異常處理,
dao層面 事務,檢查性能等等
都不關心縱向切面主要業務邏輯,AOP關心切面時間和順序

AOP簡單原理
目標類是接口則用 JDKProxy實現,否則用Cglib實現

JDKProxy:InvocationHandler接口和Proxy類。 在BeanPostProcessor下利用Java反射,重寫postProcessAfterInitialization干預bean初始化實現的。真實實現類不在單例池中,而是代理類註冊在單例池中。

Cglib: 通過ASM(二進制字節碼操作類庫)直接修改二進制字節碼實現生成動態代理。
ASM -> AOP

ASM原來版本

ClassWriter cw = new ~;
ClassReader cr = new ~;
cr.accept(cw,0);//新的字節碼產生。利用訪問者設計模式,結構不變情況下動態改變對內部元素
byte[] res = cw.toByteArray();

ASM AOP版本
ClassWriter cw = new ~;
ClassReader cr = new ~;
ClassVisitor cv = new ~(){
	重寫訪問者和適配器,實現AOP
}
cr.accept(cv,0);//

2.5 SpringMVC到SpringBoot源碼演變

傳統SpringMVC下配置

web.xml //功能初始化Spring上下文
applicationContext.xml 
springmvc.xml

1 web.xml下

<context-param> //配置applicationContext.xml參數給listener
<listener>
<servlet> //給容器tomcat/Jetty註冊一個servlet攔截所有請求

tomcat是一個程序入口,而tomcat入口是web.xml,web.xml啓動spring上下文,tomcat啓動時加載web.xml

2 applicationContext.xml
掃描業務類 DAO等

3 springmvc.xml
掃描controller,可以配置視圖解析(不是必須要的)

SpringBoot沒有web.xml如何註冊DispatcherServlet?
boot使用java代碼完成0配置註冊和實例化

public void onStartup(ServletContext ~){
	register(config.class);
	refresh();
	DispatcherServlet ~ = new ~
	//這些Spring整個流程我們前幾章也講過
}

爲什麼tomcat/Jetty能夠開啓onStartup方法?

tomcat 8版本以後,對應servlet3.0以後版本
servlet3.0版本規定規範:META-INF下的services下實現的ServletContainerInitializer接口,容器(Tomcat)必須實現onStartup方法。
如果加上@HandleTypes註解,也必須啓動該接口實現類onStartup方法

然後boot中gradle加入tomcat依賴後實現了tomcat類,利用該類調用tomcat的API

SpringBootApplicationContext包含三個註釋
@EnableAutoConfiguration 啓動自動bean加載機制
@ComponentScan 掃描應用程序所在的包
@Configuration 允許Spring註冊額外的bean或導入其他配置類

@configuration和@Component區別
@Controller @Service @Repository @Aspect @configuration等等都是@Component元註解實現的 configuration實例化一次後就會從單例池中拿bean,而component會不斷重複實例化

2.6 Spring下mybatics原理

第一步 實現mybatics代理對象
簡易版本mybatics代理對象源碼

public class Session{
	public static Object queryMapper(Class clazz){
		Class [] clas = new Class[]{clazz};
		//爲什麼clas是數組,因爲防止多個類impl該接口
		Object proxy = Proxy.newProxyInstance(dao.class.classLoader,clas,new yunyunInvocationHandler);
	}
}

public class yunyunInvocationHandler implements InvocationHandler{
	@Override
	~invoke(Object proxy, Method method, Object[] args){
		//1 連接JDBC
		//environment 環境下傳入config->
		//configuration 講xml實例化對象 -> 
		//sqlsessionFactory sql 加入configuration參數 -> 
		//Dao mapper = sql.getMapper(Dao.class) 
		//實例化mybatics的Dao接口(用來連接mysql的接口), 
		//因爲接口不能實例化,這裏用到了JDK動態代理
		//2 找Dao中select註解
		Select selects = method.getAnnotation(Dao.class);
		if(selects!=null){
			String s = selects.value()[0];//拿到select中sql語句
		}
		//3 執行JDBC
		return ~
	}
}

public class Test{
	~ main ~{
		Dao dao = (Dao) Session.queryMapper(Dao.class);
		dao.list();
	}
}

第二步把代理對象注入Spring容器中

有4中方法

1 @Bean
2 API registerSingleton
3 factoryBean(真實mybatics使用的方法)
4 factoryMethod

方法1

//Appconfig類中{
	@Bean
	public Dao dao(){
		Dao dao = session.querryMapper(Dao.class);
		return dao;
	}
}
//弊端也很大當有上千上萬個dao則要重複配置上千上萬個
	

方法2

//啓動類下
AnnotationConfigApplicationContext ac = new ~;
ac.register(config.class);
//拆開context實現類底層,refresh初始化前,手動配置Dao.class
Dao dao = (Dao) session.querryMapper(Dao.class);
ac.refresh();

//缺點依然很明顯,讓不懂Spring底層的程序員怎麼活

方法3(Mybatics使用的方法)
使用了FactoryBean,這是一個特殊bean
需要Impl FactoryBean接口重寫接口三個方法,則可以注入spring容器

@Service
public class yunyunFactoryBean implement FactoryBean{
	public Object getObject(){
		return session.queryMapper(Dao.class);
	}

	public class<?> getObjectType(){
		return mapperInterface;
	}

	~ isSingleton(){}

這段代碼對應了xml配置

<bean id="userMapper" class="yunyunFactoryBean">
	<property name="mapperInterface" value="Dao"/>
</bean>

那麼一個xml只能配置一個,如何一次性掃描多個?
傳入BeanDefinitions即可一次性掃描多個

public ~ yunyunBeanDefinitionRegister implement ImportBeanDefinition{
	~ bd1 = BeanDefinitionBuilder.genericBeanDefinition(yunyunFactoryBean.class);
	~ bd2 = bd1.getBeanDefinition(~);
	bd2.getPropertyValues().add("xxx.mapper.Dao");
}

然後就是識別這個yunyunBeanDefinitionRegister類

@Retention(~)
@Import(yunyunBeanDefinitionRegister.class)
public @interface yunyunScan{
}

然後在config.class加上@yunyunScan註解即大功告成

2.7 SpringMVC源碼運行流程

1 DispatcherServlet類攔截所有請求,然後分發到controller上面
2 DispatcherServlet類繼承FrameworkServlet,FrameworkServlet中實現了servlet的doPost/doGet/service方法
3 所以攔截的請求都會先到FrameworkServlet下doGet方法
4 FrameworkServlet下doGet和doPost都是調用processRequest(request,response)方法
5 processRequest方法下調用了doService方法
6 doService下調用了doDispatch方法
7 doDispatch方法,是關鍵
定義對象,checkMultipart(request)方法檢查文件上傳,getHandler(processRequest) Handler就是controller對象,拿到controller

大致流程理論
我們應用時通常@Controller然後@RequestMapping("/攔截的請求路徑")
所以需要SpringMVC通過請求,找到Controller
1 Spring構建beanFactory時候實現掃描整個項目
2 因爲掃描過了,找到所有@Controller的類
3 遍歷類中所有方法對象
4 判斷是否加了@RequestMapping
5 把所有@RequestMapping("/攔截的請求路徑")下,攔截的請求路徑作爲key,RequestMapping對應的method對象作爲value存入map中

6 根據用戶發送的請求拿到URI
http://localhost:8080/yunyun.do是URL,yunyun.do是URI
7 根據URI作爲上文map的key值找value返回值,方法對象
8 比如如果註解方式實現controller,則利用反射調用方法

具體實現
SpringMVC通過handlerMapping類來找controller

使用controller有兩種類型,三種實現
一種類型和實現是@Controller註解
一種類型和兩個實現方式是@Component(提供beanName)+implement Controller接口/HttpRequestHandler接口(雖然沒人用)
所以handlerMapping有兩種
上文說的map集合就在handlerMapping中

找到controller類後,通過getHandlerAdapter找適配器,調用方法

因爲controller有兩種類型,三種實現,所以需要適配器判斷由哪種方式實現的controller
根據不同的實現方式,調用controller

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章