在嵌套对象中查找特定类型的属性

时间:2017-05-26 17:20:15

标签: java reflection

我经常需要处理包含其他DTO的DTO,并且我想要扫描一个对象的属性(以及它们自己的属性,递归地)并检索类Bingo的每个可访问对象在整个层次结构中。

例如,当我有以下内容时:

public static class Bingo {
    // the one I want to get
}

public static class Foo {
    private Bar bar;
    private Bingo bingo;
    private List<Bingo> bingos;

    // getters & setters
}

public static class Bar {

    private Bingo bingo;

    // getters & setters
}

我想在我的Bingo对象的属性中找到Foo的所有实例,包括Bar对象和List中的对象。

是否有方便的图书馆?

更完整的测试用例(使用一些JUnit):

public static class Bingo {
    private final int id;

    public Bingo(int in_id) {
        id = in_id;
    }

    @Override
    public String toString() {
        return "Bingo#"+String.valueOf(id);
    }

}

public static class BingoWrapper {

    private Bingo bingo;

    public Bingo getBingo() {
        return bingo;
    }

    public void setBingo(Bingo in_bingo) {
        bingo = in_bingo;
    }
}

public static class BingoFactory {

    private final List<Bingo> ALL_BINGOS = new ArrayList<>();
    private int sequence = 0;

    public Bingo createBingo(){
        Bingo l_bingo = new Bingo(sequence++);
        ALL_BINGOS.add(l_bingo);
        return l_bingo;
    }

    public BingoWrapper createBingoWrapper(){
        BingoWrapper l_bar = new BingoWrapper();
        l_bar.setBingo(createBingo());
        return l_bar;
    }

    public List<Bingo> getAllBingos(){
        return ALL_BINGOS.stream().collect(Collectors.toList());
    }

}

public static class Foo {

    private Bingo bingo;
    private BingoWrapper wrapper;
    private Bingo[] array;
    private Collection<Object> collection;
    private Map<Object,Object> map;

    public Bingo getBingo() {
        return bingo;
    }
    public void setBingo(Bingo in_bingo) {
        bingo = in_bingo;
    }
    public BingoWrapper getWrapper() {
        return wrapper;
    }
    public void setWrapper(BingoWrapper in_bar) {
        wrapper = in_bar;
    }
    public Bingo[] getArray() {
        return array;
    }
    public void setArray(Bingo[] in_array) {
        array = in_array;
    }
    public Collection<Object> getCollection() {
        return collection;
    }
    public void setCollection(Collection<Object> in_collection) {
        collection = in_collection;
    }
    public Map<Object, Object> getMap() {
        return map;
    }
    public void setMap(Map<Object, Object> in_map) {
        map = in_map;
    }
}

@Test
public void test(){
    BingoFactory l_bingoFactory = new BingoFactory();

    Foo l_foo = new Foo();
    l_foo.setBingo(l_bingoFactory.createBingo());                  // one in a field
    l_foo.setWrapper(l_bingoFactory.createBingoWrapper());         // one in a field of a field

    l_foo.setArray(new Bingo[]{l_bingoFactory.createBingo()});     // one in an array in a field

    l_foo.setCollection(Arrays.asList(
            l_bingoFactory.createBingo(),                          // one in Collection in a field
            l_bingoFactory.createBingoWrapper()));                 // one in a field of an item in a Collection in a field

    Map<Object,Object> l_map = new HashMap<>();
    l_foo.setMap(l_map);
    l_map.put("key", l_bingoFactory.createBingo());                // one as a key in a Map in a field
    l_map.put(l_bingoFactory.createBingo(), "value");              // one as a value in a Map in a field
    l_map.put("keyAgain", l_bingoFactory.createBingoWrapper());    // one wrapped in a value in a Map in a Field 
    l_map.put(l_bingoFactory.createBingoWrapper(), "valueAgain");  // one wrapped in a key in a Map in a field 

    List<Bingo> l_found = BeanUtils.scanObjectForType(l_foo, Bingo.class);   // Magic happens here

    System.out.println(l_found);                                   // for debug
    Assert.assertTrue(l_found.containsAll(l_bingoFactory.getAllBingos())); // I want them ALL
}

1 个答案:

答案 0 :(得分:0)

使用Spring's BeanUtils的解决方案:(我已经添加了一个布尔值来决定输入类的对象是否需要扫描。(即,您希望Bingo个对象包含其他对象吗? Bingo类型的对象?))

public static <T> List<T> scanObjectForType(Object in_object, Class<T> in_type, boolean in_scanSameType){
    return scanObjectForType(in_object, in_type, in_scanSameType, new HashSet<>());
}

private static <T> List<T> scanObjectForType(Object in_object, Class<T> in_type, boolean in_scanSameType, Set<Object> in_alreadyScanned){
    if(in_type == null){
        throw new IllegalArgumentException("in_type should not be null");
    }
    if(in_object instanceof Class){
        throw new IllegalArgumentException("in_type should not be a Class");
    }
    if(in_object == null || in_alreadyScanned.contains(in_object)){
        return Collections.emptyList();
    }
    in_alreadyScanned.add(in_object);   // to prevent infinite loop when inner object references outer object

    if(in_type.isInstance(in_object)){
        return Collections.singletonList((T) in_object);
    }

    List<T> l_result = new ArrayList<>();
    if(in_type.isInstance(in_object)){
        l_result.add((T) in_object);
        if(!in_scanSameType){
            return l_result;
        }
    }
    if(in_object instanceof Object[]){
        for(Object l_item : (Object[]) in_object){
            l_result.addAll(scanObjectForType(l_item, in_type, in_scanSameType, in_alreadyScanned));
        }
    } else if(in_object instanceof Collection){
        for(Object l_item : (Collection<Object>) in_object){
            l_result.addAll(scanObjectForType(l_item, in_type, in_scanSameType, in_alreadyScanned));
        }
    } else if(in_object instanceof Map){
        Map<Object,Object> l_map = (Map<Object,Object>) in_object;
        for(Map.Entry<Object, Object> l_entry : l_map.entrySet()){
            l_result.addAll(scanObjectForType(l_entry.getKey(), in_type, in_scanSameType, in_alreadyScanned));      
            l_result.addAll(scanObjectForType(l_entry.getValue(), in_type, in_scanSameType, in_alreadyScanned));
        }
    } else {
        PropertyDescriptor[] l_descriptors = org.springframework.beans.BeanUtils.getPropertyDescriptors(in_object.getClass());
        for(PropertyDescriptor l_descriptor : l_descriptors){
            Method l_readMethod = l_descriptor.getReadMethod();
            if(l_readMethod != null){
                try {
                    Object l_readObject = l_readMethod.invoke(in_object);
                    if(l_readObject != null 
                            && !l_readObject.equals(in_object)     // prevents infinite loops
                            && !(l_readObject instanceof Class)){  // prevents weird loops when accessing properties of classes
                        l_result.addAll(scanObjectForType(l_readObject,in_type, in_scanSameType, in_alreadyScanned));
                    }
                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
                    // too bad but continue
                    LOGGER.warn("Got an error trying to access field : ", e);
                    continue;
                }
            }
        }
    }
    return l_result;
}

其局限性:

  • 仅扫描具有公共访问者的属性
  • 不扫描Class类型(以防止扫描整个ClassLoader的类,并且因为用例是面向DTO的。)
  • 依赖递归。我想在一个BeanVisitor嵌套bean的循环上运行Set对象可能更漂亮。
  • 将扫描可能不是属性的getter方法返回的对象。
  • 未经遗传测试。