인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

정동규님의 프로필 이미지
정동규

작성한 질문수

생산성을 향상시키는 스프링부트 기반의 API 템플릿 프로젝트 구현

KakaoTokenDto 오류

해결된 질문

작성

·

352

0

안녕하세요 강사님!! 강의 정말 잘 듣고 있습니다. 다름이 아니라 템플릿 프로젝트를 기반으로 다른 프로젝트를 진행하고 있었는데, Spring Security를 적용하고 회원가입 구현을 조금 진행 해봤는데 localhost:8080/kakao/login 에서 카카오 로그인 버튼을 클릭하자 다음과 같은 에러 메시지가 출력되는 것을 확인하였습니다.

feign.codec.DecodeException: Type definition error: [simple type, class com.server.seb41_main_11.web.kakaotoken.dto.KakaoTokenDto$Response]; nested exception is com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of com.server.seb41_main_11.web.kakaotoken.dto.KakaoTokenDto$Response (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)

at [Source: (org.springframework.util.StreamUtils$NonClosingInputStream); line: 1, column: 2]

로그를 확인하고 KakaoTokenController의 requestKakaoToken 메서드를 통해 KakaoTokenDto.Response를 반환하는 문제가 생긴 것 같아

해당 에러를 구글링 해봤는데, 기본 생성자를 추가해줘야 한다고 해서 @NoArgsConstructor를 rsponse 부분에 추가를 하고, Builder패턴을 사용하고 있으니 @AllArgsConstructor 어노테이션 또한 추가를 해주었습니다.

변경한 코드입니다

@ToString
    @NoArgsConstructor
    @AllArgsConstructor
    @Builder @Getter
    public static class Response {
        private String token_type;
        private String access_token;
        private Integer expires_in;
        private String refresh_token;
        private Integer refresh_token_expires_in;
        private String scope;
    }

다음과 같이 수정하니 정상적으로 토큰을 반환받는 것을 확인하였습니다.

따라서 어노테이션을 추가했을 때와 추가하지 않았을 때를 비교하기 위해서 build 폴더의 KakaoTokenDto.Response 구현 부분을 확인을 해봤는데, 어노테이션을 추가했을 때와 추가하지 않았을 때의 구현부가 같았습니다.

해당 에러가 왜 생기는지 잘 모르겠고, 어노테이션을 붙였을 때 왜 해결이되는지도 잘 모르겠습니다 ㅠㅠ

답변 1

0

구파고님의 프로필 이미지
구파고
지식공유자

안녕하세요! jackson 라이브러리를 사용해서 역직렬화를 할 때 기본 생성자를 사용하는것으로 보입니다. 이게 버전이나 환경에 따라서 어떤분은 되고, 어떤분은 안되는거 같더라구요! 그래서 api reqeust, response로 @Builder를 사용할 때는 항상 기본생성자와 전체 생성자가 생성되게 어노테이션을 붙여주시는게 좋을꺼 같습니다. 아래는 @AllArgsConstructor @NoArgsConstructor가 없을때의 .class 파일이고

public static class Response {
    private String token_type;
    private String access_token;
    private Integer expires_in;
    private String refresh_token;
    private Integer refresh_token_expires_in;
    private String scope;

    Response(final String token_type, final String access_token, final Integer expires_in, final String refresh_token, final Integer refresh_token_expires_in, final String scope) {
        this.token_type = token_type;
        this.access_token = access_token;
        this.expires_in = expires_in;
        this.refresh_token = refresh_token;
        this.refresh_token_expires_in = refresh_token_expires_in;
        this.scope = scope;
    }

    public static ResponseBuilder builder() {
        return new ResponseBuilder();
    }

    public String toString() {
        String var10000 = this.getToken_type();
        return "KakaoTokenDto.Response(token_type=" + var10000 + ", access_token=" + this.getAccess_token() + ", expires_in=" + this.getExpires_in() + ", refresh_token=" + this.getRefresh_token() + ", refresh_token_expires_in=" + this.getRefresh_token_expires_in() + ", scope=" + this.getScope() + ")";
    }

    public String getToken_type() {
        return this.token_type;
    }

    public String getAccess_token() {
        return this.access_token;
    }

    public Integer getExpires_in() {
        return this.expires_in;
    }

    public String getRefresh_token() {
        return this.refresh_token;
    }

    public Integer getRefresh_token_expires_in() {
        return this.refresh_token_expires_in;
    }

    public String getScope() {
        return this.scope;
    }

    public static class ResponseBuilder {
        private String token_type;
        private String access_token;
        private Integer expires_in;
        private String refresh_token;
        private Integer refresh_token_expires_in;
        private String scope;

        ResponseBuilder() {
        }

        public ResponseBuilder token_type(final String token_type) {
            this.token_type = token_type;
            return this;
        }

        public ResponseBuilder access_token(final String access_token) {
            this.access_token = access_token;
            return this;
        }

        public ResponseBuilder expires_in(final Integer expires_in) {
            this.expires_in = expires_in;
            return this;
        }

        public ResponseBuilder refresh_token(final String refresh_token) {
            this.refresh_token = refresh_token;
            return this;
        }

        public ResponseBuilder refresh_token_expires_in(final Integer refresh_token_expires_in) {
            this.refresh_token_expires_in = refresh_token_expires_in;
            return this;
        }

        public ResponseBuilder scope(final String scope) {
            this.scope = scope;
            return this;
        }

        public Response build() {
            return new Response(this.token_type, this.access_token, this.expires_in, this.refresh_token, this.refresh_token_expires_in, this.scope);
        }

        public String toString() {
            return "KakaoTokenDto.Response.ResponseBuilder(token_type=" + this.token_type + ", access_token=" + this.access_token + ", expires_in=" + this.expires_in + ", refresh_token=" + this.refresh_token + ", refresh_token_expires_in=" + this.refresh_token_expires_in + ", scope=" + this.scope + ")";
        }
    }
}

 

아래는 @AllArgsConstructor @NoArgsConstructor가 있을 때 .class 파일 입니다. 보시면 기본생성자와 모든 필드를 파라미터로 받는 전체 생성자가 있는것을 확인할 수 있습니다. 파일이 똑같지 않을텐데 gradle에서 clean -> build를 통해서 어노테이션이 있을때와 없을때를 다시 한번 확인해보시면 좋을꺼 같습니다.

 

public static class Response {
    private String token_type;
    private String access_token;
    private Integer expires_in;
    private String refresh_token;
    private Integer refresh_token_expires_in;
    private String scope;

    public static ResponseBuilder builder() {
        return new ResponseBuilder();
    }

    public String toString() {
        String var10000 = this.getToken_type();
        return "KakaoTokenDto.Response(token_type=" + var10000 + ", access_token=" + this.getAccess_token() + ", expires_in=" + this.getExpires_in() + ", refresh_token=" + this.getRefresh_token() + ", refresh_token_expires_in=" + this.getRefresh_token_expires_in() + ", scope=" + this.getScope() + ")";
    }

    public Response(final String token_type, final String access_token, final Integer expires_in, final String refresh_token, final Integer refresh_token_expires_in, final String scope) {
        this.token_type = token_type;
        this.access_token = access_token;
        this.expires_in = expires_in;
        this.refresh_token = refresh_token;
        this.refresh_token_expires_in = refresh_token_expires_in;
        this.scope = scope;
    }

    public Response() {
    }

    public String getToken_type() {
        return this.token_type;
    }

    public String getAccess_token() {
        return this.access_token;
    }

    public Integer getExpires_in() {
        return this.expires_in;
    }

    public String getRefresh_token() {
        return this.refresh_token;
    }

    public Integer getRefresh_token_expires_in() {
        return this.refresh_token_expires_in;
    }

    public String getScope() {
        return this.scope;
    }

    public static class ResponseBuilder {
        private String token_type;
        private String access_token;
        private Integer expires_in;
        private String refresh_token;
        private Integer refresh_token_expires_in;
        private String scope;

        ResponseBuilder() {
        }

        public ResponseBuilder token_type(final String token_type) {
            this.token_type = token_type;
            return this;
        }

        public ResponseBuilder access_token(final String access_token) {
            this.access_token = access_token;
            return this;
        }

        public ResponseBuilder expires_in(final Integer expires_in) {
            this.expires_in = expires_in;
            return this;
        }

        public ResponseBuilder refresh_token(final String refresh_token) {
            this.refresh_token = refresh_token;
            return this;
        }

        public ResponseBuilder refresh_token_expires_in(final Integer refresh_token_expires_in) {
            this.refresh_token_expires_in = refresh_token_expires_in;
            return this;
        }

        public ResponseBuilder scope(final String scope) {
            this.scope = scope;
            return this;
        }

        public Response build() {
            return new Response(this.token_type, this.access_token, this.expires_in, this.refresh_token, this.refresh_token_expires_in, this.scope);
        }

        public String toString() {
            return "KakaoTokenDto.Response.ResponseBuilder(token_type=" + this.token_type + ", access_token=" + this.access_token + ", expires_in=" + this.expires_in + ", refresh_token=" + this.refresh_token + ", refresh_token_expires_in=" + this.refresh_token_expires_in + ", scope=" + this.scope + ")";
        }
    }
}
정동규님의 프로필 이미지
정동규
질문자

명쾌한 답변 감사드립니다!

정동규님의 프로필 이미지
정동규

작성한 질문수

질문하기