輕鬆理解Spring

前言

這篇文章在前一段時間看到一位大佬寫的,由於寫的邏輯清晰、通俗易懂,當時我就整理了一下筆記,但願往後本身也行寫出這樣的文章。若是想對spring瞭解和入門的同窗,相信認真閱讀了本篇文章定會收穫頗豐。java

特此說明:此篇文章主要目的是當作範文,以此鼓勵本身在不斷學習總結中創做出更多更好的優質原創文章;找了半天沒有找到原文章,若有打擾即刻刪除。程序員

主要內容:

默認題主說的Spring是Spring framework,而不是Spring家族spring

  • 盲點
  • Spring說,萬物皆可定義
  • 默默付出的後置處理器
  • 利用後置處理器返回代理對象

盲點

若是你從未獨立看過源碼(和我同樣),那麼你極可能至今都未曾注意某兩個概念。編程

你覺得我會說IOC和AOP?No。app

看到這裏,一部分讀者內心一驚:臥槽,說的啥玩意,Spring不就IOC和AOP嗎?!這兩個都不說,你這篇文章爲啥能寫這麼長?框架

不錯,我就是這麼長。其實我要講的是:模塊化

  • BeanDefinition
  • BeanPostProcessor

大部分人一聽到「請你談談對Spring的理解」,就會下意識搬出IOC和AOP兩座大山,趕忙糊弄過去。大概是這樣的:post

IOC學習

所謂的控制反轉。通俗地講,就是把本來須要程序員本身建立和維護的一大堆bean通通交由Spring管理。測試

也就是說,Spring將咱們從盤根錯節的依賴關係中解放了。當前對象若是須要依賴另外一個對象,只要打一個@Autowired註解,Spring就會自動幫你安裝上。

AOP

所謂的面向切面編程。通俗地講,它通常被用來解決一些系統交叉業務的織入,好比日誌啦、事務啥的。打個比方,UserService的method1可能要打印日誌,BrandService的method2可能也須要。亦即:一個交叉業務就是要切入系統的一個方面。具體用代碼展現就是:

交叉業務的編程問題即爲面向切面編程。AOP的目標就是使交叉業務模塊化。作法是將切面代碼移動到原始方法的周圍:

原先不用AOP時(圖一),交叉業務的代碼直接硬編碼在 方法內部的先後,而AOP則是把交叉業務寫在 方法調用先後。那麼,爲何AOP不把代碼也寫在方法內部的先後呢?兩點緣由:

  • 首先,這與AOP的底層實現方式有關:動態代理其實就是代理對象調用目標對象的同名方法,並在調用先後加加強代碼。

  • 其次,這兩種最終運行效果是同樣的,因此沒什麼好糾結的。

而所謂的模塊化,我我的的理解是將切面代碼作成一個可管理的狀態。好比日誌打印,再也不是直接硬編碼在方法中的零散語句,而是作成一個切面類,經過通知方法去執行切面代碼。

我相信大部分培訓班出來的朋友也就言盡於此,講完上面內容就準備收拾打卡下班了。

怎麼說呢,IOC按上面的解釋,雖然很淺,但也馬馬虎虎吧。然而AOP,不少人對它的認識是很是片面的...

這樣吧,我問你一個問題,如今我本身寫了一個UserController,以及UserServiceImpl implements UserService,而且在UserController中注入Service層對象:

@Autowired
private UserService userService;
複製代碼

若是你聽不懂我要問什麼,說明你對Spring的AOP理解仍是太少了。

實際上,Spring依賴注入的對象並不必定是咱們本身寫的類的實例,也多是userServiceImpl的代理對象。下面分別演示這兩種狀況:

  • 注入userServiceImpl對象

  • 注入userServiceImpl的代理對象(CGLib動態代理)

爲何兩次注入的對象不一樣?

由於第二次我給UserServiceImpl加了@Transactional 註解。

此時Spring讀取到這個註解,便知道咱們要使用事務。而咱們編寫的UserService類中並無包含任何事務相關的代碼。若是給你,你會怎麼作?動態代理嘛!

看到這裏,我彷彿聽到有一部分兄弟默默說了句:臥槽...

可是,上面對IOC和AOP的理解,也僅僅是應用級別,是一個面。僅僅瞭解到這個程度,對Spring的瞭解仍是很是扁平的,不夠立體。

Spring說,萬物皆可定義

上帝說,要有光。因而特斯拉搞出了交流電。

Java說,萬物皆對象。可是Spring另外搞了BeanDefinition...

什麼BeanDefinition呢?其實它是bean定義的一個頂級接口:

正如BeanDefinition的類註釋所言:一個BeanDefinition是用來描述一個bean實例的

哎呀臥槽,啥玩意啊。描述一個bean實例?我咋想起了Class類呢。

其實,二者並無矛盾。

Class只是描述了一個類有哪些字段、方法,可是沒法描述如何實例化這個bean!若是說,Class類描述了一塊豬肉,那麼BeanDefinition就是描述如何作紅燒肉:

  • 單例嗎?
  • 是否須要延遲加載?
  • 須要調用哪一個初始化方法/銷燬方法?

大部分初學者覺得Spring解析或者@Bean後,就直接搞了一個bean存到一個大Map中,其實並非。

  • Spring首先會掃描解析指定位置的全部的類獲得Resources(能夠理解爲.Class文件)
  • 而後依照TypeFilter和@Conditional註解決定是否將這個類解析爲BeanDefinition
  • 稍後再把一個個BeanDefinition取出實例化成Bean

就比如什麼呢?你從海里吊了一條魚,可是你還沒想好清蒸仍是紅燒,那就乾脆先曬成魚乾吧。一條鹹魚,其實蘊藏着無線可能,由於它可能會翻身!

默默付出的後置處理器

接下來,咱們討論一下鹹魚如何翻身。

最典型的例子就是AOP。上面AOP的例子中我說過了,若是不加@Transactional,那麼Controller層注入的就是普通的userServiceImpl,而加了之後返回的實際是代理對象。

爲何要返回代理對象?由於咱們壓根就沒在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接口,可是做爲容器,其實它是用來包含各類各樣的組件的,而不是存bean:

那麼,Spring是如何給鹹魚加佐料(事務代碼的織入)的呢?關鍵就在於後置處理器。

後置處理器其實能夠分好多種,屬於Spring的擴展點之一。

上面BeanFactory、BeanDefinitionRegistryPostProcessor、BeanPostProcessor都算是後置處理器,這裏篇幅有限,只介紹一下BeanPostProcessor。

BeanFactoryPostProcessor是用來干預BeanFactory建立的,而BeanPostProcessor是用來干預Bean的實例化。不知道你們有沒有試過在普通Bean中注入ApplicationContext實例?你第一時間想到的是:

@Autowired
ApplicationContext annotationConfigApplicationContext;
複製代碼

除了利用Spring自己的IOC容器自動注入之外,你還有別的辦法嗎?

咱們可讓Bean實現ApplicationContextAware接口:

後期,Spring會調用setApplicationContext()方法傳入ApplicationContext實例。

Spring官方文檔:通常來講,您應該避免使用它,由於它將代碼耦合到Spring中,而且不遵循控制反轉樣式

這是我認爲Spring最牛逼的地方:代碼具備高度的可擴展性,甚至你本身都懵逼,爲何實現了一個接口,這個方法就被莫名其妙調用,還傳進了一個對象...

這其實就是後置處理器的工做!

什麼意思呢?

就是說啊,明面上咱們看得見的地方只要實現一個接口,可是背地裏Spring在本身框架的某一處搞了個for循環,遍歷全部的BeanPostProcessor,其中就包括處理實現了ApplicationContextAware接口的bean的後置處理器:ApplicationContextAwareProcessor。

上面這句話有點繞,你們停下來多想幾遍。

也就是說,要擴展的類是不肯定的,可是處理擴展類的流程是寫死的。總有一個要定下來吧。也就是說,在這個Bean實例化的某一緊要處,必然要通過不少BeanPostProcessor。可是,BeanPostProcessor也不是誰都處理,有時也會作判斷。好比:

if (bean instanceof Aware) {
	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

使用步驟:

  1. 實現BeanPostProcessor
  2. @Component加入Spring容器
@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,並返回代理對象:

相關文章
相關標籤/搜索