자바 SPI 07-SPI 프로필 자동 생 성 방법

7744 단어 spi
계열 목록
spi 01-pi 가 뭐 예요?입문 사용
spi 02-pi 의 실전 해결 slf4j 패키지 충돌 문제
spi 03-pi jdk 원본 코드 분석 실현
spi 04-pi dubbo 원본 코드 분석 실현
spi 05-dubbo adaptive extension 적응 확장
spi 06-자필 로 SPI 프레임 워 크 구현
spi 07-SPI 프로필 자동 생 성 방식
돌이켜보다
지난 절 에 우 리 는 스스로 간단 한 버 전의 SPI 를 실현 했다.
이 절 에서 우 리 는 함께 구 글 auto 와 유사 한 도 구 를 실현 합 니 다.
데모 사용
클래스 실현
  • Say.java

  • 정의 인터페이스
    @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");
        }
    
    }

    실행 효과
    집행mvn clean install후.META-INF/services/폴 더 에서 파일 자동 생 성com.github.houbb.spi.bs.spi.Say내용 은 다음 과 같다.
    good=com.github.houbb.spi.bs.spi.impl.SayGood
    bad=com.github.houbb.spi.bs.spi.impl.SayBad

    코드 구현
    이 부분 은 주로 컴 파일 할 때 주 해 를 사용 하 는데 난이도 가 상대 적 으로 높다.
    모든 원본 코드 가 lombok-ex 에서 시작 되 었 습 니 다.
    주해 정의
    @Retention(RetentionPolicy.SOURCE)
    @Target({ElementType.TYPE})
    @Documented
    public @interface SPIAuto {
    
        /**
         *   
         * @return   
         * @since 0.1.0
         */
        String value() default "";
    
        /**
         *      
         * @return    
         * @since 0.1.0
         */
        String dir() default "META-INF/services/";
    
    }

    사실 여기 dir()는 노출 을 하지 않 아 도 되 고 여기 후기 에 더욱 유연 한 확장 을 하고 싶 어서 잠 정적 으로 이렇게 정 했다.
    핵심 실현
    @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 classList = super.getClassList(roundEnv, getAnnotationClass());
            Map> 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 aliasAndDirPair = getAliasAndDir(lClass);
                String newLine = aliasAndDirPair.getValueOne()+"="+fullName;
    
                //      :   +   
                String filePath = aliasAndDirPair.getValueTwo()+spiClassName;
    
                Set lineSet = spiClassMap.get(filePath);
                if(lineSet == null) {
                    lineSet = new HashSet<>();
                }
                lineSet.add(newLine);
                spiClassMap.put(filePath, lineSet);
            }
    
            //     
            generateNewFiles(spiClassMap);
    
            return true;
        }
    }

    전체 흐름:
    (1)모든 종 류 를 옮 겨 다 니 며SPIAuto주석 이 있 는 종 류 를 찾 습 니 다.
    (2)클래스 정보,주해 정보 에 따라 모든 클래스 를 SPI 인터페이스 로 나 누 어 map 에 저장 합 니 다.
    (3)맵 의 정보 에 따라 해당 하 는 프로필 정 보 를 생 성 합 니 다.
    SPI 인터페이스 방법 이름 가 져 오기
    현재 클래스 의 모든 인 터 페 이 스 를 가 져 오고 첫 번 째 로@SPI표 시 된 인 터 페 이 스 를 찾 아 되 돌려 줍 니 다.
    /**
     *       spi  
     * @param lClass    
     * @return   
     * @since 0.1.0
     */
    private String getSpiClassName(final LClass lClass) {
        List typeList =  lClass.classSymbol().getInterfaces();
        if(null == typeList || typeList.isEmpty()) {
            return "";
        }
        //         
        SPIAuto auto = lClass.classSymbol().getAnnotation(SPIAuto.class);
        for(Type type : typeList) {
            Symbol.ClassSymbol tsym = (Symbol.ClassSymbol) type.tsym;
            //TOOD:           。
            if(tsym.getAnnotation(SPI.class) != null) {
                return tsym.fullname.toString();
            }
        }
        return "";
    }

    주석 정보 가 져 오기
    주 해 는 주로 더욱 유연 하 게 지정 하고 상대 적 으로 간단 하 며 다음 과 같이 실현 하기 위해 서 이다.
    클래스 에 대한 별명 은 기본적으로 클래스 이니셜 소문 자로 spring 과 유사 합 니 다.
    private Pair getAliasAndDir(LClass lClass) {
        //         
        SPIAuto auto = lClass.classSymbol().getAnnotation(SPIAuto.class);
        //1.   
        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());
    }

    파일 생 성
    파일 생 성 은 가장 핵심 적 인 배 고 픔 을 실현 하 는 부분 입 니 다.주로 구 글 의 auto 실현 을 참고 합 니 다.
    사실 주요 난점 은 파일 의 경 로 를 가 져 오 는 것 입 니 다.이 점 은 컴 파일 할 때 주석 이 번 거 로 워 서 코드 가 많이 쓰 입 니 다.
    /**
     *       
     * key:     
     * value:        
     * @param spiClassMap       
     * @since 0.1.0
     */
    private void generateNewFiles(Map> spiClassMap) {
        Filer filer = processingEnv.getFiler();
        for(Map.Entry> entry : spiClassMap.entrySet()) {
            String fullFilePath = entry.getKey();
            Set 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 oldLines = readServiceFile(existingFile.openInputStream());
                System.out.println("Looking for existing resource file set " + oldLines);
                //   
                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.
                //          
                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);
            }
        }
    }

    기타
    전체적인 사고방식 은 바로 이렇다.그리고 일부 세부 사항 은 여기 서 더 이상 전개 되 지 않 는 다.
    github lombok-ex 에 오신 것 을 환영 합 니 다.
    도움 이 된다 면 스타 에 게 작가 님 을 격려 해 주세요~
    진보 적 사고
    생태 는 구조의 일부분 으로서 주로 사용자 에 게 편 의 를 제공 하기 위 한 것 이다.
    실제로 이 도 구 는 더 유연 하 게 만 들 수 있 습 니 다.예 를 들 어 dubbo spi 에 spi 설정 파일 을 자동 으로 생 성 할 수 있 습 니 다.
    참고 자료
    AutoServiceProcessor

    좋은 웹페이지 즐겨찾기