Java 从一个对象到另一个对象的条件映射?

Java conditional mapping from one object to another?

希望构建一个 API 让客户端指定他们想要从内部域对象投影到外部域资源的字段

DB --> Foo Entity --> Foo Mapper --> Foo Resource

客户端发送一个请求参数叫fieldsToProject

例如

fieldsToProject: ["id", "name", "description", "basePrice", "unitPrice", "manufacturer"]

我写了一个非常粗糙的方法,但它是这样工作的

public FooResource toProjectedFooResource(Foo foo, List<String> fieldsToProject) {
    FooResource resource = new FooResource();

    if (fieldsToProject.contains("id")) {
        resource.setId(foo.getId());
    }

    if (fieldsToProject.contains("name")) {
        resource.setName(foo.getName());
    }

    if (fieldsToProject.contains("basePrice")) {
        resource.setBasePrice(foo.getBasePrice());
    }

    if (fieldsToProject.contains("unitPrice")) {
        resource.setUnitPrice(foo.getUnitPrice());
    }
    
    //etc.
    return resource;
}

有没有更简洁或更酷的方法来执行此操作而无需使用所有这些 if 语句的 400 行函数?

此外,如果客户端发送的字段拼写或大小写不正确,那么解决方案应该忽略它,而不是抛出异常。

注意我正在使用 Spring Boot 2.3 和 Spring Hateoas + Rest

您可以为此使用反射。我做了一些简单的例子让你看看它是如何工作的:

public class Main {

    public static void main(String[] args) {
        List<String> fieldsToProject = Arrays.asList("test1");
        Test input = new Test();
        input.setTest1("1234");
        input.setTest2("5678");


        Test result = new Test();
        for (String field : fieldsToProject) {
            try {
                //Fields need to be public for this to work
                Field inputField = input.getClass().getField(field);
                Field outputField = result.getClass().getField(field);
                outputField.set(inputField.get(input), result);
            } catch (Exception e) {
                e.printStackTrace();
            }

            try {
                //TODO: Place here some function to change field to camel case
                String fieldCamelCase = "Test1";
                Method inputGetMethod = Arrays.stream(input.getClass().getMethods())
                                       .filter(x -> x.getName().equals("get" + fieldCamelCase))
                                       .findFirst().orElseGet(null);

                Method outputSetMethod = Arrays.stream(input.getClass().getMethods())
                                       .filter(x -> x.getName().equals("set" + fieldCamelCase))
                                       .findFirst().orElseGet(null);

                Object value = inputGetMethod.invoke(input);
                outputSetMethod.invoke(result, value);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        System.out.println(result.getTest1());
        System.out.println(result.getTest2());
    }

}



public class Test {
    private String test1;
    private String test2;

    public String getTest1() {
        return test1;
    }

    public Test setTest1(String test1) {
        this.test1 = test1;
        return this;
    }

    public String getTest2() {
        return test2;
    }

    public Test setTest2(String test2) {
        this.test2 = test2;
        return this;
    }
}

它不会涵盖所有情况,但它是一个起点。

使用反射确实可以创建更紧凑的代码。我的方法是这样的:

    public FooResource toProjectedFooResource(Foo foo, List<String> fieldsToProject) {
        FooResource fr = new FooResource();
        for (Field field : foo.getClass().getDeclaredFields()) {
            if (fieldsToProject.contains(field.getName())) {
                try {
                    // Notice the use property descriptor for simplicity instead of constructing the getter setter method name by ourselves
                    new PropertyDescriptor(field.getName(), FooResource.class).getWriteMethod().invoke(fr,
                            new PropertyDescriptor(field.getName(), Foo.class).getReadMethod().invoke(foo, (Object[]) null));
                } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException
                        | IntrospectionException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }
        return fr;
    }

我会为 fieldsToProject 使用 HashSet 而不是列表,它的性能会更好。