带有自定义验证组的自定义约束注释导致 E2E 测试中的 java.lang.ArrayStoreException

问题描述

我正在为我的 REST 应用程序编写 E2E 测试。因此,位于前端的测试调用已部署的后端,除了一个小问题外,它运行得非常好: 当调用接收到的 readEntity 上的 Response 方法时,会发生 java.lang.ArrayStoreException。我的观察表明问题出在自写的约束注释中:

我已经为 Bean-Validation 编写了自己的 Constraint-Annotation @Future,我的 POJO 使用这个 Annotation 来验证 LocalDateTime 是未来的。我还提供了两个简单的接口,用作验证组:

public interface ExistingInstance extends Default {

}

public interface NewInstance extends Default {

}

当我在自定义约束注释中使用这些接口之一作为验证组时,如下所示:

public class Book {

   @Future(groups=NewInstance.class)
   private LocalDateTime validFrom;

   private LocalDateTime validTo;

   // Getters & Setters...
}

我收到此异常:

java.lang.ArrayStoreException
    at sun.reflect.annotation.AnnotationParser.parseClassArray(AnnotationParser.java:736)
    at sun.reflect.annotation.AnnotationParser.parseArray(AnnotationParser.java:543)
    at sun.reflect.annotation.AnnotationParser.parseMemberValue(AnnotationParser.java:367)
    at sun.reflect.annotation.AnnotationParser.parseAnnotation2(AnnotationParser.java:298)
    at sun.reflect.annotation.AnnotationParser.parseAnnotations2(AnnotationParser.java:132)
    at sun.reflect.annotation.AnnotationParser.parseAnnotations(AnnotationParser.java:84)
    at java.lang.reflect.AccessibleObject.getAnnotationsFromCache(AccessibleObject.java:313)
    at java.lang.reflect.Field.declaredAnnotations(Field.java:1167)
    at java.lang.reflect.Field.getDeclaredAnnotations(Field.java:1160)
    at com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector._findFields(AnnotatedFieldCollector.java:86)
    at com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector._findFields(AnnotatedFieldCollector.java:71)
    at com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector.collect(AnnotatedFieldCollector.java:48)
    at com.fasterxml.jackson.databind.introspect.AnnotatedFieldCollector.collectFields(AnnotatedFieldCollector.java:43)
    at com.fasterxml.jackson.databind.introspect.AnnotatedClass._fields(AnnotatedClass.java:371)
    at com.fasterxml.jackson.databind.introspect.AnnotatedClass.fields(AnnotatedClass.java:343)
    at com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector._addFields(POJOPropertiesCollector.java:493)
    at com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector.collectAll(POJOPropertiesCollector.java:421)
    at com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector.getPropertyMap(POJOPropertiesCollector.java:386)
    at com.fasterxml.jackson.databind.introspect.POJOPropertiesCollector.getProperties(POJOPropertiesCollector.java:233)
    at com.fasterxml.jackson.databind.introspect.BasicBeanDescription._properties(BasicBeanDescription.java:164)
    at com.fasterxml.jackson.databind.introspect.BasicBeanDescription.findProperties(BasicBeanDescription.java:239)
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._findCreatorsFromProperties(BasicDeserializerFactory.java:328)
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory._constructDefaultValueInstantiator(BasicDeserializerFactory.java:272)
    at com.fasterxml.jackson.databind.deser.BasicDeserializerFactory.findValueInstantiator(BasicDeserializerFactory.java:223)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.buildBeanDeserializer(BeanDeserializerFactory.java:261)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerFactory.createBeanDeserializer(BeanDeserializerFactory.java:150)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer2(DeserializerCache.java:414)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createDeserializer(DeserializerCache.java:349)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:264)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
    at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
    at com.fasterxml.jackson.databind.DeserializationContext.findContextualValueDeserializer(DeserializationContext.java:558)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.createContextual(CollectionDeserializer.java:188)
    at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.createContextual(CollectionDeserializer.java:28)
    at com.fasterxml.jackson.databind.DeserializationContext.handlePrimaryContextualization(DeserializationContext.java:765)
    at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.resolve(BeanDeserializerBase.java:535)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCache2(DeserializerCache.java:293)
    at com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(DeserializerCache.java:244)
    at com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(DeserializerCache.java:142)
    at com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(DeserializationContext.java:591)
    at com.fasterxml.jackson.databind.ObjectReader._prefetchRootDeserializer(ObjectReader.java:2340)
    at com.fasterxml.jackson.databind.ObjectReader.forType(ObjectReader.java:723)
    at com.fasterxml.jackson.jaxrs.base.ProviderBase.readFrom(ProviderBase.java:804)
    at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.invokeReadFrom(ReaderInterceptorExecutor.java:233)
    at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor$TerminalReaderInterceptor.aroundReadFrom(ReaderInterceptorExecutor.java:212)
    at org.glassfish.jersey.message.internal.ReaderInterceptorExecutor.proceed(ReaderInterceptorExecutor.java:132)
    at org.glassfish.jersey.message.internal.MessageBodyFactory.readFrom(MessageBodyFactory.java:1072)
    at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:885)
    at org.glassfish.jersey.message.internal.InboundMessageContext.readEntity(InboundMessageContext.java:845)
    at org.glassfish.jersey.client.ClientResponse.readEntity(ClientResponse.java:340)
    at org.glassfish.jersey.client.InboundJaxrsResponse$2.call(InboundJaxrsResponse.java:104)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:292)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:274)
    at org.glassfish.jersey.internal.Errors.process(Errors.java:205)
    at org.glassfish.jersey.process.internal.RequestScope.runInScope(RequestScope.java:365)
    at org.glassfish.jersey.client.InboundJaxrsResponse.runInScopeIfPossible(InboundJaxrsResponse.java:244)
    at org.glassfish.jersey.client.InboundJaxrsResponse.readEntity(InboundJaxrsResponse.java:101)
    at my.package.structure.books.BooksClient.readEntityFromResponse(BooksClient.java:309)
    at my.package.structure.books.BooksClient.getBooks(BooksClient.java:95)
    at my.package.structure.books.BooksClientTest.getBooks_Test(BooksClientTest.java:43)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:90)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:55)
    at java.lang.reflect.Method.invoke(Method.java:508)
    at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:436)
    at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:115)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$6(TestMethodTestDescriptor.java:170)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor$$Lambda$251.00000000126A8D40.execute(Unknown Source)
    at org.junit.jupiter.engine.execution.ThrowableCollector.execute(ThrowableCollector.java:40)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:166)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:113)
    at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:58)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:112)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor$$Lambda$143.000000001120E090.execute(Unknown Source)
    at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:120)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor$$Lambda$146.000000001148A600.accept(Unknown Source)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:195)
    at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:186)
    at java.util.Iterator.forEachRemaining(Iterator.java:127)
    at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1812)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:523)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:513)
    at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:162)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:185)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:245)
    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:429)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:120)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor$$Lambda$143.000000001120E090.execute(Unknown Source)
    at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$2(HierarchicalTestExecutor.java:120)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor$$Lambda$146.000000001148A600.accept(Unknown Source)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:195)
    at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:186)
    at java.util.Iterator.forEachRemaining(Iterator.java:127)
    at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1812)
    at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:523)
    at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:513)
    at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:162)
    at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:185)
    at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:245)
    at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:429)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.lambda$executeRecursively$3(HierarchicalTestExecutor.java:120)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor$$Lambda$143.000000001120E090.execute(Unknown Source)
    at org.junit.platform.engine.support.hierarchical.SingleTestExecutor.executeSafely(SingleTestExecutor.java:66)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.executeRecursively(HierarchicalTestExecutor.java:108)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor$NodeExecutor.execute(HierarchicalTestExecutor.java:79)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:55)
    at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:43)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:170)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:154)
    at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:90)
    at org.eclipse.jdt.internal.junit5.runner.JUnit5TestReference.run(JUnit5TestReference.java:86)
    at org.eclipse.jdt.internal.junit.runner.TestExecution.run(TestExecution.java:38)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:538)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:760)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:460)
    at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:206)

现在是令人困惑的部分:

如果我在 Java 提供的现有注释上使用我的验证组接口,例如 @NotNull

public class Book {
   
   @NotNull(groups=NewInstance.class)
   private LocalDateTime validFrom;

   private LocalDateTime validTo;

   // Getters & Setters...
}

它工作得很好。即使我使用 JDK 中提供的类,例如 Default.class 作为我的自定义注释中的一个组:

public class Book {
   
   @NotNull(groups=Default.class)
   private LocalDateTime validFrom;

   private LocalDateTime validTo;

   // Getters & Setters...
}

有效!

我错过了什么?

我使用:

  • Java v8
  • JavaEE v7
  • 泽西客户端 v2.33
  • 泽西HK2 v2.33
  • 休眠验证器 v5.4.3

注意:我知道已经提供了注解`@Future`,但是在带有 JavaEE 7 的 Java 8 中它不支持“新的”Time-API。

编辑 这是我重现错误的代码:

服务器端: 返回实体的包装

public class Wrapper<T> {
   
   private T returnValue;
   private String exception;
   private Integer processingState;
   private String processingMessage;

   //Getter & Setter
}
@Path("books")
public class BookService {

   @EJB
   private BookRepo repository;

   @Path("all")
   public Response getAllBooks() {
      Response response = null;
      try {
         // findAll returns a List wrapped inside the above shown Wrapper class
         response = Response.status(200).entity(new GenericEntity<Wrapper<List<Book>>>(repository.findAll()){}).build();
      } catch (Exception e) {
         response = Response.status(500).build();
      }

      return response;
   }

}

客户端:

public class BooksClient {

   private ObjectMapper mapper;
   private Client client;
   private WebTarget baseTarget;
   
   public BooksClient(String endpointAdress) {
      this.mapper = new ObjectMapper();
      this.mapper.registerModule(new JavaTimeModule());
      this.mapper.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS,false);

      JacksonJsonProvider provider = new JacksonJsonProvider(this.mapper);

      this.client = ClientBuilder.newClient(new ClientConfig().register(provider));

      this.baseTarget = this.client.target(endpointAdress);
   }

   public Wrapper<List<Book>> getBooks() {
      WebTarget bookTarget = baseTarget.path("all");
      
      Response response = bookTarget.request(MediaType.APPLICATION_JSON).get();

      return this.readEntityFromResponse(response);

   }

   private Wrapper<List<Book>> readEntityFromResponse(Response response) {
      Wrapper<List<Book>> result = null;

      GenericType<List<Book>> responseType = new GenericType<Wrapper<List<Book>>>(){};

      result = response.readEntity(responseType); // <--- root of Exception

      return result;
   }

}

测试类

public class BooksClientTest {

   @Spy
   private BooksClient client = new BooksClient("http://localhost:1234/ws/books");

   @Test
   public void getBooks_Test() {
      Wrapper<List<Book>> result = this.client.getBooks();
      assertThat(result).isNotNull();
   }

}

自定义注释:

/**
 * Validates a Time Value.
 */
@Documented
@Retention(RUNTIME)
@Target(FIELD)
@Constraint(validatedBy = { FutureLocalDateTimeValidator.class,FutureLocalDateValidator.class,FutureLocalTimeValidator.class,FutureZonedDateTimeValidator.class })
public @interface Future {

  String message() default "Error while validating the date!";

  Class<?>[] groups() default {};

  Class<? extends Payload>[] payload() default {};
}

对应的验证器:

public class FutureLocalDateTimeValidator implements ConstraintValidator<Future,LocalDateTime> {

  @Override
  public void initialize(Future constraintAnnotation) {
    // not needed.
  }

  @Override
  public boolean isValid(LocalDateTime value,ConstraintValidatorContext context) {
    if (value == null) {
      return true;
    }

    LocalDateTime today = LocalDateTime.now();
    return value.isAfter(today);
  }

}

我尝试了不同的自写注释,结果完全相同。

应用程序部署在 WAS 9.0 上

如果我使用实际的前端,则不会发生错误。

希望这会有所帮助,提前致谢!

解决方法

暂无找到可以解决该程序问题的有效方法,小编努力寻找整理中!

如果你已经找到好的解决方法,欢迎将解决方案带上本链接一起发送给小编。

小编邮箱:dio#foxmail.com (将#修改为@)