개발자로 후회없는 삶 살기
[문법] Spring 프레임워크 사용 목적 및 Web 통신 원리 본문
서론
※ 과거에 기록한 내용에서 중요한 부분만 발췌하여 모두가 이해하기 쉽게 다시 서술한다.
본론
- 화면 동작 원리
웹 어플리케이션에서 첫 진입점은 컨트롤러이다. 도메인/URL로 요청이 오면 매칭되는 메서드를 호출한다.
웹 브라우저에서 요청을 하면, 내장 톰켓이 받고, 스프링으로 전달하여 요청에 매칭되는 메서드를 찾고 실행시킨다.
1) 스프링 프론트 컨트롤러에서 해당 메서드를 실행
2) 인자로 Model을 넣어주고 addAttribute로 키-벨류 형태로 저장
3) 뷰 리졸버가 반환형에 명시된 파일명으로 주소를 만듦
4) 브라우저에게 template 폴더에 있는 html을 반환
반환된 문자열은 논리적 주소일 뿐이며, 이를 뷰 리졸버가 물리 주소로 변환하여 html을 찾고 브라우저에게 반환한다. resources:templates/{viewName}.html로 바뀌게 된다.
- 통신 방식
1. 정적 컨텐츠
스프링은 static 폴더에 작성한 html 파일을 실행 환경이 아닌 정적 환경에서 반환할 수 있다. 브라우저가 도메인/URL.html로 호출시 톰켓이 스프링에게 전달하고, 정적 컨텐츠를 찾더라도, 컨트롤러의 매핑 메서드가 우선 순위를 가져서 매칭되는 매핑 메서드를 먼저 찾는다. 매칭되는 메서드가 없을 경우 뷰 리졸버가 호출되지 않고, static 폴더에서 매칭되는 static한 html을 찾아서 브라우저에 반환한다.
2. API 방식
html이 아닌 데이터만을 요청할 때 API 방식을 사용한다. 서버에서 화면을 렌더링 하는 경우가 아니라,
1) 프론트에는 데이터를 주기만 하고, 화면 구성은 프론트에서 할 때
2) 서버끼리 데이터를 주고 받을 때
위 경우 API 방식을 사용한다.
ResponseBody를 넣으면 Http 바디에 return에 있는 내용을 직접 넣겠다는 의미이며, 실행 시 데이터만 넘어간다.
-> 동작 원리
브라우저에서 URL 요청을 하고, 톰켓을 거쳐 스프링으로 전달된 후, 컨트롤러에서 매핑 메서드를 찾는다. @ResponseBody가 없으면 뷰 리졸버가 동작하며,
문자열 : StringConverter
객체 : JsonConverter
이 경우에는 뷰 리졸버 대신, HttpMessageConverter가 동작하여 객체를 키-벨류로 변환하여 브라우저에게 전달한다.
이를 객체 지향적으로 할 수 있다. 객체를 만들고 데이터를 넣어 반환하면 Json 형식으로 데이터만 반환된다.
- 스프링 빈과 의존 관계
스프링은 필요한 객체를 빈으로 등록하고 의존 관계를 맺어준다.
어노테이션을 붙이면 스프링을 처음 띄울 때 스프링 컨테이너가 생기고 그 안에 어노테이션이 있는 클래스의 객체를 생성해서 넣어두고 스프링이 관리한다. 이제부터 스프링 컨테이너에 등록하고 딱 하나만 쓰게 된다.
-> 등록
스프링은 최초 시작할 때, 컨테이너를 띄우고 사용할 객체를 컨테이너에 넣어둔다. 클래스의 생성자를 호출해서 빈으로 등록한다. 이때 생성자에 의존 관계가 있으면 해당 클래스를 컨테이너에서 찾아서 인자로 넣어준다. @Component가 붙은 클래스의 객체를 컨테이너에 등록하고, @Autowired가 붙은 생성자의 의존관계를 맺어준다.
=> 빈 등록 방법
1. 컴포넌트 스캔 방식
@Component가 붙은 클래스를 스프링이 시작할 때 자동으로 빈으로 등록하는 방식이다. @Component가 붙은 객체는 스프링이 빈 등록할 때 싱글톤으로 등록하며, 하나만 등록해서 공유한다.
2. 직접 등록
@Configuration
public class SpringConfig {
@Bean
public MemberService memberService(MemberRepository memberRepository) {
return new MemberService(memberRepository);
}
@Bean
public MemberRepository memberRepository() {
return new MemoryMemberRepository();
}
}
정형화된 코드는 컴포넌트 스캔을 하고 상황에 따라 구현 클래스를 변경해야 하면 직접 등록한다.
=> 빈 객체 변경
스프링을 사용하는 이유과 객체 지향이 좋은 이유는 클래스를 부품 교체하듯이 기능을 확장하고 바꾸면서 개발을 할 수 있기 때문이다. 스프링은 OCP, DIP와 같은 원칙을 편리하게 지킬 수 있도록 도와준다. 인터페이스를 만들고 구현체를 다르게 구현하여 교체하는 것을 스프링이 편리하게 지원한다. 객체 지향에서 다형성의 개념을 잘 활용하면 기능을 완전히 변경해도 어플 전체를 수정할 필요가 없다.
✅ 좋은 객체 지향 프로그램이 뭘까?
역할과 구현을 분리하고 부품 교체하듯이 개발할 수 있는 프로그램이다.
-> 다형성의 실세계 비유
자동차의 역할과 구현체를 분리하여 개발하면 위와 같은 모습이다. K3를 타다가 아반떼로 바꾸면 운전자에게 영향이 있을까? 운전자는 자동차 역할에 맞게 기능을 이용하기 때문에 어떤 구현체가 오더라도 상관없다.
//역할
public interface Car {
void myName();
}
// 구현
public class K3 implements Car{
@Override
public void myName() {
System.out.println("나는 K3");
}
}
//구현
public class AvanThe implements Car{
@Override
public void myName() {
System.out.println("나는 아반떼");
}
}
// 클라이언트
public class Human {
//필드
private Car car;
//메서드
public void getCar(Car car) {
this.car = car;
car.myName(); // 자동차 인터페이스
}
}
// 실행 시점에 유연하게 변경
public class CarTest {
public static void main(String[] args) {
Human human = new Human();
Scanner scanner = new Scanner(System.in);
int chooseCar = scanner.nextInt();
if(chooseCar == 1){
human.getCar(new AvanThe());
}
else {
human.getCar(new K3());
}
}
}
유연하고 변경이 용이하다는 뜻은 자동차를 바꿔도 운전자는 변화없이 운전할 수 있다는 것으로 자동차 인터페이스에 따라, 자동차를 구현했기 때문에 운전자는 자동차 역할에 대해서만 의존하고 있고 내부 구조를 몰라도 된다. 따라서, 자동차 역할만 알고 있으면 클라이언트에게 영향을 주지 않는다. 이것을 변경이 용이하고 무한히 확장 가능하다고 하며, 클라이언트에게 영향을 주지 않고 새로운 기능을 제공할 수 있다.
-> 다형성의 본질
클라이언트에서 코드 하나 변경하지 않고 서버의 구현 기능을 유연하게 변경할 수 있는 것이 다형성의 본질이다.
✅ 스프링과 객체지향 OCP 원칙
다형성만으로는 OCP와 DIP를 만족할 수 없다. 스프링은 이러한 다형성의 본질을 편리하게 사용하여 객체지향 원칙을 만조하도록 도와준다.
OCP 원칙을 지키려면, 새로운 기능을 추가해도 코드의 변경이 없어야 한다. 테슬라 구현체라는 새로운 구현체 클래스의 기능 추가에는 코드의 변경이 발생하지만, 클라이언트인 운전자에는 코드 변경이 없어야 한다.
위처럼, 역할과 구현을 잘 만들었지만, 기존 코드에 변경이 있어서 OCP가 깨지게 되는데 이 관계를 맺어주는 별도의 조립, 설정자가 스프링 컨테이어이다.
-> DIP
개발자는 인터페이스에 의존해야지, 구현체에 의존하면 안 된다는 원칙으로, 의존성 주입은 이 원칙을 따르는 방법 중 하나이다. 클라이언트 코드가 구현 코드를 바라보지 말고 인터페이스만 바라보라는 뜻이다.
위처럼, 구현체인 메모리 멤버 레포지토리에 의존하면 안된다. 이를 스프링이 편리하게 지원한다.
-> 정리
객체 지향의 핵심은 다형성으로 다형성만으로는 OCP, DIP를 지킬 수 없는데 스프링이 이를 해결해준다.
- IoC (Inversion of Control) 제어의 역전이란?
예전에는 개발자가 직접 클래스를 호출했는데 이제는 프레임워크가 대신 호출해 주는 것을 보고 제어권이 바뀐다고 해서 IoC이다.
이전에는 직접 FixDiscountPolicy를 호출했지만,
이젠 AppConfig가 생성해서 넣어준다. 서비스 코드에는 의존관계 관련 코드는 사라지고 로직 관련 코드만 남게 된다.
- 의존관계 주입이란?
1. 정적 클래스 의존 관계
import로 코드를 실행하기 전에 사용하는 클래스 정보를 알 수 있다. 하지만, Member 레포지토리 인터페이스에 어떤 구현체가 들어오는지 알 수 없고, 실행해봐야 알 수 있다.
2. 동적 클래스 의존 관계
어플리케이션 실행 시점에 외부에서 실제 구현 객체를 생성하고 클라이언트에 전달해서 실제 의존 관계가 연결되는 것을 의존 관계 주입이라고 한다.
※ IoC 컨테이너와 DI 컨테이너
스프링 컨테이너를 빈 객체를 등록하고 연결 시켜준다고 해서 IoC 컨테이너라고 부른다.
Application Context가 IoC 컨테이너이다. 생성자의 AppConfig 설정 정보를 통해 @Bean 붙은 애들을 컨테이너에 객체로 생성해서 넣어준다.
컨테이너 생성자 호출 시 AppConfig.class라는 Class 타입으로 메서드 영역에 등록된 class 파일 정보를 가져오고 기본 생성자를 호출해서 reader와 scanner를 초기 셋팅한다.
이후 AppConfig에 있는 설정 정보를 통해 스프링 @Bean 어노테이션이 붙은 애들을 객체로 생성해서 넣는다.
-> 정리
스프링 빈은 @Bean이 붙은 메서드를 빈 이름으로 쓴다. key는 빈 이름, 값은 빈 객체가 된다. 위 경우 4개의 빈이 등록된다.
등록이 되면, 의존 관계를 넣어준다. 스프링 컨테이너는 설정 정보를 참고해서 의존관계를 주입한다.
'[백엔드] > [spring | 학습기록]' 카테고리의 다른 글
[문법] 빈 생명주기 콜백 (0) | 2024.08.01 |
---|---|
[문법] 의존관계 자동 주입 (0) | 2024.07.31 |
spring PART.중간점검 5 (0) | 2023.05.24 |
spring PART.트랜잭션 전파 활용 (0) | 2023.05.23 |
spring PART.트랜잭션 전파 기본 (0) | 2023.05.22 |