코드스테이츠

코드스테이츠 34일차

안형준 2022. 6. 14. 18:12

학습 목표

  • Spring Framework이 무엇인지 이해할 수 있다.
  • Spring Framework을 왜 배워야하는지 이해할 수 있다.
  • Spring Framework의 아키텍처를 버드 아이 뷰 관점에서 이해할 수 있다.
  • Spring Framework 모듈이 무엇이고 Spring Framework에서 지원하는 모듈에는 어떤 것이 있는지 이해할 수 있다.
  • Spring Boot이 무엇이고 Spring Boot을 왜 사용해야하는지 이해할 수 있다.

 

Framework이란?

 

Framework은 기본적으로 프로그래밍을 하기 위한 어떠한 틀이나 구조를 제공 한다라는 것을 알 수 있다.

그리고 Framework은 프로그래밍을 위한 기본 구조를 제공하는 것 이상의 많은 기능들을 제공한다.

개발하고자 하는 애플리케이션을 그 밑바닥부터 일일이 전부 개발하는 것이 아닌 다양한 기능들을 Framework이 라이브러리 형태로 제공함으로써 개발자가 애플리케이션의 핵심 로직을 개발하는 것에 집중할 수 있도록 해준다.

 

Framework과 Library의 차이는?

애플리케이션의 구현을 위해 필요한 여러가지 기능들을 제공한다라는 의미에서 Framework과 Library는 유사하다고 볼 수 있지만 이 둘은 애플리케이션에 대한 제어권에서 차이점이 존재한다.

 

개발자가 짜 놓은 코드내에서 필요한 기능이 있으면 해당 라이브러리를 호출해서 사용하는 것이 바로 Library이다.

즉, 애플리케이션 흐름의 주도권이 개발자에게 있는 것이다.

 

그렇다면 Framework는?

예를들어 Spring Framework에서 지원하는 기능을 이용해 작업을 하면, Spring Framework에서는 개발자가 작성한 코드를 사용해서 애플리케이션의 흐름을 만들어낸다.

즉, 애플리케이션 흐름의 주도권이 개발자가 아닌 Framework에 있는 것이다.

 

Spring Framework을 배워야 하는 이유

Spring Framework이 등장하기 전에는 JSP, 서블릿(Servlet)을 사용했으나 코드가 간결하지 못하고 협업하는 데에 큰 불편함이 있어 Spring MVC가 등장하게 되었다.

하지만 Spring MVC은 매우 복잡한 설정으로 이루어져 있어 입문하기에 어려움이 있었다.

그렇게 복잡한 설정을 대신 해줄 수 있는 Spring Boot가 등장하게 되었다.

** JPA 자바 프로그래밍을  때에 영구적으로 데이터를 저장하기 위해 필요한 인터페이스를 JPA라고 한다.

심화 학습

Servlet(서블릿)

클라이언트의 요청을 처리하고, 그 결과를 반환하는 Servlet 클래스의 구현 규칙을 지킨 자바 웹 프로그래밍 기술

예를 들어, 한 사용자가 로그인을 할 때. 사용자는 아이디와 비밀번호를 입력하고, 로그인 버튼을 누른다. 

그때 서버는 클라이언트의 아이디와 비밀번호를 확인하고, 다음 페이지를 띄워주어야 하는데, 이러한 역할을 수행하는 

것이 서블릿(Servlet)이다.

 

Servlet Container(서블릿 컨테이너)

Servlet Container(서블릿 컨테이너)는 서블릿을 관리해주는 컨테이너라고 볼 수 있다.

서블릿 컨테이너는 클라이언트의 요청(Request)을 받아주고 응답(Response)할 수 있게, 웹서버와 소켓으로 통신하며 대표적인 예로 톰캣(Tomcat)이 있다. 톰캣(Tomcat)은 실제로 웹 서버와 통신하여 JSP(자바 서버 페이지)와 Servlet이 작동하는 환경을 제공해준다.

 

 

학습 목표

  • POJO(Plain Old Java Object)
  • IoC(Inversioin of Control)/DI(Dependency Injection)
  • AOP(Aspect Oriented Programming)
  • PSA(Portable Service Abstraction)

 

POJO(Plain Old Java Object)

POJO는 Spring에서 사용하는 핵심 개념들에 둘러 싸여져 있는 모습이다.

이는 POJO라는 것을 IoC/DI, AOP, PSA를 통해서 달성할 수 있다는 것을 의미한다.

 

POJO에서 ‘PO’는 Java로 생성하는 순수한 객체를 의미한다.

 

POJO 프로그래밍이란?

POJO 프로그래밍이란 POJO를 이용해서 프로그래밍 코드를 작성하는 것을 의미한다.

POJO 프로그래밍을 위해서 크게 두가지의 규칙이 있다.

 

  1. Java나 Java의 스펙(사양)에 정의된 것 이외에는 다른 기술이나 규약에 얽매이지 않아야 한다.

1번과 같이 하는 이유는? 

특정 기술을 상속 해서 코드를 작성하게 되면 나중에 애플리케이션의 요구사항이 변경되서 다른 기술로 변경하려면 변경이 필요한 부분들을 전부 다 일일이 제거하거나 수정해야하는 불편함이 있다.

또한 Java는 다중 상속을 지원하지 않기 때문에 ‘extends’ 키워드를 사용해서 한 번 상속을 하게되면 상위 클래스를 상속받아서 하위 클래스를 확장하는 객체지향 설계 기법을 적용하기 어려워진다.

  1. 특정 환경에 종속적이지 않아야 한다.

2번과 같이 하는 이유는? 

순수 Java로 작성한 애플리케이션 코드내에서 Tomcat이 지원하는 API를 직접 가져다가 사용한다고 가정을 해보자

만약에 시스템의 요구 사항이 변경되어서 Tomcat이 아닌 다른 Servlet Container를 사용하게 된다면 애플리케이션 코드에서 사용하고 있는 Tomcat API 코드들을 모두 걷어내고 수정해야 하는 절망적인 순간이 올 것이다.

 

POJO 프로그래밍이 필요한 이유

  • 특정 환경이나 기술에 종속적이지 않으면 재사용 가능하고, 확장 가능한 유연한 코드를 작성할 수 있다.
  • 저수준 레벨의 기술과 환경에 종속적인 코드를 애플리케이션 코드에서 제거 함으로써 코드가 깔끔해진다.
  • 코드가 깔끔해지기 때문에 디버깅하기도 상대적으로 쉽다.
  • 특정 기술이나 환경에 종속적이지 않기 때문에 테스트 역시 단순해진다.
  • 객체지향적인 설계를 제한없이 적용할 수 있다.(가장 중요한 이유)

 

한줄로 정리하자면 Spring은 POJO 프로그래밍을 지향하는 Framework이다.

객체지향 프로그래밍 - 설계 5대 원칙, SOLID

1. SRP (단일책임의 원칙: Single Responsibility Principle)

한 클래스는 하나의 책임(기능)을 가진다는 것이 단일책임의 원칙이다.

예를 들어 자동차를 설계한다고 가정하자

차를 구성하고 있는 요소들은 정말 다양한데 이것들을 car라는 클래스에 전부 넣어버리면 코드가 너무 복잡해진다.

그렇기에 여러개로 쪼개서 사용을 권고하는 것이 단일책임의 원칙이다.

2. OCP (개방폐쇄의 원칙: Open Close Principle)

기능을 추가할 때는 기존의 코드를 변경하지 않고도 추가할 수 있어야 한다는 것이 개방폐쇄의 원칙이다.

예를들어 animal 클래스를 만들었다고 가정하자

이 클래스에 ‘울기’라는 메서드로 강아지라면 -> 멍멍, 고양이라면 -> 야옹이 셋팅되어 있을 때 다른 동물들의 울음소리를 추가할 때에는 매번 기존의 코드를 수정 해줘야하는 번거로움이 있다.

그렇기에 상위 클래스 또는 인터페이스를 중간에 둠으로써 다양한 동물의 울음소리가 생긴다고 해도 다른 동물들의 울음 메서드에는 영향을 주지 않게 된다.

 

3. LSP (리스코브 치환의 원칙: The Liskov Substitution Principle)

상위타입의 객체를 사용하다가 이것을 상속받은 하위 타입의 객체로 사용해도 정상작동 해야 한다는 것이 리스코브 치환의 원칙이다.

예를 들어 로봇이라는 상위 객체가 있고, AI로봇이라는 하위 객체가 있다고 가정하자

로봇의 기본 포장 규격이 10x10이라고 칠 때, AI 로봇은 여러가지 기능들이 추가되기 때문에 규격을 초과하게 되어 실행이 되지 않는다.

4. ISP (인터페이스 분리의 원칙: Interface Segregation Principle)

하나의 인터페이스를 특정 클래스가 구현하고 있을 때 큰 범위의 인터페이스를 사용하지 않고 작은 범위로 나누어 사용해야 한다는 것이 인터페이스 분리의 원칙이다.

예를 들어 동물 인터페이스(짖는 기능, 걷는 기능 두가지의 기능이 있다고 가정)가 있고, 강아지라는 클래스가 있다고 치자

그런데 고양이를 추가해야 할 때 고양이는 짖지 못하고 걸을 수만 있다고 생각해보자

하지만 동물 인터페이스를 사용하기 위해 짖는 기능도 고양이에서 구현을 해야 하는 불편함이 있다.

그렇기에 인터페이스를 짖는 기능과 걷는 기능으로 따로 나누어 사용하면 각 클래스에 맞게 구현할 수 있다.

5. DIP (의존성역전의 원칙: Dependency Inversion Principle)

의존 관계를 맺을 때 좀 더 일반적이고 추상적인 것에 의존해야 한다는 게 의존성역전의 원칙이다.

예를 들어 토끼라는 클래스가 있고, 토끼라는 클래스 안에 먹이라는 내부 필드가 있다고 가정하자

내부 필드에는 당근이라는 데이터 타입을 받게 될 것이다. 그런데 당근에서 사과로 먹이를 바꿀 때 기존의 당근을 사과로 바꿔주기 위해 코드를 다 열어서 수정을 해야 한다.

하지만 여기서 당근과 사과 모두 Vegetable이라는 상위 클래스를 상속받고 있다고 가정하면 당근이나 사과와 같은 하위 개념이 아닌 Vegetable이라는 상위 개념으로 의존하는 것이 좋다는 것이다.

IoC(Inversion of Control)/DI(Dependency Injection)

Library는 애플리케이션 흐름의 주도권이 개발자에게 있고, Framework은 애플리케이션 흐름의 주도권이 Framework에 있다.

여기서 말하는 애플리케이션 흐름의 주도권이 뒤바뀐 것을 바로 IoC(Inversion of Control)라고 한다.

원칙대로라면 개발자가 작성한 코드를 순차적으로 실행하는게 애플리케이션의 일반적인 제어 흐름이다.

 

Java 웹 애플리케이션에서 IoC가 적용되는 예

Java 콘솔 애플리케이션이 아니라 웹 상에서 돌아가는 Java 웹 애플리케이션의 경우를 살펴보자

Java 콘솔 애플리케이션의 경우 main() 메서드가 종료되면 애플리케이션의 실행이 종료되지만 웹에서 동작하는 애플리케이션의 경우 클라이언트가 외부에서 접속해서 사용하는 서비스이기 때문에 main() 메서드가 종료되지 않아야한다.

그런데 서블릿 컨테이너에는 서블릿 사양(Specification)에 맞게 작성된 서블릿 클래스만 존재하지 별도의 main() 메서드가 존재하지 않는다.

main() 메서드처럼 애플리케이션이 시작되는 지점을 엔트리 포인트(Entry point)라고도 부른다.

 

그렇다면 어떻게 main() 메서드가 없는데 애플리케이션이 실행 되는걸까?

서블릿 컨테이너의 경우, 클라이언트의 요청이 들어올 때마다 서블릿 컨테이너 내의 컨테이너 로직(메서드)가 서블릿을 직접 실행시켜 주기 때문에 main() 메서드가 필요하지 않다.

이 경우에는 서블릿 컨테이너가 서블릿을 제어하고 있기 때문에 애플리케이션의 주도권은 서블릿 컨테이너에 있다.

이런 경우를 서블릿과 웹 애플리케이션 간에 IoC(제어의 역전)의 개념이 적용되어 있는 것이라고 볼 수 있다.

 

DI(Dependency Injection)란?

IoC(제어의 역전)는 서버 컨테이너 기술, 디자인 패턴, 객체 지향 설계 등에 적용하게 되는 일반적인 개념인데 반해 DI(Dependency Injection)는 IoC 개념을 조금 구체화 시킨 것이라고 볼 수 있다.

DI(Dependency Injection)를 번역하면 의존성 주입이라는 뜻인데 

이때 의존한다는 뜻은 예를 들어 B클래스의 기능을 사용하기 위해 B 클래스에 구현되어 있는 어떤 메서드를 호출하는 A클래스가 있다고 가정하자

이런 경우 A 클래스가 B 클래스의 기능을 사용 할 때, ‘A클래스는 B클래스에 의존한다’라고 말할 수 있다.

생성자를 통해 어떤 클래스의 객체를 전달 받는 것을 ‘의존성 주입’ 이라고 한다.

 

new 키워드를 사용해서 의존 객체를 생성할 때, 클래스들 간에 강하게 결합(Tight Coupling)되어 있다라고 한다.

이 경우 참조 할 클래스가 바뀌게 될 때, 이 클래스를 사용하는 모든 클래스들을 수정할 수 밖에 없는 불편함이 있다.

그렇기에 의존성 주입을 할 때에는 느슨한 결합(Loose Coupling)이 필요하다.

 

느슨한 의존성 주입은 어떻게 할까?

Java에서 클래스들 간의 관계를 느슨하게 만드는 대표적인 방법은 바로 인터페이스(Interface)를 사용하는 것이다.

인터페이스를 통해 클래스를 직접적으로 의존하는게 아니라 클래스 이름은 같지만 인터페이스를 의존하게 만드는 것이다.

하지만 아무리 느슨하게 만들려고 해도 객체를 생성하기 위해 new 키워드를 사용해야 하는 경우가 많은데 이런 점을 Spring이 대신 해결해준다.

 

어떻게 없애주는데?

Config 클래스를 통해 없앨 수 있다.

Config 클래스에 정의해둔 A 객체를 Spring의 도움을 받아서 B 클래스에게 제공을 해준다.

 

하지만 이렇게 생각할 수도 있을 것이다.

Config 클래스 안에서 new 키워드로 객체를 생성 했으니까 이것도 문제인거 아냐??

Config 클래스는 단순한 클래스가 아니라 Spring Framework의 영역에 해당하는 것이고 이 Config 클래스가 실제 애플리케이션의 핵심 로직에 관여하지 않고 있다.

즉 온전히 Spring Framework의 영역인 것이다.

 

이처럼 Spring 기반의 애플리케이션에서는 Spring 의존 객체들을 주입해주기 때문에 애플리케이션 코드를 유연하게 구성할  있다.