最近看了點Spring的源碼,於是來稍微扯一扯,希望能幫一部分培訓班出身的朋友撕開一道口子,透透氣。
廣義上的Spring指的是Spring整個項目,包含SpringBoot、SpringCloud、SpringFramework、SpringData等等,
本系列文章只討論狹義上的Spring,也就是SpringFrameWork。
主要內容:
如果你恰好非科班轉行且從未獨立看過源碼,那麼你很可能至今都不曾注意某兩個概念。
你以為我會說IOC和AOP?NO。
看到這裡,一部分讀者心裡一驚:臥槽,說的啥玩意,Spring不就IOC和AOP嗎?!這兩個都不說,你這篇文章為啥能寫這麼長?
不錯,我就是這麼長。其實我要講的是:
大部分人一聽到「請你談談對Spring的理解」,就會下意識地搬出IOC和AOP兩座大山,趕緊糊弄過去。大概是這樣的:
IOC
所謂的控制反轉。通俗地講,就是把原本需要程序員自己創建和維護的一大堆bean統統交由Spring管理。
也就是說,Spring將我們從盤根錯節的依賴關係中解放了。當前對象如果需要依賴另一個對象,只要打一個@Autowired註解,Spring就會自動幫你安裝上。
AOP
所謂的面向切面編程。通俗地講,它一般被用來解決一些系統交叉業務的織入,比如日誌啦、事務啥的。打個比方,UserService的method1可能要列印日誌,BrandService的method2可能也需要。亦即:一個交叉業務就是要切入系統的一個方面。具體用代碼展示就是:
交叉業務的編程問題即為面向切面編程。AOP的目標就是使交叉業務模塊化。做法是將切面代碼移動到原始方法的周圍:
原先不用AOP時(圖一),交叉業務的代碼直接硬編碼在方法內部的前後,而AOP則是把交叉業務寫在方法調用前後。那麼,為什麼AOP不把代碼也寫在方法內部的前後呢?兩點原因:
而所謂的模塊化,我個人的理解是將切面代碼做成一個可管理的狀態。比如日誌列印,不再是直接硬編碼在方法中的零散語句,而是做成一個切面類,通過通知方法去執行切面代碼。
我相信大部分培訓班出來的朋友也就言盡於此,講完上面內容就準備打卡下班了。
怎麼說呢,IOC按上面的解釋,雖然很淺,但也馬馬虎虎吧。然而AOP,很多人對它的認識是非常片面的...
這樣吧,我問你一個問題,現在我自己寫了一個UserController,以及UserServiceImpl implements UserService,並且在UserController中注入Service層對象:
@Autowired private UserService userService;
那麼,這個userService一定是我們寫的UserServiceImpl的實例嗎?
如果你聽不懂我要問什麼,說明你對Spring的AOP理解還是太少了。
實際上,Spring依賴注入的對象並不一定是我們自己寫的類的實例,也可能是userServiceImpl的代理對象。下面分別演示這兩種情況:
為什麼兩次注入的對象不同?
因為第二次我給UserServiceImpl加了@Transactional 註解。
此時Spring讀取到這個註解,便知道我們要使用事務。而我們編寫的UserService類中並沒有包含任何事務相關的代碼。如果給你,你會怎麼做?動態代理嘛!上面說了,InvocationHandler作用有兩個:1.銜接調用鏈, 2.存放增強代碼。用動態代理在InvocationHandler的invoke()中開啟關閉事務即可完成事務控制。
看到這裡,我彷彿聽到有一部分兄弟默默說了句:臥槽,原來是這樣...
但是,上面對IOC和AOP的理解,也僅僅是應用級別,是一個面。僅僅瞭解到這個程度,對Spring的瞭解還是非常扁平的,不夠立體。
上帝說,要有光。於是特斯拉搞出了交流電。
Java說,萬物皆對象。但是Spring另外搞了BeanDefinition...
什麼BeanDefinition呢?其實它是bean定義的一個頂級介面:
哎呀臥槽,啥玩意啊。描述一個bean實例?我咋想起了Class類呢。
其實,兩者並沒有矛盾。
Class只是描述了一個類有哪些欄位、方法,但是無法描述如何實例化這個bean!如果說,Class類描述了一塊豬肉,那麼BeanDefinition就是描述如何做紅燒肉:
在容器內部,這些bean定義被表示為BeanDefinition對象,包含以下元數據:1.包限定的類名:通常,定義bean的實際實現類。2.Bean行為配置:它聲明Bean在容器中的行為(範圍、生命週期回調,等等)。3.Bean依賴:對其他Bean的引用。4.對當前Bean的一些設置:例如,池的大小限制或在管理連接池的bean中使用的連接數。——Spring官方文檔
在容器內部,這些bean定義被表示為BeanDefinition對象,包含以下元數據:
大部分初學者以為Spring解析<bean/>或者@Bean後,就直接搞了一個bean存到一個大Map中,其實並不是。
就好比什麼呢?你從海里吊了一條魚,但是你還沒想好清蒸還是紅燒,那就乾脆先曬成魚乾吧。一條鹹魚,其實蘊藏著無限可能,因為它可能會翻身!
接下來,我們討論一下鹹魚如何翻身。
最典型的例子就是AOP。上面AOP的例子中我說過了,如果不加@Transactional,那麼Controller層注入的就是普通的userServiceImpl,而加了註解以後返回的實際是代理對象。
為什麼Spring要返回代理對象?因為我們壓根就沒在UserServiceImpl中寫任何commit或者rollback等事務相關的代碼,但是此時此刻代理對象卻能完成事務操作。毫無疑問,這個代理對象已經被Spring加了佐料(事務增強代碼)。
那麼Spring是何時何地加佐料的呢?說來話長,我們先繞個彎子。
大部分人把Spring比作容器,其實潛意識裡是將Spring完全等同於一個Map了。其實,真正存單例對象的Map,只是Spring中很小很小的一部分,僅僅是BeanFactory子類的一個欄位,我更習慣稱它為「單例池」。
/** Cache of singleton objects: bean name --> bean instance */ private final Map<String, Object> singletonObjects = new ConcurrentHashMap<String, Object>(256);
這裡的ApplicationContext和BeanFactory是介面,實際上都有各自的子類。比如註解驅動開發時,Spring中最關鍵的就是AnnotationConfigApplicationContext和DefaultListableBeanFactory。
所以,很多人把Spring理解成一個大Map,還是太膚淺了。就拿ApplicationContext來講,它也實現了BeanFactory介面,說明它其實也是一個容器。但是同為容器,與BeanFactory不同的是,ApplicationContext主要用來包含各種各樣的組件,而不是存bean:
那麼,Spring是如何給鹹魚加佐料(事務代碼的織入)的呢?關鍵就在於後置處理器。
後置處理器其實可以分好多種,屬於Spring的擴展點之一:
前三個BeanFactoryPostProcessor、BeanDefinitionRegistryPostProcessor、BeanPostProcessor都算是後置處理器,這裡篇幅有限,暫且先只介紹一下BeanPostProcessor。
BeanFactoryPostProcessor是用來幹預BeanFactory創建的,而BeanPostProcessor是用來幹預Bean的實例化。不知道大家有沒有試過在普通Bean中注入ApplicationContext實例?你第一時間想到的是:
@Autowired ApplicationContext annotationConfigApplicationContext;
除了利用Spring本身的IOC容器自動注入以外,你還有別的辦法嗎?
我們可以讓Bean實現ApplicationContextAware介面:
後期,Spring會調用setApplicationContext()方法傳入ApplicationContext實例。
Spring官方文檔:一般來說,您應該避免使用它,因為它將代碼耦合到Spring中,並且不遵循控制反轉樣式。
這是我認為Spring最牛逼的地方:代碼具有高度的可擴展性,甚至你自己都懵逼,為什麼實現了一個介面,這個方法就被莫名其妙調用,還傳進了一個對象...
這其實就是後置處理器的工作!
什麼意思呢?
也就是說,雖然表面上在我們只要讓Bean實現一個介面就能完成ApplicationContext組件的注入,看起來很簡單,但是背地裡Spring做了很多事情。Spring會在框架的某一處搞個for循環,遍歷當前容器中所有的BeanPostProcessor,其中就包括一個叫ApplicationContextAwareProcessor的後置處理器,它的作用是:處理實現了ApplicationContextAware介面的Bean。
上面這句話有點繞,大家停下來多想幾遍。
要擴展的類(Bean)是不確定的,但是處理擴展類的流程(循環BeanPostProcessor)是寫死的。因為一個程序,再怎麼高度可擴展,總有一個要定下來吧。也就是說,在這個Bean實例化的某一緊要處,必然要經過很多BeanPostProcessor。但是,BeanPostProcessor也不是誰都處理,有時也會做判斷。比如:
if (bean instanceof Aware) { if (bean instanceof EnvironmentAware) { ((EnvironmentAware) bean).setEnvironment(this.applicationContext.getEnvironment()); } if (bean instanceof EmbeddedValueResolverAware) { ((EmbeddedValueResolverAware) bean).setEmbeddedValueResolver(this.embeddedValueResolver); } if (bean instanceof ResourceLoaderAware) { ((ResourceLoaderAware) bean).setResourceLoader(this.applicationContext); } if (bean instanceof ApplicationEventPublisherAware) { ((ApplicationEventPublisherAware) bean).setApplicationEventPublisher(this.applicationContext); } if (bean instanceof MessageSourceAware) { ((MessageSourceAware) bean).setMessageSource(this.applicationContext); } if (bean instanceof ApplicationContextAware) { ((ApplicationContextAware) bean).setApplicationContext(this.applicationContext); } }
所以,此時此刻一個類實現ApplicationContextAware介面,有兩層含義:
大致瞭解Spring Bean的創建流程後,接下來我們嘗試著用BeanPostProcessor返回當前Bean的代理對象。
pom.xml
<dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.3.12.RELEASE</version> </dependency> </dependencies>
AppConfig
@Configuration //JavaConfig方式,即當前配置類相當於一個applicationConotext.xml文件 @ComponentScan //默認掃描當前配置類(AppConfig)所在包及其子包 public class AppConfig {
}
Calculator
public interface Calculator { public void add(int a, int b); }
CalCulatorImpl
@Component public class CalculatorImpl implements Calculator { public void add(int a, int b) { System.out.println(a+b); } }
後置處理器MyAspectJAutoProxyCreator
使用步驟:
@Component public class MyAspectJAutoProxyCreator implements BeanPostProcessor { public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { return bean; }
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { final Object obj = bean; //如果當前經過BeanPostProcessors的Bean是Calculator類型,我們就返回它的代理對象 if (bean instanceof Calculator) { Object proxyObj = Proxy.newProxyInstance( this.getClass().getClassLoader(), bean.getClass().getInterfaces(), new InvocationHandler() { public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { System.out.println("開始計算...."); Object result = method.invoke(obj, args); System.out.println("結束計算..."); return result; } } ); return proxyObj; } //否則返回本身 return obj; } }
測試類
public class TestPostProcessor { public static void main(String[] args) {
System.out.println("容器啟動成功!"); AnnotationConfigApplicationContext applicationContext = new AnnotationConfigApplicationContext(AppConfig.class);
String[] beanDefinitionNames = applicationContext.getBeanDefinitionNames(); //列印當前容器所有BeanDefinition for (String beanDefinitionName : beanDefinitionNames) { System.out.println(beanDefinitionName); }
System.out.println("============");
//取出Calculator類型的實例,調用add方法 Calculator calculator = (Calculator) applicationContext.getBean(Calculator.class); calculator.add(1, 2); }
先把MyAspectJAutoProxyCreator的@Component注釋掉,此時Spring中沒有我們自定義的後置處理器,那麼返回的就是CalculatorImpl:
把@Component加上,此時MyAspectJAutoProxyCreator加入到Spring的BeanPostProcessors中,會攔截到CalculatorImpl,並返回代理對象:
本文是Spring源碼系列的第一篇,僅僅是介紹了兩個重要概念:BeanDefinition和BeanPostProcessor。更詳細的內容, 比如Bean的生命週期流程及九大類後置處理器的介紹,以後有機會再慢慢更新。通過本文,大家只要有朦朧的Spring Bean生命週期的概念,以及知道BeanDefinition和BeanPostProcessor即可。
2019-06-25 12:58:54