爬虫做视频网站,外发加工网磨字,vs做网站用3层架构,wordpress 显示菜单最近#xff0c;我们为您提供了新的HTTP框架HttpMate。 在引言文章中 #xff0c;我们将请求和响应映射到域对象称为“最复杂的技术细节”#xff0c;以及如何通过另一个伴侣MapMate帮助我们。 实际上#xff0c;在将请求属性映射到您的域对象时#xff0c;MapMate减轻了… 最近我们为您提供了新的HTTP框架HttpMate。 在引言文章中 我们将请求和响应映射到域对象称为“最复杂的技术细节”以及如何通过另一个伴侣MapMate帮助我们。 实际上在将请求属性映射到您的域对象时MapMate减轻了HttpMate的负担。 它负责将响应转换为适当的格式JSONXMLYAML等从本质上执行反序列化和序列化但还要执行更多的工作。 在本文中我们将重点介绍MapMate如何帮助我们以可控和可预测的方式处理反序列化请求/响应对象。 自定义基元 让我们回顾一下上一篇文章中的示例 我们有一个简单的UseCase发送电子邮件。 为此我们需要一个Email对象该对象应具有 发件人 接收者 学科 身体 所有这些字段都可以表示为字符串甚至可以表示为字节数组。 选择用来表示数据的通用类型越多以后解释它的可能性就越大。 想象一下以下方法 public Object sendEmail(final Object sender, final Object receiver, final Object subject, final Object body) {...
} 这给我们留下了很多未解决的问题 是发件人instanceOf字符串还是字节[] 编码是什么 拉链被压缩了吗 清单继续。 尽管在某些情况下这可能是适当的但我敢打赌您会更满意 public String sendEmail(final String sender, final String receiver, final String subject, final String body) {...
} 后者留出了较少的解释空间例如我们不再需要假设编码或完全质疑参数的类型。 但是它仍然是模棱两可的发件人字段是否带有用户名或她的电子邮件地址 同样的歧义是编写单元测试时产生无限不确定性的原因……在某种程度上使用随机字符串生成器测试了一种方法人们知道该方法只能接受电子邮件地址。 对于人员和编译器以下方法签名在歧义方面做得更好 public Receipt sendEmail(final EmailAddress sender, final EmailAddress receiver, final Subject subject, final Body body) {...
} 我们可以以相同的方式相信字符串是字符串整数是整数现在我们可以相信EmailAddress是电子邮件地址主题实际上是主题–它们成为了send email方法的自定义原语。 发件人和接收者不是面面俱到的“字符串”它们与“主题”和“正文”有很大不同。 它们是电子邮件地址我们可以通过使用一些理智的正则表达式来验证其值来表示它们。 提防ReDoS 使用工厂方法作为创建“始终有效”对象的方法的合理性已得到广泛讨论和验证。 考虑到这一点我们将为示例用例创建一个EmailAddress类然后将其用作Sender和Receiver字段的自定义原始类型。 public final class EmailAddress {private final String value;private EmailAddress(final String value) {this.value value;}public static EmailAddress fromStringValue(final String value) {final String validated EmailAddressValidator.ensureEmailAddress(value, emailAddress);return new EmailAddress(validated);}
} 由于–唯一的实例变量是私有的且是最终变量因此只能使用私有的构造函数进行赋值该私有构造函数只能在验证输入之前使用公共工厂方法从类外部调用该方法会验证输入然后将其传递给构造函数–我们可以请确保每当我们收到EmailAddress实例时它都是有效的。 如果您现在对EmailAddressValidator实现感到好奇请确保签出此示例项目的源代码 。 现在我们的域对象不仅可以使用默认原语例如StringDoubleInteger等还可以使用自定义原语例如EmailAddress和BodySubject等。通常尽管我们需要能够将域对象存储在数据库中或将其传达给其他服务或UI。 尽管没有其他方知道名为EmailAddress的自定义基元。 因此我们需要它的“表示形式”即HTTP持久性和人性化的东西–字符串。 public final class EmailAddress {private final String value;public static EmailAddress fromStringValue(final String value) {final String validated EmailAddressValidator.ensureEmailAddress(value, emailAddress);return new EmailAddress(validated);}public String stringValue() {return this.value;}
} 我们添加的方法“ stringValue”是自定义基元的字符串表示形式。 现在我们可以发送EmailAddress的“ stringValue”然后根据接收到的值对其进行重构。 本质上“ fromString”和“ stringValue”方法分别是EmailAddress的“反序列化”和“序列化”机制。 按照这种方法我们还可以为电子邮件的正文和主题创建自定义基元 public final class Body {private final String value;public static Body fromStringValue(final String value) {final String emailAddress LengthValidator.ensureLength(value, 1, 1000, body);return new Body(emailAddress);}public String stringValue() {return this.value;}
}public final class Subject {private final String value;public static Subject fromStringValue(final String value) {final String validated LengthValidator.ensureLength(value, 1, 256, subject);return new Subject(validated);}public String stringValue() {return this.value;}
}数据传输对象 有了我们的自定义基元我们现在可以创建适当的数据传输对象–电子邮件这是非常简单的任务因为它基本上是一个不变的结构 public final class Email {public final EmailAddress sender;public final EmailAddress receiver;public final Subject subject;public final Body body;
} 相同的“始终有效”的方法也适用于数据传输对象除了在这里由于我们利用了自定义基元因此时间更短。 DTO的工厂方法可以像验证强制字段的存在一样简单也可以像应用跨字段验证一样复杂。 public final class Email {public final EmailAddress sender;public final EmailAddress receiver;public final Subject subject;public final Body body;public static Email restore(final EmailAddress sender,final EmailAddress receiver,final Subject subject,final Body body) {RequiredParameterValidator.ensureNotNull(sender, sender);RequiredParameterValidator.ensureNotNull(receiver, receiver);RequiredParameterValidator.ensureNotNull(body, body);return new Email(sender, receiver, subject, body);
} 不幸的是现代反序列化和验证框架无法与这种DTO一起使用。 这是一个JSON示例如果您使用默认配置将电子邮件DTO馈送到这样的框架则可能会获得最佳效果 {sender: {value: senderexample.com},receiver: {value: receiverexample.com},subject: {value: subject},body: {value: body}
} 虽然人们期望的是 {sender: senderexample.com,receiver: receiverexample.com,subject: subject,body: body
} 尽管可以使用大量样板代码来缓解此问题但是验证是另一种野兽当您想从服务器“立即报告所有验证错误”时验证就变得致命。 为什么不立即告诉用户发送方和接收方均无效而不是发送寻求许可A38的请求发送给用户。 实际上这就是我们在尝试编写现代微服务时同时又遵循Clean Code的最佳实践的感觉Domain Driven DesignDomain Driven Security的“始终有效”方法…… 这就是MapMate需要解决的问题。 MapMate 与HttpMate一样我们确保提供一个易于构建的构建器同时保留细粒度定制的可能性。 这是使我们的电子邮件示例序列化反序列化和验证我们的自定义基元和DTO的绝对最低配置。 public static MapMate mapMate() {return MapMate.aMapMate(com.envimate.examples.email_use_case).usingJsonMarshallers(new Gson()::toJson, new Gson()::fromJson).build();
} 这部分将使以下JSON成为有效请求 {sender: senderexample.com,receiver: receiverexample.com,subject: Hello world!,body: Hello from Sender to Receiver!
} 您必须指定要扫描递归的程序包和一对非编组器。 可以是任何可以从Map生成字符串的内容反之亦然。 这是使用ObjectMapper的示例 final ObjectMapper objectMapper new ObjectMapper();
return MapMate.aMapMate(com.envimate.examples.email_use_case).usingJsonMarshallers(value - {try {return objectMapper.writeValueAsString(value);} catch (JsonProcessingException e) {throw new UnsupportedOperationException(Could not parse value value, e);}}, new Unmarshaller() {OverridepublicT unmarshal(final String input, final Classtype) {try {return objectMapper.readValue(input, type);} catch (final IOException e) {throw new UnsupportedOperationException(Could not parse value input to type type, e);}}}).withExceptionIndicatingValidationError(CustomTypeValidationException.class).build(); 承诺的验证异常聚合又如何呢 在我们的示例中如果自定义原语或DTO无效则所有验证都返回CustomTypeValidationException的实例。 添加以下行以指示MapMate将您的Exception类识别为验证错误的指示。 public static MapMate mapMate() {return MapMate.aMapMate(com.envimate.examples.email_use_case).usingJsonMarshallers(new Gson()::toJson, new Gson()::fromJson).withExceptionIndicatingValidationError(CustomTypeValidationException.class).build();
} 现在如果我们尝试以下请求 {sender: not-a-valid-sender-value,receiver: not-a-valid-receiver-value,subject: Hello world!,body: Hello from Sender to Receiver!
} 我们将收到以下答复 HTTP/1.1 400 Bad Request
Date: Tue, 04 Jun 2019 18:30:51 GMT
Transfer-encoding: chunked{message:receiver: Invalid email address: not-a-valid-receiver-value,sender: Invalid email address: not-a-valid-sender-value}最后的话 此处提供的MapMate构建器可简化初始使用。 但是所有描述的默认值都是可配置的此外您还可以从“自定义基元”和“ DTO”中排除包和类可以配置哪些异常被视为“验证错误”以及如何对其进行处理还可以为“自定义”指定其他方法名称原始序列化或者提供您的lambda来同时进行这两个序列的反序列化。 有关MapMate的更多示例和详细信息请查看MapMate存储库 。 让我们知道您的想法以及您接下来想在MapMate中看到的功能 翻译自: https://www.javacodegeeks.com/2019/08/deserialization-and-validation-of-custom-primitives-and-dtos.html