Java编译时注解应用-生成格式化源文件

本文介绍了如何使用Java编译时注解来简化Builder模式的样板代码。通过定义注解和注解处理器,在编译阶段自动生成源文件,减少手动编写重复代码,提高开发效率。示例中详细讲解了注解声明、注解解释器的编写以及编译参数设置,强调了使用javac的-s标志来指定源文件输出位置的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

有许多开源框架在编译时通过注解信息生成新的源文件,已到达简化样板代码的书写,比如说典型的Builder模式,或者实现框架的功能的桥接代码。因为我使用编译时注解只是想要简化样板代码,下面我就以Builder模式作为示例。

Builder模式

Builder模式主要是解决创建对象过程中的必须参数和非必须参数的的传入,以及以及传入过程中导致对象的中间状态暴露在外面。下面是简单Builder模式的实现:

#User.java
package Model.builder;
public class User{
    //定义为final 的属性必需在构造函数中初始化
    private final String id; //必需
    private final String sex; //必需
    private final String name; //必需
    private String location; //非必需
    private String email; //非必需
    private User(String id,String sex,String name){
        this.id= id ;
        this.name =name ;
        this.sex = sex;
    }

    private User(User origin){
        this.id = origin.id;
        this.sex = origin.sex;
        this.name = origin .name;
        this.location = origin.location;
        this.email = origin.email;
    }

    public String getId() {
        return id;
    }

    public String getSex() {
        return sex;
    }

    public String getName() {
        return name;
    }

    public String getLocation() {
        return location;
    }

    public String getEmail() {
        return email;
    }

    static class UserBuilder{
        private User target;
        public UserBuilder(String id ,String sex,String name){
            target = new User(id, sex, name);
        }

        public UserBuilder location(String location){
            target.location = location;
            return this;
        }

        public UserBuilder email(String email){
            target.email = email;
            return this;
        }

        public User build(){
            return new User(target);
        }
    }
}

如果我们要使用上面的builder创建行的User对象的话,像下面这样调用
User user = new User.UserBuilder(“123”,”man”,”sosky”).email(“1xxxxx@qq.com”).location(“xxxxxx”).build()
像这样链式的调用写起来思路很清晰(虽然这里并没有很复杂的逻辑,但是如果这里是多线程下网络通信和UI操作,这样写的话逻辑就非常容易理清了,可以参考一下Rxjava的链式API),并且避免了User对象中间状态暴露。

代码虽然好用,但是除了规定那些是必需的成员变量,那些是非必需的成员变量,其他的代码完全就是重复的样板代码。因此我们就可以采用编译时注解帮我们处理这些样板代码,只需要我们规定那些是必需的成员变量就行了;

注解的声明如下:

package Annotation;
//标记builder属性
import java.lang.annotation.ElementType;
import java.lang.annotation.Target;

@Target({ElementType.TYPE,ElementType.FIELD})
@Retention(RetentionPolicy.CLASS)
public @interface toBuilder {
    boolean essential();
}
package Annotation;
//标记builder类
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface BuilderClass {

}

我们像这样定义了一个注解,其中essential表示是否是必需的成员变量,如果是必需的话我们就需要在新的源文件中定义为final的变量,并在构造函数中初始化它;

注解解释器声明:

package Annotation;


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


/**
 * toBuilder注解解析器
 */
@SupportedSourceVersion(SourceVersion.RELEASE_8)
@SupportedAnnotationTypes({"Annotation.toBuilder","Annotation.BuilderClass"})
public class toBuilderProcess extends AbstractProcessor{
    private Filer filer;
    private String className;

    //essential为真的属性名
    private List<String> fields_t;
    //essential为假的属性名
    private List<String> fields_f;
    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
    }

    @Override
    public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
        //标记的类
        Set<? extends Element> classAnnotated = roundEnv.getElementsAnnotatedWith(BuilderClass.class);
        for (Element classelement :classAnnotated){
            className = classelement.getSimpleName().toString();
        }
        //标记的成员变量
        Set<? extends Element> fieldAnnotated = roundEnv.getElementsAnnotatedWith(toBuilder.class);
        for (Element field: fieldAnnotated){
            if(field.getAnnotation(toBuilder.class).essential()) {
                fields_t.add(field.getSimpleName().toString());
            }else{
                fields_f.add(field.getSimpleName().toString());
            }
        }
        //创建源文件
        createFile();

        return true;
    }


    private void createFile() {

        StringBuilder cls = new StringBuilder();
        cls.append("package Annotation;\n\npublic class ")
                .append(className)
             。。。。。。。
             源文件内容,根据之前注解信息拼接字符串;
        try {
            JavaFileObject sourceFile = filer.createSourceFile("Annotation." + className);
            Writer writer = sourceFile.openWriter();
            writer.write(cls.toString());
            writer.flush();
            writer.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

只有我们就可以通过javac使用编译后的注解解析器了:
javac -encoding UTF-8 -cp {注解器和注解的包路径,或者class路径} -processor {包名.注解器名} -d out\production -s src\ src\Annotation*.java
在这里注意一下 javac -s标志的含义,是指定源文件位置,而我们在注解器中JavaFileObject sourceFile = filer.createSourceFile(“Annotation.” + className); sourceFile对象的Writer的输出位置就是其指定的路径,如果没有- s标志的话,默认会生成的源文件会生成到-d 指定的路径下。我写的时候遇到了源文件生成位置不对的坑,就是这个原因。

结语

当然现在有很多IDE插件能帮我们自动生成Builder代码,这只是一个例子,有很多其他样板代码,或者因为小小的不同,需要重复写的业务逻辑代码,都可以用这个方法简化。更重要的假如我们的框架能够通过不同注解配置其功能,这样的高度自定义的框架使用编译时注解是最为合适的了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值