Java 是传统意义上的静态语言,严格的类型限制和原始结构的保护,相应的IDE 也有严格的语法控制,并不像Ruby, Python 等动态语言的完全开放,这也就限制了Java 的扩展性,尤其针对纯技术型Framework 的开发的限制非常明显。目前,大都数技术型框架都是通过动态代理(Dynamic Proxy) 的方式实现技术逻辑的封装,例如:Cglib、ASM等,均为运行时动态生成字节码的形式进行逻辑封装,这样的封装会带来几个问题:

  1. 没有意义的接口定义:Java 中的Interface 是一种对(可变)依赖的抽象和封装,是面向对象设计中的重要概念,但动态代理时,接口转变为依赖的描述,丧失的抽象和封装的意义,反而演变为一种新的编程模型,将简单的问题复杂化。
  2. 增加了堆栈深度:由于动态代理是在运行期动态生成字节码,如果业务代码出现异常,通过堆栈信息很难找出根源,定位故障的成本极高。Sping 的核心技术就是动态代理,新手在使用SpringBoot 时,出现异常后总是无从下手,堆栈信息毫无价值。
  3. 代码逻辑复杂:有经验的程序员在调试SpingBoot 的源码时就会发现,其中的逻辑会让人极度崩溃,根本就不知道到底运行的实例的构造过程,也完全不符合Spring 创始人最初的愿景。

JDK 1.5 版本发布了JSR 269 Pluggable Annotation Processing API规范,其实也就是在javac 的过程中允许根据自定义的Anntation 生成代码,其原理就是通过API 干预Java 编译的语法树,可以在原有的Class 中的增加字段和方法,也可以修改原有语法树中的元素。

<dependency>
    <groupId>com.sun</groupId>
    <artifactId>tools</artifactId>
    <version>1.0</version>
    <scope>system</scope>
    <systemPath>${java.home}/../lib/tools.jar</systemPath>
    <optional>true</optional>
</dependency>

ObjectiveSQL 中的使用的方法、字段和内部类生成为例,详细介绍Java 是如何动态生成代码。

1)DomainModel 注解定义

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

2) AbstractProcessor 扩展,用于响应自定义Annotation 处理逻辑

import org.mangosdk.spi.ProviderFor;
import javax.annotation.processing.Processor;
import javax.annotation.processing.AbstractProcessor;

@ProviderFor(Processor.class)
public class DomainModelCodeGenerator extends AbstractProcessor{

     @Override
     public Set<String> getSupportedAnnotationTypes() {
	  Set<String> supportedOptions = new HashSet<>();
	  supportedOptions.add(DomainModel.class.getCanonicalName());
	  return supportedOptions;
     }

     @Override
     public SourceVersion getSupportedSourceVersion() {
	  return SourceVersion.latestSupported();
     }

    @Override
    public synchronized void init(ProcessingEnvironment env) {
        super.init(env);

        this.messager = processingEnv.getMessager();
        this.elementUtils = processingEnv.getElementUtils();
        this.javacTrees = JavacTrees.instance(processingEnv);

        Context context = ((JavacProcessingEnvironment) env).getContext();
        this.treeMaker = TreeMaker.instance(context);
        this.names = Names.instance(context);
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations,
                           RoundEnvironment env) {
        final Class<? extends Annotation> annClass = getAnnotationClass();
        final Set<? extends Element> elements = env
                .getElementsAnnotatedWith(annClass);
        for(Element element : elements) {
            JCTree ast = javacTrees.getTree(element);
            // The ‘ast’ is the abstract syntax tree of Java compiled.
            // You can adjust the ‘ast’ to change the Java code
        }
     }

}

 上述代码可以查看JDK 的源代码,可以得到更详细的解决,在此不作更多介绍。

3)生成Method 处理逻辑

public JCTree.JCMethodDecl build(String name, int modifiers) {
    if (returnType == null)
        returnType = treeMaker.TypeIdent(TypeTag.VOID);

    if (returnStatement != null)
        statements.append(treeMaker.Return(returnStatement));

    return treeMaker.MethodDef(treeMaker.Modifiers(modifiers),
            aptBuilder.toName(name),
            returnType,
            List.<JCTree.JCTypeParameter>nil(),
            parameters.toList(),
            throwsClauses.toList(),
            treeMaker.Block(0, statements.toList()), null);
}

4)生成Field 逻辑

private void handleTableName(APTBuilder aptBuilder) {
    TreeMaker treeMaker = aptBuilder.getTreeMaker();

    JCModifiers modifiers = treeMaker.Modifiers(Flags.PUBLIC | Flags.STATIC | Flags.FINAL);

    JCMethodInvocation methodInvocation = treeMaker.Apply(List.nil(),
            treeMaker.Select(aptBuilder.typeRef(Tables.class), aptBuilder.toName("getTableName")),
            List.of(aptBuilder.classRef(aptBuilder.getClassName())));
    JCVariableDecl tableNameField = treeMaker.VarDef(modifiers,
            aptBuilder.toName("TABLE_NAME"), aptBuilder.typeRef(String.class), methodInvocation);

    aptBuilder.inject(tableNameField);
}

4 生成内部类逻辑

private void handleInnerTableClass(APTBuilder aptBuilder) {
    JCClassDecl classDecl = aptBuilder.classDef(Flags.PUBLIC | Flags.FINAL | Flags.STATIC,
            "Table", AbstractTable.class);
    TreeMaker treeMaker = aptBuilder.getTreeMaker();
    StatementBuilder constructorStatement = aptBuilder.createStatementBuilder();
    MethodBuilder asTableMethod = aptBuilder.createMethodBuilder();

    constructorStatement.append("super", aptBuilder.classRef(aptBuilder.getClassName()));
    JCMethodDecl constructor = aptBuilder.createConstructor(Flags.PRIVATE, List.nil(), constructorStatement.build());
    classDecl.defs = classDecl.defs.append(constructor);

    asTableMethod.setReturnType(aptBuilder.typeRef(aptBuilder.getClassName() + ".Table"));
    asTableMethod.setReturnStatement(treeMaker.NewClass(null, List.nil(), aptBuilder.typeRef("Table"),
            List.nil(), null));

    JCVariableDecl[] fields = aptBuilder.getFields();
    for (JCVariableDecl field : fields) {
        if (!aptBuilder.isStatic(field.mods)) {
            JCExpression init = aptBuilder.staticMethodCall(DefaultColumn.class, "create",
                    aptBuilder.classRef(aptBuilder.getClassName()),
                    aptBuilder.varRef("this"), treeMaker.Literal(field.name.toString()));
            JCVariableDecl var = aptBuilder.newVar(Flags.PUBLIC | Flags.FINAL,
                    Column.class, field.name.toString(), init);

            classDecl.defs = classDecl.defs.append(var);
        }
    }

    aptBuilder.inject(asTableMethod.build("asTable", Flags.PUBLIC | Flags.STATIC | Flags.FINAL));
    aptBuilder.inject(classDecl);
}

JSR 269 - "Pluggable Annotation Processing API" 中涉及太多概念,其中对Java 代码中涉及的模型进行抽象、封装,和传统的应用程序开发的区别比较大,有兴趣的同学可以访问:"ObjectiveSQL 代码生成" 查看完整代码。

05-27 10:20