스택 초과 단점을 개선하기 위해 만들어진 개념
재귀 호출을 함수의 마지막 return 문으로만 사용한다.
호출 스택을 추가로 사용할 필요 없이 스택의 현재 영역을 재사용할 수 있다.
→ by Tail Call Optimization. 컴파일러가 자동 최적화 해주는 것. 그러나 파이썬은 안 된다.
current
: 현재 계산 중인 피보나치 수
next
: 다음 피보나치 수
재귀 호출이 계속 진행되면서 current
는 next
로
next
는 current + next
로 업데이트 된다.
# 꼬리 재귀함수로 구현한 피보나치 수
def fib_tail(n, current=0, next=1):
if n <= 1:
return next
return fib_tail(n-1, next, current+next)
LoginAuthenticationProvider
테스트는 Member 조회 부분이 외부 상태(DB)에 따라 테스트 결과가 달라진다. 문제의 코드
body = request.getReader()
.lines()
.collect(Collectors.joining());
return objectMapper.readValue(body, LoginRequestDto.class);
필터에서 로그인 정보를 추출해 VO 객체로 역직렬화하는 코드를 작성했다.
이 코드에 달린 리뷰는 Request body 데이터는 한 번 읽으면 이후 소실될 가능성이 있으니 주의가 필요하다는 것이다.
A ServletRequest object provides data including parameter name and values, attributes, and an input stream. Interfaces that extend ServletRequest can provide additional protocol-specific data (for example, HTTP data is provided by
jakarta.servlet.http.HttpServletRequest
.
ServletRequest
는 서블릿 컨네이너가 요청이 들어오면 요청에 포함된 정보를 맵핑하는 객체다.
input stream 스트림도 담고 있다.
ServletRequest
에는 input stream 을 처리하는 메서드가 2개가 존재한다.
getReader()
는 BufferedReader
를 사용해 스트림을 문자열로 처리해준다. /**
* Retrieves the body of the request **as character data** using a <code>**BufferedReader**</code>. The reader translates
* the character data according to the character encoding used on the body. Either this method or
* {@link #getInputStream} may be called to read the body, not both.
*
* @return a <code>BufferedReader</code> containing the body of the request
*
* @exception java.io.UnsupportedEncodingException if the character set encoding used is not supported and the text
* cannot be decoded
* @exception IllegalStateException if {@link #getInputStream} method has been called on this request
* @exception IOException if an input or output exception occurred
*
* @see #getInputStream
*/
BufferedReader getReader() throws IOException;
getInputStream()
은 ServletInputStream
****을 사용해 스트림을 바이너리 데이터로 처리한다. /**
* Retrieves the body of the request **as binary data** using a {@link **ServletInputStream**}. Either this method or
* {@link #getReader} may be called to read the body, not both.
*
* @return a {@link ServletInputStream} object containing the body of the request
*
* @exception IllegalStateException if the {@link #getReader} method has already been called for this request
* @exception IOException if an input or output exception occurred
*/
ServletInputStream getInputStream() throws IOException;
주석에 친절하게 둘 다 사용할 순 없다고 명시해놨다….
IO 를 처리하는 클래스를 열어보면 왜 두 메서드를 모두 사용하면 안되는지 답이 있다.
/**
* Tests if this input stream supports the {@code mark} and
* {@code reset} methods. Whether or not {@code mark} and
* {@code reset} are supported is an invariant property of a
* particular input stream instance. The {@code markSupported} method
* of {@code InputStream} returns {@code false}.
*
* @return {@code true} if this stream instance supports the mark
* and reset methods; {@code false} otherwise.
* @see java.io.InputStream#mark(int)
* @see java.io.InputStream#reset()
*/
public boolean markSupported() {
return false;
}
BufferedReader
의 슈퍼클래스 Reader
와 ServletInputStream
의 슈퍼 클래스인 InputStream
모두 mark()
와 reset()
을 지원하지 않는다.
즉 한번 소비가 끝난 스트림은 처음 위치로 돌아가 다시 읽을 수 없다.
그래서 stream 을 한 번 소비하면 다시 읽을 방법이 없다.
해결 방법은 Request body 의 Stream 을 캐싱해야된다.
Java IllegalStateException: “getInputStream() has already been called for this request” | Baeldung