Spring框架中ImportBeanDefinitionRegistrar的應(yīng)用詳解
前言
我們講過如果一個(gè)類實(shí)現(xiàn)了ImportSelector接口,并且在配置類中被@Import加入到Spring容器中以后。
Spring容器就會把ImportSelector接口方法返回的字符串?dāng)?shù)組中的類new出來對象然后放到工廠中去。
并且做了一個(gè)功能開關(guān)的例子輔助講解其功能。這次我們就接著上次講解ImportSelector接口的內(nèi)容繼續(xù)擴(kuò)展講解ImportBeanDefinitionRegistrar的用法。
ImportBeanDefinitionRegistrar
按照慣例我們還是先介紹一下這個(gè)接口里面最重要的方法:registerBeanDefinitions。
public interface ImportBeanDefinitionRegistrar { //雖然是倆方法,但是等于一個(gè)方法 default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) { //直接調(diào)用了下面的方法 registerBeanDefinitions(importingClassMetadata, registry); } default void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { } }
里面一共兩個(gè)方法而且都被default注解了,可見確實(shí)用的不多。但是這個(gè)方法卻擁有ImportSelector接口內(nèi)方法的一切功能,而且更強(qiáng)大。這倆方法是重載方法,區(qū)別就在于有沒有BeanNameGenerator,這個(gè)接口是Spring內(nèi)置的BeanName生成器,無關(guān)大雅。但是注意到第一個(gè)方法其實(shí)是調(diào)用了第二個(gè)方法去實(shí)現(xiàn)的,可以說方法一是一個(gè)擴(kuò)展,也可以說方法一等于方法二。那就直接解析參數(shù)。
- 第一個(gè)參數(shù)AnnotationMetadata importingClassMetadata:這個(gè)參數(shù)和ImportSelector中的一樣,可以拿到被@Import注解過的類的元數(shù)據(jù),具體到例子就是筆者一直寫的配置類AppConfig.class。因?yàn)橐膊淮蛩氵M(jìn)行修改,所以這個(gè)不多說。
- 第二個(gè)參數(shù)BeanDefinitionRegistry registry:這個(gè)參數(shù)厲害了。BeanDefinitionRegistry這個(gè)接口我們以前說過,Spring想要把一個(gè)類變成對象就一定會把這個(gè)類變成一個(gè)BeanDefinition對象。這個(gè)過程怎么來的呢?就是通過實(shí)現(xiàn)BeanDefinitionRegistry接口類的構(gòu)造方法做的。
Spring把在方法中把這個(gè)接口開放出來,就意味著我們可以在這里手動添加一個(gè)BeanDefinition給Spring容器,然后構(gòu)建對象出來。通過registry我們就可以注冊一個(gè)BeanDefinition進(jìn)入Spring容器,就使用下面的這個(gè)方法:
registry.registerBeanDefinition(String beanName, BeanDefinition beanDefinition)
ImportBeanDefinitionRegistrar例子
按照常規(guī)我們先把必要的類都先創(chuàng)建出來。一個(gè)業(yè)務(wù)接口ImportTestDao,一個(gè)業(yè)務(wù)類依賴該接口ImportTestService,一個(gè)配置類AppConfig,一個(gè)測試類Test,以及一個(gè)實(shí)現(xiàn)了ImportBeanDefinitionRegistrar的類MyImportDBR。
public interface ImportTestDao { public void query(); }
@Component public class ImportTestService { @Autowired ImportTestDao importTestDao; public void find(){ System.out.println("ImportTestService importTestDao.query()"); importTestDao.query(); } }
@ComponentScan("com.demo") public class AppConfig { }
public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext anno= new AnnotationConfigApplicationContext(AppConfig.class); ImportTestDao importTestDao= (ImportTestDao) anno.getBean("importTestDao"); importTestDao.query(); } }
public class MyImportDBR implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { } }
要完成MyImportDBR最重要的就是實(shí)現(xiàn)里面的方法。所以先分析一下如何使用這個(gè)方法:首先看參數(shù)需要一個(gè)beanName和一個(gè)beanDefinition,那么第一步就是需要得到要注冊的bean的beanDefinition。Spring也給我們提供了相應(yīng)的類BeanDefinitionBuilder。那么最終這個(gè)類構(gòu)造成這個(gè)樣子:
public class MyImportDBR implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //得到BD,掃描接口 BeanDefinitionBuilder builder= BeanDefinitionBuilder.genericBeanDefinition(ImportTestDao.class); GenericBeanDefinition beanDefinition= (GenericBeanDefinition) builder.getBeanDefinition(); beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName()); registry.registerBeanDefinition("importTestDao",beanDefinition); } }
但是這里有一個(gè)問題:因?yàn)槲覀冞@里雖然把ImportTestDao.class寫在這里了,想要構(gòu)建一個(gè)BeanDefinition。但是我們沒有辦法使用,因?yàn)镮mportTestDao是一個(gè)接口, Spring沒有辦法給你new一個(gè)出來,也就是說沒有辦法實(shí)例化。那么怎么辦呢?我們知道這里用的不應(yīng)該是一個(gè)類,而是應(yīng)該是ImportTestDao這個(gè)接口的一個(gè)代理類。所以要怎么樣才能獲取到這個(gè)代理呢?這需要提起來我們很早以前就提到的一個(gè)知識點(diǎn)FactoryBean。那我們就需要構(gòu)造這么一個(gè)FactoryBean,以及為了構(gòu)造一個(gè)代理對象還需要一個(gè)InvocationHandler。
public class MyfactoryBean implements FactoryBean { private Class clazz; public MyfactoryBean(Class clazz) { this.clazz=clazz; } @Override public Object getObject() throws Exception { Class[] clazzes=new Class[]{this.clazz}; Object proxy= Proxy.newProxyInstance(this.getClass().getClassLoader(),clazzes,new MyInvocation()); return proxy; } @Override public Class<?> getObjectType() { return this.clazz; } @Override public boolean isSingleton() { return false; } }
public class MyInvocation implements InvocationHandler { public MyInvocation() { } @Override public Object invoke(Object proxy, Method method, Object[] args) { System.out.println("This is a proxy of ImportTestDao"); return null; } }
然后把這個(gè)FactoryBean加入進(jìn)去。
public class MyImportDBR implements ImportBeanDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { //得到BD,掃描接口,這里筆者寫死了,但是其實(shí)可以做一個(gè)包掃描,把某一個(gè)包下的所有類都掃描進(jìn)來,就像Mybatis的@MapperScan注解一樣 BeanDefinitionBuilder builder= BeanDefinitionBuilder.genericBeanDefinition(ImportTestDao.class); //拿到一個(gè)BeanDefinition, 這里使用一個(gè)其中一個(gè)子類來接收,代表這里構(gòu)建的就是一個(gè)普通的BeanDefinition GenericBeanDefinition beanDefinition= (GenericBeanDefinition) builder.getBeanDefinition(); //這里打印是因?yàn)楣P者當(dāng)時(shí)不確定類名要不要包含包名 System.out.println(beanDefinition.getBeanClassName()); //給我們的beanDefinition添加一個(gè)構(gòu)造方法,并且傳入我們需要的bean名字 beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName()); //把代理對象給賦予給BeanDefinition beanDefinition.setBeanClass(MyfactoryBean.class); //這里的名字可以隨便取,這里是隨著Spring名規(guī)范取的。話說這個(gè)接口的另一個(gè)方法有BeanNameGenerator這個(gè)參數(shù)就是讓大家自由發(fā)揮的 registry.registerBeanDefinition("importTestDao",beanDefinition); } }
為了更有逼格一些,我們把這個(gè)ImportBeanDefinitionRegistrar封裝成為一個(gè)注解MyScaner,這個(gè)是仿照@MapperScan的
@Retention(RetentionPolicy.RUNTIME) @Import(MyImportDBR.class) public @interface MyScaner { }
直接加載到AppConfig類上。
@ComponentScan("com.demo") @MyScaner public class AppConfig { } 運(yùn)行,拿到結(jié)果: This is a proxy of ImportTestDao query
這樣就完成了從外部直接加載一個(gè)BeanDefinition到Spring容器的過程。但是小伙伴們看到這里一定會覺得:你這一頓操作猛如虎,一看戰(zhàn)績0比5。搞這么多有個(gè)毛線用???
ImportBeanDefinitionRegistrar作用
看起來筆者的例子卵用沒有,但是仔細(xì)想想,大家經(jīng)常使用的Mybatis是不是就是這個(gè)原理?我特意把Mybatis官網(wǎng)的代碼調(diào)出來,想必大家都配置過。
<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" /> <property name="sqlSessionFactory" ref="sqlSessionFactory" /> </bean>
一般的博客會說,這個(gè)是把我們自己定義的UserMapper注冊給MapperFactoryBean,看到這里的同學(xué),筆者可以明確的告訴你,這個(gè)解釋是錯的。我們要想轉(zhuǎn)換UserMapper成為MapperFactoryBean,你就必須顯式的告訴Spring他們之間的關(guān)系。然后Spring拿到UserMapper接口傳入MapperFactoryBean,再由MapperFactoryBean動態(tài)產(chǎn)生UserMapper的代理對象,然后你程序里使用的一直都是這個(gè)代理對象,這一切的原理就是我們寫的MyfactoryBean的一系列操作。
為了驗(yàn)證我的說法咱們?nèi)ybatis的MapperFactoryBean的源碼看下,是不是和我們寫的基本上解構(gòu)一樣。
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> { //通過這個(gè)接口反射代理對象,就是我們例子中的clazz private Class<T> mapperInterface; public MapperFactoryBean() { // intentionally empty } public MapperFactoryBean(Class<T> mapperInterface) { this.mapperInterface = mapperInterface; } ...無關(guān),略... /** * {@inheritDoc} */ @Override public T getObject() throws Exception { //通過傳入接口反射得到代理對象,如果沒有接口你就沒有辦法獲取代理對象 //這個(gè)接口怎么拿到,就是通過上面的xml配置的 return getSqlSession().getMapper(this.mapperInterface); } /** * {@inheritDoc} */ @Override public Class<T> getObjectType() { return this.mapperInterface; } /** * {@inheritDoc} */ @Override public boolean isSingleton() { return true; } // ...無關(guān),略... }
所以我們配置的這個(gè)Mybatis的xml是干嘛的呢?就是為了讓MapperFactoryBean生成我們需要的(UserMapper)代理對象而已,根本就不是什么注冊。
到此這篇關(guān)于Spring框架中ImportBeanDefinitionRegistrar的應(yīng)用詳解的文章就介紹到這了,更多相關(guān)ImportBeanDefinitionRegistrar應(yīng)用內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
TransmittableThreadLocal通過javaAgent實(shí)現(xiàn)線程傳遞并支持ForkJoin
這篇文章主要介紹了TransmittableThreadLocal通過javaAgent實(shí)現(xiàn)線程傳遞并支持ForkJoin詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-06-06Myeclipse工程發(fā)布時(shí)端口占用問題的解決方法
這篇文章主要介紹了Myeclipse工程發(fā)布時(shí)端口占用問題的解決方法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-12-12maven中resource配置的實(shí)現(xiàn)示例
我們在使用Maven組件來構(gòu)建項(xiàng)目的時(shí)候,通常將配置文件放在資源文件目錄下,本文主要介紹了maven中resource配置的實(shí)現(xiàn)示例,具有一定的參考價(jià)值,感興趣的可以了解一下2023-09-09Java+EasyExcel實(shí)現(xiàn)文件的導(dǎo)入導(dǎo)出
在項(xiàng)目中我們常常需要Excel文件的導(dǎo)入與導(dǎo)出,手動輸入相對有些繁瑣,所以本文教大家如何在Java中輕松導(dǎo)入與導(dǎo)出Excel文件,感興趣的可以學(xué)習(xí)一下2021-12-12spring boot 統(tǒng)一JSON格式的接口返回結(jié)果的實(shí)現(xiàn)
這篇文章主要介紹了spring boot 統(tǒng)一JSON格式的接口返回結(jié)果的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-10-10Mybatis注解方式完成輸入?yún)?shù)為list的SQL語句拼接方式
這篇文章主要介紹了Mybatis注解方式完成輸入?yún)?shù)為list的SQL語句拼接方式,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-11-11Gradle jvm插件系列教程之Java?Library插件權(quán)威詳解
這篇文章主要介紹了Java?Library插件權(quán)威詳解,本文通過示例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01