聊聊基於jdk實現的spi如何與spring整合實現依賴注入

前置知識

什麼是SPI

之前有寫過一篇文章——>java之spi機制簡介(

http://t。hk。uy/K6N

)不瞭解spi的朋友,可以先查閱這篇文章瞭解下,再閱讀下文

前言

假設大家已經對SPI有一定的瞭解,有使用過JDK提供的SPI的朋友,應該會發現JDK的SPI是無法實現按需載入。那如何解決這個短板問題?

這邊提供2種思路,一種是自己實現一套SPI,另外一種在實現元件很常用的手段,就是當前元件無法滿足時,可以藉助其他元件或者再加代理層。本文實現的思路,就是利用spring的IOC,spring的ioc本質上就是一個鍵值對map,將jdk spi生成的物件注入到spring ioc容器中,間接也擁有了key——>value的對映功能

實現思路

專案啟動時,利用spi載入類並生成物件

將生成的物件注入到spring容器

在業務專案中,使用 @Autowired +

@Qualifier註解,按需引用SPI生成的bean物件

核心程式碼片段

1、spi載入實現

public Map getSpiMap(Class clz){ listServicesAndPutMapIfNecessary(clz,true); return spiMap; } private List listServicesAndPutMapIfNecessary(Class clz,boolean isNeedPutMap){ List serviceList = new ArrayList(); ServiceLoader services = ServiceLoader。load(clz); Iterator iterator = services。iterator(); while(iterator。hasNext()){ T service = iterator。next(); serviceList。add(service); setSevices2Map(isNeedPutMap, service); } return serviceList; } @SneakyThrows private void setSevices2Map(boolean isNeedPutMap, T service) { if(isNeedPutMap){ String serviceName = StringUtils。uncapitalize(service。getClass()。getSimpleName()); service = getProxyIfNecessary(service); spiMap。put(serviceName,service); } }

2、注入spring容器

public class SpiRegister implements ImportBeanDefinitionRegistrar,BeanFactoryAware { private DefaultListableBeanFactory beanFactory; @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { registerSingleton(importingClassMetadata); } private void registerSingleton(AnnotationMetadata importingClassMetadata) { Class<?> spiInterface = getSpiInterface(importingClassMetadata); if(spiInterface != null){ Map spiMap = new SpiFactory()。getSpiMap(spiInterface); if(MapUtil。isNotEmpty(spiMap)){ spiMap。forEach((beanName,bean) -> { registerSpiInterfaceSingleton(spiInterface, bean); beanFactory。registerSingleton(beanName,bean); }); } } } private void registerSpiInterfaceSingleton(Class<?> spiInterface, Object bean) { Spi spi = spiInterface。getAnnotation(Spi。class); String defalutSpiImplClassName = spi。defalutSpiImplClassName(); if(StringUtils。isBlank(defalutSpiImplClassName)){ defalutSpiImplClassName = spi。value(); } String beanName = bean。getClass()。getName(); if(bean。toString()。startsWith(SpiProxy。class。getName())){ SpiProxy spiProxy = (SpiProxy) Proxy。getInvocationHandler(bean); beanName = spiProxy。getTarget()。getClass()。getName(); } if(beanName。equals(defalutSpiImplClassName)){ String spiInterfaceBeanName = StringUtils。uncapitalize(spiInterface。getSimpleName()); beanFactory。registerSingleton(spiInterfaceBeanName,bean); } } private Class<?> getSpiInterface(AnnotationMetadata importingClassMetadata) { List basePackages = getBasePackages(importingClassMetadata); for (String basePackage : basePackages) { Reflections reflections = new Reflections(basePackage); Set> spiClasses = reflections。getTypesAnnotatedWith(Spi。class); if(!CollectionUtils。isEmpty(spiClasses)){ for (Class<?> spiClass : spiClasses) { if(spiClass。isInterface()){ return spiClass; } } } } return null; } private List getBasePackages(AnnotationMetadata importingClassMetadata) { Map enableSpi = importingClassMetadata。getAnnotationAttributes(EnableSpi。class。getName()); String[] spiBackagepackages = (String[]) enableSpi。get(“basePackages”); List basePackages = Arrays。asList(spiBackagepackages); if(CollectionUtils。isEmpty(basePackages)){ basePackages = new ArrayList<>(); basePackages。add(ClassUtils。getPackageName(importingClassMetadata。getClassName())); } return basePackages; } @Override public void setBeanFactory(BeanFactory beanFactory) throws BeansException { this。beanFactory = (DefaultListableBeanFactory)beanFactory; }}

業務專案如何使用

示例

1、定義spi服務介面

@Spipublic interface HelloService { String sayHello(String username);}

注:

@Spi用來指定哪些spi服務介面需要注入到spring 容器中,同時@Spi還有一個defalutSpiImplClassName屬性,用來指定預設注入spi實現類

2、定義具體實現類

public class HelloServiceCnImpl implements HelloService { @Override @InterceptorMethod(interceptorClasses = {HelloServiceCnInterceptor。class, HelloServiceCnOtherInterceptor。class}) public String sayHello(String username) { return “你好:” + username; }}

public class HelloServiceEnImpl implements HelloService { @Override @InterceptorMethod(interceptorClasses = HelloServiceEnInterceptor。class) public String sayHello(String username) { return “hello:” + username; }}

注:

@InterceptorMethod這個註解是用來做方法增強,和本文的關係不大,可以忽略

3、src/main/resources/下建立/META-INF/services 目錄,新增一個以介面命名的檔案

com。github。lybgeek。spi。HelloService

4、介面命名的檔案填入如下內容

com。github。lybgeek。spi。en。HelloServiceEnImpl

com。github。lybgeek。spi。cn。HelloServiceCnImpl

5、編寫業務controller

@RestController@RequestMapping(“/test”)public class SpiTestController { @SpiAutowired(“helloServiceCnImpl”) private HelloService helloService; @GetMapping(value=“/{username}”) public String sayHello(@PathVariable(“username”) String username){ return helloService。sayHello(username); }}

注:

@SpiAutowired是一個自定義註解,該註解可以看成是@Autowired + @Qualifier

6、啟動類上加@EnableSpi(basePackages = “com。github。lybgeek。spi”)

注:

basePackages用來指定掃描spi的包

7、測試

當 @SpiAutowired(“helloServiceCnImpl”)時,頁面渲染為

聊聊基於jdk實現的spi如何與spring整合實現依賴注入

當 @SpiAutowired(“helloServiceEnImpl”)時,頁面渲染為

聊聊基於jdk實現的spi如何與spring整合實現依賴注入

當指定@Autowired @Spi(“com。github。lybgeek。spi。cn。HelloServiceCnImpl”)

此時頁面渲染為

聊聊基於jdk實現的spi如何與spring整合實現依賴注入

注:

這邊沒有用@SpiAutowired,是因為@SpiAutowired需要指定名稱

總結

本文基於spi按需載入是依賴spring,在某種程度上和spring耦合,有機會的話,再講下如何實現自定義鍵值對SPI

demo連結

https://github。com/lyb-geek/springboot-learning/tree/master/springboot-spi-ioc