WEB

CodecException: 500 Server Error

PSAwesome 2019. 8. 21. 03:32
반응형

글 순서 : 프로젝트 생성(의존성의 관계) -> 발생했던 상황 -> 에러 코드 -> 원인 확인 -> 문제의 지점 체크 -> 해결 방안

핵심 상황:

Spring Boot의 initializar를 통해 WebFlux 라이브러리를 추가한 예제 프로젝트에서

1. RestTemplate.exchange()를 사용할 때,

2. 또 WebClient를 사용했을 때 발생한 Exception이다.

3. 두 Request는 공통적으로 사용자 정의 객체를 담고있는 List와 Flux이다.

Error 내용

...더보기

응답 메시지

{
  "timestamp": "2019-08-20T17:40:30.328+0000",
  "path": "/non-blocking",
  "status": 500,
  "error": "Internal Server Error",
  "message": "Type definition error: [simple type, class com.example.webclientdemo.demo.repository.User]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.webclientdemo.demo.repository.User` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)\n at [Source: UNKNOWN; line: -1, column: -1]"
}

 

...더보기

오류 로그 (blocking)

2019-08-21 03:02:02.870  INFO 15544 --- [ctor-http-nio-2] c.e.w.demo.domain.HelloController        : Starting BLOCKING, type = false
2019-08-21 03:02:06.043 ERROR 15544 --- [ctor-http-nio-2] a.w.r.e.AbstractErrorWebExceptionHandler : [4d67195c] 500 Server Error for HTTP GET "/blocking"

org.springframework.http.converter.HttpMessageConversionException: Type definition error: [simple type, class com.example.webclientdemo.demo.repository.User]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.webclientdemo.demo.repository.User` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (PushbackInputStream); line: 1, column: 3] (through reference chain: java.util.ArrayList[0])

주요 포인트 :  (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) 

 

 

...더보기

오류 로그 (non-blocking)

2019-08-21 02:40:28.071  INFO 10904 --- [ctor-http-nio-2] c.e.w.demo.domain.HelloController        : Starting NON_BLOCKING, type = false
2019-08-21 02:40:28.140  INFO 10904 --- [ctor-http-nio-2] c.e.w.demo.domain.HelloController        : Exiting NON-BLOCKING
2019-08-21 02:40:30.325  WARN 10904 --- [ctor-http-nio-6] io.netty.util.ReferenceCountUtil         : Failed to release a message: DefaultLastHttpContent(data: PooledSlicedByteBuf(freed), decoderResult: success)

io.netty.util.IllegalReferenceCountException: refCnt: 0, decrement: 1
at io.netty.util.internal.ReferenceCountUpdater.toLiveRealRefCnt(ReferenceCountUpdater.java:74) ~[netty-common-4.1.38.Final.jar:4.1.38.Final]
2019-08-21 02:40:30.332 ERROR 10904 --- [ctor-http-nio-8] a.w.r.e.AbstractErrorWebExceptionHandler : [15d850da] 500 Server Error for HTTP GET "/non-blocking"

org.springframework.core.codec.CodecException: Type definition error: [simple type, class com.example.webclientdemo.demo.repository.User]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.example.webclientdemo.demo.repository.User(no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

주요 포인트 : org.springframework.core.codec.CodecException: (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator) 

위 내용의 오류와 함께 500 Server error를 던져준다.

기본 생성자가 존재하지 않는다는 에러 메시지를 확인할 수 있다.

로그 내용 중com.example.webclientdemo.repository.User의 클래스 경로가 확인할 수 있는데 이 객체를 JSON으로 Encoder/Decoder 할 때 오류가 발생한 것이다.

결론부터 말하면  간단하게 무인자 생성자 생성을 해주면 해결된다.

public class User {
    private String name;
    private String email;

	//추가 되어야할 코드
	//public User(){}
   
    public User(String name, String email) {
        this.name = name;
        this.email = email;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }
}

Controller에서 어떻게 활용 했는지도 확인이 필요할 것이다.

@RestController
public class HelloController {

	@GetMapping("/slow-service-users")
      private List<User> getAllUsers() throws InterruptedException {
        Thread.sleep(2000L);
        return Arrays.asList(
        new User("restTemplate","pp@Sync")
        , new User("WebClient","ss@Async")
        , new User("sounds good to me","yy@POP"));
      }
      
	@GetMapping(value = "/blocking")
    public <T> List<User> getBlocking() {
        boolean type = getType();
        log.info("Starting BLOCKING, type = " + type);
        final String uri = getSlowServiceUri(type);
        var restTemplate = new RestTemplate();
        ResponseEntity<List<User>> response = restTemplate.exchange(uri, HttpMethod.GET, null, new ParameterizedTypeReference<List<User>>() {});
        List<User> result = response.getBody();
        result.forEach(o -> log.info(o.toString()));
        log.info("Exiting BLOCKING");
        return result;
    }

      @GetMapping(value = "/non-blocking", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
      public <T> Flux<User>  getNonBlocking(){
          boolean type = true;
          log.info("Starting NON_BLOCKING, type = " + type);
          Flux<User> flux =  WebClient.create()
                  .get()
                  .uri(getSlowServiceUri(type))
                  .retrieve()
                  .bodyToFlux(User.class);
          flux.subscribe(obj -> log.info(obj.toString()));
          log.info("Exiting NON-BLOCKING");
          return flux;
      }
      
      	private String getSlowServiceUri(boolean getA) {
        	return getA ? "http://localhost:8080/slow-service-users" : "";
    }
    }

localhost:8080/blocking 요청이 오면 slow-service-users를 호출하여 그 반환 값을 가져다 주는 방식이고 Thread를 잠깐 재워서 지연시키는 과정에서 Blocking을 확인하는 예제이다.

이때 객체를 List에 담아 반환하고 그 반환한 것을

ParameterizedTypeReference<List<User>>(){}

에 감싸서 ResponseEntity<List<User>>로 받는 코드인데 이 구문에서 HttpMessageConversionException이 발생한 것.

바로 아랫줄에 AbstractJacson2HttpMessageConverter.java:242의 encodeValue부터 추적해보니,

protected Object _readMapAndClose(JsonParser p0, JavaType valueType)
        throws IOException
    {
        try (JsonParser p = p0) {
            Object result;
            JsonToken t = _initForReading(p, valueType);
            final DeserializationConfig cfg = getDeserializationConfig();
            final DeserializationContext ctxt = createDeserializationContext(p, cfg);
            if (t == JsonToken.VALUE_NULL) {
                // Ask JsonDeserializer what 'null value' to use:
                result = _findRootDeserializer(ctxt, valueType).getNullValue(ctxt);
            } else if (t == JsonToken.END_ARRAY || t == JsonToken.END_OBJECT) {
                result = null;
            } else {
                JsonDeserializer<Object> deser = _findRootDeserializer(ctxt, valueType);
                if (cfg.useRootWrapping()) {
                    result = _unwrapAndDeserialize(p, ctxt, cfg, valueType, deser);
                } else {
                
                
                //포인터 지점
                    result = deser.deserialize(p, ctxt);
                    
                    
                }
                ctxt.checkUnresolvedObjectId();
            }
            if (cfg.isEnabled(DeserializationFeature.FAIL_ON_TRAILING_TOKENS)) {
                _verifyNoTrailingTokens(p, ctxt, valueType);
            }
            return result;
        }
    }

ObjectMapper 클래스의 _readMapAndClose 메서드의 deser.deserialize(p, ctxt); 구문에서 jackson.databind.exc.InvalidDefinitionException이 발생한 것.

Convert를 하기 위해서 무인자 생성자를 추가하니 데이터를 정상적으로 가지고 올 수 있었다.

※다른 포스팅에서 같은 내용의 문제는 DTO 객체에 getter/setter가 없어서 발생한 에러가 있는 것으로 확인.

내 경험은 getter/setter/NoArgsConstructor 의 조건이 맞아야 바인딩 시 직렬화/역직렬화가 가능하다는 결론이 첫 번째이다.

직렬화를 할 때 무인자 생성자를 통해 객체를 생성하고 getter/setter를 활용한다는 것인가?

나는 두 번째 해결 방법에서 더 많은 생각을 하게 되었다.

 

두 번째 방법은 이후 포스팅에서 남길 예정이다.

예제 소스코드 :

 

반응형