我在Glasshfish 5中运行一个Web应用程序,它使用Jersey JAX-RS提供REST端点。它还使用bean验证。我遇到的问题是任何以“get”开头的方法总是在返回具有@Valid注释的东西时被调用。
例:
@Path("/hello")
public class HelloResource {
@GET
public @Valid HelloMessage getSomething() {
HelloMessage helloMessage = new HelloMessage();
helloMessage.setMessage("Hello World!");
return helloMessage;
}
@POST
public @Valid HelloMessage updateMessage(@Valid HelloMessage message) {
return message;
}
}
如果我对/ hello执行POST,您将看到在调用updateMessage之前调用getSomething方法。如果我在getSomething方法的返回类型上删除@Valid注释,则不会调用getSomething。
这是根据规格吗?你基本上不会在REST类中命名以“get”开头的方法吗?
在过去,我已经在github上报告了这个问题,但从未收到回复。
https://github.com/eclipse-ee4j/jersey/issues/3743
其他课程:
@ApplicationPath("/")
public class HelloApplication extends Application {
@Override
public Set<Class<?>> getClasses() {
return Collections.singleton(HelloResource.class);
}
}
public class HelloMessage {
@Size(max = 100)
private String message;
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}
在https://github.com/robertatgh/stackoverflow-50658396/tree/develop找到的最小项目
因此,由于命名惯例,这被证明是一个有趣的问题。通过泽西源代码进行调试,您将看到它通过
org.glassfish.jersey.server.validation.internal.DefaultConfiguredValidator.onValidate(ValidationInterceptorContext)行:166
public void onValidate(final ValidationInterceptorContext ctx) {
final Object resource = ctx.getResource();
final Invocable resourceMethod = ctx.getInvocable();
final Object[] args = ctx.getArgs();
final Set<ConstraintViolation<Object>> constraintViolations = new HashSet<>();
final BeanDescriptor beanDescriptor = getConstraintsForClass(resource.getClass());
// Resource validation.
if (beanDescriptor.isBeanConstrained()) {
constraintViolations.addAll(validate(resource));
}
if (resourceMethod != null
&& configuration.getBootstrapConfiguration().isExecutableValidationEnabled()) {
final Method handlingMethod = resourceMethod.getHandlingMethod();
有趣的部分是围绕着
// Resource validation.
if (beanDescriptor.isBeanConstrained()) {
constraintViolations.addAll(validate(resource));
}
同样的定义是
@Override
public final boolean isBeanConstrained() {
return hasConstraints() || !constrainedProperties.isEmpty();
}
现在,如果你看一下constrainedProperties
的值,它会在下面显示
所以它认为getSomething
意味着属性something
然后在属性本身插入验证。
所以现在我们重命名如下方法
@GET
public @Valid HelloMessage doGetSomething() {
System.out.println("* * * *---==** getSomething() called **==---* * * *");
HelloMessage helloMessage = new HelloMessage();
helloMessage.setMessage("H");
return helloMessage;
}
@POST
public @Valid HelloMessage updateMessage(@Valid HelloMessage message) {
message.setMessage("H");
System.out.println("* * * *---==** updateMessage() called **==---* * * *");
return message;
}
然后从命令行再次运行它
当然,如果我用有效数据更正返回值
只需在Tarun's answer上加两分钱。
当我看到Jersey验证资源类时,我试图考虑用于验证它的原因。我能想出的原因是我们将@PathParam
s和其他@XxxParam
s作为字段注入资源
@Path("/person/{email}")
public class PersonResource {
@Email // email constraint validator
@PathParam("email")
private String email;
}
我们将它作为一个字段注入,而不是像往常一样将@PathParam
注入资源方法参数。当资源得到验证时,email
字段将通过电子邮件约束验证器。
就属性而言,它们可以是字段或JavaBean样式方法(也称为“属性”),它们分别是以get
和set
开头的getter和setter方法。因此,我们使用get
和set
命名方法会将它们添加到属性列表中。
现在我不知道开发人员在设计代码时是否想到这一点,但如果他们这样做并且他们认为这不是问题,我猜他们的论点是这样的:使用Jersey进行bean验证是为了验证传入数据;这可以是从客户端作为实体主体或作为不同参数(例如路径,标头或查询字符串)的数据。常见的因素是它们都来自客户端。因此,如果违反了某些约束,则会出现客户端错误,因此400 Bad Request响应状态,这意味着客户端错误。
话虽这么说,当我们在资源方法中返回值时,那些不是客户端收集的数据;那些是服务器端处理产生的值。因此,如果在这些对象上违反了某些约束,则不应使用与客户端传入对象相同的语义处理它们。是的,您可能希望验证这些对象,但是IMO应该在不同的进程中进行验证,并且不应该导致与客户端错误相同的400。它应该导致500内部服务器错误。返回值存在问题绝对不是客户的错。我们作为开发者应该进行这些检查。
现在,如果您确实希望自己验证返回值,则可以使用验证API手动进行验证。为了保持DRY并透明地完成,您可以使用HK2(Jersey DI)的AOP capabilities拦截方法调用。我在这个GitHub repo中放了一个PoC。