기술 면접(스프링 부트 1)

유요한·2024년 2월 23일
0

기술면접

목록 보기
5/27
post-thumbnail

스프링 부트

스프링 부트는 구성을 스프링이 관리해서 개발을 도와주어서 비즈니스 로직에만 집중할 수 있습니다. 스프링 부트는 빌드 플로그인을 제공하고 이를 실행하면 단독 실행 가능한 JAR파일을 만들 수 있습니다. 그리고 기능별로 라이브러리 의존성을 포함한 starter를 제공합니다.

스프링 부트는 자동 구성 기능을 제공한다. 그래서 특히 조건들이 충족되면 미리 설정된 자바 설정 클래스가 동작하고 애플리케이션을 구성합니다. 스프링 부트의 모듈인 spring-boot-autoconfigure는 스프링에서 사용할 수있는 수많은 기능을 자동 설정으로 제공합니다. 그리고 기본 모니터링 지표와 헬스 체크 기능을 기본으로 제공합니다. 그래서 모니터링 솔루션을 이용해서 각 서버들의 상태와 지표를 수집하기 매우 쉽습니다.

정리

  • 라이브러리 관리 자동화
    스프링 부트는 스타터(Starter)라는 것을 이용해 특정 기능에 필요한 라이브러리 의존성을 더욱 간단히 처리할 수 있다.

  • 설정의 자동화
    스프링 부트에서는 프로젝트에 추가된 라이브러리를 기반으로 실행에 필요한 환경을 자동으로 설정해준다. 따라서 개발자들은 복잡한 타임리프 설정을 하지 않고도 바로 화면 개발에 들어갈 수있다.

  • 라이브러리 버전 자동 관리
    스프링을 사용하여 개발할 때 가장 신경쓰이는 것이 라이브러리와 버전관리다. 스프링 부트를 사용하면 스프링 부트 버전에 해당하는 스프링 라이브러리뿐만 아니라 서드파티 라이브러리들도 호환되는 버전으로 다운로드해준다.

  • 테스트 환경
    스프링 부트로 생성한 프로젝트에는 JUnit을 비롯한 테스트 관련 라이브러리들이 기본적으로 포함되어 있다. 따라서 컨트롤러를 비롯한 다양한 계층의 클래스들에 대해서 테스트 케이스를 쉽게 작성할 수 있다.

  • 독립적으로 실행 가능한 JAR
    애플리케이션을 개발하고 테스트까지 마쳤으면 애플리케이션을 실제 운영 서버에 배포하기 위해서는 패키징을 해줘야 한다. 프로젝트가 일반 자바 프로젝트면 간단하게 JAR파일로 패키징하면 되지만 웹 프로젝트라면 WAR파일로 패키징 해야 한다. 하지만 스프링 부트는 독립적으로 실행 가능한 애플리케이션을 빠르게 개발하는 것을 목표로 하기 때문에 웹 애플리케이션도 WAR이 아닌 JAR파일로 패키징하여 사용할 수 있다.

  • 내장 톰캣
    톰캣 서버를 내장하고 있어서 빠르게 실행 결과를 볼 수 있다.

💡Spring과 SpringBoot의 차이

SpringBoot는 Spring Framework에서 사용하는 프로젝트를 간편하게 셋업할 수 있는 서브 프로젝트입니다. SpringBoot는 다른 프레임워크가 아닙니다. Spring에서 버전이 업그레이드 되어서 SpringBoot가 되었습니다.

Spring에서는 개발자가 직접 설정 파일(XML)을 작성하여 스프링 컨테이너를 구성하고, 필요한 빈 객체를 등록하고, 빈 객체 간의 의존성을 설정해야 합니다. Spring은 특정한 구성을 위해 추가적인 라이브러리와 설정이 필요합니다. 반면, Spring Boot는 스프링 프레임워크를 보다 쉽게 사용할 수 있도록 만든 프레임워크입니다. Spring Boot에서는 개발자가 설정 파일을 작성할 필요 없이, 프로젝트의 설정과 라이브러리 의존성을 자동으로 처리해주는 기능을 제공합니다.또한, Spring Boot는 실행 가능한 JAR 파일을 만들 수 있습니다.

💡JSP → Spring Framework → Spring Boot 이렇게 사용하는 이유

JSP를 사용해보면 servlet을 사용해서 개발을 하는데 먼저, JSP란 자바 코드안에 html을 사용할 수 있습니다. 여기까지 보면 좋아보일 수 있지만 프로젝트를 하다보면 데이터를 조회하는 레포지토리 등 다양한 코드가 모두 JSP에 노출되어 있습니다. JSP가 많은 역할을 한다. 가독성이 떨어지고 유지보수가 어렵습니다.

그러다보니 Spring Framework가 나왔는데 일일히 코드를 작성하지 않아도 어노테이션으로 기능을 이끌어 낼 수 있어서 어노테이션을 잘 파악하고 있으면 프로젝트를 진행하기 편합니다. 하지만 아직 XML에 의존적인 경향이 있습니다.

그래서 Spring Boot가 나왔고 스프링 부트는 설정을 개발자가 직접 설정하지 않아도 쉽게 설정할 수 있고 내장된 서버가 있어서 배포하는데 번거로운 작업을 줄여줍니다. 그리고 스타터 의존성을 제공하므로 개발자는 개발하는데 집중할 수 있게 됨으로서 편리함을 줍니다.


MVC 패턴

MVC 패턴은 모델, , 컨트롤러로 이루어진 디자인 패턴입니다.

모델

모델은 애플리케이션의 데이터와 비즈니스 로직을 나타냅니다. 데이터베이스, 웹 서비스, 파일 시스템 등과 같은 데이터 소스로부터 데이터를 가져와 처리하고, 그 결과를 뷰(View)나 컨트롤러(Controller)에 전달합니다. 모델은 뷰나 컨트롤러와 직접적으로 통신하지 않으며, 데이터를 가공하고 유지하기 위한 로직을 포함합니다.

모델을 기반으로 사용자가 볼 수 있는 화면을 뜻합니다. 모델이 가지고 있는 정보를 따로 저장하지 않아야 하며 단순히 사각형 모양 등 화면에 표시하는 정보만 가지고 있어야 합니다. 또한 변경이 일어나면 컨트롤러에 이를 전달해야 합니다.

컨트롤러

컨트롤러는 하나 이상의 모델과 하나 이상의 뷰를 잇는 다리 역할을 하며 이벤트 등 메인 로직을 담당합니다. 또한, 모델과 뷰의 생명 주기도 관리하며 모델이나 뷰의 변경 통지를 받으면 이를 해석하여 각각의 구성 요소에 해당 내용에 대해 알려줍니다.

💡왜 MVC 패턴을 사용해야 할까?

서로 분리되어 각자의 역할에 집중할 수 있게끔하여 개발을 하고 그렇게 애플리케이션을 만든다면, 유지보수성, 애플리케이션의 확장성, 그리고 유연성이 증가하고, 중복 코딩이라는 문제점 또한 사라지게 되는 것입니다. 그러기 위한 MVC패턴입니다.

💡스프링 MVC 기본 구조

스프링 프레임워크는 하나의 기능을 위해서만 만들어진 프레임 워크가 아닌 코어라고 할 수 있는 여러 서브 프로젝트들을 결합해서 다양한 상황에 대처할 수 있도록 개발되었습니다. 그 중 하나가 스프링 MVC 구조입니다.

스프링 MVC에서 어떤 단계를 거쳐서 실행되는지를 이해해야 문제 발생 시 빠른 대처와 대안을 찾을 수 있다.

웹 프로젝트는 3-tier(티어) 방식으로 구성합니다.

Persistance ↔ Business ↔ Presentation

Persistance Tier(영속 계층, 데이터 계층)

  • 데이터를 어떤 방식으로 보관하고, 사용하는가에 대한 설계가 들어가는 계층
  • 일반적으로 DB를 많이 이용하지만, 상황에 따라서 네트워크 호출 혹은 원격 호출 등의 기술이 접목된다.

Business Tier(비즈니스 계층)

  • 순수한 비즈니스 로직을 담고 있는 영역
  • 고객(외주업체)이 원하는 요구사항을 반영하는 계층이기 때문에 중요한 영역이다.
  • 이 영역의 설계는 고객의 요구 사항과 정확히 일치해야 하며, ~~~Service와 같은 이름으로 구성한다.

Presentation Tier(화면 계층)

  • 화면에 보여주는 기술을 사용하는 영역
  • 스프링 MVC가 담당하는 영역이며 화면 구성이 이에 속한다.

💡스프링 MVC Controller의 특징

HttpServletRequest, HttpServletResponse를 거의 사용할 필요가 없이 기능 구현 다양한 타입의 파라미터 처리, 다양한 타입의 리턴 타입 사용 가능 Get방식, Post방식 등 전송 방식에 대한 처리를 어노테이션으로 처리 가능 상속/인터페이스 방식 대신 어노테이션으로 간단하게 설정 가능합니다.

💡어떻게 하나의 컨트롤러로 여러 요청을 받을까?

컨트롤러는 기본적으로 컴포넌트 스캔되면서 스프링 빈 컨테이너에 올라가있고 싱글톤 패턴으로 구현되어있기 때문에 여러 스레드의 요청이 들어와도 하나의 컨트롤러 객체를 공유하면서 처리한다. 여기서 주의할 점은 싱글톤 패턴으로 구현되어 있어 있다는 것은 Thread-Safe하지 않다는 의미이므로 상태를 저장하는 코드가 없게 Stateless하게 설계해야 한다. 결과적으로 내부에는 상태가 존재하지 않으니 메서드에 대한 정보만 같이 공유해서 쓰는 것이다.

💡MVC 패턴에서 Service Model의 역할

View는 자신이 요청할 Controller만 알면 되며 Controller는 넘어온 매개변수를 이용해 Service 객체를 호출하기만 하면 된다.

Service는 POJO객체로 구성된다. Controller처럼 Request / Response를 받지도 않고, DAO처럼 DB와 데이터를 주고받지도 않는다. Controller의 요청에 맞추어 Repository에서 받은 정보를 가공하여 Controller에게 넘겨주는 비지니스 로직입니다.

여기서 의문이 생긴다. 그러면 Service가 왜 필요할까?

DAO는 단일 데이터 접근 로직이다. 말 그대로 SQL 하나 보내고 결과를 받는 것이 전부인 로직이다. 하지만 비즈니스 로직이 단순이 SQL 하나 보내서 끝나는 것이 아니다. 여러번의 DB 접근이 필요하고 DB에서 받아온 값을 가공해서 필요한 것만 줄 필요가 있습니다. 그렇기 떄문에 Service라는 개념이 나온 것이다. 하나의 서비스를 위해 여러개의 DAO를 묶은 트랜잭션이 생성되고, Service는 곧 트랜잭션의 단위가 된다. 또 다른 점으로, Controller 내부에서 필요한 여러 Service를 구분하는 필요성을 가진다. 비슷한 요청이더라도 내부 로직이 달라야한다면 Controller는 매우 복잡해질 가능성이 있다. 이러한 점을 분리하여 Controller는 단순이 요청을 받아 해당 요청에 맞는 Service에 데이터를 주입하는 역할이다.

재사용이라는 Spring의 큰 장점은 Service가 중요한 작용점이다.

💡DTO란?

DTO(Data Transfer Object)란 계층간 데이터 교환을 위해 사용하는 객체(Java Beans)입니다. DTO는 주로 클라이언트에서 서버 쪽으로 전송하는 요청 데이터를 전달 받을 때, 서버에서 클라이언트 쪽으로 전송하는 응답 데이터를 전송하기 위한 용도로 사용된다. 데이터 교환만을 위해 사용하므로 로직은 없고 데이터를 담아서 받고 보내는 역할을 합니다.

장점

  • 하나의 객체로 모두 전달 받을 수 있기 때문에 코드 자체가 간결해진다.
  • 데이터 유효성(Validation) 검증이 단순해진다.
  • DTO 데이터를 받기 때문에 필요한 정보들만 노출된다.
  • 계층간 데이터를 전달하는 과정에서 데이터가 변조되지 않는다. 인터페이스, API 안정성
  • DTO 를 통해 여러 데이터를 한꺼번에 보낼 수 있기에 좋습니다. 비용 절감

단점
클래스가 너무 많아져서 관리가 안될 수도 있다는 단점이 있습니다.
Service layer에서의 DTO는 DB에 맞춰 DTO를 모두 만드는 시간과 객체 관계 맵핑에 대해 고민하는 비용이 매우 큽니다.

💡Spring의 스코프 프로토 타입 빈에 대해 설명해주세요.

프로토타입 빈싱글톤(default bean) 빈과는 달리 컨테이너에게 빈을 요청할 때마다 매번 새로운 객체를 생성하여 반환해줍니다. 이렇게 빈의 scope를 간단하게 관리해줄 수 있는 것이 spring의 장점입니다.

💡가비지 컬렉터에 대해 설명하고, 가비지 컬렉션 과정에 대해 설명해주세요

가비지 컬렉터메모리 누수 방지를 위한 GC(Garbage Collection)를 수행하는 주체입니다. GC는 메모리 관리 기법 중 하나로, 동적으로 할당 했던 메모리 영역(Heap) 중 필요 없게 된 영역을 해제하는 기능입니다. 가비지 컬렉터는 자바 가상 머신(JVM)의 메모리 관리를 담당하는 부분입니다. 자바는 가비지 컬렉션을 통해 더 이상 참조되지 않는 객체들을 자동으로 수거하여 메모리를 관리합니다. JVM은 프로그램이 실행되는 동안 생성되는 객체를 추적하고, 더 이상 참조되지 않는 객체를 식별하여 메모리에서 해제하는 일을 가비지 컬렉터에게 맡깁니다. 이로인해 메모리 누스를 방지하고 애플리케이션 성능을 최적화하는데 도움이 됩니다.

가비지 컬렉터와 스프링 부트의 연관성은 스프링 부트는 JVM 위에서 동작하므로 가비지 컬렉터가 메모리 관리하는데 중요한 역할을 합니다.

💡싱글톤에 대해 설명해주세요

  • 클래스의 인스턴스가 딱 1개만 생성되는 것을 보장하는 디자인 패턴이다.

  • 객체 인스턴스를 2개 이상 생성하지 못하도록 막아야한다.

    private 생성자를 사용해서 외부에서 임의로 new 키워드를 사용하지 못하도록 막아야 한다.

  • 고객 트래픽이 초당 100이 나오면 초당 100개 객체가 생성되고 소멸된다.

    메모리 낭비가 심하다. 해결방안은 해당 객체가 딱 1개만 생성되고 공유하도록 생성하면 된다.(싱글톤 패턴)

싱글톤 단점

  • 싱글톤 패턴을 구현하는 코드 자체가 많이 들어간다.
  • 의존관계상 클라이언트가 구체 클래스에 의존한다.

    DIP를 위반

  • 테스트하기 어렵다.
  • 내부 속성을 변경하거나 초기화하기 어렵다.
  • private 생성자로 자식 클래스를 만들기 어렵다.

결론적으로 유연성이 떨어진다.

스프링 싱글톤 패턴

객체의 생성을 스프링에 의존함으로써 스프링 컨테이너가 관리하여 자바 언어 레벨에서 직접 구현하기 위한 내용들이 모두 제거되어 앞선 싱글톤 패턴의 단점이 제거 됩니다.

Spring Container

컨테이너는 보통 객체의 Life Cycle을 관리하며, 생성된 인스턴스에게 추가적인 기능을 제공하도록 하는 것입니다. 객체 인스턴스를 싱글톤(1개만 생성)으로 관리합니다.

컨테이너의 종류로는 BeanFactory, ApplicationContext가 있고 ApplicationContext가 조금 더 많은 기능을 가지고 있습니다.

  • 스프링 컨테이너는 싱글턴 패턴을 적용하지 않아도, 객체 인스턴스를 싱글톤으로 관리한다.
  • 스프링 컨테이너는 싱글톤 컨테이너 역할을 한다. 이렇게 싱글톤 객체를 생성하고 관리하는 기능을 싱글톤 레지스트리라 한다.
  • 싱글톤 패턴을 위한 지저분한 코드가 들어가지 않아도 된다.
  • DIP, OCP, 테스트, private 생성자로 부터 자유롭게 싱글톤을 사용할 수 있다.

적용후

  • private 생성자가 필요 없어 상속이 가능
  • 테스트하기 편함
  • 프레임워크를 통해 1개의 객체 생성을 보장받음
  • 객체지향적으로 개발할 수 있다.

스프링에서는 bean 생성시 별다른 설정이 없으면 디폴트로 싱글톤이 적용됩니다. 스프링은 컨테이너를 통해 직접 싱글톤 객체를 생성하고 관리하는데 요청이 들어올 때마다 매번 객체를 생성하지 않고 이미 만들어진 객체를 공유하기 때문에 효율적인 사용이 가능합니다.

💡싱글톤 방식의 주의점

  • 싱글톤 패턴이든, 스프링 같은 싱글톤 컨테이너를 사용하든, 객체 인스턴스를 하나만 생성해서 공유하는 싱글톤 방식은 여러 클라이언트가 하나의 같은 객체 인스턴스를 공유하기 때문에 싱글톤 객체는 상태유지(stateful)하게 설계하면 안된다.

  • 무상태(stateless)로 설계해야 한다.

    • 특정 클라이언트에 의존적인 필드가 있으면 안된다.
    • 특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
    • 가급적 읽기만 가능해야 한다.
    • 필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.
  • 스프링 빈의 필드에 공유 값을 설정하면 큰 장애가 발생할 수 있다.

💡특별한 설정이 없다면 싱글톤 패턴인데 멀티스레드 환경에 어떤 문제가 생길까?

멀티스레드 환경에서 싱글톤에서 문제가 생겼다면 메서드를 호출하는 환경이 스레드 세이프하게 구현되지 않았다던가, 싱글톤 패턴으로 생성되는 객체가 전역변수를 가졌기 때문이다. 싱글톤 패턴은 하나의 객체를 공유하기 때문에 전역변수 같은 것은 되도록이면 사용하지 않아야 한다. 변수의 공유로 인한 문제라면 지역변수로 해결 수 있다면 지역변수로 해결한다. 반면에 지역 변수로 해결할 수 없는 경우라면 ThreadLocal 을 사용해 해결한다. 메서드 자체에 접근을 막아야 한다면 synchronized 키워드 로 묶어서 동기화시킬 수 있다. 하지만 synchronized는 성능상 이슈가 있기 때문에 특정 블록만 잡도록 하는 것이 좋습니다.

💡@SpringBootApplication이란 무엇인가요?

스프링 부트로 프로젝트를 실행할때 Application클래스를 만듭니다. @SpringApplication는 Application 클래스에 쓰는 어노테이션입니다. 이 어노테이션으로 인해 스프링 부트의 Bean을 읽어와서 자동으로 등록(생성)해줍니다.

어노테이션안의 @ComponentScan을 통해 @Component, @Controller 등등 Bean을 등록. @EnableAutoConfiguration을 통해 사전에 정의한 라이브러리들 중 조건에 맞는 Bean을 등록.

@SpringApplication이 있는 위치부터 설정을 읽어가기 때문에 항상 프로젝트의 최상단에 위치해야합니다. SpringApplication.run()을 통해 해당 클래스를 run하면, 내장 WAS를 실행시킵니다.

내장 WAS의 장점: 외부 WAS를 설치 및 설정해두지 않아도 되기 때문에 매우 편리

💡디스패처 서블릿(Dispatcher Servlet)에 대해 설명하세요

디스패처 서블릿HTTP 프로토콜로 들어오는 요청을 가장 먼저 받아서 적합한 컨트롤러에게 전달해주는 프론트 컨트롤러입니다. 디스패처 서블릿을 사용하게 되면 공통작업을 코드 중복없이 처리할 수 있습니다.


MVP 패턴

MVP 패턴은 MVC 패턴으로부터 파생되었으며 MVC에서 C에 해당하는 컨트롤러가 프레전터(presenter)로 교체된 패턴입니다.

뷰와 프레젠트는 일대일 관계이기 때문에 MVC 패턴보다 더 강한 결합을 지닌 디자인 패턴이라고 할 수 있습니다.


MVVM 패턴

MVVM 패턴은 MVC의 C에 해당하는 컨트롤러가 뷰모델(view model)로 바뀐 패턴입니다.

여기서 뷰모델은 뷰를 더 추상화한 계층이며 MVVM 패턴은 MVC 패턴과는 다르게 커맨드와 데이터 바인딩을 가지는 것이 특징입니다. 뷰와 뷰모델 사이의 양방향 데이터 바인딩을 지원하며 UI를 별도의 코드 수정 없이 재사용할 수 있고 단위 테스팅하기 쉽다는 장점이 있습니다.

💡MVC 패턴을 설명하고 MVVM 패턴과의 차이는 무엇인지 설명해보시오

MVC 패턴은 모델, 뷰, 컨트롤러로 이루어진 디자인 패턴입니다. 앱의 구성 요소를 세 가지 역할로 구분하여 개발 프로세스에서 각각의 구성 요소에만 집중해서 개발할 수 있다는 점과 재사용확장성이 용이하다는 장점이 있고 애플리케이션이 복잡해질 수록 모델과 뷰의 관계 또한 복잡해지는 단점이 있습니다.

MVVM 패턴은 MVC의 C에 해당하는 뷰 모델로 바뀐 패턴입니다. 여기서 뷰모델은 뷰를 더 추상화한 계층이며, MVVM 패턴은 MVC 패턴과는 다르게 커맨드와 데이터 바인딩을 가지는 것이 특징입니다. 뷰와 뷰모델 사이의 양방향 데이터 바인딩을 지원하며 UI를 별도의 코드 수정 없이 재사용할 수 있고 단위 테스팅하기 쉽다는 장점이 있습니다.


💡@Controller 와 @RestController의 차이에 대해 설명하세요

@Controller는 주로 View를 반환하기 위해 사용합니다. 뷰리졸버를 통해 View를 찾아 랜더링합니다. 데이터를 반환하고 싶으면 @ResponseBody 어노테이션을 활용하여 Json 형태로 데이터를 반환할 수 있습니다. @RestController는 2개의 어노테이션이 합친 것으로, json형태로 객체 데이터를 반환해줍니다.


스프링과 객체 지향

  • 스프링은 다형성을 극대화해서 이용할 수 있게 도와준다.
  • 스프링에서 이야기하는 제어의 역전(IoC), 의존관계 주입(DI)은 다형성을 활용해서 역할과 구현을 편리하게 다룰 수 있도록 지원한다.
  • 스프링을 사용함으로써 마치 레고 블록 조립하듯이 구현을 편리하게 변경할 수 있다.

객체지향 프로그래밍

프로그래밍에서 필요한 데이터를 추상화 시켜 상태와 행위를 가진 객체로 만들고, 객체들간의 상호작용을 통해 로직을 구성하는 프로그래밍 방법

특징
추상화 , 캡슐화 , 상속 , 다형성 의 네가지 특징을 가집니다.

  1. 추상화
  • 객체에서 공통된 속성과 행위를 추출 하는 것
  • 공통의 속성과 행위를 찾아서 타입을 정의하는 과정
  • 추상화는 불필요한 정보는 숨기고 중요한 정보만을 표현함으로써 프로그램을 간단하게 만드는 것
  1. 캡슐화
  • 데이터 구조와 데이터를 다루는 방법들을 결합 시켜 묶는 것

    변수와 함수를 하나로 묶는 것을 뜻함

  • 낮은 결합도를 유지할 수 있도록 설계하는 것
  1. 상속
  • 클래스의 속성과 행위를 하위 클래스에 물려주거나 하위 클래스가 상위 클래스의 속성과 행위를 물려받는 것을 말한다
  • 새로운 클래스가 기존의 클래스의 데이터와 연산을 이용할 수 있게 하는 기능
  1. 다형성
  • 하나의 변수명, 함수명이 상황에 따라 다른 의미로 해석 될 수 있는 것
  • 어떠한 요소에 여러 개념을 넣어 놓는 것

장점

  • 클래스 단위로 모듈화시켜서 개발하기 때문에 업무 분담이 편리하고 대규모 소프트웨어 개발에 적합하다.
  • 클래스 단위로 수정이 가능해서 유지 보수에 좋다.
  • 클래스를 재사용하거나 상속을 통해 확장함으로써 코드 재사용이 용이하다.

단점

  • 처리 속도가 상대적으로 느리다.
  • 객체의 수가 많아짐에 따라 용량이 커질 수 있다.

💡좋은 객체 지향 설계의 5가지 원칙(SOLID)

클린코드로 유명한 로버트 마틴이 좋은 객체 지향 설계의 5가지 원칙을 정리

  • SRP : 단일 책임 원칙(Single Responsibility Principle)
    • 한 클래스는 하나의 책임만 가져야 한다.

      한 클래스에서 여러개가 있으면 안되고 하나의 종류만 있어야 한다.

    • 중요한 기준은 변경이다. 변경이 있을 때 파급 효과가 적으면 단일 책임 원칙에 따른 것

      ex) UI 변경, 객체의 생성과 사용을 분리

  • OCP : 개방 폐쇄 원칙(Open/Closed Principle)
    • 소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.

      유지 보수 사항이 생긴다면 코드를 쉽게 확장할 수 있도록 하고 수정할 때는 닫혀야하는 원칙

    • 다형성을 활용
    • 인터페이스를 구현한 새로운 클래스를 하나 만들어서 새로운 기능을 구현

    상위 클래스 또는 인터페이스를 중간에 둠으로써 다양한 자동차가 생긴다고 해도 객체 지향 세계의 운전자는 운전 습관에 영향을 받지 않게 됩니다. 다양한 자동차가 생긴다고 하는 것은 자동차 입장에서는 자신의 확장에는 개방되어 있는 것이고, 운전자 입장에서는 주변의 변화에 폐쇄돼 있는 것입니다.

    인터페이스로 service를 구현하고 상속받아서 serviceImpl 클래스를 만들어서 기능을 구현하는 방식과 공통된 기능은 추상 클래스로 구현하고 상속 받아서 공통되지 않는 기능을 구현하는 방식이 OCP에 해당됩니다.

  • LSP : 리스코프 치환 원칙(Liskov Substitution Principle)
    • 프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.
    • 다형성에서 하위 클래스는 인터페이스 규약을 다 지켜야 한다는 것, 다형성을 지원하기 위한 원칙, 인터페이스를 구현한 구현체를 믿고 사용하려면 이 원칙이 필요하다.

    이 말은 기능을 인터페이스로 service를 구현하고 상속받아서 serviceImpl 클래스를 만들어서 기능을 구현했는데 컨트롤러에서 service를 받아서 사용하던 상속 클래스인 serviceImpl을 받아오던 동일한 기능을 제공해야 한다는 것입니다.

  • ISP : 인터페이스 분리 원칙(Interface Segregation Principle)
    • 범용 인터페이스 하나보다 클라이언트를 위한 여러 개의 인터페이스로 구성하는 것이 좋다.
    • 인터페이스는 인터페이스를 사용하는 클라이언트를 기준으로 분리해야 한다.
    • 클라이언트가 필요로 하는 인터페이스로 분리함으로써 각 클라이언트가 사용하지 않는 인터페이스에 변경이 있어도 영향을 받지 않도록 만들어야 한다.

    예를 들어, 유저, 상품, 장바구니, 게시글이 각각 다른 역할을 수행한다면, 각각의 역할에 해당하는 작은 단위의 인터페이스로 분리하는 것이 좋습니다. 이렇게 분리된 인터페이스들은 각각의 구현 클래스에서 해당 역할에 필요한 메서드만 구현하도록 하여, 클라이언트가 필요로 하는 기능만 사용할 수 있게끔 합니다.

  • DIP : 의존관계 역전 원칙(Dependency Inversion Principle)
    • 프로그래머는 추상화에 의존해야지 구체화에 의존하면 안된다. 의존성 주입은 이 원칙을 따르는 방법 중 하나이다.
    • 쉽게 이야기해서 구현 클래스에 의존하지 말고, 인터페이스에 의존하라는 뜻

AOP

이처럼 여러 비즈니스 로직에서 반복되는 부가 기능을 하나의 공통 로직으로 처리하도록 모듈화해 삽입하는 방식을 AOP라고 합니다.

이러한 AOP를 구하는 방법은 세가지가 있습니다.
1. 컴파일 과정에 삽입하는 방식
2. 바이트코드를 메모리에 로드하는 과정에 삽입하는 방식
3. 프록시 패턴을 이용한 방식

이 가운데 스프링은 디자인 패턴 중 하나인 프락시 패턴을 통해 AOP 기능을 제공하고 있습니다. 스프링 AOP의 목적은 OOP와 마찬가지로 모듈화해서 재사용 가능한 구성을 만드는 것이고, 모듈화된 객체를 편하게 적용할 수 있게 함으로써 개발자가 비즈니스 로직을 구현하는데만 집중할 수 있게 도와주는 것입니다.

public이외의 메서드는 AOP가 걸리지 않는다.

private 메서드에 @Transactional을 선언하면 컴파일 에러가 발생합니다.

그렇다면 왜 사용할 수 없을까?

원인을 파악하려면 프록시에대해 알아봐야합니다. 스프링 aop에서 프록시는 크게 JDK Dynamic proxy또는 CGLIB으로 작동합니다. 그리고 spring boot 1.4 버전 이후부터는 default로 CGLIB을 사용합니다.

CGLIB은 동적으로 상속을 통해 프록시를 생성합니다. 따라서 private 메서드는 상속이 불가능하기 때문에 프록시로 만들어지지 않는것이죠!

그러면 아래처럼 protected나 public으로 메서드를 만들면 정상적으로 트랜잭션이 동작할까요? 정답은 protected일 때 또한 정상 동작하지 않습니다.

분명히 인텔리제이로 확인을 해보면 컴파일 에러는 나오지 않는데 말이죠. 해당 이유는 앞서 말했던 JDK Dynamic proxy가 원인이었기 때문입니다. JDK Dynamic proxy는 인터페이스를 기반으로 동작합니다. 따라서 protected 메서드에서는 프록시가 동작할 수 없는 것이죠. 그래서 스프링에서는 일관된 AOP적용을 위해서 protected로 선언된 메서드 또한 트랜잭션이 걸리지 않도록 한 것입니다. 즉, 프록시 설정에 따라 트랜잭션이 적용되었다 안되었다 하는 변칙적인 결과를 막기 위함인거죠.

같은 클래스내에서 트랜잭션이 걸린 메소드를 호출하면 트랜잭션이 작동하지 않는다.

public class A {
    public void init() {
        this.progress();
    }

    @Transactional
    public void progress() {
    }
}

예를들어 다음과 같은 상황에서 progress()는 정상적으로 트랜잭션이 적용될까요?

이번에도 안됩니다.

Spring AOP에서 프록시의 동작 과정을 보면 프록시를 통해 들어오는 외부 메서드 호출을 인터셉트 하여 작동합니다. 바로 이러한 성격때문에 self-invocation이 라고 불리는 현상이 발생합니다.

프록시의 내부 빈에서 프록시를 호출 했다는 것이죠.

main메서드에서 그림과 같이 A의 init()를 호출하고 init()에서 progress()를 호출하면 그림과 같이 프록시 내부에서 호출하게 됩니다. 따라서 proxy가 인터셉트하지 못해서 트랜잭션이 동작하지 않는것입니다.

그렇다면 내부 메서드에서 호출은 AOP적용이 불가능하다. 이건 아닙니다. 해결 방법이 있습니다.

타겟 내에서 타겟의 다른 메서드를 호출할 때, 런타임에 실제 트랜잭션이 작동하지 않는다고 합니다. 즉, 런타임 시점에 작동은 안 하지만 이것을 컴파일 시점에 적용하면 된다는 것이죠.

그렇다면 어떻게 컴파일 시점에 프록시를 적용할 수 있을까요?

스프링에서 AOP를 구현하는 방식에는 스프링 AOP도 있지만 AspectJ라는 강력한 도구도 있습니다. AspectJ는 스프링AOP와 다르게 컴파일 시점에 위빙이 이루어집니다. 따라서 AspectJ를 사용하면 self-invocation문제를 해결할수 있는것이죠!

그런데 만약 Spring AOP를 쓰고있는 상황에서 이런 문제가 발생하면 어떡하죠? 이럴경우 다른 방법도 존재합니다!

  1. AopContext를 이용하는 방법
  2. 자기 자신을 호출하는 방법

    내부에서 프록시를 호출하면 인터셉터가 작동하지 않으므로 외부에서 호출하는 방식으로 해결하는 것입니다.

    • @Resource
    • @Autowired
    • @Inject

스프링 부트 2.6 버전부터는 기본적으로 순환 참조를 금지하도록 변경되었습니다. 만약 2.6 버전 이상에서 실습해보고 싶으시다면 아래의 설정을 추가하시면 됩니다.

spring:
  main:
    allow-circular-references: true
profile
발전하기 위한 공부

0개의 댓글