你說,怎麼把Bean塞到Spring容器?

做者:小傅哥
博客:bugstack.cnhtml

沉澱、分享、成長,讓本身和他人都能有所收穫!😄java

1、前言

小傅哥,你是怎麼學習的?程序員

有不少初學編程或者碼了幾年CRUD磚的小夥伴問我,該怎麼學編程?感受什麼都不會怎麼辦?感受目前的公司沒有核心業務學到不東西呀!面試

其實我可能和很大一部分的粉絲讀者都有相似的經歷,在傳統相似外包的行業待過、從C#語言兩年開發再面Java崗、新到互聯網職場感受太多不會的技術棧等等。spring

但可能最讓我在學習編程上受益的就是不斷的折騰這些技術:數據庫

  1. 關於外包:在外包2年時仍是C#開發,時而搞搞中繼器、IO板卡、PLC。但我仍舊喜歡大學時期學的Java語言,那麼天天5:30下班回家後,就不斷的用Java語言把公司接觸到的C#工程作翻新。差很少1年的時間,把幾乎我接觸到的項目翻新了個遍,就是那個時候知道的Java還能作串口通訊,仍是蠻有意思的。
  2. 關於場景:其實不少程序員在一個相對較小的公司時,學習的最大瓶頸是眼界問題,不知道有什麼技術、不知道有什麼場景,更不知道本身不會啥。其實不少時候這都跟有關係,公司是沒有這樣的場景,可是你能夠看博客、看論壇、看視頻,加各種技術羣。若是遇到哪些發廣告的就退了,哪些好的留下,認識一些人脈,相知一些基友,這在個過程總能有所收穫,你會隨着時間的推移嗅到各種技術棧、項目、經驗、心得、面試等等,當你武裝好了本身,再出去面試也就沒那麼難了。
  3. 關於開始:時間少、要學的多,感受本身就是一把小鐵鍬,要去挖蘇伊士運河,不知道能從哪開始。這個時候建議不要盲目的收藏幾個T的資料和視頻,先打開xmind,選個好看的主題,開始梳理本身的技術棧,看看本身會什麼不會什麼,在從這些不會的內容裏選出你最想學的,把要學的內容在梳理出相應的資料庫。好,那麼這個時候你就能夠開始了,記住開始是從一點點深刻的,不要總想着一口吃個胖子。

方向對了,快是最大的障礙!,不少時候只要你能平心靜氣日積月累的學習,其實就沒有什麼不能克服的問題。編程裏又有什麼很是難的東西嗎,大部分知識都是不知道就不會而已,知道了就很簡單。編程

2、面試題

謝飛機,小記!,簡歷上我都寫精通了,要個20K沒問題,等着吧!緩存

面試官:謝飛機,技術不錯呀,都是精通,哦,有一個vb瞭解,沒事咱們不用vbmarkdown

謝飛機:還行,我學的多,你問吧。框架

面試官:嗯,自信了很多。那咱們聊聊 Spring,你這個也寫的精通。

謝飛機:來吧!

面試官:你說,怎麼把Bean塞到Spring容器?能說說它的過程嗎,你有過相關技術的使用嗎,應用了什麼場景?

謝飛機:嗯!?嗯,,好像,沒用過。我都是精通使用API,@Resource

面試官:哦,@Resource,註解是Spring哪一個模塊提供的?

謝飛機:我,,,再見!ヾ( ̄▽ ̄)

3、代理Bean註冊到Spring容器

Bean註冊

  • 關於Bean註冊的技術場景,在咱們平常用到的技術框架中,MyBatis 是最爲常見的。經過在使用 MyBatis 時都只是定義一個接口不須要寫實現類,可是這個接口卻能夠和配置的 SQL 語句關聯,執行相應的數據庫操做時能夠返回對應的結果。那麼這個接口與數據庫的操做就用到的 Bean 的代理和註冊。
  • 咱們都知道類的調用是不能直接調用沒有實現的接口的,因此須要經過代理的方式給接口生成對應的實現類。接下來再經過把代理類放到 Spring 的 FactoryBean 的實現中,最後再把這個 FactoryBean 實現類註冊到 Spring 容器。那麼如今你的代理類就已經被註冊到 Spring 容器了,接下來就能夠經過註解的方式注入到屬性中。

按照這個實現方式,咱們來操做一下,看看一個 Bean 的註冊過程在代碼中是如何實現的。

1. 定義接口

public interface IUserDao {

    String queryUserInfo();

}
複製代碼
  • 先定義一個相似 DAO 的接口,基本這樣的接口在使用 MyBatis 時仍是很是常見的。後面咱們會對這個接口作代理和註冊。

2. 類代理實現

ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
Class<?>[] classes = {IUserDao.class};    

InvocationHandler handler = (proxy, method, args) -> "你被代理了 " + method.getName();
IUserDao userDao = (IUserDao) Proxy.newProxyInstance(classLoader, classes, handler); 

String res = userDao.queryUserInfo();
logger.info("測試結果:{}", res);
複製代碼
  • Java 自己的代理方式使用起來仍是比較簡單的,用法也很固定。
  • InvocationHandler 是個接口類,它對應的實現內容就是代理對象的具體實現。
  • 最後就是把代理交給 Proxy 建立代理對象,Proxy.newProxyInstance

3. 實現Bean工廠

public class ProxyBeanFactory implements FactoryBean {

    @Override
    public Object getObject() throws Exception {

        ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
        Class[] classes = {IUserDao.class};
        InvocationHandler handler = (proxy, method, args) -> "你被代理了 " + method.getName();

        return Proxy.newProxyInstance(classLoader, classes, handler);
    }

    @Override
    public Class<?> getObjectType() {
        return IUserDao.class;
    } 

}
複製代碼
  • FactoryBean 在 spring 起到着二當家的地位,它將近有70多個小弟(實現它的接口定義),那麼它有三個方法;
    • T getObject() throws Exception; 返回bean實例對象
    • Class<?> getObjectType(); 返回實例類類型
    • boolean isSingleton(); 判斷是否單例,單例會放到Spring容器中單實例緩存池中
  • 在這裏咱們把上面使用Java代理的對象放到了 getObject() 方法中,那麼如今再從 Spring 中獲取到的對象,就是咱們的代理對象了。

4. Bean 註冊

public class RegisterBeanFactory implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
        beanDefinition.setBeanClass(ProxyBeanFactory.class);

        BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(beanDefinition, "userDao");
        BeanDefinitionReaderUtils.registerBeanDefinition(definitionHolder, registry);
    }

}
複製代碼

在 Spring 的 Bean 管理中,全部的 Bean 最終都會被註冊到類 DefaultListableBeanFactory 中,以上這部分代碼主要的內容包括:

  • 實現 BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry方法,獲取 Bean 註冊對象。
  • 定義 Bean,GenericBeanDefinition,這裏主要設置了咱們的代理類工廠。
  • 建立 Bean 定義處理類,BeanDefinitionHolder,這裏須要的主要參數;定義 Bean 和名稱 setBeanClass(ProxyBeanFactory.class)
  • 最後將咱們本身的bean註冊到spring容器中去,registry.registerBeanDefinition()

4、測試驗證

在上面咱們已經把自定義代理的 Bean 註冊到了 Spring 容器中,接下來咱們來測試下這個代理的 Bean 被如何調用。

1. 定義 spring-config.xml

<bean id="userDao" class="org.itstack.interview.bean.RegisterBeanFactory"/>
複製代碼
  • 這裏咱們把 RegisterBeanFactory 配置到 spring 的 xml 配置中,便於啓動時加載。

2. 單元測試

@Test
public void test_IUserDao() {
    BeanFactory beanFactory = new ClassPathXmlApplicationContext("spring-config.xml");
    IUserDao userDao = beanFactory.getBean("userDao", IUserDao.class);
    String res = userDao.queryUserInfo();
    logger.info("測試結果:{}", res);
}
複製代碼

測試結果

22:53:14.759 [main] DEBUG o.s.c.e.PropertySourcesPropertyResolver - Could not find key 'spring.liveBeansView.mbeanDomain' in any property source
22:53:14.760 [main] DEBUG o.s.b.f.s.DefaultListableBeanFactory - Returning cached instance of singleton bean 'userDao'
22:53:14.796 [main] INFO  org.itstack.interview.test.ApiTest - 測試結果:你被代理了 queryUserInfo

Process finished with exit code 0
複製代碼
  • 從測試結果能夠看到,咱們已經能夠經過注入到Spring的代理Bean對象,實現咱們的預期結果。
  • 其實這個過程也是不少框架中用到的方式,尤爲是在一些中間件開發,相似的 ORM 框架都須要使用到。

5、總結

  • 本章節的內容相對來講很是並不複雜,只不過這一塊的代碼是咱們從源碼的學習中提取出來的最核心流程,由於在大部分框架中也基本都是這樣的進行處理的。若是這樣的地方不瞭解,那麼很難讀懂諸如此類的框架源碼,也很難理解它是怎麼調用的。
  • 在本文中主要涉及到的技術點包括;代理、對象、註冊,以及相應的使用。尤爲是 Bean 的定義 BeanDefinitionHolder 和 Bean 的註冊 BeanDefinitionReaderUtils.registerBeanDefinition
  • 若是你還能把此類技術聯想的更多,能夠嘗試把代理的對象替換成數據庫的查詢對象,也就是對 JDBC 的操做,當你完成之後也就實現了一個簡單的 ORM 框架。其實不少技術實現都是由小作大,但最開始的那部分是整個代碼實現的核心。

6、系列推薦

相關文章
相關標籤/搜索