作者:小傅哥 博客:https://
沉淀、分享、成長,讓自己和他人都能有所收獲!😄
一、前言
小傅哥,你是怎么學(xué)習(xí)的?
有很多初學(xué)編程或者碼了幾年CRUD磚的小伙伴問我,該怎么學(xué)編程?感覺什么都不會怎么辦?感覺目前的公司沒有核心業(yè)務(wù)學(xué)到不東西呀!
其實我可能和很大一部分的粉絲讀者都有類似的經(jīng)歷,在傳統(tǒng)類似外包的行業(yè)待過、從C#語言兩年開發(fā)再面Java崗、新到互聯(lián)網(wǎng)職場感覺太多不會的技術(shù)棧等等。
但可能最讓我在學(xué)習(xí)編程上受益的就是不斷的折騰這些技術(shù):
關(guān)于外包 :在外包2年時還是C#開發(fā),時而搞搞中繼器、IO板卡、PLC。但我仍舊喜歡大學(xué)時期學(xué)的Java語言,那么每天5:30下班回家后,就不斷的用Java語言把公司接觸到的C#工程做翻新。差不多1年的時間,把幾乎我接觸到的項目翻新了個遍,就是那個時候知道的Java還能做串口通信,還是蠻有意思的。關(guān)于場景 :其實很多程序員在一個相對較小的公司時,學(xué)習(xí)的最大瓶頸是眼界問題,不知道有什么技術(shù)、不知道有什么場景,更不知道自己不會啥。其實很多時候這都跟懶
有關(guān)系,公司是沒有這樣的場景,但是你可以看博客、看論壇、看視頻,加各類技術(shù)群。如果遇到哪些發(fā)廣告的就退了,哪些好的留下,認識一些人脈,相知一些基友,這在個過程總能有所收獲,你會隨著時間的推移嗅到各類技術(shù)棧、項目、經(jīng)驗、心得、面試等等,當你武裝好了自己,再出去面試也就沒那么難了。關(guān)于開始 :時間少、要學(xué)的多,感覺自己就是一把小鐵鍬
,要去挖蘇伊士運河,不知道能從哪開始。這個時候建議不要盲目的收藏幾個T的資料和視頻,先打開xmind,選個好看的主題,開始梳理自己的技術(shù)棧,看看自己會什么不會什么,在從這些不會的內(nèi)容里選出你最想學(xué)的,把要學(xué)的內(nèi)容在梳理出相應(yīng)的資料庫。好,那么這個時候你就可以開始了,記住開始是從一點點深入的,不要總想著一口吃個胖子。
方向?qū)α?#xff0c;快是最大的障礙!
,很多時候只要你能平心靜氣日積月累的學(xué)習(xí),其實就沒有什么不能克服的問題。編程里又有什么非常難的東西嗎,大部分知識都是不知道就不會而已,知道了就很簡單。
二、面試題
謝飛機,小記!
,簡歷上我都寫精通了,要個20K沒問題,等著吧!
面試官 :謝飛機,技術(shù)不錯呀,都是精通,哦,有一個vb了解,沒事我們不用vb
謝飛機 :還行,我學(xué)的多,你問吧。
面試官 :嗯,自信了不少。那我們聊聊 Spring,你這個也寫的精通。
謝飛機 :來吧!
面試官 :你說,怎么把Bean塞到Spring容器?能說說它的過程嗎,你有過相關(guān)技術(shù)的使用嗎,應(yīng)用了什么場景?
謝飛機 :嗯!?嗯,,好像,沒用過。我都是精通使用API,@Resource
面試官 :哦,@Resource,注解是Spring哪個模塊提供的?
謝飛機 :我,,,再見!ヾ( ̄▽ ̄)
三、代理Bean注冊到Spring容器
關(guān)于Bean注冊的技術(shù)場景,在我們?nèi)粘S玫降募夹g(shù)框架中,MyBatis 是最為常見的。通過在使用 MyBatis 時都只是定義一個接口不需要寫實現(xiàn)類,但是這個接口卻可以和配置的 SQL 語句關(guān)聯(lián),執(zhí)行相應(yīng)的數(shù)據(jù)庫操作時可以返回對應(yīng)的結(jié)果。那么這個接口與數(shù)據(jù)庫的操作就用到的 Bean 的代理和注冊。 我們都知道類的調(diào)用是不能直接調(diào)用沒有實現(xiàn)的接口的,所以需要通過代理的方式給接口生成對應(yīng)的實現(xiàn)類。接下來再通過把代理類放到 Spring 的 FactoryBean 的實現(xiàn)中,最后再把這個 FactoryBean 實現(xiàn)類注冊到 Spring 容器。那么現(xiàn)在你的代理類就已經(jīng)被注冊到 Spring 容器了,接下來就可以通過注解的方式注入到屬性中。
按照這個實現(xiàn)方式,我們來操作一下,看看一個 Bean 的注冊過程在代碼中是如何實現(xiàn)的。
1. 定義接口
public interface IUserDao {
String queryUserInfo ( ) ;
}
先定義一個類似 DAO 的接口,基本這樣的接口在使用 MyBatis 時還是非常常見的。后面我們會對這個接口做代理和注冊。
2. 類代理實現(xiàn)
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 ( "測試結(jié)果:{}" , res) ;
Java 本身的代理方式使用起來還是比較簡單的,用法也很固定。 InvocationHandler 是個接口類,它對應(yīng)的實現(xiàn)內(nèi)容就是代理對象的具體實現(xiàn)。 最后就是把代理交給 Proxy 創(chuàng)建代理對象,Proxy.newProxyInstance
。
3. 實現(xiàn)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多個小弟(實現(xiàn)它的接口定義),那么它有三個方法;
T getObject() throws Exception; 返回bean實例對象 Class<?> getObjectType(); 返回實例類類型 boolean isSingleton(); 判斷是否單例,單例會放到Spring容器中單實例緩存池中 在這里我們把上面使用Java代理的對象放到了 getObject() 方法中,那么現(xiàn)在再從 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 中,以上這部分代碼主要的內(nèi)容包括:
實現(xiàn) BeanDefinitionRegistryPostProcessor.postProcessBeanDefinitionRegistry方法,獲取 Bean 注冊對象。 定義 Bean,GenericBeanDefinition,這里主要設(shè)置了我們的代理類工廠。 創(chuàng)建 Bean 定義處理類,BeanDefinitionHolder,這里需要的主要參數(shù);定義 Bean 和名稱 setBeanClass(ProxyBeanFactory.class)
。 最后將我們自己的bean注冊到spring容器中去,registry.registerBeanDefinition()
四、測試驗證
在上面我們已經(jīng)把自定義代理的 Bean 注冊到了 Spring 容器中,接下來我們來測試下這個代理的 Bean 被如何調(diào)用。
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 ( "測試結(jié)果:{}" , res) ;
}
測試結(jié)果
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 - 測試結(jié)果:你被代理了 queryUserInfo
Process finished with exit code 0
從測試結(jié)果可以看到,我們已經(jīng)可以通過注入到Spring的代理Bean對象,實現(xiàn)我們的預(yù)期結(jié)果。 其實這個過程也是很多框架中用到的方式,尤其是在一些中間件開發(fā),類似的 ORM 框架都需要使用到。
五、總結(jié)
本章節(jié)的內(nèi)容相對來說非常并不復(fù)雜,只不過這一塊的代碼是我們從源碼的學(xué)習(xí)中提取出來的最核心流程,因為在大部分框架中也基本都是這樣的進行處理的。如果這樣的地方不了解,那么很難讀懂諸如此類的框架源碼,也很難理解它是怎么調(diào)用的。 在本文中主要涉及到的技術(shù)點包括;代理、對象、注冊,以及相應(yīng)的使用。尤其是 Bean 的定義 BeanDefinitionHolder
和 Bean 的注冊 BeanDefinitionReaderUtils.registerBeanDefinition
。 如果你還能把此類技術(shù)聯(lián)想的更多,可以嘗試把代理的對象替換成數(shù)據(jù)庫的查詢對象,也就是對 JDBC 的操作,當你完成以后也就實現(xiàn)了一個簡單的 ORM 框架。其實很多技術(shù)實現(xiàn)都是由小做大,但最開始的那部分是整個代碼實現(xiàn)的核心。
六、系列推薦
認知自己的技術(shù)棧盲區(qū) LinkedList插入速度比ArrayList快?你確定嗎? 除了JDK、CGLIB,還有3種類代理方式?面試又卡住! ReentrantLock之AQS原理分析和實踐使用 咋嘞?你的IDEA過期了吧!加個Jar包就破解了,為什么?