在Spring中如何注入動(dòng)態(tài)代理Bean
在Spring中注入動(dòng)態(tài)代理Bean
在Springboot中我們可以通過(guò)內(nèi)置的注解如@Service
,@Component
,@Repository
來(lái)注冊(cè)bean,也可以在配置類中通過(guò)@Bean
來(lái)注冊(cè)bean。這些都是Spring內(nèi)置的注解。
除此之外,還可以用@WebFilter
,@WebServlet
,@WebListener
注解結(jié)合@ServletComponentScan
自動(dòng)注冊(cè)Bean。但這里的@WebFilter
,@WebServlet
,@WebListener
并不是Spring的注解,而是Servlet 3+ 的注解。為什么這些注解的類能自動(dòng)注冊(cè)為Spring的Bean,其實(shí)現(xiàn)原理是什么呢?
如果進(jìn)入@ServletComponentScan
中查看可以發(fā)現(xiàn),該注解上有另外一個(gè)注解:@Import(ServletComponentScanRegistrar.class)
,進(jìn)一步查看可知:class ServletComponentScanRegistrar implements ImportBeanDefinitionRegistrar
。這里的關(guān)鍵就是ImportBeanDefinitionRegistrar
接口。
ImportBeanDefinitionRegistrar
Spring中最經(jīng)典的設(shè)計(jì)就是AOP和IOC,使得Spring框架具有良好的擴(kuò)展性,而ImportBeanDefinitionRegistrar
就是其中用來(lái)擴(kuò)展的hook之一。
通常情況下,Spring中的bean就是通過(guò)XML配置文件,Spring中的注解或配置類來(lái)注冊(cè)的。但有時(shí)候,可能需要在運(yùn)行時(shí)根據(jù)某些條件動(dòng)態(tài)地注冊(cè)一些bean,這時(shí)就可以使用ImporterBeanDefinitionRegistrar
接口來(lái)實(shí)現(xiàn)此功能。
具體來(lái)說(shuō),實(shí)現(xiàn)了ImporterBeanDefinitionRegistrar
接口的類可以在@Importer
注解中被引入,Spring在初始化容器時(shí)會(huì)調(diào)用這個(gè)實(shí)現(xiàn)類的regisgterBeanDefinitions
方法,以便在運(yùn)行時(shí)根據(jù)需要需要注冊(cè)一些額外的bean。
這個(gè)接口通常用于一些高級(jí)的場(chǎng)景,比如根據(jù)運(yùn)行時(shí)環(huán)境來(lái)動(dòng)態(tài)的注冊(cè)不同的bean,或者根據(jù)某些外部配置來(lái)決定是否注冊(cè)某些bean等。通過(guò)這種方式使得Spring應(yīng)用程序的配置更加靈活和動(dòng)態(tài)化。
動(dòng)態(tài)注冊(cè)Bean
下面通過(guò)ImportBeanDefinitionRegistrar
來(lái)動(dòng)態(tài)注冊(cè)Bean。
首先將@ServletComponentScan
抄過(guò)來(lái)改一下名字:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({MetaAutoConfigureRegistrar.class}) public @interface MetaComponentScan { @AliasFor("basePackages") String[] value() default {}; @AliasFor("value") String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; }
然后實(shí)現(xiàn)自定義注冊(cè)器:
public class MetaComponentScanRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { private ResourceLoader resourceLoader; private Environment environment; @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Override public void registerBeanDefinitions(AnnotationMetadata metaData, BeanDefinitionRegistry registry) { MetaBeanDefinitionScanner scanner = new MetaBeanDefinitionScanner(registry, this.environment, this.resourceLoader); Set<String> packagesToScan = this.getBasePackages(metaData); scanner.scan(packagesToScan.toArray(new String[]{})); } private static class MetaBeanDefinitionScanner extends ClassPathBeanDefinitionScanner { public MetaBeanDefinitionScanner(BeanDefinitionRegistry registry, Environment environment, ResourceLoader resourceLoader) { super(registry, false, environment, resourceLoader); registerFilters(); } protected void registerFilters() { addIncludeFilter(new AnnotationTypeFilter(Meta.class)); } } private Set<String> getBasePackages(AnnotationMetadata metadata) { AnnotationAttributes attributes = AnnotationAttributes .fromMap(metadata.getAnnotationAttributes(MetaComponentScan.class.getName())); String[] basePackages = attributes.getStringArray("basePackages"); Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses"); Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages)); for (Class<?> basePackageClass : basePackageClasses) { packagesToScan.add(ClassUtils.getPackageName(basePackageClass)); } if (packagesToScan.isEmpty()) { packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName())); } return packagesToScan; } }
自定義注冊(cè)器必須實(shí)現(xiàn)ImportBeanDefinitionRegistrar
, ResourceLoaderAware
, EnvironmentAware
這三個(gè)接口,然后覆寫(xiě)registerBeanDefinitions
方法,該方法在Spring容器初始化的時(shí)候被調(diào)用。
在該方法中,需要一個(gè)掃描器,該掃描器中有一個(gè)過(guò)濾器,用于過(guò)濾自定義的注解類。因此,需要一個(gè)自定義注解:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Meta { }
所有使用該注解的類都將被掃描器掃到并注冊(cè)為bean。掃描時(shí)需要知道要掃描的路徑,通過(guò)getBasePackages
方法獲取。最后調(diào)用ClassPathBeanDefinitionScanner
的scan方法來(lái)掃描和注冊(cè)bean,這部分是Spring中的固有實(shí)現(xiàn)。
現(xiàn)在來(lái)創(chuàng)建一個(gè)通過(guò)@Meta
注解的類,看一下是否被自動(dòng)注冊(cè)為bean:
@Meta public class DemoBean { public DemoBean() { System.out.println("DemoBean register!"); } }
啟動(dòng)SpringBootApplication,會(huì)發(fā)現(xiàn)控制臺(tái)日志中有如下輸出:
DemoBean register!
表明確實(shí)調(diào)用了DemoBean
的構(gòu)造方法,自動(dòng)注冊(cè)了一個(gè)bean。
注入動(dòng)態(tài)代理bean
如果不是在第三方框架中,正常情況下,普通的類完全沒(méi)必要自定義注冊(cè),直接用Spring內(nèi)置的注解如@Component
即可。
那使用自定義注解來(lái)動(dòng)態(tài)注冊(cè)Spring中的bean還有什么使用場(chǎng)景呢?
Mapper注入原理
如果了解Feign
或者mybatis的Mapper
應(yīng)該知道,在通過(guò)feign調(diào)用遠(yuǎn)程接口或者通過(guò)mapper訪問(wèn)數(shù)據(jù)庫(kù)時(shí),是不需要實(shí)現(xiàn)類的,而是直接通過(guò)接口進(jìn)行調(diào)用的。
下面以Mapper
為例(mapper-spring:4.3.0)看下是如何實(shí)現(xiàn)的。
同樣的,首先需要在Springboot的啟動(dòng)類上加上注解@MapperScan
,該注解中通過(guò)@Importer
引入了MapperScannerRegistrar
,而這個(gè)注冊(cè)器實(shí)現(xiàn)了ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware
接口,并覆寫(xiě)了registerBeanDefinitions
方法。在該方法中,調(diào)用了ClassPathBeanDefinitionScanner
的子類ClassPathMapperScanner
的doScan
方法來(lái)對(duì)符合條件的包進(jìn)行掃描并注冊(cè)bean,其代碼如下:
@Override public Set<BeanDefinitionHolder> doScan(String... basePackages) { Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages); if (beanDefinitions.isEmpty()) { logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration."); } else { processBeanDefinitions(beanDefinitions); } return beanDefinitions; }
可以看到,該方法首先調(diào)用了父類的doScan
方法,也就是Spring類ClassPathBeanDefinitionScanner
中的doScan方法,通過(guò)BeanDefinitionReaderUtils
來(lái)注冊(cè)bean,代碼如下:
public static void registerBeanDefinition( BeanDefinitionHolder definitionHolder, BeanDefinitionRegistry registry) throws BeanDefinitionStoreException { // Register bean definition under primary name. String beanName = definitionHolder.getBeanName(); registry.registerBeanDefinition(beanName, definitionHolder.getBeanDefinition()); // Register aliases for bean name, if any. String[] aliases = definitionHolder.getAliases(); if (aliases != null) { for (String alias : aliases) { registry.registerAlias(beanName, alias); } } }
reigstry
有三個(gè)實(shí)現(xiàn),這里主要看DefaultListableBeanFactory
,在該類的registerBeanDefinition
方法里,從beanDefinitionMap
中根據(jù)beanName來(lái)獲取beanDefinition
,如果不存在,就將自定義的beanDefinition
放到beanDefinitionMap
中。
調(diào)用完父類的doScan
方法之后,接下來(lái)調(diào)用processBeanDefinitions
方法對(duì)beanDefinitions
進(jìn)行處理。在該方法中,將beanClass
由mapper
接口類變成了MapperFactoryBean
,而MapperFactoryBean
實(shí)現(xiàn)了FactoryBean
接口。這將使得最終生成的bean為代理對(duì)象。
當(dāng)Spring容器啟動(dòng)時(shí),它會(huì)掃描應(yīng)用程序中的所有Bean定義,并實(shí)例化那些需要實(shí)例化的Bean。如果遇到實(shí)現(xiàn)了FactoryBean接口的Bean定義,Spring將會(huì)為該Bean創(chuàng)建一個(gè)特殊的代理對(duì)象,以便在需要時(shí)調(diào)用FactoryBean的方法來(lái)創(chuàng)建實(shí)際的Bean實(shí)例。
當(dāng)需要使用由FactoryBean創(chuàng)建的Bean時(shí),Spring將會(huì)調(diào)用代理對(duì)象的getObject()方法來(lái)獲取實(shí)際的Bean實(shí)例。有需要的話,Spring還會(huì)調(diào)用代理對(duì)象的getObjectType()方法來(lái)確定實(shí)際Bean實(shí)例的類型。
如果FactoryBean創(chuàng)建的Bean是單例模式,那么Spring將在第一次調(diào)用getObject()方法時(shí)創(chuàng)建實(shí)例,并將其緩存起來(lái)。以后每次調(diào)用getObject()方法時(shí),都會(huì)返回同一個(gè)實(shí)例。如果FactoryBean創(chuàng)建的Bean不是單例模式,則每次調(diào)用getObject()方法時(shí)都會(huì)創(chuàng)建一個(gè)新的實(shí)例。
至此,Mapper
接口注入到Spring中的過(guò)程就比較清晰了。
自定義注入
下面仿照Mapper的實(shí)現(xiàn)原理來(lái)自定義注解和代理工廠,實(shí)現(xiàn)自定義注入動(dòng)態(tài)代理Bean。
同樣地,先定義基礎(chǔ)注解,通過(guò)該注解引入Registrar:
@Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented @Import({MapperScanRegistrar.class}) public @interface MapperScan { @AliasFor("basePackages") String[] value() default {}; @AliasFor("value") String[] basePackages() default {}; Class<?>[] basePackageClasses() default {}; } @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) @Documented public @interface Mapper { } public class MapperScanRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware, EnvironmentAware { private ResourceLoader resourceLoader; private Environment environment; @Override public void setEnvironment(Environment environment) { this.environment = environment; } @Override public void setResourceLoader(ResourceLoader resourceLoader) { this.resourceLoader = resourceLoader; } @Override public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) { ClassPathScanningCandidateComponentProvider scanner = new ClassPathScanningCandidateComponentProvider(false, environment) { @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { return beanDefinition.getMetadata().isIndependent() && !beanDefinition.getMetadata().isAnnotation(); } }; scanner.setResourceLoader(this.resourceLoader); scanner.addIncludeFilter(new AnnotationTypeFilter(Mapper.class)); Set<String> basePackages = getBasePackages(metadata); for (String pkg : basePackages) { Set<BeanDefinition> beanDefinitions = scanner.findCandidateComponents(pkg); for (BeanDefinition candidate : beanDefinitions) { if (candidate instanceof AnnotatedBeanDefinition annotatedBeanDefinition) { AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata(); String className = annotationMetadata.getClassName(); Class<?> beanClass = ClassUtils.resolveClassName(className, ClassUtils.getDefaultClassLoader()); String beanName = ClassUtils.getShortName(className); BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder .genericBeanDefinition(MapperBeanFactory.class) .addPropertyValue("type", beanClass); registry.registerBeanDefinition(beanName, definitionBuilder.getBeanDefinition()); } } } } private Set<String> getBasePackages(AnnotationMetadata metadata) { AnnotationAttributes attributes = AnnotationAttributes .fromMap(metadata.getAnnotationAttributes(MapperScan.class.getName())); String[] basePackages = attributes.getStringArray("basePackages"); Class<?>[] basePackageClasses = attributes.getClassArray("basePackageClasses"); Set<String> packagesToScan = new LinkedHashSet<>(Arrays.asList(basePackages)); for (Class<?> basePackageClass : basePackageClasses) { packagesToScan.add(ClassUtils.getPackageName(basePackageClass)); } if (packagesToScan.isEmpty()) { packagesToScan.add(ClassUtils.getPackageName(metadata.getClassName())); } return packagesToScan; } }
這里的注冊(cè)邏輯是重點(diǎn)。
其中Scanner
不是繼承自ClassPathBeanDefinitionScanner
的,而是與其同級(jí)的,需要覆寫(xiě)isCandidateComponent
方法。ClassPathBeanDefinitionScanner
是直接用于掃描Bean并注冊(cè)的類,它繼承了ClassPathScanningCandidateComponentProvider
,并添加了注冊(cè)Bean定義的功能。
而ClassPathScanningCandidateComponentProvider
是掃描候選組件的provider,它負(fù)責(zé)識(shí)別符合條件的類,但不負(fù)責(zé)注冊(cè)這些類。換句話說(shuō),注冊(cè)Bean定義的功能需要自己實(shí)現(xiàn)。
注冊(cè)Bean定義的代碼如下:
if (candidate instanceof AnnotatedBeanDefinition annotatedBeanDefinition) { AnnotationMetadata annotationMetadata = annotatedBeanDefinition.getMetadata(); String className = annotationMetadata.getClassName(); Class<?> beanClass = ClassUtils.resolveClassName(className, ClassUtils.getDefaultClassLoader()); String beanName = ClassUtils.getShortName(className); BeanDefinitionBuilder definitionBuilder = BeanDefinitionBuilder .genericBeanDefinition(MapperBeanFactory.class) .addPropertyValue("type", beanClass); registry.registerBeanDefinition(beanName, definitionBuilder.getBeanDefinition()); }
先獲取bean定義的元數(shù)據(jù),這其中包含bean的類名,可以借此通過(guò)反射來(lái)獲取類對(duì)象。
然后更新bean定義,主要是更新beanClass,將其由原始的接口類更改為MapperBeanFactory
。同時(shí),還添加了一個(gè)type
字段,值為原始的接口類。這樣實(shí)例化bean時(shí)就能生成代理對(duì)象了,且代理對(duì)象的類型為接口類。
最終看下MapperBeanFactory的實(shí)現(xiàn):
public class MapperBeanFactory<T> implements FactoryBean<T> { private Class<T> type; public MapperBeanFactory() { } public MapperBeanFactory(Class<T> type) { this.type = type; } @Override public Class<T> getObjectType() { return type; } @Override public T getObject() { return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, (proxy, method, args) -> { System.out.printf("Class %s, execute %s method, parameters=%s%n", method.getDeclaringClass().getName(), method.getName(), args[0]); return switch (method.getName()) { case "sayHello" -> "hello, " + args[0]; case "sayHi" -> "hi, " + args[0]; default -> "hello, world!"; }; }); } public void setType(Class<T> type) { this.type = type; } }
這里的setType方法是必須的,添加的"type"屬性就是通過(guò)此set方法設(shè)置進(jìn)來(lái)的。getObject
方法用于生成實(shí)際的代理對(duì)象,具體是由Proxy.newProxyInstance
來(lái)生成的。該方法需要三個(gè)參數(shù),分別是: 代理類的加載器,代理類要實(shí)現(xiàn)的接口列表,代理類handler(InvocationHandler接口的實(shí)現(xiàn)類)。其中,第三個(gè)參數(shù)是一個(gè)匿名類對(duì)象(這里用lambda表達(dá)式進(jìn)行了簡(jiǎn)化),該匿名類實(shí)現(xiàn)了InvocationHandler
接口,并覆寫(xiě)了invoke
代理方法。在代理方法中,根據(jù)原始調(diào)用方法的不同返回不同的值。
接下來(lái)看一下Mapper注解的接口和接口controller:
@Mapper public interface UserMapper { String sayHello(String userName); String sayHi(String userName); } @RestController @RequestMapping("/sample") public class HelloController { @Resource private UserMapper userMapper; @RequestMapping("/hello") public String sayHello(@RequestParam String userName) { return userMapper.sayHello(userName); } @RequestMapping("/hi") public String sayHi(@RequestParam String userName) { return userMapper.sayHi(userName); } }
當(dāng)系統(tǒng)啟動(dòng)后,訪問(wèn)http://localhost:8080/sample/hello?userName=test
和http://localhost:8080/sample/hi?userName=test
會(huì)返回不同的結(jié)果。
這里UserMapper接口中的方法并沒(méi)有實(shí)現(xiàn),真正的實(shí)現(xiàn)邏輯是在代理方法中根據(jù)方法名做的。
可以做一下合理的推測(cè),除了Mapper之外,Spring Data JPA中的接口訪問(wèn)數(shù)據(jù)庫(kù)的具體邏輯,也是在代理方法中實(shí)現(xiàn)的。
總結(jié)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java實(shí)例化一個(gè)抽象類對(duì)象的方法教程
大家都知道抽象類無(wú)法實(shí)例化,就無(wú)法創(chuàng)建對(duì)象。所以下面這篇文章主要給大家介紹了關(guān)于Java實(shí)例化一個(gè)抽象類對(duì)象的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友可以參考借鑒,下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-12-12SpringBoot項(xiàng)目微信云托管入門(mén)部署實(shí)踐
本文主要介紹了SpringBoot項(xiàng)目微信云托管入門(mén)部署實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-03-03Spring中FactoryBean的高級(jí)用法實(shí)戰(zhàn)教程
FactoryBean是Spring框架的高級(jí)特性,允許自定義對(duì)象的創(chuàng)建過(guò)程,適用于復(fù)雜初始化邏輯,本文給大家介紹Spring中FactoryBean的高級(jí)用法實(shí)戰(zhàn),感興趣的朋友跟隨小編一起看看吧2024-09-09Spring boot定時(shí)任務(wù)的原理及動(dòng)態(tài)創(chuàng)建詳解
這篇文章主要給大家介紹了關(guān)于Spring boot定時(shí)任務(wù)的原理及動(dòng)態(tài)創(chuàng)建的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2019-03-03SpringMVC實(shí)現(xiàn)用戶登錄全過(guò)程
這篇文章主要介紹了SpringMVC實(shí)現(xiàn)用戶登錄全過(guò)程,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-09-09JAVA PDF操作之實(shí)現(xiàn)截取N頁(yè)和多個(gè)PDF合并
這篇文章主要為大家詳細(xì)介紹了java關(guān)于PDF的一些操作,例如截取N頁(yè)并生成新文件,轉(zhuǎn)圖片以及多個(gè)PDF合并,文中的示例代碼講解詳細(xì),感興趣的可以了解下2025-01-01MyBatis動(dòng)態(tài)<if>標(biāo)簽的使用
本文主要介紹了MyBatis動(dòng)態(tài)<if>標(biāo)簽的使用,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2023-05-05java實(shí)現(xiàn)科研信息管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了java科研信息管理系統(tǒng),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-02-02