如果数组在List或Map中注释,则Java 8 TYPE_USE注释不起作用 [英] Java 8 TYPE_USE annotations not behaving if array is annotated inside List or Map

查看:160
本文介绍了如果数组在List或Map中注释,则Java 8 TYPE_USE注释不起作用的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试编写一个简单的验证库,其中包含使用Java 8中新TYPE_USE目标的注释。访问这些东西的方法非常复杂,给我留下了两个非常混乱的代码,它们完全相同,但也与他们实际上要做的事情非常耦合。所以我决定创建一个简单的类集来以一种非常简单直观的方式保存这些信息。基本上有TypedClass,这是一个类包装,但它可以转换为ListClass(集合或数组)或MapClass作为TypedClass访问其组件(或键/值)。最重要的部分是这个应该转换的助手:

  static TypedClass<?> create(Field f){
final TypeResolver typeResolver = new TypeResolver();
ResolvedType type = typeResolver.resolve(f.getGenericType());
return create(f.getAnnotations(),type,f.getAnnotatedType());
}

private static TypedClass<?>创建(Annotation []注释,ResolvedType类型,AnnotatedType at){
if(type.isArray()){
AnnotatedType childAnnotatedType =((AnnotatedArrayType)at).getAnnotatedGenericComponentType();
ResolvedType childType = type.getArrayElementType();
返回新的ListClass<>(type.getErasedType(),annotations,create(at.getAnnotations(),childType,childAnnotatedType));
} else if(type.isInstanceOf(Collection.class)){
AnnotatedType childAnnotatedType =((AnnotatedParameterizedType)at).getAnnotatedActualTypeArguments()[0];
ResolvedType childType = type.typeParametersFor(Collection.class).get(0);
返回新的ListClass<>(type.getErasedType(),annotations,createForGenerics(childType,childAnnotatedType));
} else if(type.isInstanceOf(Map.class)){
AnnotatedType [] att =((AnnotatedParameterizedType)at).getAnnotatedActualTypeArguments();
List< ResolvedType> types = type.typeParametersFor(Map.class);
TypedClass<?> key = createForGenerics(types.get(0),att [0]);
TypedClass<?> value = createForGenerics(types.get(1),att [1]);
返回新的MapClass<>(type.getErasedType(),annotations,key,value);
}
返回新的TypedClass<>(type.getErasedType(),annotations);
}

private static TypedClass<?> createForGenerics(ResolvedType childType,AnnotatedType childAnnotatedType){
return create(childAnnotatedType.getAnnotations(),childType,childAnnotatedType);





$ b这里我也使用com.fasterxml.classmate的ResolvedType,它只提供用Java连接Type和Class,这也是一个痛苦。我认为这不是问题的关键,因为生成的结构是可以的,只有注释是错误的。



它似乎工作得很好,除了当List或Map中有一个数组。例如:

  @First(array)List< @First(string)String> @First(list)[] arrayOfListOfString; 

工作正常(注解与相应类型匹配)。但是,当我解析

 列表< @First(int [])Integer @First(int)[]> ; listOfArrayOfInteger; 

数组和Integer都与@First(int)注释关联。 p>

我调试了我的代码,我无法在任何地方找到对@First(int [])注释的引用。我要么失去一些非常简单的东西,要么没有任何意义。该代码似乎适用于所有其他场景,甚至更复杂的场景。我一直在尝试这一点,最近才得到这个解决方案,我认为它会起作用。原来我以前使用的两个耦合方法不适用于这种情况(没有这个特定的测试)。所以现在我很困难。



有什么不对?

解决方案

在您对 create 的初始调用中,您传递字段注释,但不传递 AnnotatedType 。因此, create 方法负责调用 at.getAnnotations()。如果你看看你的实现,你会发现它只会在数组情况下这样做。在所有其他情况下,您的方法会将逻辑切换为传入的注释数组是与 at 关联的数组。



问题是你的第一个例子似乎是偶然发生的。你没有显示你的 @First 注释的声明,但我怀疑这是允许的 @Target s , ElementType.FIELD ElementType.TYPE_USE 。在这种情况下,表单的声明


  @First(array)List ... [] fieldName; 

是不明确的,注解将被记录为 ,一个字段注释对于 fieldName List ... [] 类型的注释。因此,在递归期间一个注释数组丢失的事实在第一个示例中无法识别,因为它恰好匹配字段注释。但是,一旦仅允许注释 TYPE_USE ,但不允许 FIELD 目标,那么您的代码甚至不适用于您的第一个例如。

因此,您必须决定传入的注释数组是否与参数中的关联或周围的环境。如果两者都相关联,则会更容易,因为在这种情况下,您可以通过让方法检索该数组而非调用方来完全摆脱该参数。



您应该请记住:


  1. 如果您想记录递归类型字段注释的所有类型注释,您将拥有比类型节点多一个批注数组

  2. 如果您想要主要处理 TYPE_USE 批注,则不需要处理


  3. 以下是一个简单直接的解析代码,用于查找所有注释:

      public static void fullType(Field f){
    AnnotatedType at = f.getAnnotatedType();
    fullType(\t,at);
    System.out.println(f.getName());
    }
    public static void fullType(String header,AnnotatedType at){
    final boolean arrayType = at instanceof AnnotatedArrayType;
    if(arrayType){
    fullType(header +\t,
    ((AnnotatedArrayType)at).getAnnotatedGenericComponentType()); (注解a:at.getAnnotations())
    System.out.println(header + a);
    }

    if(arrayType){
    System.out.println(header +[]);
    }
    else if(at AnnotatedParameterizedType){
    AnnotatedParameterizedType apt =(AnnotatedParameterizedType)at;
    System.out.println(header
    +((ParameterizedType)apt.getType())。getRawType()。getTypeName());
    System.out.println(header +'<');
    String subHeader = header +\t;
    for(AnnotatedType typeArg:
    apt.getAnnotatedActualTypeArguments())
    fullType(subHeader,typeArg);
    System.out.println(header +'>');
    }
    else if(在AnnotatedTypeVariable的实例中){
    //出现在Field的类型中时,它引用类的类型变量
    System.out.println(header + at 。.getType()getTypeName());
    }
    else if(at AnnotatedWildcardType){
    System.out.println(header +?);
    final AnnotatedWildcardType awt =(AnnotatedWildcardType)at;
    AnnotatedType [] bounds = awt.getAnnotatedLowerBounds();
    if(bounds == null || bounds.length == 0){
    bounds = awt.getAnnotatedUpperBounds();
    if(bounds == null || bounds.length == 0)return;
    System.out.println(header +extends);
    }
    else System.out.println(header +super);
    header + =\t;
    for(AnnotatedType b:bounds)fullType(header,b);
    }
    else {
    assert at.getType()。getClass()== Class.class;
    System.out.println(header + at.getType()。getTypeName());


    它可以在您的示例字段中完美工作。


    I'm trying to write a simple validation lib with annotations that use the new TYPE_USE target from Java 8. The way to access these things is really complex, and left me with two very messy codes that do exactly the same thing, but are also very coupled to what they actually were meant to do. So I decided to create a simple set of classes to hold this information in a very easy and intuitive way. Basically there is the TypedClass, that is a Class wrapper, but it can be cast to ListClass (Collection or array) or MapClass to access its components (or key / value ) as TypedClass. The most important piece is this helper that is supposed to convert:

    static TypedClass<?> create(Field f) {
        final TypeResolver typeResolver = new TypeResolver();
        ResolvedType type = typeResolver.resolve(f.getGenericType());
        return create(f.getAnnotations(), type, f.getAnnotatedType());
    }
    
    private static TypedClass<?> create(Annotation[] annotations, ResolvedType type, AnnotatedType at) {
        if (type.isArray()) {
            AnnotatedType childAnnotatedType = ((AnnotatedArrayType) at).getAnnotatedGenericComponentType();
            ResolvedType childType = type.getArrayElementType();
            return new ListClass<>(type.getErasedType(), annotations, create(at.getAnnotations(), childType, childAnnotatedType));
        } else if (type.isInstanceOf(Collection.class)) {
            AnnotatedType childAnnotatedType = ((AnnotatedParameterizedType) at).getAnnotatedActualTypeArguments()[0];
            ResolvedType childType = type.typeParametersFor(Collection.class).get(0);
            return new ListClass<>(type.getErasedType(), annotations, createForGenerics(childType, childAnnotatedType));
        } else if (type.isInstanceOf(Map.class)) {
            AnnotatedType[] att = ((AnnotatedParameterizedType) at).getAnnotatedActualTypeArguments();
            List<ResolvedType> types = type.typeParametersFor(Map.class);
            TypedClass<?> key = createForGenerics(types.get(0), att[0]);
            TypedClass<?> value = createForGenerics(types.get(1), att[1]);
            return new MapClass<>(type.getErasedType(), annotations, key, value);
        }
        return new TypedClass<>(type.getErasedType(), annotations);
    }
    
    private static TypedClass<?> createForGenerics(ResolvedType childType, AnnotatedType childAnnotatedType) {
        return create(childAnnotatedType.getAnnotations(), childType, childAnnotatedType);
    }
    

    Here I am also using ResolvedType from com.fasterxml.classmate, it only serves to bridge between Type and Class in Java, which is also a pain. I don't think it is relevant for the problem, as the structure generated is ok, only the annotations are misplaced.

    It seems to be working pretty ok, except when there is an array inside a List or Map. For example:

    @First("array") List<@First("string") String> @First("list") [] arrayOfListOfString;
    

    Works fine (the annotations are matched with the corresponding types). But when I parse

    List<@First("int[]") Integer @First("int") []> listOfArrayOfInteger;
    

    Both the array and the Integer get associated with the @First("int") annotation.

    I have debugged my code and I can't find reference to the @First("int[]") annotation anywhere. I'm either missing something very simple or nothing makes sense. The code seems to work in every other scenarios, even the more complex ones. I've been trying to to this for weeks, just recently got to this solution that I thought it would work. Turns out the two coupled methods I had before don't work for this scenario as well (didn't have this specific test). So now I am very stuck.

    What's wrong?

    解决方案

    In your initial call to create you pass in the field annotations but not the annotations for the AnnotatedType at. Therefore, the method create is responsible for calling at.getAnnotations(). If you look at your implementation you will find out that it will do so only in the array case. In all other cases, your method switches the logic to "the passed in annotation array is the one associated with at".

    The problem is that your first example seems to work by accident. You didn’t show the declaration of your @First annotation but I suspect that it is allowed for both @Targets, ElementType.FIELD and ElementType.TYPE_USE. In this case, a declaration of the form

    @First("array") List… [] fieldName;
    

    is ambiguous and the annotation will be recorded as both, a field annotation for fieldName and an annotation for the List…[] type. So the fact that one annotation array is lost during your recursion is not recognized in the first example because it happens to match the field annotations. But once an annotation is allowed for TYPE_USE only but not FIELD targets, your code doesn’t even work with your first example.

    So you have to decide whether the passed in annotation array shall be the one associated with the at parameter or of the surrounding context. It would be easier if both are associated, as in this case you could get rid of that parameter entirely by letting the method retrieve that array rather than the caller.

    You should keep in mind:

    1. If you want to record all type annotations of a recursive type and the field annotations, you will have one more annotation array than type nodes
    2. If you want to process TYPE_USE annotation primarily, you don’t need to deal with field annotations at all

    Here is a simple, straight-forward parsing code finding all annotations:

    public static void fullType(Field f) {
        AnnotatedType at = f.getAnnotatedType();
        fullType("\t", at);
        System.out.println(f.getName());
    }
    public static void fullType(String header, AnnotatedType at) {
        final boolean arrayType = at instanceof AnnotatedArrayType;
        if(arrayType) {
            fullType(header+"\t",
                ((AnnotatedArrayType)at).getAnnotatedGenericComponentType());
        }
        for(Annotation a: at.getAnnotations())
            System.out.println(header+a);
        if(arrayType) {
            System.out.println(header+"[]");
        }
        else if(at instanceof AnnotatedParameterizedType) {
            AnnotatedParameterizedType apt = (AnnotatedParameterizedType)at;
            System.out.println(header
                +((ParameterizedType)apt.getType()).getRawType().getTypeName());
            System.out.println(header+'<');
            String subHeader=header+"\t";
            for(AnnotatedType typeArg:
                apt.getAnnotatedActualTypeArguments())
                fullType(subHeader, typeArg);
            System.out.println(header+'>');
        }
        else if(at instanceof AnnotatedTypeVariable) {
            // when appearing in a Field’s type, it refers to Class’ type variables
            System.out.println(header+at.getType().getTypeName());
        }
        else if(at instanceof AnnotatedWildcardType) {
            System.out.println(header+"?");
            final AnnotatedWildcardType awt = (AnnotatedWildcardType)at;
            AnnotatedType[] bounds=awt.getAnnotatedLowerBounds();
            if(bounds==null || bounds.length==0) {
                bounds=awt.getAnnotatedUpperBounds();
                if(bounds==null || bounds.length==0) return;
                System.out.println(header+"extends");
            }
            else System.out.println(header+"super");
            header+="\t";
            for(AnnotatedType b: bounds) fullType(header, b);
        }
        else {
            assert at.getType().getClass()==Class.class;
            System.out.println(header+at.getType().getTypeName());
        }
    }
    

    It works flawlessly with your example fields.

    这篇关于如果数组在List或Map中注释,则Java 8 TYPE_USE注释不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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