美文网首页
源码Java干货分享 | 如何用Bull转换任意类型的Java

源码Java干货分享 | 如何用Bull转换任意类型的Java

作者: 源码时代官方 | 来源:发表于2020-05-08 18:11 被阅读0次

    Bull(Bean Utils Light Library)是一个将数据从一个对象递归地复制到另一个对象的Java-bean到Javabean转换器。它是通用的,灵活的,可重用的,可配置的,而且非常快。

    它是唯一能够在没有任何自定义配置的情况下转换可变、不可变和混合bean的库。

    本文介绍了如何使用它,并给出了每个可用特性的具体示例。

    1.依赖性
    <dependency>

    <groupId>com.hotels.beans</groupId>
    
    <artifactId>bull-bean-transformer</artifactId>
    
    <version>1.7.1</version>
    

    </dependency>

    该项目提供两个不同的构建。,与jdk 8(或以上)jdk 11或者更高。

    库的最新版本可以从自述档案或来自变化量g(万一你需要一个jdk 8-兼容版本请参阅Changelog-JDK 8).

    2.特征
    本文中解释的宏特性如下:

    bean变换

    bean验证

    3.bean变换
    bean转换由Transformer对象,该对象可以执行以下指令获得:

    BeanTransformer transformer = new BeanUtils().getTransformer();

    一旦我们有了BeanTransformer对象实例,我们可以使用该方法transform把我们的对象复制到另一个。

    使用的方法是:K transform(T sourceObj, Class<K> targetObject);其中,第一个参数表示源对象,第二个参数表示目标类。

    例如,给定源和目标类:

    public class FromBean { public class ToBean {

    private final String name; public BigInteger id;

    private final BigInteger id; private final String name;

    private final List<FromSubBean> subBeanList; private final List<String> list;

    private List<String> list; private final List<ImmutableToSubFoo> nestedObjectList;

    private final FromSubBean subObject; private ImmutableToSubFoo nestedObject;

    // all args constructor // constructors

    // getters and setters... // getters and setters

    } }

    转换可以通过以下代码行获得:

    ToBean toBean = new BeanUtils().getTransformer().transform(fromBean, ToBean.class);

    请注意,字段顺序与此无关。

    不同字段名副本
    给定两个字段数相同但名称不同的类:

    public class FromBean { public class ToBean {

    private final String name; private final String differentName;

    private final int id; private final int id;

    private final List<FromSubBean> subBeanList; private final List<ToSubBean> subBeanList;

    private final List<String> list; private final List<String> list;

    private final FromSubBean subObject; private final ToSubBean subObject;

    // all constructors // all args constructor

    // getters... // getters...

    } }

    我们需要定义适当的字段映射并将其传递给Transformer目的:

    // the first parameter is the field name in the source object

    // the second one is the the field name in the destination one

    FieldMapping fieldMapping = new FieldMapping("name", "differentName");

    Tansformer transformer = new BeanUtils().getTransformer().withFieldMapping(fieldMapping);

    然后,我们可以执行转换:

    ToBean toBean = transformer.transform(fromBean, ToBean.class);

    源对象和目标对象之间的映射字段
    案例1:必须从源对象中的嵌套类中检索目标字段值

    假设对象FromSubBean声明如下:

    public class FromSubBean {

    private String serialNumber;

    private Date creationDate;

    // getters and setters...

    }

    我们的源类和目标类描述如下:

    public class FromBean { public class ToBean {

    private final int id; private final int id;

    private final String name; private final String name;

    private final FromSubBean subObject; private final String serialNumber;

                                                               private final Date creationDate;                   
    

    / all args constructor // all args constructor

    // getters... // getters...

    } }

    .而字段的值serialNumber和creationDate进入ToBean对象需要从subObject,可以定义分隔的属性点的整个路径:

    FieldMapping serialNumberMapping = new FieldMapping("subObject.serialNumber", "serialNumber");

    FieldMapping creationDateMapping = new FieldMapping("subObject.creationDate", "creationDate");

    ToBean toBean = new BeanUtils().getTransformer()

                   .withFieldMapping(serialNumberMapping, creationDateMapping)
    
                   .transform(fromBean, ToBean.class);
    

    案例2:必须从源类根检索目标字段值(在嵌套类中)
    前面的示例突出显示了如何从源对象中获取值;而这个示例则解释了如何将值放入嵌套对象中。

    给予:

    public class FromBean { public class ToBean {

    private final String name; private final String name;

    private final FromSubBean nestedObject; private final ToSubBean nestedObject;

    private final int x;

    // all args constructor // all args constructor

    // getters... // getters...

    } }

    以及:

    public class ToSubBean {

    private final int x;

    // all args constructor

    } // getters...

    假设x应该映射到字段:x载于ToSubBean对象,必须将字段映射定义为:

    FieldMapping fieldMapping = new FieldMapping("x", "nestedObject.x");

    然后,我们只需要把它传递给Transformer并执行转换:

    ToBean toBean = new BeanUtils().getTransformer()

                     .withFieldMapping(fieldMapping)
    

    .transform(fromBean, ToBean.class);

    定义构造器args的不同字段名
    还可以通过添加@ConstructorArg构造函数参数旁边的注释。

    这个@ConstructorArg接受源对象中对应字段的名称作为输入。

    public class FromBean { public class ToBean {

    private final String name; private final String differentName;

    private final int id; private final int id;

    private final List<FromSubBean> subBeanList; private final List<ToSubBean> subBeanList;

    private final List<String> list; private final List<String> list;

    private final FromSubBean subObject; private final ToSubBean subObject;

    // all args constructor

    // getters...

                                                               public ToBean(@ConstructorArg("name") final String differentName,
    
                                                                        @ConstructorArg("id") final int id,
    

    } @ConstructorArg("subBeanList") final List<ToSubBean> subBeanList,

                                                                       @ConstructorArg(fieldName ="list") final List<String> list,
    
                                                                        @ConstructorArg("subObject") final ToSubBean subObject) {
    
                                                                        this.differentName = differentName;
    
                                                                        this.id = id;
    
                                                                        this.subBeanList = subBeanList;
    
                                                                        this.list = list;
    
                                                                        this.subObject = subObject;
    
                                                                    }
    
                                                                    // getters...          
    
                                                            }
    

    然后:

    ToBean toBean = beanUtils.getTransformer().transform(fromBean, ToBean.class);

    对特定字段Lambda函数应用自定义转换
    我们知道,在现实生活中,很少需要在两个Javabean之间复制几乎相同的信息,经常会发生这样的情况:

    目标对象的结构与源对象完全不同。

    在复制特定字段值之前,我们需要对它执行一些操作。

    必须验证目标对象的字段

    目标对象有一个比源对象更多的字段,需要填充来自不同源的内容。

    公牛给出了在特定领域执行任何一种操作的可能性,实际上是利用了Lambda表达式,开发人员可以定义自己的方法,在复制a值之前应用该方法。

    让我们用一个例子更好地解释它:

    鉴于以下情况Source班级:

    public class FromFoo {

    private final String id;

    private final String val;

    private final List<FromSubFoo> nestedObjectList;

    // all args constructor

    // getters

    }

    以及以下内容Destination班级:

    public class MixedToFoo {

    public String id;

    @NotNull

    private final Double val;

    // constructors

    // getters and setters

    }

    假设val在我们的变压器中,场需要乘以一个随机值,我们有两个问题:

    这个val字段的类型与Source对象,确实是String一个是Double

    我们需要指导图书馆如何应用数学运算。

    这很简单,您只需定义自己的lambda表达式即可:

    FieldTransformer<String, Double> valTransformer =

     new FieldTransformer<>("val",
    
                      n -> Double.valueOf(n) * Math.random());
    

    表达式将应用于具有名称的字段。val在目标对象中。

    最后一步是将函数传递给Transformer例如:

    MixedToFoo mixedToFoo = new BeanUtils().getTransformer()

      .withFieldTransformer(valTransformer)
    
      .transform(fromFoo, MixedToFoo.class);
    

    在源对象中缺少字段的情况下指定默认值
    有时,在目标对象比源对象具有更多字段的情况下会发生这种情况;在本例中,BeanUtils库将引发异常,通知它它们无法执行映射,因为它们不知道必须从何处检索值。

    典型的情况如下:

    public class FromBean { public class ToBean {

    private final String name; @NotNull

    private final BigInteger id; public BigInteger id;

                                                               private final String name;                
    
                                                               private String notExistingField; // this will be null and no exceptions will be raised
    

    // constructors... // constructors...

    // getters... // getters and setters...

    } }

    但是,我们可以将库配置为为字段类型分配默认值(如:0为int类型,null为String等等)

    ToBean toBean = new BeanUtils().getTransformer()

                      .setDefaultValueForMissingField(true)
    
       .transform(fromBean, ToBean.class);
    

    在源对象中缺少字段的情况下应用转换函数
    下面的示例演示如何在源对象中不存在的字段上分配默认值(或lambda函数的结果):

    public class FromBean { public class ToBean {

    private final String name; @NotNull

    private final BigInteger id; public BigInteger id;

                                                               private final String name;                
    
                                                               private String notExistingField; // this will have value: sampleVal
    

    // all args constructor // constructors...

    // getters... // getters and setters...

    } }

    我们需要做的是分配一个FieldTransformer函数到特定字段:

    FieldTransformer<String, String> notExistingFieldTransformer =

                    new FieldTransformer<>("notExistingField", () -> "sampleVal");
    

    上述函数将为该字段指定一个固定值。notExistingField,但是我们可以返回任何内容,例如,我们可以调用一个外部方法,该方法返回一组操作后获得的值,如下所示:

    FieldTransformer<String, String> notExistingFieldTransformer =

                    new FieldTransformer<>("notExistingField", () -> calculateValue());
    

    但是,最后,我们只需要将它传递给Transformer.

    ToBean toBean = new BeanUtils().getTransformer()

    .withFieldTransformer(notExistingFieldTransformer)

    .transform(fromBean, ToBean.class);

    将转换函数应用于嵌套对象中的特定字段
    案例1:应用于嵌套类中特定字段的Lambda转换函数
    给予:

    public class FromBean { public class ToBean {

    private final String name; private final String name;

    private final FromSubBean nestedObject; private final ToSubBean nestedObject;

    // all args constructor // all args constructor

    // getters... // getters...

    } }

    以及:

    public class FromSubBean { public class ToSubBean {

    private final String name; private final String name;

    private final long index; private final long index;

    // all args constructor // all args constructor

    // getters... // getters...

    } }

    假设lambda转换函数只应用于字段name载于ToSubBean对象时,必须将转换函数定义为:

    FieldTransformer<String, String> nameTransformer = new FieldTransformer<>("nestedObject.name", StringUtils::capitalize);

    然后,将函数传递给Transformer目的:

    ToBean toBean = new BeanUtils().getTransformer()

                      .withFieldTransformer(nameTransformer)
    
                      .transform(fromBean, ToBean.class);
    

    案例2:应用于特定字段的Lambda转换函数与其位置无关
    想象一下在我们Destination类中,有更多相同名称的字段出现在不同的类中,并且我们希望对所有这些类应用相同的转换函数;有一个允许这样做的设置。

    以上面的对象为例,并假设我们希望利用name字段独立于它们的位置,我们可以这样做:

    FieldTransformer<String, String> nameTransformer = new FieldTransformer<>("name", StringUtils::capitalize);

    然后:

    ToBean toBean = beanUtils.getTransformer()

      .setFlatFieldTransformation(true)
    
                    .withFieldTransformer(nameTransformer)
    
                    .transform(fromBean, ToBean.class);
    

    静态变压器功能:
    BeanUtils 提供了转换器方法的“静态”版本,当需要在复合lambda表达式中应用时,它可以是一个附加值。

    例如:

    List<FromFooSimple> fromFooSimpleList = Arrays.asList(fromFooSimple, fromFooSimple);

    这一转变应该由以下几个方面来完成:

    BeanTransformer transformer = new BeanUtils().getTransformer();

    List<ImmutableToFooSimple> actual = fromFooSimpleList.stream()

                .map(fromFoo -> transformer.transform(fromFoo, ImmutableToFooSimple.class))
    
                .collect(Collectors.toList());
    

    由于这个特性,可以为给定的对象类创建特定的转换器函数:

    Function<FromFooSimple, ImmutableToFooSimple> transformerFunction = BeanUtils.getTransformer(ImmutableToFooSimple.class);

    然后,可以将列表转换为:

    List<ImmutableToFooSimple> actual = fromFooSimpleList.stream()

                .map(transformerFunction)
    
                .collect(Collectors.toList());
    

    但是,可能发生的情况是,我们已经配置了一个BeanTransformer具有多个字段、映射和转换函数的实例,我们也希望在此转换中使用它,因此我们需要做的是从我们的转换器创建转换器函数:

    BeanTransformer transformer = new BeanUtils().getTransformer()

    .withFieldMapping(new FieldMapping("a", "b"))

    .withFieldMapping(new FieldMapping("c", "d"))

    .withTransformerFunction(new FieldTransformer<>("locale", Locale::forLanguageTag));

    Function<FromFooSimple, ImmutableToFooSimple> transformerFunction = BeanUtils.getTransformer(transformer, ImmutableToFooSimple.class);

    List<ImmutableToFooSimple> actual = fromFooSimpleList.stream()

                .map(transformerFunction)
    
                .collect(Collectors.toList());
    

    启用Java Bean验证
    库提供的特性之一是bean验证。它包括检查转换后的对象是否满足在其上定义的约束。验证在两种默认情况下都有效。javax.constraints还有定制的。

    假设字段id在FromBean实例是null.

    public class FromBean { public class ToBean {

    private final String name; @NotNull

    private final BigInteger id; public BigInteger id;

                                                               private final String name;
    

    // all args constructor // all args constructor

    // getters... // getters and setters...

    } }

    添加以下配置后,将在转换过程结束时执行验证,在我们的示例中,将抛出一个异常,通知对象无效:

    ToBean toBean = new BeanUtils().getTransformer()

                       .setValidationEnabled(true)
    
                       .transform(fromBean, ToBean.class);
    

    在现有实例上复制
    即使库能够创建给定类的新实例,并用给定对象中的值填充它,也可能需要在已有实例上注入值,因此给出以下Java bean:

    public class FromBean { public class ToBean {

    private final String name; private String name;

    private final FromSubBean nestedObject; private ToSubBean nestedObject;

    // all args constructor // constructor

    // getters... // getters and setters...

    } }

    如果我们需要对已经存在的对象执行副本,则只需将类实例传递给transform职能:

    ToBean toBean = new ToBean();

    new BeanUtils().getTransformer().transform(fromBean, toBean);

    给定字段集上的跳跃变换
    如果我们将源对象值复制到已经存在的实例中(某些值已经设置),则可能需要避免转换操作覆盖现有值。下面的示例说明了如何做到这一点:

    public class FromBean { public class ToBean {

    private final String name; private String name;

    private final FromSubBean nestedObject; private ToSubBean nestedObject;

    // all args constructor // constructor

    // getters... // getters and setters...

    } }

    public class FromBean2 {

    private final int index;

    private final FromSubBean nestedObject;

    // all args constructor

    // getters...

    }

    如果我们需要跳过一组字段的转换,只需将它们的名称传递给skipTransformationForField方法。例如,如果我们想跳过字段上的转换nestedObject,这就是我们需要做的:

    ToBean toBean = new ToBean();

    new BeanUtils().getTransformer()

      .skipTransformationForField("nestedObject")
    
      .transform(fromBean, toBean);
    

    此功能允许转换保存来自不同源的数据的对象.

    为了更好地解释这个函数,让我们假设ToBean(上文定义)应改为:

    name字段值已从FromBean对象

    nestedObject字段值已从FromBean2对象

    可以通过这样做来实现目标:

    // create the destination object

    ToBean toBean = new ToBean();

    // execute the first transformation skipping the copy of: 'nestedObject' field that should come from the other source object

    new BeanUtils().getTransformer()

      .skipTransformationForField("nestedObject")
    
      .transform(fromBean, toBean);
    

    // then execute the transformation skipping the copy of: 'name' field that should come from the other source object

    new BeanUtils().getTransformer()

      .skipTransformationForField("name")
    
      .transform(fromBean2, toBean);
    

    字段类型转换
    在字段类型与源类和目标不同的情况下,我们有以下示例:

    public class FromBean {

    public class ToBean {

    private final String index;

    private int index;

    // all args constructor

    // constructor

    // getters...

    // getters and setters...

    }

    }

    它可以使用特定的转换函数进行转换:
    FieldTransformer<String, Integer> indexTransformer = new FieldTransformer<>("index", Integer::parseInt);

    ToBean toBean = new BeanUtils()

    .withFieldTransformer(indexTransformer)

    .transform(fromBean, ToBean.class);

    用Builder模式实现Java Bean的转换
    库使用不同类型的Builder模式支持JavaBean的转换:标准模式(默认支持)和自定义模式。让我们详细了解它们,以及如何启用自定义Builder类型转换。

    让我们从标准一默认支持:

    public class ToBean {

    private final Class<?> objectClass;
    
    private final Class<?> genericClass;
    
    ToBean(final Class<?> objectClass, final Class<?> genericClass) {
    
        this.objectClass = objectClass;
    
        this.genericClass = genericClass;
    
    }
    
    public static ToBeanBuilder builder() {
    
        return new ToBean.ToBeanBuilder();
    
    }
    
    // getter methods
    
    public static class ToBeanBuilder {
    
        private Class<?> objectClass;
    
        private Class<?> genericClass;
    
        ToBeanBuilder() {
    
        }
    
        public ToBeanBuilder objectClass(final Class<?> objectClass) {
    
            this.objectClass = objectClass;
    
            return this;
    
        }
    
        public ToBeanBuilder genericClass(final Class<?> genericClass) {
    
            this.genericClass = genericClass;
    
            return this;
    
        }
    
        public com.hotels.transformer.model.ToBean build() {
    
            return new ToBean(this.objectClass, this.genericClass);
    
        }
    
    }
    

    }

    如前所述,这不需要额外的设置,因此可以通过执行以下操作来执行转换:

    ToBean toBean = new BeanTransformer()

                         .transform(sourceObject, ToBean.class);
    

    自定义生成器模式:

    public class ToBean {

    private final Class<?> objectClass;

    private final Class<?> genericClass;

    ToBean(final ToBeanBuilder builder) {

    this.objectClass = builder.objectClass;

    this.genericClass = builder.genericClass;

    }
    

    public static ToBeanBuilder builder() {

    return new ToBean.ToBeanBuilder();

    }
    

    // getter methods

    public static class ToBeanBuilder {

    private Class<?> objectClass;

    private Class<?> genericClass;

    ToBeanBuilder() {

    }

    public ToBeanBuilder objectClass(final Class<?> objectClass) {

    this.objectClass = objectClass;

    return this;

    }

    public ToBeanBuilder genericClass(final Class<?> genericClass) {

    this.genericClass = genericClass;

    return this;

    }

    public com.hotels.transformer.model.ToBean build() {

    return new ToBean(this);

    }

    }

    }

    要转换上面的Bean,要使用的指令是:

    ToBean toBean = new BeanTransformer()

                         .setCustomBuilderTransformationEnabled(true)
    
                         .transform(sourceObject, ToBean.class);
    

    4.bean验证

    对一组规则的类验证是非常宝贵的,特别是当我们需要确保对象数据符合我们的期望时。

    “字段验证”方面是Bull提供的特性之一,它是完全自动的--您只需要用一个现有的javax.validation.约束(或定义一个自定义的约束)对您的字段进行注释,然后对其执行验证。

    给定以下bean:

    public class SampleBean {

    @NotNull

    private BigInteger id;

    private String name;

    // constructor

    // getters and setters...

    }

    上述对象的实例:

    SampleBean sampleBean = new SampleBean();

    和一行代码,例如:

    new BeanUtils().getValidator().validate(sampleBean);

    这将抛出一个InvalidBeanException,作为田野id是null.

    结语

    我试着用例子来解释如何使用Bull项目提供的主要功能。然而,查看完整的源代码可能更有帮助。

    可以找到更多的示例,查看在Bull项目上实现的测试用例。这里.

    GitHub还包含一个示例弹簧靴项目,该项目使用库在不同层之间转换请求/响应对象,可以找到这里.

    相关文章

      网友评论

          本文标题:源码Java干货分享 | 如何用Bull转换任意类型的Java

          本文链接:https://www.haomeiwen.com/subject/dylqnhtx.html