How spi implemented based on jdk integrates with spring to achieve dependency injection
Pre-knowledge
What is SPI
I have written an article before -> [Introduction to java spi mechanism] Friends who don’t know spi can check this article first to understand, and then read the following
Preface
Assuming that you already have a certain understanding of SPI, friends who have used the SPI provided by the JDK should find that the SPI of the JDK cannot be loaded on demand. How to solve this shortcoming problem?
Here are two ways of thinking, one is to implement a set of SPI by yourself, and the other is a very common way to implement components, that is, when the current component cannot be satisfied, you can use other components or add a proxy layer. The idea of this article is to use spring's IOC, spring's ioc is essentially a key-value pair map, injecting the objects generated by jdk spi into the spring ioc container, and indirectly also has the key->value mapping function
Realization ideas
When the project starts, use spi to load classes and generate objects
Inject the generated object into the spring container
In business projects, use @Autowired +
@Qualifier annotations to reference bean objects generated by SPI on demand
Core code snippet
1.Implementation of spi loading
public Map<String,T> getSpiMap(Class<T> clz){
listServicesAndPutMapIfNecessary(clz,true);
return spiMap;
}
private List<T> listServicesAndPutMapIfNecessary(Class<T> clz,boolean isNeedPutMap){
List<T> serviceList = new ArrayList();
ServiceLoader<T> services = ServiceLoader.load(clz);
Iterator<T> 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.Inject into the spring container
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<String,?> 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<String> basePackages = getBasePackages(importingClassMetadata);
for (String basePackage : basePackages) {
Reflections reflections = new Reflections(basePackage);
Set<Class<?>> spiClasses = reflections.getTypesAnnotatedWith(Spi.class);
if(!CollectionUtils.isEmpty(spiClasses)){
for (Class<?> spiClass : spiClasses) {
if(spiClass.isInterface()){
return spiClass;
}
}
}
}
return null;
}
private List<String> getBasePackages(AnnotationMetadata importingClassMetadata) {
Map<String, Object> enableSpi = importingClassMetadata.getAnnotationAttributes(EnableSpi.class.getName());
String[] spiBackagepackages = (String[]) enableSpi.get("basePackages");
List<String> 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;
}}
How to use business items
Example
1.Define the spi service interface
@Spipublic interface HelloService {
String sayHello(String username);
}
Note: @Spi is used to specify which spi service interfaces need to be injected into the spring container, and @Spi also has a defalutSpiImplClassName attribute, which is used to specify the default injection spi implementation class
2.Define the specific implementation class
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;
}}
Note: The annotation @InterceptorMethod is used for method enhancement, which has little to do with this article and can be ignored
3.Create a /META-INF/services directory under src/main/resources/ and add a file named after the interface
com.github.lybgeek.spi.HelloService
4.Fill in the file named interface with the following content
com.github.lybgeek.spi.en.HelloServiceEnImpl
com.github.lybgeek.spi.cn.HelloServiceCnImpl
5.Write business 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);
}}
Note: @SpiAutowired is a custom annotation, which can be regarded as @Autowired + @Qualifier
6.Add @EnableSpi(basePackages = "com.github.lybgeek.spi") to the startup class
Note: basePackages is used to specify the package for scanning spi
7.Test
When @SpiAutowired("helloServiceCnImpl"), the page is rendered as
When @SpiAutowired("helloServiceEnImpl"), the page is rendered as
当指定@Autowired @Spi(“com.github.lybgeek.spi.cn.HelloServiceCnImpl”)
At this point the page is rendered as
Note: @SpiAutowired is not used here, because @SpiAutowired needs to specify the name
Summarize
This article is based on the spi on-demand loading is dependent on spring, to some extent coupled with spring, if there is a chance, let me talk about how to implement a custom key-value pair SPI
0 Comments