第二章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種方式
- Implement接口,重寫intit post方法
- Xml配置 init post
- 加註解@
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