为Lombok创建自定义注释

我在我的代码中使用了Lombok来自动生成GetterSetter代码,现在我想添加其他个人Annotations并使用它。

例如,我想添加@Existvalidation现有密钥列表:

  @Getter @Setter public class User { private String name; private List keys; public boolean existKeys(Integer key) { boolean exist = keys.contains(key); return exist; } } 

在创建Annotation之后,我将只需执行以下操作:

 @Getter @Setter public class User { private String name; @Exist private List keys; } 

一般考虑因素

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

  1. 使用@Target(FIELD)@Retention(SOURCE)定义Exists注释
  2. 创建一个处理程序

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

    处理您的注释。 Handler类包必须以lombok.开头lombok. 字首。 如果除了javac之外还需要支持Eclipse等,那么您需要编写更多处理程序来扩展适当的框架类。

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


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

注释: Getter.java

处理程序: HandleGetter.java

您还可以查看其他注释和处理程序的 来源 ,以了解如何生成特定代码。

您需要在lombok,JDK tools.jar上添加依赖项。


一些资源:

  • lombok-pg项目,包含一堆自定义lombok注释的源代码,特别是FluentSetter.java , HandleFluentSetter.java / FluentSetterHandler.java

  • 自定义转换概述

  • 带解释的简单注释示例 。


注意,这里有一些要考虑的要点

  • 这是一堆非常重要的代码来编写和维护。 如果你计划使用5-6次注释,那就不值得了。
  • 您可能需要使用lombok升级更改注释处理器实现。
  • lombok依赖的编译器中的漏洞也可能被关闭(然后整个Lombok项目将发生巨大变化或停止存在;在这种情况下,如果你广泛使用Lombok,即使只是为了@Getter,你也会遇到更严重的问题)。

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


龙目岛的例子

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

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

它将生成fieldName Contains()成员方法,该方法被委托给fieldName .contains()。

注意,代码只是快速而肮脏(但工作)的样本。 对于生产等级注释,您将需要处理许多边界条件,检查正确的类型,处理Lombok配置等,因为它可以在lombok或lombok-pg库源中观察到。


样品用法


SomeEnity.java

 @Getter @Setter public class SomeEntity { @NonNull @Contains private Collection fieldOne = new ArrayList<>(); @NonNull @Contains private Collection fieldTwo = new ArrayList<>(); } 

SomeEntityTest.java

 public class SomeEntityTest { @Test public void test() { SomeEntity entity = new SomeEntity(); Collection test1 = Arrays.asList(new String[] { "1", "2" }); entity.setFieldOne(test1); assertSame(test1, entity.getFieldOne()); Collection 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")); try { entity.setFieldOne(null); fail("exception expected"); } catch (Exception ex) { } try { entity.setFieldTwo(null); fail("exception expected"); } catch (Exception ex) { } } } 

注释实现


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 { @Override public void handle(AnnotationValues 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 annotations; if (sig.isDeprecated) { annotations = com.sun.tools.javac.util.List.of(maker.Annotation(genJavaLangTypeRef(annotation, "Deprecated"), com.sun.tools.javac.util.List.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 params = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer(); ListBuffer args = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer(); ListBuffer thrown = sig.type.getThrownTypes().isEmpty() ? null : new ListBuffer(); ListBuffer typeParams = sig.type.getTypeVariables().isEmpty() ? null : new ListBuffer(); ListBuffer typeArgs = sig.type.getTypeVariables().isEmpty() ? null : new ListBuffer(); Types types = Types.instance(annotation.getContext()); for (TypeMirror param : sig.type.getTypeVariables()) { Name name = ((TypeVar) param).tsym.name; ListBuffer bounds = new ListBuffer(); 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  com.sun.tools.javac.util.List toList(ListBuffer collection) { return collection == null ? com.sun.tools.javac.util.List.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 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; } }