Jersey JAX-RS REST“getter”方法总是被调用

问题描述 投票:5回答:2

我在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找到的最小项目

jersey jax-rs bean-validation
2个回答
3
投票

因此,由于命名惯例,这被证明是一个有趣的问题。通过泽西源代码进行调试,您将看到它通过

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 is a property

所以它认为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;
  }

然后从命令行再次运行它

getSomething is not Called

当然,如果我用有效数据更正返回值

getSomething not called


1
投票

只需在Tarun's answer上加两分钱。

当我看到Jersey验证资源类时,我试图考虑用于验证它的原因。我能想出的原因是我们将@PathParams和其他@XxxParams作为字段注入资源

@Path("/person/{email}")
public class PersonResource {

    @Email // email constraint validator
    @PathParam("email")
    private String email;
}

我们将它作为一个字段注入,而不是像往常一样将@PathParam注入资源方法参数。当资源得到验证时,email字段将通过电子邮件约束验证器。

就属性而言,它们可以是字段或JavaBean样式方法(也称为“属性”),它们分别是以getset开头的getter和setter方法。因此,我们使用getset命名方法会将它们添加到属性列表中。

现在我不知道开发人员在设计代码时是否想到这一点,但如果他们这样做并且他们认为这不是问题,我猜他们的论点是这样的:使用Jersey进行bean验证是为了验证传入数据;这可以是从客户端作为实体主体或作为不同参数(例如路径,标头或查询字符串)的数据。常见的因素是它们都来自客户端。因此,如果违反了某些约束,则会出现客户端错误,因此400 Bad Request响应状态,这意味着客户端错误。

话虽这么说,当我们在资源方法中返回值时,那些不是客户端收集的数据;那些是服务器端处理产生的值。因此,如果在这些对象上违反了某些约束,则不应使用与客户端传入对象相同的语义处理它们。是的,您可能希望验证这些对象,但是IMO应该在不同的进程中进行验证,并且不应该导致与客户端错误相同的400。它应该导致500内部服务器错误。返回值存在问题绝对不是客户的错。我们作为开发者应该进行这些检查。

现在,如果您确实希望自己验证返回值,则可以使用验证API手动进行验证。为了保持DRY并透明地完成,您可以使用HK2(Jersey DI)的AOP capabilities拦截方法调用。我在这个GitHub repo中放了一个PoC。

© www.soinside.com 2019 - 2024. All rights reserved.