Frame: 틀
work: 동작하다
프레임워크를 풀어서 설명하자면 틀 안에서 동작을 한다는 것이다. 하나의 구슬을 담고 있는 틀을 상하좌우로 움직인다고 생각해 보자. 이 구슬은 틀 안에서만 움직이고 틀을 벗어나지 않을 것이다. 즉, 개발자가 이 틀을 벗어나지 않고 프로그램을 만들 수 있도록 도와주는 것이 바로 프레임워크이다.
소스 코드가 공개되어 있다. 스프링이 어떻게 만들어졌는지 내부를 볼 수 있고 내부를 뜯어고칠 수도 있다.
스프링에게 주도권을 빼앗겼다. 개발자가 아닌 스프링이 직접 주도권을 가진다.
간단히 Class, Object, Instance에 대해 알아보자.
예를 들어 가구는 추상적인 것으로 실체화가 불가한 것이다. 의자나 침대 같은 경우는 실체화가 가능하기 때문에 객체라고 할 수 있다. 이런 것들은 객체로 존재하다가 세상에 나오는 순간 실체화되었기 때문에 인스턴스라고 할 수 있다.
개발자가 직접 의자 객체를 생성한다고 가정하자.
직접 의자 객체를 생성하였기 때문에 이를 실체화 시켜서 힙 메모리 영역에 띄우게 된다. 이 주소는 s가 들고 있고 s는 의자를 가리키게 되며 해당 객체를 생성한 메서드 내부에서 관리하게 된다. 이때 s는 메서드가 실행되는 순간에만 메모리에 떠있다.
public void make() {
의자 s = new 의자();
}
만약 다른 곳에서 의자를 사용하고 싶다면 아래처럼 메서드를 작성하게 될 것이다.
public void make() {
의자 s = new 의자();
}
이럴 경우 메모리 공간에 새로운 의자가 하나 더 떠있게 되는 것이고 위의 의자와 같다고 볼 수 없다. 이 주소들은 각각의 메서드들이 관리하고 있다고 하였는데 이렇게 되면 나중에 의자를 공유하는 것이 힘들어진다. 그렇기 때문에 스프링이 수많은 객체들을 직접 스캔하고 힙 메모리 영역에 띄워 해당 객체들을 관리하게 되는데 이것이 바로 제어의 역전이다. 개발자가 객체를 관리하는 것이 아닌 스프링이 관리하게 된다.
스프링이 직접 수많을 객체들을 스캔하여 힙 메모리 영역에 띄워서 관리한다고 했다. 옛날에는 개발자가 원하는 곳에서 객체를 생성하고 객체의 주소를 원하는 곳에서 관리를 했다면, 이제는 스프링이 스캔을 해서 메모리에 띄웠기 때문에 이는 스프링이 관리하는 것이고 힙 메모리 영역에 띄운 객체들은 내가 원하는 모든 클래스의 메서드에서 해당 객체를 가져와서 공유해서 사용할 수 있다. 즉, 힙 메모리 영역에 띄워져 있는 객체를 필요한 곳으로 가져와서 사용할 수 있도록 하는 것이다.
A나라와 B나라가 있다. 현재 A나라와 B나라는 전쟁 중이며 B나라의 스파이들이 A나라의 성 안으로 몰래 진입하고 있다. 따라서 A나라는 성의 입구에서 신원을 확인하고 검열하는 작업을 진행하게 될 것이다. 이러한 검열의 기능을 하는 것을 필터라고 한다. 검열을 거쳐 들어온 사람들이 왕의 집으로 들어가려 할 때에도 또다시 새로운 검열을 거쳐야 할 것이다. 이것 또한 필터라고 볼 수 있다. 필터를 문지기라고 보면 된다. 문지기에게 임무를 주는 것이다. 임무를 직접 줄 수도 있고 스프링 자체가 가지고 있는 필터를 사용할 수도 있다. 여기서 성이 톰켓이고 왕의 집은 스프링 컨테이너이다. 첫 번째 필터는 말 그대로 필터라고 불리고 이 필터의 기능을 하는 파일을 web.xml이라고 한다. 두번째 필터는 인터셉터라고 불리고 권한을 체크한다.
어노테이션은 컴파일러가 무언가를 체킹 할 수 있도록 힌트를 주는 주석이다. 일반적인 주석과는 다르게 컴파일러가 무시하지 않는다.
아래의 클래스를 살펴보자.
컴파일러는 Dog 클래스를 컴파일 할 때 @Override 어노테이션을 보고 Animal 클래스로 가서 run 메서드의 존재를 확인해 볼 것이다. run 메서드가 있다면 에러가 나지 않을 것이고 메서드가 없다면 에러가 날 것이다.
Class Animal {
run();
}
Class Dog extends Animal {
@Override
run();
}
이처럼 스프링에서는 어노테이션을 만들고 어노테이션이 어떤 역할을 하는지 미리 약속하여 컴파일러에게 힌트를 준다.
아래의 어노테이션을 살펴보자. 아래와 같은 역할을 하는 어노테이션이다.
스프링이 A 클래스를 스캔해서 힙 메모리 영역에 A 클래스를 로딩한다.
@Component
Class A {
}
힙 메모리 영역에서 A와 같은 타입의 데이터가 존재한다면 a 변수에 객체를 집어넣는다.
Class B{
@Autowired
A a;
}
여기서 스프링이 해당 클래스를 스캔할 때 클래스 내부에 어떤 것들이 있는지 분석하는 기법을 리플렉션이라고 한다. 리플렉션을 통해 어떤 필드, 메서드, 어노테이션이 있는지 체킹 하며 어떤 행동을 하라고 설정할 수도 있다. 이는 런타임 때 일어난다.
영어를 사용하는 A라는 사람과 한국어를 사용하는 B라는 사람이 있다. 서로 메시지를 주고받고 싶은데 A는 영어밖에 못하니까 Hello라고 메시지를 보내고 B는 안녕이라고 메시지를 보낼 것이다. 하지만 서로의 언어를 이해하지 못 해 결국 의사소통을 하지 못할 것이다.
따라서 A와 B가 모두 이해할 수 있는 중간 언어가 필요하다. 이렇게 중간 언어의 역할을 하는 것이 바로 json이다.
자바 프로그램과 파이썬 프로그램이 있다. 각 프로그램의 오브젝트는 생긴 것이 다르기 때문에 서로의 오브젝트를 이해할 수 없다. 그래서 자바 프로그램이 파이썬 프로그램에게 데이터를 요청(request) 할 때 json 데이터로 변경되고 파이썬 프로그램으로부터 응답(response) 받을 때도 json 데이터로 변경이 일어난다. 이처럼 중간 언어인 json 데이터로 변경해 주는 역할을 하는 것이 바로 메시지 컨버터이다. 메시지 컨버터는 Jackson으로 설정되어 있고 이는 json 데이터로 변경해 주는 라이브러리이다.
BufferedReader와 BufferedWriter는 바이트 스트림을 통해서 데이터를 통신할 때 전송 단위가 문자열로 가변 길이의 데이터를 사용할 수 있게 해주는 클래스이다.
바이트 스트림을 통해 데이터를 전송하면 자바에서 InputStream을 이용하여 데이터를 읽는다. 읽는 단위가 바이트이고 바이트는 문자가 아니니 이걸 문자로 변형하기 위해서 InputStreamReader를 이용한다. InputStreamReader는 문자 하나 또는 배열로 문자열을 받을 수 있는데 여기서 배열은 크기를 미리 정해 두어야 하기 때문에 공간의 낭비가 일어날 수 있다. 하지만 BufferedReader를 이용하면 가변 길이의 문자를 받을 수 있게 된다.
@ResponseBody 어노테이션을 사용하면 BufferedWriter가 존재하며 @RequestBody 어노테이션을 사용하면 BufferedReader가 존재한다.
출처: 인프런 스프링 부트 개념정리(이론) by 최주호