寫在前面
入坑Java已經快兩年的時間了(之前在C#坑中混跡六七年),工作之余也喜歡鑽研技術,一直想積累些學習成果,但是由於能力一般水平有限,遲遲未曾着手,現在終於下定決心寫些技術博客,結果7天能憋出6個字。。。好了,廢話不多說,開始進入正題吧,至於文字功底,大家當做批改小學生作文就好。至於文章中有哪些不對的地方,還望大家多多指正。
前言
相信現在好多單位都已經在使用SpringBoot進行開發了,開發過程中,肯定會有很多的疑問,比如常聽說的IOC、AOP都是什么;為什么我們controller使用service時,不需要實例化,直接就可以調用方法;很多的注解都是什么意思,作用是什么,什么時候被使用了;等等一系列的問題,這些問題都會在我接下來的源碼專題中得到解答。
我在學習源碼的過程中,也試着畫了代碼的詳細講解圖,不知道在博客園上怎么上傳文件,有需要的加我QQ找我要吧。
整體流程
在正式閱讀源碼之前,我們需要先了解啟動的整體流程,這樣有助於我們對源碼的理解。
SpringBoot核心就是IOC和AOP,其中最最重要的核心點就是IOC。對於這二者的概念、控制反轉、依賴注入、切面編程這些大家肯定也都在網上有所了解了,這里不再贅述,接下來簡單的學習下IOC的過程,舉個例子:你到了需要女朋友的年齡,然后自己去按照要求找女朋友,過程很復雜,如果國家現在規定,成立一個婚姻分配局,婚姻分配局把女朋友都放到一個房子里,你提出來了自己的尋找女朋友的要求,“膚白貌美大長腿”,婚姻分配局就會按照你的要求給你分配一個女朋友,省去了你自己尋找女朋友的時間,是不是很方便(不要糾結女朋友是全局的,大家共有的問題吧... ...)。這個婚姻分配局做的事就是一個IOC思想,那個房子就是一個IOC容器。
在之前的開發中,我們需要使用哪個類,就new一個對象,然后拿來使用,這樣是比較笨拙的,整個項目中,我們也會增加了很多的代碼,new了很多的對象,先不說這些對象對內存的占用問題,單單編寫代碼時就會增加了不少代碼量,那有沒有一種方式讓我們可以不再自己創建對象呢,比如說我們把需要用到的對象放到一個箱子(容器)里面,用的時候直接到里面拿取,答案是肯定的。
使用過spring或者springBoot開發的朋友都知道,我們是怎么實例化、初始化我們需要的類呢,可以通過xml、注解或者配置類的方式來完成。但是框架又是怎么完成實例化和初始化的呢?如果我們有一些額外的需求,框架有沒有給我們留下什么可擴展的地方呢?
單單靠語言描述,可能無法直觀的描述清楚這個過程,我畫個圖大家看一下:
圖1
1 首先BeanDefinitionReader接口的實現類會把類的信息加載成BeanDefinition;
2 beanFactory接口通過反射的方式,針對每一個BeanDefinition完成實例化,來看看這個接口:
public interface BeanFactory { String FACTORY_BEAN_PREFIX = "&"; Object getBean(String var1) throws BeansException; <T> T getBean(String var1, @Nullable Class<T> var2) throws BeansException; Object getBean(String var1, Object... var2) throws BeansException; <T> T getBean(Class<T> var1) throws BeansException; <T> T getBean(Class<T> var1, Object... var2) throws BeansException; boolean containsBean(String var1); boolean isSingleton(String var1) throws NoSuchBeanDefinitionException; boolean isPrototype(String var1) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String var1, ResolvableType var2) throws NoSuchBeanDefinitionException; boolean isTypeMatch(String var1, @Nullable Class<?> var2) throws NoSuchBeanDefinitionException; @Nullable Class<?> getType(String var1) throws NoSuchBeanDefinitionException; String[] getAliases(String var1); }
beanFactory接口里面有多個geBean,這個就是用來查找創建對象的方法,(如果此時有讀過源碼的朋友會知道,按照getBean->doGetBean->createBean->doCreateBean->createBeanInstance這樣一層層方法跟蹤,就會看到創建的過程,后續源碼學習中會一起來研究這里)。
2.1 在實例化之前,框架給我們預留了一個非常重要的接口,就是BeanFactoryPostProcessor,我們可以通過實現這個接口的方式,完成額外信息的添加,如:
@Service public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor { public MyBeanFactoryPostProcessor() { super(); System.out.println("這是BeanFactoryPostProcessor實現類構造器!!"); } @Override public void postProcessBeanFactory(ConfigurableListableBeanFactory arg0) throws BeansException { System.out.println("BeanFactoryPostProcessor調用postProcessBeanFactory方法"); BeanDefinition bd = arg0.getBeanDefinition("person2"); bd.getPropertyValues().addPropertyValue("phone", "110"); } }
我們看一下執行了這個接口方法之后的效果:
圖2
執行之后,我們的person2里面已經包含了一個phone的屬性,值為110,(我知道看到這里,大家肯定會有疑問,MyBeanFactoryPostProcssor是什么時候調用,別急這篇文章先有個印象,后續代碼研讀中會有詳細的講解)。
3 完成實例化及初始化的工作
3.1及3.2 框架為我們預留了另外一個非常重要的接口類:beanPostprocessor,里面包含了兩個方法:
public interface BeanPostProcessor { @Nullable default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; } @Nullable default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { return bean; } }
這兩個方法分別如上圖所示,before在初始化之前執行,after在初始化之后執行;
先看下我測試的代碼:
繼承beanPostProcessor接口的類代碼:
@Component public class MyBeanPostProcessor implements BeanPostProcessor { public MyBeanPostProcessor() { super(); System.out.println("這是BeanPostProcessor實現類構造器!!"); // TODO Auto-generated constructor stub
} @Override public Object postProcessBeforeInitialization(Object arg0, String arg1) throws BeansException { System.out.println("BeanPostProcessor接口方法postProcessBeforeInitialization對屬性進行更改!"); //當程序執行到myTestPostprocessor這個類的時候 看看效果
if(arg1.contains("myTestPostprocessor")){ MyTestPostprocessor myTestPostprocessor = (MyTestPostprocessor)arg0; System.out.println("加載MmyTestPostprocessor -- before : mytestController.controllerName = "+ myTestPostprocessor.getName()); } return arg0; } @Override public Object postProcessAfterInitialization(Object arg0, String arg1) throws BeansException { System.out.println("BeanPostProcessor接口方法postProcessAfterInitialization對屬性進行更改 當前beanName="+arg1); //當程序執行到myTestPostprocessor這個類的時候 看看效果
if(arg1.contains("myTestPostprocessor")){ MyTestPostprocessor myTestPostprocessor = (MyTestPostprocessor)arg0; System.out.println("加載MmyTestPostprocessor -- after : mytestController.controllerName = "+ myTestPostprocessor.getName()); } return arg0; } }
上面代碼中的MyTestPostProcessor類的代碼:
@Service
public class MyTestPostprocessor{
public String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//初始化方法 給name屬性賦值
@PostConstruct
public void init() {
this.setName("月夜星空-測試");
System.out.println("MyTestPostprocessor --- 執行初始化方法 init");
}
}
執行之后我看一下執行的結果:
首先執行了Before方法,此時name屬性還沒有值,然后執行了初始化方法init,name屬性賦值了,然后執行了after方法,可以看出我們取到的myTestPostProcessor的name屬性已經有值了--“月夜星空-測試”。
試想SpringBoot的框架為什么這么流行,就是因為它的肆意妄為的擴展性,可以讓你在任何的階段進行擴展,除了上面提到的這些可擴展的接口以外,如果我們想在其他的某些階段做一些特殊的處理,應該怎么做呢 -- 監聽器。SpringBoot框架對外提供了很多的監聽器,可以方便我們的擴展,這里我先舉一個例子,我們可以通過監聽器來監聽當前在線的用戶,不廢話了,上代碼吧:
先自定義一個監聽器,當session發生變化時,會進入到sessionCreated方法,count++,記錄當前新登錄的人數:
@Component public class MyHttpSessionListener implements HttpSessionListener { public Integer count=0; @Override public void sessionCreated(HttpSessionEvent httpSessionEvent) { System.out.println("新用戶上線"); count++; httpSessionEvent.getSession().getServletContext().setAttribute("count",count); } @Override public void sessionDestroyed(HttpSessionEvent httpSessionEvent) { System.out.println("新用戶下線"); count--; httpSessionEvent.getSession().getServletContext().setAttribute("count",count); } }
創建一個測試類:
@RestController @RequestMapping("/TestSessionListener") public class TestSessionListener { @GetMapping(value = "/login") public String login(HttpServletRequest request) { Integer count = (Integer) request.getSession().getServletContext().getAttribute("count"); System.out.println("在線人數:" + count); return "在線人數:" + count; } }
我們測試一下:
我們還可以通過自定義事件和監聽器的方式來擴展,這里先不舉例了。
這篇文章主要介紹了IOC的過程,並且提到了兩個重要的接口,並沒有真正的研讀代碼,包括我們上面提到的這兩個接口的調用的時機、如何調用等問題都還沒有解答,這些都會在后續的文章中詳細學習,通過本文的學習,需要對框架啟動、IOC的過程有一個大致的了解。
我知道看完這篇文章之后會有很多朋友一腦子的問號,“框架如何通過xml或者注解來讀取類信息的”、“BeanDefinition是啥,包括啥”、“beanFactoryPostProcessor接口何時執行、如何執行的”、“beanFactory如何創建對象的”等等,今天我們主要學習的是流程和思想,真正的源碼不多,請大家整理好這些問題,后面會有詳細的源碼解讀,等到詳細學習源碼時,這些問題都會得到一一講解。