Front knowledge
What is the SPI
If you don’t know anything about SPI, you can read this article first and then read the following
preface
Assuming you already have some knowledge of SPI, those of you who have used the SPI provided by the JDK will find that the JDK’S SPI is not available for loading on demand. How to solve this problem?
There are two ideas, one is to implement a set of SPI, the other is a common way to implement components, that is, when the current component cannot meet, you can use other components or add a proxy layer. The implementation idea of this article is to use spring IOC, which is essentially a key-value map. The objects generated by JDK SPI are injected into spring IOC container, which indirectly has the key–>value mapping function
Implementation approach
- When the project starts, spi is used to load classes and generate objects
- Inject the generated objects into the Spring container
- In the business project, the SPI generated bean object is referenced on demand using the @Autowired + @Qualifier annotation
Core code snippet
1. Spi loading implementation
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); }}Copy the code
2. Inject 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); }}privateClass<? > getSpiInterface(AnnotationMetadata importingClassMetadata) { List<String> basePackages = getBasePackages(importingClassMetadata);for (String basePackage : basePackages) {
Reflections reflections = newReflections(basePackage); Set<Class<? >> spiClasses = reflections.getTypesAnnotatedWith(Spi.class);if(! CollectionUtils.isEmpty(spiClasses)){for(Class<? > spiClass : spiClasses) {if(spiClass.isInterface()){
returnspiClass; }}}}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; }}Copy the code
How are business items used
The sample
1. Define spi service interface
@Spi
public interface HelloService {
String sayHello(String username);
}
Copy the code
Note: @SPI specifies which Spi service interfaces to inject into the Spring container, and @SPI also has a defalutSpiImplClassName attribute that specifies the DEFAULT Spi implementation class to inject
2. Define concrete implementation classes
public class HelloServiceCnImpl implements HelloService {
@Override
@InterceptorMethod(interceptorClasses = {HelloServiceCnInterceptor.class, HelloServiceCnOtherInterceptor.class})
public String sayHello(String username) {
return "Hello."+ username; }}Copy the code
public class HelloServiceEnImpl implements HelloService {
@Override
@InterceptorMethod(interceptorClasses = HelloServiceEnInterceptor.class)
public String sayHello(String username) {
return "hello:"+ username; }}Copy the code
Note: The @interceptorMethod annotation is used for method enhancement and is not relevant to this article
SRC /main/resources/ create/meta-inf /services directory and add a new file named after the interface
com.github.lybgeek.spi.HelloService
Copy the code
4. Enter the following information in the interface name file
com.github.lybgeek.spi.en.HelloServiceEnImpl
Copy the code
com.github.lybgeek.spi.cn.HelloServiceCnImpl
Copy the code
5. Write the service controller
@RestController
@RequestMapping("/test")
public class SpiTestController {
@SpiAutowired("helloServiceCnImpl")
private HelloService helloService;
@GetMapping(value="/{username}")
public String sayHello(@PathVariable("username") String username){
returnhelloService.sayHello(username); }}Copy the code
Note: @spiautowired is a custom annotation that can be thought of as @autowired + @Qualifier
@enablespi (basePackages = “com.github. Lybgeek.spi “)
Note: basePackages is used to specify the packages to scan spi
7, test,
- When @SpiAutowired(“helloServiceCnImpl”), the page renders as
- When @SpiAutowired(“helloServiceEnImpl”), the page renders as
- When specifying the @autowired @ Spi (” com. Making. Lybgeek. Spi. Cn. HelloServiceCnImpl “)
The page is rendered as
Note:I don’t use @spiautowired here because @Spiautowired needs to specify a name
conclusion
This article relies on Spring for on-demand loading based on SPI, and to some extent coupling with Spring. If there is an opportunity, how to implement custom key-value pairs of SPI
The demo link
Github.com/lyb-geek/sp…