当前位置: 首页 > 后端技术 > Java

说说基于jdk的spi如何与spring集成实现依赖注入

时间:2023-04-01 23:44:01 Java

前置知识什么是SPI?之前写过一篇文章-->Java的spi机制。不懂spi的朋友可以先看看这篇文章。明白了,再看下面的前言。假设大家对SPI都有一定的了解,用过JDK提供的SPI的朋友应该会发现JDK的SPI是不能按需加载的。那么如何解决这个短板问题呢?这里有两种思路,一种是自己实现一套SPI,另一种是很常见的实现组件的方法,即当当前组件不能满足要求时,可以使用其他组件或者增加代理层.这篇文章的思路是利用spring的IOC。spring的IOC本质上是一个key-valuepairmap,将jdkspi生成的对象注入到springioc容器中,间接具有key-->value映射的功能。项目启动时,使用spi加载类,生成对象。将生成的对象注入到spring容器中。在业务项目中,根据需要使用@Autowired+@Qualifier注解来引用SPI生成的bean对象。核心代码片段1.spi加载实现publicMap}privateListlistServicesAndPutMapIfNecessary(Classclz,booleanisNeedPutMap){ListserviceList=newArrayList();ServiceLoaderservices=ServiceLoader.load(clz);Iteratoriterator=services.iterator();while(iterator.hasNext()){Tservice=iterator.next();serviceList.add(服务);setSevices2Map(isNeedPutMap,服务);}返回服务列表;}@SneakyThrowsprivatevoidsetSevices2Map(booleanisNeedPutMap,Tservice){if(isNeedPutMap){字符串服务名称=StringUtils.uncapitalize(service.getClass().getSimpleName());服务=getProxyIfNecessary(服务);spiMap.put(服务名称,服务);}}2、注册spring容器publicclassSpiRegisterimplementsImportBeanDefinitionRegistrar,BeanFactoryAware{privateDefaultListableBeanFactorybeanFactory;@OverridepublicvoidregisterBeanDefinitions(AnnotationMetadataimportingClassMetadata,BeanDefinitionRegistryregistry){registerSingleton(importingClassMetadata);}privatevoidregisterSingleton(AnnotationMetadataimportingClassMetadata){ClassspiInterface=getSpiInterface(importingClassMetadata);if(spiInterface!=null){MapspiMap=newSpiFactory().getSpiMap(spiInterface);if(MapUtil.isNotEmpty(spiMap)){spiMap.forEach((beanName,bean)->{registerSpiInterfaceSingleton(spiInterface,bean);beanFactory.registerSingleton(beanName,bean);});}}}privatevoidregisterSpiInterfaceSingleton(ClassspiInterface,Objectbean){Spispi=spiInterface.getAnnotation(Spi.class);字符串defalutSpiImplClassName=spi.defalutSpiImplClassName();if(StringUtils.isBlank(defalutSpiImplClassName)){defalutSpiImplClassName=spi.value();}StringbeanName=bean.getClass().getName();if(bean.toString().startsWith(SpiProxy.class.getName())){SpiProxyspiProxy=(SpiProxy)Proxy.getInvocationHandler(bean);beanName=spiProxy.getTarget().getClass().getName();}if(beanName.equals(defalutSpiImplClassName)){StringspiInterfaceBeanName=StringUtils.uncapitalize(spiInterface.getSimpleName());beanFactory.registerSingleton(spiInterfaceBeanName,bean);}}私有类getSpiInterface(AnnotationMetadataimportingClassMetadata){ListbasePackages=getBasePackages(importingClassMetadata);对于(StringbasePackage:basePackages){Reflectionsreflections=newReflections(basePackage);设置<类>spiClasses=reflections.getTypesAnnotatedWith(Spi.class);if(!CollectionUtils.isEmpty(spiClasses)){for(ClassspiClass:spiClasses){if(spiClass.isInterface()){返回spiClass;}}}}返回空;}privateListgetBasePackages(AnnotationMetadataimportingClassMetadata){MapenableSpi=importingClassMetadata.getAnnotationAttributes(EnableSpi.class.getName());String[]spiBackagepackages=(String[])enableSpi.get("basePackages");列表basePackages=Arrays.asList(spiBackagepackages);如果(CollectionUtils.isEmpty(basePackages)){basePackages=newArrayList<>();basePackages.add(ClassUtils.getPackageName(importingClassMetadata.getClassName()));}返回基础包;}@OverridepublicvoidsetBeanFactory(BeanFactorybeanFactory)throwsBeansException{this.beanFactory=(DefaultListableBeanFactory)beanFactory;}}业务项目使用示例1.定义spi服务接口@SpipublicinterfaceHelloService{StringsayHello(Stringusername);}注:@Spi用于指定需要将哪些spi服务接口注入到spring容器中。同时@Spi还有一个defaultSpiImplClassName属性,用于指定默认注入的spi实现类2.定义具体实现类.class})publicStringsayHello(Stringusername){return"Hello:"+username;}}公共类HelloServiceEnImpl实现HelloService{@Override@InterceptorMethod(interceptorClasses=HelloServiceEnInterceptor.class)publicStringsayHello(Stringusername){return"hello:"+username;}}注:@InterceptorMethod用于方法增强,与本文关系不大。忽略3.在src/main/resources/下创建一个/META-INF/services目录,添加一个以接口com.github.lybgeek.spi.HelloService4命名的文件。以接口命名的文件填写如下:com.github.lybgeek.spi.en.HelloServiceEnImplcom.github.lybgeek.spi.cn.HelloServiceCnImpl5,写入businesscontroller@RestController@RequestMapping("/test")publicclassSpiTestController{@SpiAutowired("helloServiceCnImpl")privateHelloServicehelloService;@GetMapping(value="/{username}")publicStringsayHello(@PathVariable("username")Stringusername){returnhelloService.sayHello(username);}}注意:@SpiAutowired是自定义注解,可以看做是@Autowired+@Qualifier6。在启动类中添加@EnableSpi(basePackages="com.github.lybgeek.spi")。注:basePackages用于指定扫描spi的包。7、测试@SpiAutowired("helloServiceCnImpl")时,页面渲染为当前@SpiAutowired("helloServiceEnImpl"),页面渲染为指定@Autowired@Spi("com.github.lybgeek.spi.cn.HelloServiceCnImpl")时,页面渲染为注意:这里没有使用@SpiAutowired,因为@SpiAutowired需要指定一个名字总结一下,本文是基于依赖spring的spi按需加载,在一定程度上耦合了spring,有机会再说说如何实现自定义键值对.SPIdemo链接https://github.com/lyb-geek/springboot-learning/tree/master/springboot-spi-ioc