Series Catalog
What is spi 01-spi?Getting Started
spi 02-spi battle resolution slf4j package conflict problem
spi 03-spi jdk for source code parsing
spi 04-spi dubbo for source code resolution
spi 05-dubbo adaptive extension adaptive expansion
spi 06 - Implement SPI framework from scratch
spi 07-Automatically generate SPI profile implementation
review
In the last section, we implemented a simple version of the SPI by ourselves.
Together, we implement a tool similar to google auto.
Use Demo
Class implementation
- Say.java
Define interfaces
@SPI public interface Say { void say(); }
- SayBad.java
@SPIAuto("bad") public class SayBad implements Say { @Override public void say() { System.out.println("bad"); } }
- SayGood.java
@SPIAuto("good") public class SayGood implements Say { @Override public void say() { System.out.println("good"); } }
Execution effect
After executing mvn clean install.
Automatically generate files under META-INF/services/folderCom.github.houbb.Spi.bs.spi.Say
The contents are as follows:
good=com.github.houbb.spi.bs.spi.impl.SayGood bad=com.github.houbb.spi.bs.spi.impl.SayBad
code implementation
This section is mainly used for compile-time annotations, which are relatively difficult.
All sources are open source at lombok-ex
Annotation Definition
@Retention(RetentionPolicy.SOURCE) @Target({ElementType.TYPE}) @Documented public @interface SPIAuto { /** * Alias * @return Alias * @since 0.1.0 */ String value() default ""; /** * Target Folder * @return Folder * @since 0.1.0 */ String dir() default "META-INF/services/"; }
In fact, here dir() can not be exposed, here later want to do more flexible expansion, so tentatively.
Core implementation
@SupportedAnnotationTypes("com.github.houbb.lombok.ex.annotation.SPIAuto") @SupportedSourceVersion(SourceVersion.RELEASE_7) public class SPIAutoProcessor extends BaseClassProcessor { @Override public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) { java.util.List<LClass> classList = super.getClassList(roundEnv, getAnnotationClass()); Map<String, Set<String>> spiClassMap = new HashMap<>(); for (LClass lClass : classList) { String spiClassName = getSpiClassName(lClass); String fullName = lClass.classSymbol().fullname.toString(); if(StringUtil.isEmpty(spiClassName)) { throw new LombokExException("@SPI class not found for class: " + fullName); } Pair<String, String> aliasAndDirPair = getAliasAndDir(lClass); String newLine = aliasAndDirPair.getValueOne()+"="+fullName; // Full path: folder + interface name String filePath = aliasAndDirPair.getValueTwo()+spiClassName; Set<String> lineSet = spiClassMap.get(filePath); if(lineSet == null) { lineSet = new HashSet<>(); } lineSet.add(newLine); spiClassMap.put(filePath, lineSet); } // Generate Files generateNewFiles(spiClassMap); return true; } }
Overall process:
(1) Traverse through all classes to find classes with SPIAuto annotations
(2) Grouping all classes into SPI interfaces based on class information and annotation information and storing them in map
(3) Generate the corresponding profile information based on the information in the map.
Get SPI interface method name
Get all the interfaces of the current class, and find the first interface to return labeled with @SPI.
/** * Get the corresponding spi class * @param lClass Class Information * @return Result * @since 0.1.0 */ private String getSpiClassName(final LClass lClass) { List<Type> typeList = lClass.classSymbol().getInterfaces(); if(null == typeList || typeList.isEmpty()) { return ""; } // Get the value for the comment SPIAuto auto = lClass.classSymbol().getAnnotation(SPIAuto.class); for(Type type : typeList) { Symbol.ClassSymbol tsym = (Symbol.ClassSymbol) type.tsym; //TOOD: Add an extension here later. if(tsym.getAnnotation(SPI.class) != null) { return tsym.fullname.toString(); } } return ""; }
Get annotation information
Notes are designed to be more flexible and relatively simple, as follows:
Aliases for classes default to lowercase class names, similar to spring.
private Pair<String, String> getAliasAndDir(LClass lClass) { // Get the value for the comment SPIAuto auto = lClass.classSymbol().getAnnotation(SPIAuto.class); //1. Alias String fullClassName = lClass.classSymbol().fullname.toString(); String simpleClassName = fullClassName.substring(fullClassName.lastIndexOf(".")); String alias = auto.value(); if(StringUtil.isEmpty(alias)) { alias = StringUtil.firstToLowerCase(simpleClassName); } return Pair.of(alias, auto.dir()); }
Generate Files
Generating files is the core hunger part, referring primarily to google's auto Realization:
In fact, the main difficulty lies in the path acquisition of the file, which is more cumbersome in compile-time annotations, resulting in redundant code writing.
/** * Create a new file * key: File Path * value: Corresponding Content Information * @param spiClassMap Target File Path * @since 0.1.0 */ private void generateNewFiles(Map<String, Set<String>> spiClassMap) { Filer filer = processingEnv.getFiler(); for(Map.Entry<String, Set<String>> entry : spiClassMap.entrySet()) { String fullFilePath = entry.getKey(); Set<String> newLines = entry.getValue(); try { // would like to be able to print the full path // before we attempt to get the resource in case the behavior // of filer.getResource does change to match the spec, but there's // no good way to resolve CLASS_OUTPUT without first getting a resource. FileObject existingFile = filer.getResource(StandardLocation.CLASS_OUTPUT, "",fullFilePath); System.out.println("Looking for existing resource file at " + existingFile.toUri()); Set<String> oldLines = readServiceFile(existingFile.openInputStream()); System.out.println("Looking for existing resource file set " + oldLines); // Write in newLines.addAll(oldLines); writeServiceFile(newLines, existingFile.openOutputStream()); return; } catch (IOException e) { // According to the javadoc, Filer.getResource throws an exception // if the file doesn't already exist. In practice this doesn't // appear to be the case. Filer.getResource will happily return a // FileObject that refers to a non-existent file but will throw // IOException if you try to open an input stream for it. // File does not exist System.out.println("Resources file not exists."); } try { FileObject newFile = filer.createResource(StandardLocation.CLASS_OUTPUT, "", fullFilePath); try(OutputStream outputStream = newFile.openOutputStream();) { writeServiceFile(newLines, outputStream); System.out.println("Write into file "+newFile.toUri()); } catch (IOException e) { throw new LombokExException(e); } } catch (IOException e) { throw new LombokExException(e); } } }
Other
That's the whole idea, and there are some details that won't expand here.
Welcome to github lombok-ex.
If it helps you, give a star a boost to the author~
A Reflection on Progress
As part of the framework, ecology is primarily intended to facilitate users.
This tool is actually more flexible, such as automatically generating spi profiles for Dubbo spis.