AbstractProcessor应用--自动生成 openfeign 的Fallback

我们在使用 spring cloud 的时候,在使用微服务 openfeign 调用的时候;一般都要写 fallback 的降级逻辑,但是在实现这一块基本都是统一的逻辑,要么是统一抛出异常;要么返回空值;这样的逻辑比较单一,但是又必须要实现,写起来比较麻烦。

1. 抽象 openfeign 的接口

这里我们不引用 openfeign 的依赖,自己写一个jar包,模拟 openfeign 的定义;如下图:
AbstractProcessor应用--自动生成 openfeign 的Fallback_第1张图片

首先是 FeignClient 注解,模拟 openfeign 的注解:

package com.feign.api;

import java.lang.annotation.*;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface FeignClient {

    String value() default "";

    Class<?> fallbackFactory() default void.class;

}

然后是 FallbackFactory.java,模拟 openfeignFallbackFactory 的降级策略:

package com.feign.api;

public interface FallbackFactory<T> {

    T create(Throwable var1);

}

最后是 ProcessException.java,定义所有的降级的方法抛出异常:

package com.feign.api;

public class ProcessException extends RuntimeException {

    public ProcessException(String msg) {
        super(msg);
    }
}

2. 完成 AbstractProcessor 工具

先看一下目录接口:
AbstractProcessor应用--自动生成 openfeign 的Fallback_第2张图片

首先我们我们要引入 tools.jar,此包在我们安装 java 目录的 lib/tools.jar,如下:

<dependency>
    <groupId>com.sungroupId>
    <artifactId>toolsartifactId>
    <version>1.8version>
    <scope>systemscope>
    <systemPath>JAVA_HOME/lib/tools.jarsystemPath>
dependency>

其次定义 FallbackClassInfo.java,用于存储要生成的 FallbackFactory 类结构:

package com.feign.fallback.auto;

import java.util.List;

public class FallbackClassInfo {

    /**
     * 所在包名
     */
    private String pg;

    /**
     * 需要引入的包
     */
    private List<String> ipts;

    /**
     * 创建的类名称
     */
    private String className;

    /**
     * 泛型的名称
     */
    private String genericName;

    /**
     * 方法列表
     */
    private List<String> methods;
	
	// Getter and Setter

然后完成 AutoFallbackProcessor 工具,如下:

package com.feign.fallback.auto;

import com.feign.api.FallbackFactory;
import com.feign.api.FeignClient;
import com.sun.source.util.TreePath;
import com.sun.tools.javac.api.JavacTrees;
import com.sun.tools.javac.processing.JavacProcessingEnvironment;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.TreeTranslator;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Names;

import javax.annotation.processing.*;
import javax.lang.model.SourceVersion;
import javax.lang.model.element.Element;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.TypeElement;
import javax.tools.JavaFileObject;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;

@SupportedAnnotationTypes(value = {"com.feign.api.FeignClient"})
@SupportedSourceVersion(SourceVersion.RELEASE_8)
public class AutoFallbackProcessor extends AbstractProcessor {

    /**
     * 文件系统
     */
    private Filer filer;

    /**
     * java AST 编译时代码树
     */
    private JavacTrees javacTrees;

    /**
     * 对 java AST 操作的工具
     */
    private TreeMaker treeMaker;

    /**
     * 符号封装类,统一处理名称
     */
    private Names names;

    /**
     * 打印信息
     */
    private Messager messager;

    /**
     * 初始化编译时操作对象
     * @param processingEnv
     */
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        javacTrees = JavacTrees.instance(processingEnv);
        final Context context = ((JavacProcessingEnvironment) processingEnv).getContext();
        treeMaker = TreeMaker.instance(context);
        names = Names.instance(context);
        messager = processingEnv.getMessager();
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        final Set<? extends Element> dataAnnotations = roundEnv.getElementsAnnotatedWith(FeignClient.class);
        for (Element element : dataAnnotations) {
            FeignClient feignClient = element.getAnnotation(FeignClient.class);
            String feignClientString = feignClient.toString();
            if (feignClientString.contains("fallbackFactory=void")) {
                addAnnotationProperty(element);
            }
        }
        return true;
    }

    /**
     * 为注解添加 fallbackFactory 属性
     * @param element   节点
     */
    public void addAnnotationProperty(Element element) {
        // 创建类
        FallbackClassInfo classInfo = resolving(element);
        createSourceClass(classInfo);

        // 创建引入
        TreePath treePath = javacTrees.getPath(element);
        JCTree.JCCompilationUnit jccu = (JCTree.JCCompilationUnit) treePath.getCompilationUnit();
        JCTree.JCImport anImport = treeMaker.Import(
                treeMaker.Select(
                        treeMaker.Ident(names.fromString(classInfo.getPg())),
                        names.fromString(classInfo.getClassName())
                ), false);
        jccu.defs = jccu.defs.prepend(anImport);

        // 重构注解
        jccu.accept(new TreeTranslator() {
            @Override
            public void visitAnnotation(JCTree.JCAnnotation jcAnnotation) {
                super.visitAnnotation(jcAnnotation);
                jcAnnotation.args = jcAnnotation.args.append(
                        treeMaker.Assign(
                                treeMaker.Ident(names.fromString("fallbackFactory")),
                                treeMaker.Select(
                                        treeMaker.Ident(names.fromString(classInfo.getClassName())),
                                        names.fromString("class")
                                )
                        )
                );
            }
        });
    }

    /**
     * 生成 fallbackFactory 类
     * @param classInfo     FallbackFactory 类配置信息
     */
    public void createSourceClass(FallbackClassInfo classInfo) {
        StringBuilder builder = new StringBuilder()
                .append("package " + classInfo.getPg() + ";\n\n");
        for (String ipt : classInfo.getIpts()) {
           builder.append("import " + ipt + ";\n");
        }
        builder.append("\n")
                .append("public class " + classInfo.getClassName() + " implements FallbackFactory<" + classInfo.getGenericName() + "> { \n\n")
                .append("\tpublic " + classInfo.getGenericName() + " create(Throwable var1) {\n")
                .append("\t\treturn new " + classInfo.getGenericName() + "() {\n");
        for (String method : classInfo.getMethods()) {
            builder.append(method + "\n");
        }
        builder.append("};\n")
                .append("\t}\n")
                .append("}\n");
        try {
            JavaFileObject source = filer.createSourceFile(classInfo.getPg() + "." + classInfo.getClassName());
            Writer writer = source.openWriter();
            writer.write(builder.toString());
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public FallbackClassInfo resolving(Element element) {
        TreePath treePath = javacTrees.getPath(element);
        JCTree.JCCompilationUnit jccu = (JCTree.JCCompilationUnit) treePath.getCompilationUnit();

        String className = element.getSimpleName().toString();
        String packageName = element.toString().replaceAll("." + className, "");

        FallbackClassInfo classInfo = new FallbackClassInfo();
        classInfo.setPg(packageName + ".fallback");
        classInfo.setClassName(className + "FallbackFactory");
        classInfo.setGenericName(className);
        List<String> ipts = new ArrayList<>();
        classInfo.setIpts(ipts);
        List<String> methods = new ArrayList<>();
        classInfo.setMethods(methods);

        // 格式化引入包名
        ipts.add("com.feign.api.FallbackFactory");
        ipts.add("com.feign.api.ProcessException");
        ipts.add(packageName + "." + className);

        JCTree.JCClassDecl classDecl = null;
        for (JCTree pTree : jccu.defs) {
            if (pTree instanceof JCTree.JCImport) {
                ipts.add(((JCTree.JCImport) pTree).getQualifiedIdentifier().toString());
            } else if (pTree instanceof JCTree.JCClassDecl){
                classDecl = (JCTree.JCClassDecl) pTree;
            }
        }
        if (classDecl == null) {
            return null;
        }
        for (JCTree cTree : classDecl.defs) {
            if (!(cTree instanceof JCTree.JCMethodDecl)) {
                continue;
            }
            JCTree.JCMethodDecl methodDecl = (JCTree.JCMethodDecl) cTree;
            Set<String> set = methodDecl.getModifiers().getFlags().stream().map(Modifier::toString).collect(Collectors.toSet());
            if(set.contains("static") || set.contains("default")) {
                continue;
            }
            String method = "\t\t\tpublic " + methodDecl.getReturnType() + " " + methodDecl.getName() + "(" + methodDecl.getParameters() + ") {\n"
                    + "\t\t\t\tthrow new ProcessException(\"系统异常~~\");\n"
                    + "\t\t\t}\n";
            methods.add(method);
        }
        return classInfo;
    }
}

最后记得 resource 文件夹下的 META-INFO/services/javax.annotation.processing.ProcessorSPI 文件:

com.feign.fallback.auto.AutoFallbackProcessor

然后,我们创建一个新的项目,引入上面两个依赖;然后定义一个接口,然后添加上 FeignClient 注解,不加 fallbackFactory 属性,如下:
AbstractProcessor应用--自动生成 openfeign 的Fallback_第3张图片
然后进行编译,得到如下结果:
AbstractProcessor应用--自动生成 openfeign 的Fallback_第4张图片

你可能感兴趣的:(java,spring,boot,开发语言)