为 Lombok 创建自定义注释 [英] Create custom annotation for Lombok

查看:54
本文介绍了为 Lombok 创建自定义注释的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在我的代码中使用了 Lombok 来自动生成 getter 和 setter 代码.我想添加其他个人注释并使用它.

例如,我想添加一个 @Exist 方法来验证列表中某个键的存在:

@Getter @Setter公共类用户{私人字符串名称;私人列表<整数>钥匙;公共布尔存在键(整数键){布尔存在 = keys.contains(key);返回存在;}}

创建注释后,我会做类似的事情:

@Getter @Setter公共类用户{私有字符串名称;@存在私人列表<整数>钥匙;}

解决方案

一般注意事项

如果您已经在使用 Lombok,则可以添加自定义 Lombok 转换注释和处理程序.

  1. 使用 @Target(FIELD)@Retention(SOURCE)

    定义现有注解
  2. 创建一个处理程序

    @ProviderFor(JavacAnnotationHandler.class)公共类 HandleExists 扩展 JavacAnnotationHandler{ ...`

    处理您的注释.处理程序类包必须以 lombok. 前缀开头.如果除了 javac 还需要支持 Eclipse 等,则需要编写更多的处理程序来扩展适当的框架类.

  3. 在处理程序中覆盖/实现 handle() 方法以通过 AST 操作生成所需的代码.


您可以将 @Getter 实现作为示例:

注解:Getter.java

处理程序:HandleGetter.java

您还可以查看其他注释的来源handlers 了解如何生成特定代码.>

您需要添加对 lombok、JDK tools.jar 的依赖.


一些资源:


注意,这里有几点需要考虑

  • 这是一堆需要编写和维护的重要代码.如果您打算使用注释 5-6 次,那是不值得的.
  • 您可能需要通过 lombok 升级来更改注释处理器实现.
  • lombok 依赖的编译器中的漏洞也可能被关闭(然后整个 Lombok 项目将发生巨大变化或不复存在;在这种情况下,如果您广泛使用 Lombok,无论如何您都会遇到更严重的问题,即使只是对于@Getter).

没有 Lombok 的更复杂的替代方法是使用标准的 代码生成的注释处理但是,AFAIK,你不能更改原始类,必须生成/使用扩展它们的类(除非您 利用与 Lombok 相同的后门,或者使用 CGLib 或 ASM 等代码操作).


龙目岛示例

下面是一些用于创建自定义 Lombok 注释的工作代码,我称之为 @Contains.

它只是 javac 实现,没有 Eclipse 等.我想为 Eclipse 或其他 IDE 创建一个类似的处理程序并不难.

它将生成委托给 fieldName.contains() 的 fieldNameContains() 成员方法.

注意,代码只是快速而肮脏(但有效)的示例.对于生产级注释,您将需要处理许多边界条件、检查正确类型、处理 Lombok 配置等,这些都可以在 lombok 或 lombok-pg 库源中观察到.


示例用法


SomeEnity.java

@Getter@Setter公共类 SomeEntity {@非空@包含私人收藏fieldOne = new ArrayList<>();@非空@包含私人收藏fieldTwo = new ArrayList<>();}

SomeEntityTest.java

public class SomeEntityTest {@测试公共无效测试(){SomeEntity entity = new SomeEntity();集合<字符串>test1 = Arrays.asList(new String[] { "1", "2" });entity.setFieldOne(test1);assertSame(test1, entity.getFieldOne());集合<字符串>test2 = new HashSet(Arrays.asList(new String[] { "3", "4" }));entity.setFieldTwo(test2);assertSame(test2, entity.getFieldTwo());assertTrue(entity.fieldOneContains(1"));assertTrue(entity.fieldOneContains(2"));assertFalse(entity.fieldOneContains(3"));assertFalse(entity.fieldOneContains(4"));assertFalse(entity.fieldTwoContains(1"));assertFalse(entity.fieldTwoContains(2"));assertTrue(entity.fieldTwoContains("3"));assertTrue(entity.fieldTwoContains(4"));尝试 {entity.setFieldOne(null);失败(预期异常");} 捕捉(异常前){}尝试 {entity.setFieldTwo(null);失败(预期异常");} 捕捉(异常前){}}}


注解实现


Contains.java

@Target({ElementType.FIELD})@Retention(RetentionPolicy.SOURCE)公共@interface 包含 {Class[] types() default {};Class[] excludes() default {};}

HandleContains.java

@ProviderFor(JavacAnnotationHandler.class)@HandlerPriority(65536)@ResolutionResetNeeded公共类 HandleContains 扩展 JavacAnnotationHandler{@覆盖public void handle(AnnotationValues annotation, JCAnnotation ast, JavacNode annotationNode) {尝试 {JavacNode 节点 = annotationNode.up();如果 (node.getKind() != Kind.FIELD) {annotationNode.addError(@Contains 只允许用于字段");返回;}Name delegateName = annotationNode.toName(node.getName());JavacResolution reso = new JavacResolution(annotationNode.getContext());JCTree 成员 = node.get();if (member.type == null) {reso.resolveClassMember(node);}类型 delegateType = member.type;if (delegateType instanceof ClassType) {ClassType ct = (ClassType) 委托类型;//TODO 验证这个字段是一个集合类型//if(!集合)//annotationNode.addError(@Contains 只能用于集合");final String methodName = "contains";MethodSig methodSig = getMethodBinding(methodName, ct, annotationNode.getTypesUtil());if (methodSig == null) throw new Exception("no method" + methodName + "in " + ct.tsym.name);JCMethodDecl methodDecl = createDelegateMethod(methodSig, annotationNode, delegateName);注入方法(node.up(),methodDecl);} 别的 {annotationNode.addError(@Contains 只能使用具体的类类型");返回;}} 捕捉(异常前){//ex.printStackTrace();annotationNode.addError("@Contains 意外错误:" + ex.getMessage());}}public JCMethodDecl createDelegateMethod(MethodSig sig, JavacNode annotation, Name delegateName) 抛出 TypeNotConvertibleException {JavacTreeMaker maker = annotation.getTreeMaker();com.sun.tools.javac.util.List注释;如果(sig.isDeprecated){注释 = com.sun.tools.javac.util.List.of(maker.Annotation(genJavaLangTypeRef(annotation, Deprecated"), com.sun.tools.javac.util.List.nil()));} 别的 {注释 = com.sun.tools.javac.util.List.nil();}JCModifiers mods = maker.Modifiers(PUBLIC, annotations);JCExpression returnType = JavacResolution.typeToJCTree((Type) sig.type.getReturnType(), annotation.getAst(), true);boolean useReturn = sig.type.getReturnType().getKind() != TypeKind.VOID;ListBufferparams = sig.type.getParameterTypes().isEmpty() ?null : new ListBuffer();ListBufferargs = sig.type.getParameterTypes().isEmpty() ?null : new ListBuffer();ListBuffer抛出 = sig.type.getThrownTypes().isEmpty() ?null : new ListBuffer();ListBuffertypeParams = sig.type.getTypeVariables().isEmpty() ?null : new ListBuffer();ListBuffertypeArgs = sig.type.getTypeVariables().isEmpty() ?null : new ListBuffer();类型类型 = Types.instance(annotation.getContext());for (TypeMirror 参数: sig.type.getTypeVariables()) {Name name = ((TypeVar) param).tsym.name;ListBufferbounds = new ListBuffer();对于(类型类型:types.getBounds((TypeVar)参数)){bounds.append(JavacResolution.typeToJCTree(type, annotation.getAst(), true));}typeParams.append(maker.TypeParameter(name, bounds.toList()));typeArgs.append(maker.Ident(name));}for (TypeMirror ex : sig.type.getThrownTypes()) {throw.append(JavacResolution.typeToJCTree((Type) ex, annotation.getAst(), true));}int idx = 0;String[] paramNames = sig.getParameterNames();布尔变量 = sig.elem.isVarArgs();for (TypeMirror 参数: sig.type.getParameterTypes()) {long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, annotation.getContext());JCModifiers paramMods = maker.Modifiers(flags);Name name = annotation.toName(paramNames[idx++]);if (varargs && idx == paramNames.length) {paramMods.flags |= VARARGS;}params.append(maker.VarDef(paramMods, name, JavacResolution.typeToJCTree((Type) param, annotation.getAst(), true), null));args.append(maker.Ident(name));}JCExpression accessor = maker.Select(maker.Ident(annotation.toName(this")), delegateName);JCExpression delegateCall = maker.Apply(toList(typeArgs), maker.Select(accessor, sig.name), toList(args));JCStatement 正文 = useReturn ?maker.Return(delegateCall) : maker.Exec(delegateCall);JCBlock bodyBlock = maker.Block(0, com.sun.tools.javac.util.List.of(body));StringBuildergeneratedMethodName = new StringBuilder(delegateName);generateMethodName.append(sig.name.toString());generateMethodName.setCharAt(delegateName.length(), Character.toUpperCase(generatedMethodName.charAt(delegateName.length())));return recursiveSetGeneratedBy(maker.MethodDef(mods, annotation.toName(generatedMethodName.toString()), returnType, toList(typeParams), toList(params), toList(thrown), bodyBlock, null), annotation.get(), annotation.getContext());}公共静态<T>com.sun.tools.javac.util.ListtoList(ListBuffer 集合){返回集合 == 空?com.sun.tools.javac.util.List.<T>nil() : collection.toList();}公共静态类 MethodSig {最终名称名称;最终的 ExecutableType 类型;最终布尔值 isDeprecated;最终的 ExecutableElement 元素;MethodSig(Name name, ExecutableType type, boolean isDeprecated, ExecutableElement elem) {this.name = 名称;this.type = 类型;this.isDeprecated = isDeprecated;this.elem = elem;}字符串[] getParameterNames() {列表paramList = elem.getParameters();String[] paramNames = new String[paramList.size()];for (int i = 0; i < paramNames.length; i++) {paramNames[i] = paramList.get(i).getSimpleName().toString();}返回参数名称;}@Override 公共字符串 toString() {return (isDeprecated ? "@Deprecated " : "") + 名称 + ";"+ 类型;}}public MethodSig getMethodBinding(字符串名称,ClassType ct,JavacTypes 类型){MethodSig 结果 = null;TypeSymbol tsym = ct.asElement();if (tsym == null) throw new IllegalArgumentException("no class");for(符号成员:tsym.getEnclosedElements()){if (member.getKind() != ElementKind.METHOD || !name.equals(member.name.toString())) {继续;}如果 (member.isStatic()) 继续;如果 (member.isConstructor()) 继续;ExecutableElement exElem = (ExecutableElement) 成员;如果 (!exElem.getModifiers().contains(Modifier.PUBLIC)) 继续;ExecutableType methodType = (ExecutableType) types.asMemberOf(ct, member);boolean isDeprecated = (member.flags() & DEPRECATED) != 0;result = new MethodSig(member.name, methodType, isDeprecated, exElem);}如果(结果 == 空){如果(ct.supertype_field instanceof ClassType){结果 = getMethodBinding(name, (ClassType) ct.supertype_field, types);}如果(结果 == 空){如果(ct.interfaces_field != null){对于(输入接口:ct.interfaces_field){如果(类类型的接口实例){result = getMethodBinding(name, (ClassType) iface, types);如果(结果!= null){休息;}}}}}}返回结果;}}

I have used Lombok in my code to automatically generate getter and setter code. I want to add other personal annotations and use it.

For example, I want to add an @Exist method which verifies the existence of a key in a list:

@Getter    @Setter
public class User {

    private String name;
    private List<Integer> keys;

    public boolean existKeys(Integer key) {
        boolean exist = keys.contains(key);
        return exist;
    }
}

After creating the annotation, I would do something like:

@Getter    @Setter
public class User {

    private String name;
    @Exist
    private List<Integer> keys;
} 

解决方案

General Considerations

If you are already using Lombok, you can add custom Lombok transformation annotation and handler.

  1. Define Exists annotation with @Target(FIELD) and @Retention(SOURCE)

  2. Create a handler

    @ProviderFor(JavacAnnotationHandler.class)
    public class HandleExists extends JavacAnnotationHandler<Exists>{ ...` 
    

    to process your annotation. Handler class package must start with the lombok. prefix. If you need to support Eclipse, etc. in addition to javac, you'll need to write more handlers extending appropriate framework classes.

  3. In the handler override/implement the handle() method to generate the required code through AST manipulation.


You can take as a sample the @Getter implementation:

Annotation: Getter.java

Handler: HandleGetter.java

You can also look into sources of other annotations and handlers to see how to generate particular code.

You'll need to add dependencies on lombok, JDK tools.jar.


Some resources:


Note, there are some points to consider here

  • This is a bunch of non-trivial code to write and maintain. If you plan to use annotation 5-6 times it is just not worth it.
  • You may need to change your annotation processor implementation with lombok upgrades.
  • The hole in compiler that lombok relies on also may be closed (then the whole Lombok project will change dramatically or cease to exist; in this case you'll have a more serious problem anyway if you use Lombok extensively, even if just for @Getter).

A more complex alternative without Lombok is to use standard annotation processing for code generation but, AFAIK, you can't change original classes and must generate/use classes that extend them (unless you'll exploit the same back-door as Lombok or resort to a code manipulation like CGLib or ASM).


Lombok Example

Below is some working code to create custom Lombok annotation that I've called @Contains.

It is javac implementation only, no Eclipse, etc. I guess it will be not hard to create a similar handler for Eclipse or other IDE.

It will generate fieldNameContains() member method which is delegated to the fieldName.contains().

Note, the code is just quick and dirty (but working) sample. For production grade annotation, you will need to handle many boundary conditions, check correct types, handle Lombok configuration and so on, as it can be observed in lombok or lombok-pg library sources.


Sample usage


SomeEnity.java

@Getter
@Setter
public class SomeEntity {

    @NonNull
    @Contains
    private Collection<String> fieldOne = new ArrayList<>();

    @NonNull
    @Contains
    private Collection<String> fieldTwo = new ArrayList<>();

}

SomeEntityTest.java

public class SomeEntityTest {

    @Test
    public void test() {
        SomeEntity entity = new SomeEntity();

        Collection<String> test1 = Arrays.asList(new String[] { "1", "2" });
        entity.setFieldOne(test1);
        assertSame(test1, entity.getFieldOne());

        Collection<String> test2 = new HashSet<String>(Arrays.asList(new String[] { "3", "4" }));
        entity.setFieldTwo(test2);
        assertSame(test2, entity.getFieldTwo());

        assertTrue(entity.fieldOneContains("1"));
        assertTrue(entity.fieldOneContains("2"));
        assertFalse(entity.fieldOneContains("3"));
        assertFalse(entity.fieldOneContains("4"));

        assertFalse(entity.fieldTwoContains("1"));
        assertFalse(entity.fieldTwoContains("2"));
        assertTrue(entity.fieldTwoContains("3"));
        assertTrue(entity.fieldTwoContains("4"));

        try {
            entity.setFieldOne(null);
            fail("exception expected");
        } catch (Exception ex) {
        }

        try {
            entity.setFieldTwo(null);
            fail("exception expected");
        } catch (Exception ex) {
        }

    }
}


Annotation Implementaiton


Contains.java

@Target({ElementType.FIELD})
@Retention(RetentionPolicy.SOURCE)
public @interface Contains {
    Class<?>[] types() default {};
    Class<?>[] excludes() default {};
}

HandleContains.java

@ProviderFor(JavacAnnotationHandler.class) 
@HandlerPriority(65536) 
@ResolutionResetNeeded 
public class HandleContains extends JavacAnnotationHandler<Contains> {
    
    @Override 
    public void handle(AnnotationValues<Contains> annotation, JCAnnotation ast, JavacNode annotationNode) {
        
        try {
            JavacNode node = annotationNode.up();
            if (node.getKind() != Kind.FIELD) {
                annotationNode.addError("@Contains is allowed only on fields");
                return;
            }
            Name delegateName = annotationNode.toName(node.getName());
            JavacResolution reso = new JavacResolution(annotationNode.getContext());
            JCTree member = node.get();
            if (member.type == null) {
                reso.resolveClassMember(node);
            }
            Type delegateType = member.type;
            if (delegateType instanceof ClassType) {
                ClassType ct = (ClassType) delegateType;
                //TODO validate that this field is a collection type
                // if(!Collection)
                //   annotationNode.addError("@Contains can only be used on collections");
                final String methodName = "contains";
                MethodSig methodSig = getMethodBinding(methodName, ct, annotationNode.getTypesUtil());
                if (methodSig == null) throw new Exception("no method " + methodName + " in " + ct.tsym.name);
                JCMethodDecl methodDecl = createDelegateMethod(methodSig, annotationNode, delegateName);
                injectMethod(node.up(), methodDecl);
            } else {
                annotationNode.addError("@Contains can only use concrete class types");
                return;
            }
        } catch (Exception ex) {
            //ex.printStackTrace();
            annotationNode.addError("@Contains unexpected error: " + ex.getMessage());
        }
        
    }
    
    public JCMethodDecl createDelegateMethod(MethodSig sig, JavacNode annotation, Name delegateName) throws TypeNotConvertibleException {
        
        JavacTreeMaker maker = annotation.getTreeMaker();
        
        com.sun.tools.javac.util.List<JCAnnotation> annotations;
        if (sig.isDeprecated) {
            annotations = com.sun.tools.javac.util.List.of(maker.Annotation(genJavaLangTypeRef(annotation, "Deprecated"), com.sun.tools.javac.util.List.<JCExpression>nil()));
        } else {
            annotations = com.sun.tools.javac.util.List.nil();
        }
        
        JCModifiers mods = maker.Modifiers(PUBLIC, annotations);
        JCExpression returnType = JavacResolution.typeToJCTree((Type) sig.type.getReturnType(), annotation.getAst(), true);
        boolean useReturn = sig.type.getReturnType().getKind() != TypeKind.VOID;
        ListBuffer<JCVariableDecl> params = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer<JCVariableDecl>();
        ListBuffer<JCExpression> args = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer<JCExpression>();
        ListBuffer<JCExpression> thrown = sig.type.getThrownTypes().isEmpty() ? null : new ListBuffer<JCExpression>();
        ListBuffer<JCTypeParameter> typeParams = sig.type.getTypeVariables().isEmpty() ? null : new ListBuffer<JCTypeParameter>();
        ListBuffer<JCExpression> typeArgs = sig.type.getTypeVariables().isEmpty() ? null : new ListBuffer<JCExpression>();
        Types types = Types.instance(annotation.getContext());
        
        for (TypeMirror param : sig.type.getTypeVariables()) {
            Name name = ((TypeVar) param).tsym.name;
            
            ListBuffer<JCExpression> bounds = new ListBuffer<JCExpression>();
            for (Type type : types.getBounds((TypeVar) param)) {
                bounds.append(JavacResolution.typeToJCTree(type, annotation.getAst(), true));
            }
            
            typeParams.append(maker.TypeParameter(name, bounds.toList()));
            typeArgs.append(maker.Ident(name));
        }
        
        for (TypeMirror ex : sig.type.getThrownTypes()) {
            thrown.append(JavacResolution.typeToJCTree((Type) ex, annotation.getAst(), true));
        }
        
        int idx = 0;
        String[] paramNames = sig.getParameterNames();
        boolean varargs = sig.elem.isVarArgs();
        for (TypeMirror param : sig.type.getParameterTypes()) {
            long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, annotation.getContext());
            JCModifiers paramMods = maker.Modifiers(flags);
            Name name = annotation.toName(paramNames[idx++]);
            if (varargs && idx == paramNames.length) {
                paramMods.flags |= VARARGS;
            }
            params.append(maker.VarDef(paramMods, name, JavacResolution.typeToJCTree((Type) param, annotation.getAst(), true), null));
            args.append(maker.Ident(name));
        }
        
        JCExpression accessor = maker.Select(maker.Ident(annotation.toName("this")), delegateName);
        
        JCExpression delegateCall = maker.Apply(toList(typeArgs), maker.Select(accessor, sig.name), toList(args));
        JCStatement body = useReturn ? maker.Return(delegateCall) : maker.Exec(delegateCall);
        JCBlock bodyBlock = maker.Block(0, com.sun.tools.javac.util.List.of(body));
        StringBuilder generatedMethodName = new StringBuilder(delegateName);
        generatedMethodName.append(sig.name.toString());
        generatedMethodName.setCharAt(delegateName.length(), Character.toUpperCase(generatedMethodName.charAt(delegateName.length())));
        return recursiveSetGeneratedBy(maker.MethodDef(mods, annotation.toName(generatedMethodName.toString()), returnType, toList(typeParams), toList(params), toList(thrown), bodyBlock, null), annotation.get(), annotation.getContext());
    }
    
    public static <T> com.sun.tools.javac.util.List<T> toList(ListBuffer<T> collection) {
        return collection == null ? com.sun.tools.javac.util.List.<T>nil() : collection.toList();
    }
    
    public static class MethodSig {
        final Name name;
        final ExecutableType type;
        final boolean isDeprecated;
        final ExecutableElement elem;
        
        MethodSig(Name name, ExecutableType type, boolean isDeprecated, ExecutableElement elem) {
            this.name = name;
            this.type = type;
            this.isDeprecated = isDeprecated;
            this.elem = elem;
        }
        
        String[] getParameterNames() {
            List<? extends VariableElement> paramList = elem.getParameters();
            String[] paramNames = new String[paramList.size()];
            for (int i = 0; i < paramNames.length; i++) {
                paramNames[i] = paramList.get(i).getSimpleName().toString();
            }
            return paramNames;
        }
        
        @Override public String toString() {
            return (isDeprecated ? "@Deprecated " : "") + name + " " + type;
        }
    }
    
    public MethodSig getMethodBinding(String name, ClassType ct, JavacTypes types) {
        MethodSig result = null;
        TypeSymbol tsym = ct.asElement();
        if (tsym == null) throw new IllegalArgumentException("no class");
        
        for (Symbol member : tsym.getEnclosedElements()) {
            if (member.getKind() != ElementKind.METHOD || !name.equals(member.name.toString())) {
                continue;
            }
            if (member.isStatic()) continue;
            if (member.isConstructor()) continue;
            ExecutableElement exElem = (ExecutableElement) member;
            if (!exElem.getModifiers().contains(Modifier.PUBLIC)) continue;
            ExecutableType methodType = (ExecutableType) types.asMemberOf(ct, member);
            boolean isDeprecated = (member.flags() & DEPRECATED) != 0;
            result = new MethodSig(member.name, methodType, isDeprecated, exElem);
        }
        if (result == null) {
            if (ct.supertype_field instanceof ClassType) {
                result = getMethodBinding(name, (ClassType) ct.supertype_field, types);
            }
            if (result == null) {
                if (ct.interfaces_field != null) {
                    for (Type iface : ct.interfaces_field) {
                        if (iface instanceof ClassType) {
                            result = getMethodBinding(name, (ClassType) iface, types);
                            if (result != null) {
                                break;
                            }
                        }
                    }
                }
            }
        }
        return result;
    }
}

这篇关于为 Lombok 创建自定义注释的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆