기술 면접 준비

[기술면접] Spring - 1/3

Lea Hwang 2023. 3. 2. 16:26

Spring 핵심 키워드

  • AOP
  • IOC
  • DI
  • Spring MVC 구조
  • 빈, 컨테이너 개념
  • 디자인 패턴 

 

[기술면접] Spring - 1/3의 목차

  • WAS(Web Application Server)와 WS(Web Server)의 차이
  • Spring Framework
  • @RequestBody, @RequestParam, @ModelAttribute의 차이
  • Spring MVC
  • 제어의 역전 (IoC, Inversion of Control)
  • 스프링 빈의 라이프사이클은 어떻게 관리되는지 설명
  • Spring Filter와 Interceptor에 대해 설명하고, 사용 예시를 설명
  • 관점지향 프로그래밍(AOP, Aspect Oriented Programming)는 무엇이고, 언제 사용할 수 있을까요?

 


 

 

 

😎 WAS(Web Application Server)와 WS(Web Server)의 차이를 설명해주세요.

WAS(Web Application Server)

  • 비즈니스 로직을 넣을 수 있음
  • Tomcat, PHP, ASP, .NET 등

WS(Web Server)

  • 비즈니스 로직을 넣을 수 없음
  • Nginx, Apache

 

 

▶ 꼬리질문1 - Web Server와 WAS(Web Application Server)에 대해 상세히 설명해주세요.

Web Server와 WAS

 

1. Web Server

- 클라이언트에게 요청을 받고 정적인 Request라면 (html, css 등) 정적인 콘텐츠를 Response 합니다.

- 클라이언트에게 요청을 받고 동적인 Request라면 WAS로 처리를 이관한 뒤 WAS에서 처리한 결과를 클라이언트에게 전달합니다.

대표적인 Web Server :  Apache, WebtoB, Nginx, IIS 등등

 

2. WAS

DB 조회가 필요하거나 사용자의 입력을 받아 서버에서 가변적으로 로직을 수행하는 등의 동적인 처리가 필요한 요청을 처리하기 위해 만들어졌습니다. Web Container의 JSP/Servlet 구동 환경을 제공하고 프로그래밍 언어(JSP, ASP, PHP 등)로 작성한 뒤 HTML 문서로 만들고 Web Server로 전달합니다.

대표적인 WAS : Tomcat, Jeus, JBoss 등등

 

1. 프로그램 실행 환경과 데이터베이스 접속 기능을 제공.

2. 여러 개의 트랜잭션을 관리.

3. 업무를 처리하는 프로그래밍 언어의 비즈니스 로직을 수행.

4. Web Service 플랫폼으로서의 역할 병행

 

WAS 작동 프로세스

1. Web Server로 요청이 오면 컨테이너가 응답

2. 컨테이너는 web.xml을 참조하여 해당 서블릿에 대한 쓰레드 생성하고 httpServletRequest와 httpServletResponse 객체를 생성하여 전달

3. 컨테이너는 JSP/Servlet 호출

4. 호출된 Servlet의 작업을 담당하게 된 쓰레드(2번에서 만든 스레드)는 doPost()또는 doGet()을 호출

5. 호출된 doPost(), doGet() 메서드는 생성된 동적 페이지를 Response객체에 담아 컨테이너에 전달

6. 컨테이너는 전달받은 Response객체를 HTTPResponse형태로 바꿔 WebServer에 전달하고 생성되었던 스레드를 종료하고 httpServletRequest, httpServletResponse 객체를 소멸시킴

 

 

 

▶  꼬리질문2 - Web Server와 WAS 둘 다 사용하지 않고 WAS만 사용하면 되지 않을까요?  Web Server와 WAS를 분리해놓은 걸까요? 

 

이는 Web Server를 WAS 앞에 둠으로써 얻는 이점을 살펴보면 이유를 알 수 있습니다.

1. 기능을 분리하여 서버 부하 방지

Web Server를 두는 가장 큰 이유입니다.  만약 정적 콘텐츠 요청까지 WAS가 처리한다면 정적 데이터 처리로 인해 부하가 커지게 되고, 동적 콘텐츠의 처리가 지연됨에 따라 수행 속도가 느려집니다.  그렇기에 단순한 정적 컨텐츠 요청은 Web Server에서 빠르게 클라이언트에 제공하여 WAS로 넘어오지 않게 막는 것이 좋습니다. 

 

2. 물리적으로 분리하여 보안 강화

WebServer와 WAS는 Port번호가 다릅니다. 이렇게 물리적으로 두 개의 서버를 완전히 분리하여 보안을 강화시켜줄 수 있습니다. 또한 SSL에 대한 암복호화 처리에 Web Server를 사용하여 웹 서비스에 대한 보안을 강화시켜줄 수 있습니다.

 

3. 여러 대의 WAS를 연결 가능

Web Server하나에 여러 대의 WAS를 설치하고 Load Balancing을 하여 WAS의 부하를 더 낮춰줄 수도 있습니다. 

 

 

 

 

 

 

😎 Spring Framework에 대해 설명해주세요.

스프링 프레임워크는 자바 개발을 편리하게 해주는 오픈소스 프레임워크 입니다.

  • 경량 컨테이너로서 자바 객체를 직접 관리
    • 각각의 객체 생성, 소멸과 같은 라이프 사이클을 관리하며 스프링으로부터 필요한 객체를 얻어올 수 있다.
  • 제어의 역전(IoC)이라는 기술을 통해 어플리케이션의 느슨한 결합을 도모
    • 컨트롤의 제어권이 사용자가 아닌 프레임워크에 있어서 필요에 따라 스프링에서 사용자의 코드를 호출한다.
  • 의존성 주입(DI, Dependency Injection)을 지원
    • 각각의 계층이나 서비스들 간에 의존성이 존재할 경우 프레임워크가 서로 연결시켜준다.
  • 관점 지향 프로그래밍(AOP, Aspect-Oriented Programming)을 지원
    • 트랜잭션이나 로깅, 보안과 같이 여러 모듈에서 공통적으로 사용하는 기능의 경우 해당 기능을 분리하여 관리할 수 있다.

 

▶ 꼬리질문 - Spring Boot와 Spring Framework의 차이점을 설명해주세요.

가장 큰 차이점은 Auto Configuration의 차이인 것 같습니다.

Spring은 프로젝트 초기에 다양한 환경설정을 해야 하지만,
Spring Boot는 설정의 많은 부분을 자동화하여 사용자가 편하게 스프링을 활용할 수 있도록 돕습니다.
spring boot starter dependency만 추가해주면 설정은 끝나고, 내장된 톰캣을 제공해 서버를 바로 실행할 수 있습니다.

 

 

 

 

😎 @RequestBody, @RequestParam, @ModelAttribute의 차이를 설명해주세요.

@RequestBody  클라이언트가 전송하는 JSON 형태의 HTTP Body 내용을 MessageConverter를 통해 Java Object로 변환시켜주는 역할을 합니다.

 

 

@RequestParam  필수 여부가 true이기 때문에, 기본적으로 반드시 해당 파라미터가 전송되어야 합니다. 전송되지 않으면 400 Error를 유발할 수 있으며, 반드시 필요한 변수가 아니라면 required의 값을 false로 설정해줘야 합니다.

 


@ModelAttribute  HTTP Body 내용과 HTTP 요청 파라미터의 값들을 생성자,Getter,Setter를 통해 1대 1로 객체에 바인딩시킨다. 만약 값을 주입해주는 생성자나 Setter함수가 없다면 매핑을 시키지 못하고, null을 갖게 된다.

- 각각의 필드 단위로 적용되어 특정 필드에 타입이 맞지 않는 오류가 발생해도 나머지 필드는 정상 처리할 수 있고 Validator를 사용한 검증도 적용할 수 있습니다.

- 사용 예) @ModelAttribute Item item 다음에 BindingResult bindingResult 위치 시킵니다.

 

 

 

▶ 꼬리질문 - Spring에서 @RequestParam과 @PathVariable차이

Controller 단에서 위 두 어노테이션은 uri를 통해 전달된 값을 파라미터로 받아오는 역할을 합니다.

@RequestParam과 @PathVariable은 둘 다 데이터를 받아오는 데에 사용하는데

@PathVariable은 값을 하나만 받아올 수 있으므로, 쿼리 스트링 등을 이용한 여러 개 데이터를 받아올 때는 @RequestParam을 사용합니다.

 

 

 

 

 

😎 Spring MVC에 대해 설명해주세요.

  • MVC는 Model, View, Controller의 약자이며, 각 레이어간 기능을 구분하는데 중점을 둔 디자인 패턴입니다.
  • Model은 데이터 관리 및 비즈니스 로직을 처리하는 부분이며, (DAO, DTO, Service 등)
  • View는 비즈니스 로직의 처리 결과를 통해 유저 인터페이스가 표현되는 구간입니다. (html,tymeleaf등으로 화면을 구성하기도 하고, Rest API로 서버가 구현된다면 json 응답으로 구성되기도 한다.)
  • Controller는 사용자의 요청을 처리하고 Model과 View를 중개하는 역할을 합니다. Model과 View는 서로 연결되어 있지 않기 때문에 Controller가 사이에서 통신 매체가 되어줍니다.

 

▶꼬리질문 - MVC는 어떠한 흐름으로 요청을 처리하는지 설명해주세요.

DispatcherServlet : 클라이언트에게 요청을 받아 응답까지의 MVC 처리과정을 통제한다.
HandlerMapping : 클라이언트의 요청 URL을 어떤 Controller가 처리할지 결정한다.

HandlerAdapter : HandlerMapping에서 결정된 핸들러 정보로 해당 메소드를 직접 호출해주는 역할을 한다.
ViewResolver : Controller의 처리 결과(데이터)를 생성할 view를 결정한다.

 

1. 클라이언트는 URL을 통해 요청을 전송한다.
2. 디스패처 서블릿은 핸들러 매핑을 통해 해당 요청이 어느 컨트롤러에게 온 요청인지 찾는다.
3. 디스패처 서블릿은 핸들러 어댑터에게 요청의 전달을 맡긴다.
4. 핸들러 어댑터는 해당 컨트롤러에 요청을 전달한다.
5. 컨트롤러는 비즈니스 로직을 처리한 후에 반환할 뷰의 이름을 반환한다.
6. 디스패처 서블릿은 뷰 리졸버를 통해 반환할 뷰를 찾는다.
7. 디스패처 서블릿은 컨트롤러에서 뷰에 전달할 데이터를 추가한다.
8. 데이터가 추가된 뷰를 반환한다.

 

 

 

 

😎 제어의 역전 (IoC, Inversion of Control)에 대해 아는대로 설명해주세요.

모든 객체에 대한(생성, 라이프사이클 등) 제어권을 개발자가 아닌 IoC 컨테이너에게 넘긴 것을 말합니다.


스프링은 IoC 컨테이너에 객체*들을 생성하면 객체끼리 의존성을 주입(DI, Dependency Injection)하는 역할을 합니다.
  * 컨테이너에 등록한 객체들을 '빈'이라고 합니다.

 

꼬리질문1 - IoC컨테이너란?

▶ 꼬리질문2 - DI란?

    ▶ 꼬리질문2-1 스프링 빈을 등록하는 두가지 방법 (@Component, @Bean)

    ▶ 꼬리질문2-2 등록된 빈을 사용하는 방법 : 의존성 주입의 3가지 방법(Setter Injection, Constructor Injection, Field Injection)

 

 

꼬리질문1 - IoC컨테이너란?

  • IoC란
  • IoC 컨테이너(=스프링 컨테이너)
  • IoC의 분류
  • IoC 컨테이너 종류

 

 

IoC란? 

제어의 역전으로 객체의 생성, 생명주기의 관리까지 모든 객체에 대한 제어권이 바뀌었다는 것을 의미합니다.

컴포넌트 의존관계 설정(Component dependency resoulution), 설정(Configuration) 및 생명주기(LifeCycle)을 

해결하기 위한 디자인 패턴(Design Pattern)입니다.

 

 

IoC 컨테이너

컨테이너란? 보통 객체의 생명주기를 관리, 생성된 인스턴스들에게 추가적인 기능을 제공하도록 하는 것입니다.

 

스프링 프레임워크도 객체를 생성하고 관리하고 책임지고 의존성을 관리해주는 컨테이너가 있는데,

그것이 바로 IoC 컨테이너(=스프링 컨테이너) 입니다.

 

인스턴스 생성부터 소멸까지의 인스턴스 생명주기 관리를 개발자가 아닌 컨테이너가 대신 해줍니다.

따라서 개발자는 로직에 집중할 수 있는 장점이 있습니다.

  • IoC 컨테이너는 객체의 생성을 책임지고, 의존성을 관리한다.
  • POJO의 생성, 초기화, 서비스, 소멸에 대한 권한을 가진다.
  • 개발자들이 직접 POJO를 생성할 수 있지만 컨테이너에게 맡긴다.
  • 개발자는 비즈니스 로직에 집중할 수 있다.
  • 객체 생성 코드가 없으므로 TDD가 용이하다.

 


IoC의 분류

DL(Dependency Lookup) 과 DI (Dependency Injection)

  • DL : 저장소에 저장되어 있는 Bean에 접근하기 위해 컨테이너가 제공하는 API를 이용하여 Bean을 Lockup하는 것
  • DI : 각 클래스간의 의존관계를 빈 설정(Bean Definition) 정보를 바탕으로 컨테이너가 자동으로 연결해주는 것
    • Setter Injection (수정자 주입)
    • Constructor Injection (생성자 주입)
    • Method Injection (필드 주입)

 

DL 사용시 컨테이너 종속이 증가하기 때문에 주로 DI를 사용합니다.

[정리] 의존성 주입(DI, Dependency Injection)에 대해 설명해주세요.
의존성 주입은 필요한 객체를 직접 생성하는 것이 아닌 외부로부터 객체를 받아서 사용하는 것입니다.
이를 통해 객체간의 결합도를 줄이고 코드의 재사용성을 높일 수 있습니다.

의존성 주입은 생성자 주입, 필드 주입, 세터 주입의 3 가지 방법이 있습니다.
이 중 Spring에서 가장 권장하는 의존성 주입 방법은 생성자를 통한 주입 방법입니다.
그 이유는 1. 순환 참조를 방지 2. 불변성을 가짐 3. 테스트에 용이하기 때문입니다.

 

 

 

스프링 컨테이너 (= IoC 컨테이너)의 종류

스프링 컨테이너가 관리하는 객체를 빈(Bean)이라고 하고,

이 빈들을 관리한다는 의미로 컨테이너를 빈 팩토리(BeanFactory) 라고 부릅니다.

  • 객체의 생성과 객체 사이의 런타임 관계를 DI 관점에서 볼 때 컨테이너를 BeanFactory라고 한다.
  • BeanFactory에 여러가지 컨테이너 기능을 추가한 어플리케이션컨텍스트(ApplicationContext) 가 있다.

 

BeanFactory와 ApplicationContext

1.  BeanFactory

- BeanFactory 계열의 인터페이스만 구현한 클래스는 단순히 컨테이너에서 객체를 생성하고 DI를 처리하는 기능만 제공한다.

 

- Bean을 등록, 생성, 조회, 반환 관리를 한다.

 

- 팩토리 디자인 패턴을 구현한 것으로 BeanFactory는 빈을 생성하고 분배하는 책임을 지는 클래스이다.

 

- Bean을 조회할 수 있는 getBean() 메소드가 정의되어 있다.

 

- 보통은 BeanFactory를 바로 사용하지 않고, 이를 확장한 ApplicationContext를 사용한다.

 

 

2.  ApplicationContext

- Bean을 등록, 생성, 조회, 반환 관리하는 기능은 BeanFactory와 같다.

 

- 스프링의 각종 부가 기능을 추가로 제공한다.

  • BeanFactory 보다 더 추가적으로 제공하는 기능
    • 국제화가 지원되는 텍스트 메시지를 관리 해준다.
    • 이미지같은 파일 자원을 로드할 수 있는 포괄적인 방법을 제공해준다.
    • 리스너로 등록된 빈에게 이벤트 발생을 알려준다.

 

따라서 대부분의 어플리케이션에서는 빈팩토리 보다는 어플리케이션콘텍스트를 사용하는 것이 더 좋습니다.

 

 

 

 

꼬리질문2 - DI(Dependency Injection) 이란?

각 클래스간의 의존관계를 빈 설정(Bean Definition) 정보를 바탕으로 컨테이너가 자동으로 연결해주는 것

  • Setter Injection (수정자 주입)
  • Constructor Injection (생성자 주입)
  • Method Injection (필드 주입)

 

 

꼬리질문2-1. 스프링 빈을 등록하는 두가지 방법 (@Component, @Bean)

  • Bean이란?
  • 스프링 컨테이너에 Bean을 등록하는 두 가지 방법
    • 컴포넌트 스캔과 자동 의존관계 설정 (@Component)
    • 자바 코드로 직접 스프링 빈 등록
  • 등록된 스프링 빈을 @Autowired로 사용하기

 

Bean이란?

먼저 Bean을 이해하기 위해 스프링 컨테이너 (Spring Container 또는 IoC 컨테이너)에 대해서 알 필요가 있습니다.

자바 어플리케이션은 객체들로 이루어져 있습니다.

이때, 객체들은 서로 상호작용하여 동작하는 경우가 많습니다. 이를 객체의 의존성이라고 하는데,

스프링에서는 스프링 컨테이너에 객체들을 생성하면 객체끼리 의존성을 주입(DI; Dependency Injection)하는 역할을 해줍니다. 그리고 스프링 컨테이너에 등록한 객체들을 '빈'이라고 합니다.

 

 

스프링 컨테이너에 Bean을 등록하는 두 가지 방법

빈을 등록하는 방법은 기본적으로 두 가지 방법이 있습니다.

1. 컴포넌트 스캔과 자동 의존관계 설정

2. 자바 코드로 직접 스프링 빈 등록

 

 

1. 컴포넌트 스캔과 자동 의존관계 설정

스프링 부트에서 사용자 클래스를 스프링 빈으로 등록하는 가장 쉬운 방법은

클래스 선언부 위에 @Component 어노테이션을 사용하는 것입니다. 

 

@Controller, @Service, @Repository는 모두 @Component를 포함하고 있으며

해당 어노테이션으로 등록된 클래스들은 스프링 컨테이너에 의해 자동으로 생성되어 스프링 빈으로 등록됩니다.

 

 

2. 자바 코드로 직접 스프링 빈 등록

이번에는 수동으로 스프링 빈을 등록하는 방법에 대해 알아보겠습니다.

수동으로 스프링 빈을 등록하려면 자바 설정 클래스를 만들어 사용해야 합니다.

 

설정 클래스를 만들고  @Configuration 어노테이션을 클래스 선언부 위에 추가하면 됩니다.

그리고 특정 타입을 리턴하는 메소드를 만들고, @Bean 어노테이션을 붙여주면 자동으로 해당 타입의 빈 객체가 생성됩니다. 

MemberRepository는 인터페이스이고, MemoryMemberRepository가 구현체이기 때문에  MemoryMemberRepository를 new 해줍니다.

 

 

@Bean 어노테이션의 주요 내용은 아래와 같습니다.

  • @Configuration 설정된 클래스의 메소드에서 사용가능
  • 메소드의 리턴 객체가 스프링 빈 객체임을 선언함
  • 빈의 이름은 기본적으로 메소드의 이름
  • @Bean(name="name")으로 이름 변경 가능
  • @Scope를 통해 객체 생성을 조정할 수 있음
  • @Component 어노테이션을 통해 @Configuration 없이도 빈 객체를 생성할 수도 있음
  • 빈 객체에 init(), destroy() 등 라이프사이클 메소드를 추가한 다음 @Bean에서 지정할 수 있음

 

어노테이션 하나로 해결되는 1번 방법이 간단하고 많이 사용되고 있지만, 상황에 따라 2번 방법도 사용됩니다.

1번방법을 사용해서 개발을 진행하다 MemberRepository를 변경해야 할 상황이 생기면 1번 방법은 일일이 변경해줘야 하지만, 2번 방법을 사용하면 다른건 건들일 필요 없이 @Configuration에 등록된 @Bean만 수정해주면 되므로, 수정이 용이합니다.

 

 

 

등록된 스프링 빈을 @Autowired로 사용하기

스프링 부트의 경우 @Component, @Service, @Controller, @Repository, @Bean, @Configuration 등으로

빈들을 등록하고 필요한 곳에서 @Autowired를 통해 의존성 주입을 받아 사용하는 것이 일반적입니다.

 

 

[정리] 스프링에서 빈(Bean)을 등록하는 방법에 대해 말해보세요.
빈을 등록하는 방법은 기본적으로 2 가지가 있습니다.

1. 우선 가장 쉬운 방법으로 @Component 어노테이션을 사용하는 것입니다.
@Controller, @Service, @Repository는 모두 @Component를 포함하고 있습니다.

2. 설정 클래스를 따로 만들어 @Configuration 어노테이션을 붙이고,
해당 클래스 안에서 빈으로 등록할 메소드를 만들어 @Bean 어노테이션을 붙여주면 자동으로 해당 타입의 빈 객체가 생성됩니다.

 

 

 

 

이어서 등록된 빈을 사용하는 방법에 대해 기술하겠습니다. 

꼬리질문2-2. 의존성 주입의 3가지 방법

  • 의존성 주입 필요성
  • 의존성 주입의 3가지 방법
    • 생성자 주입
    • 필드 주입
    • 수정자 주입
  • 어떤 주입 방식을 선택하는게 좋을까? 그 이유는?

 

 

Spring은 @Autowired 어노테이션을 이용한 다양한 의존성 주입(DI; Dependency Injection) 방법을 제공합니다.
의존성 주입은 필요한 객체를 직접 생성하는 것이 아닌 외부로부터 객체를 받아 사용하는 것입니다.
 
이를 통해 객체간의 결합도를 줄이고 코드의 재활용성을 높일 수 있습니다.
@Autowired 는 Spring에게 의존성을 주입하는 지시자 역할로 쓰입니다.

 

 

 

의존성 주입 필요성

  • 코드의 재사용성을 높여준다.
  • 객체 간의 의존성(종속성)을 줄이거나 없앨 수 있다.
  • 객체 간의 결합도를 낮추면서 유연한 코드를 작성할 수 있다.

 

 

의존성 주입의 3가지 방법

1. 생성자 주입(Constructor Injection)
2. 필드 주입(Field Injection)
3. 수정자 주입(Setter Injection)

 

 

1. 생성자 주입(Constructor Injection)

클래스의 생성자가 하나이고, 그 생성자로 주입받을 객체가 빈으로 등록되어 있다면  @Autowired를 생략 할 수 있습니다.

 

 

 

2. 필드 주입(Field Injection)

필드에 @Autowired 어노테이션만 붙여주면 자동으로 의존성 주입됩니다.
사용법이 매우 간단하기 때문에 가장 많이 접할 수 있는 방법입니다.
 
단점

  • 코드가 간결하지만, 외부에서 변경하기 힘들다.
  • 프레임워크에 의존적이고 객체지향적으로 좋지 않다.

 

3. 수정자 주입(Setter Injection)

Setter 메소드에 @Autowired 어노테이션을 붙이는 방법입니다.
 
단점

  • 수정자 주입을 사용하면 setXXX 메서드를 public으로 열어두어야 하기 때문에 언제 어디서든 변경이 가능하다.

 

 

 

어떤 주입 방식을 사용하는게 좋을까?

Spring Framwork reference에서 권장하는 방법은 생성자를 통한 주입입니다.
생성자 주입 방법이 주는 장점에 대해 알아보도록 하겠습니다.

  • 순환 참조를 방지할 수 있다.
  • 불변성

 

순환 참조를 방지할 수 있다.

필드 주입과 수정자 주입은 빈이 생성된 후에 참조를 하기 때문에 어플리케이션이 아무런 오류없이 구동됩니다.

반면, 생성자를 통해 주입하고 실행하면 BeanCurrentlyInCreationException이 발생하게 되어 오류 체크를 할 수 있습니다.

 

 

불변성(Immutability)

생성자로 의존성을 주입할 때 final로 선언할 수 있고, 이로인해 런타임에서 의존성을 주입받는 객체가 변할 일이 없어지게 됩니다.


하지만 수정자 주입이나 일반 메소드 주입을 이용하게되면 불필요하게 수정의 가능성을 열어두게 되고,
이는 OOP의 5가지 원칙 중 OCP(Open-Closed Principal, 개방-폐쇄의 원칙)를 위반하게 됩니다.
그러므로 생성자 주입을 통해 변경의 가능성을 배제하고 불변성을 보장하는 것이 좋습니다.
또한, 필드 주입 방식은 null이 만들어질 가능성이 있는데, final로 선언한 생성자 주입 방식은 null이 불가능합니다.

 

 

 

 

 

😎 스프링 빈의 라이프사이클은 어떻게 관리되는지 설명해주세요.

먼저 스프링 Bean의 LifeCycle은 다음과 같습니다.

스프링 IoC 컨테이너 생성 → 스프링 빈 생성 → 의존관계 주입 → 초기화 콜백 메소드 호출 → 사용 → 소멸 전 콜백 메소드 호출 → 스프링 종료

스프링은 크게 3가지 방법으로 빈 생명주기 콜백을 관리합니다.

1. 인터페이스( InitializingBean, DisposableBean )
2. 설정 정보에 초기화 메소드, 종료 메소드 지정
3. @PostConstruct, @PreDestroy 어노테이션 지원

 

 

▶꼬리질문 - 빈 생명주기(Bean LifeCycle) 콜백에 대해 말씀해주세요.

  • 빈 생명주기 콜백의 필요성
  • 스프링 빈 이벤트 라이프 사이클
  • 빈 생명주기 콜백 3가지

 

스프링의 IoC 컨테이너는 Bean 객체들을 책임지고 의존성을 관리한다.

객체들을 관리한다는 것은 객체의 생성부터 소멸까지의 생명주기(LifeCycle) 관리를 개발자가 아닌 컨테이너가 대신 해준다는 말이다.

객체 관리의 주체가 프레임워크(Container)가 되기 때문에 개발자는 로직에 집중할 수 있는 장점이 있다.

 

 

 

빈 생명주기 콜백의 필요성

콜백함수를 등록하면 특정 이벤트가 발생했을 때 해당 메소드가 호출된다.
즉, 조건에 따라 실행될 수도 실행되지 않을 수도 있습니다.


스프링 프로젝트가 시작될 때 DB연결, 소켓연결, 기타 등등 시간이 걸리기에 미리 연결을 한 뒤 애플리케이션 종료시점에 연결을 종료해야하는 경우 객체의 초기화 및 종료 작업을 해야 한다.
(Ex. 커넥션 풀의 connect & disconnect)

스프링 빈도 위와 같은 원리로 초기화 작업과 종료 작업이 나눠서 진행된다.

스프링 빈은 객체를 생성 후 의존관계를 주입한 뒤 사용할 준비가 완료된다. 내가 해당 빈에서 초기화 작업들을 해주고싶다면 이런 의존관계가 모두 주입된 다음 호출해야 한다.

 

개발자 입장에서 의존관계가 모두 주입이 완료되는 시점을 알기 위해서 스프링에서는 스프링 빈이 의존관계 주입이 완료되면 콜백 메서드를 통해 초기화 시점을 알려주는 기능을 제공한다.

더하여 스프링 컨테이너의 소멸 직전 소멸 콜백을 주어서 스프링 컨테이너가 종료되기 전 로직을 수행할 수 있다.

(ex: DB와의 연결 종료)

 

 

 

스프링 빈 이벤트 라이프 사이클

 

  • 초기화 콜백 : 빈이 생성되고, 빈의 의존관계 주입이 완료된 후 호출
  • 소멸전 콜백 : 빈이 소멸되기 직전 호출

 

의존성 주입 과정

스프링 빈을 등록하고 의존성 주입시 확인할 부분이 있다. 

  • 생성자 주입 : 객체의 생성과 의존관계 주입이 동시에 일어남
  • Setter, Field 주입 : 객체의 생성 ㅡ> 의존관계 주입으로 라이프 사이클이 나누어져 있음

 

Q. 생성자 주입은 왜 객체의 생성과 의존관계 주입이 동시에 일어날까?

 

자바에서 new 연산을 호출하면 생성자가 호출이 된다.
Controller 클래스에 존재하는 Service 클래스와의 의존관계가 존재하지 않는다면,
다음과 같이 Controller 클래스는 객체 생성이 불가능할 것이다.

그렇기 때문에 생성자 주입에서는 객체 생성, 의존관계 주입이 하나의 단계에서 일어나는 것이다.


이를 통해 얻는 이점은 다음과 같다.
1. null을 주입하지 않는 한 NullPointerException은 발생하지 않는다.
2. 의존관계를 주입하지 않은 경우 객체를 생성할 수 없다. 즉, 의존관계에 대한 내용을 외부로 노출시킴으로써 컴파일 타임에 오류를 잡아낼 수 있다.

 

 

그럼 이런 의문을 가질 수 있다.

Q. 스프링 빈 라이프 사이클을 압축시키기 위해 생성자 주입을 통해 빈 생성과 초기화를 동시에 진행하면 되지 않을까?

객체의 생성과 초기화를 분리하자.

생성자는 필수 정보를 받아 메모리 할당후 객체를 생성하는 책임을 가진다.
반면에 초기화는 이렇게 생성된 값들을 활용해 기타 무거운 작업들을 수행한다. 그렇기에 생성과 초기화를 묶는 것은 SRP(단일 책임 원칙)적으로도 과한 책임을 가지게 되기에 명확하게 두 부분을 나누는 것이 유지 보수 관점 및 객체지향 프로그램 적으로도 좋다.

 

 

 

그럼 스프링은 빈 생명주기 콜백을 어떻게 관리하는가?

1. 인터페이스( InitializingBean, DisposableBean )
2. 설정 정보에 초기화 메소드, 종료 메소드 지정
3. @PostConstruct, @PreDestroy 어노테이션 지원

 

 

1. 인터페이스( InitializingBean, DisposableBean )

 

  • InitalizingBean은 afterPropertiesSet() 메소드로 초기화를 지원한다. (의존관계 주입이 끝난 후에 초기화 진행)
  • DisposableBean은 destory() 메소드로 소멸을 지원한다. (Bean 종료 전에 마무리 작업, 예를 들면 자원 해제(close() 등))

이 방식의 단점

해당 인터페이스 두개를 구현하면서 두 메서드만 오버라이딩해서 구현하면되니 편하지만, 이 인터페이스는 스프링 전용 인터페이스이기 때문에 해당 인터페이스에 의존하게 된다. 
그리고 두 인터페이스에서 제공하는 메서드 오버라이딩이기 때문에 이름을 변경할 수 없고 외부 라이브러리에는 적용할수도 없다. 

 

 

 

2. 설정 정보에 초기화 메소드, 종료 메소드 지정

스프링 빈 등록 애노테이션@Bean에 속성으로 초기화, 소멸 메서드를 지정할 수 있다.

  • @Bean(initMethod = "초기화메서드명", destroyMethod="소멸메서드명")

 

이 방식의 장점

  • 메소드명을 자유롭게 부여 가능하다.
  • 스프링 코드에 의존하지 않는다.
  • 설정 정보를 사용하기 때문에 코드를 커스터마이징 할 수 없는 외부라이브러리에서도 적용 가능하다.

이 방식의 단점

  • Bean 지정시 initMethod와 destoryMethod를 직접 지정해야 하기에 번거롭다.

 

 

3. @PostConstruct, @PreDestory 어노테이션

이 방식의 장점

  • 가장 편하고 최신 스프링에서 가장 권장하는 방법이다.
  • 초기화 메서드에 @PostConstruct를 지정하면 스프링 빈등록시 초기화 메서드로 수행된다.
  • 소멸 메서드에 @PreDestroy애노테이션을 지정하면 소멸 메서드로 지정된다. 
  • 패키지가 javax.annotation.xxx 이다. 스프링에 종속적인 기술이 아닌 JSR-250이라는 자바 표준이다. 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.
  • 컴포넌트 스캔과 잘어울린다.

이 방식의 단점

  • 커스터마이징이 불가능한 외부 라이브러리에서 적용이 불가능하다.
    • 외부 라이브러리에서 초기화, 종료를 해야 할 경우 두 번째 방법 즉, @Bean의 initMethod와 destoryMethod 속성을 사용하자.

 

 

 

😎 Spring Filter와 Interceptor에 대해 설명하고, 사용 예시를 설명해주세요.

필터는
스프링 컨테이너가 아닌 톰캣과 같은 웹 컨테이너에 의해 관리가 되는 것이고, 스프링 범위 밖에서 처리됩니다.
Dispatcher Servlet에 요청이 전달되기 전 / 후에 url 패턴에 맞는 모든 요청에 대해 부가 작업을 처리할 수 있는 기능을 제공합니다.
사용 사례 :

  • 보안 및 인증/인가 관련 작업
  • 모든 요청에 대한 로깅 또는 검사
  • 이미지/데이터 압축 및 문자열 인코딩
  • Spring과 분리되어야 하는 기능

 

인터셉터는 요청에 대한 작업 전 / 후로 가로채 요청과 응답을 참조하거나 가공하는 역할을 합니다.
웹 컨테이너에서 동작하는 필터와 달리 인터셉터는 스프링 컨텍스트에서 동작합니다.
Dispatcher Servlet이 Controller를 호출하기 전 / 후에 인터셉터가 끼어들어 요청과 응답을 참조하거나 가공할 수 있는 기능을 제공
사용 사례 :

  • 세부적인 보안 및 인증/인가 공통 작업
  • API 호출에 대한 로깅 또는 검사
  • Controller로 넘겨주는 정보(데이터)의 가공

 

 

▶꼬리질문 -  Filter(필터)와 Interceptor(인터셉터)에 대한 자세한 설명을 해주세요.

개발을 하다 보면 공통적으로 처리해야 할 업무들이 많다.

공통 업무에 관련된 코드를 페이지마다 작성한다면 중복 코드가 많아지게 되고,
프로젝트 단위가 커질수록 서버에 부하를 줄 수도 있으며, 소스 관리도 되지 않는다.

이에 Spring은 공통적으로 여러 작업을 처리함으로써 중복된 코드를 제거할 수 있는 다음과 같은 기능들을 지원하고 있다.

1. Filter(필터)
2. Interceptor(인터셉터)
3. AOP(Aspect Oriented Programming, 관점 지향 프로그래밍)


Spring에서 사용되는 위 3가지 기능들은 모두 어떤 행동을 하기 전에 먼저 실행하거나, 실행한 후에 추가적인 행동을 할 때 사용되는 기능들이다.

 

 

 

 

1. 필터(Filter)

  • 필터란?
  • 필터의 메소드 종류

1-1. 필터(Filter)란?

필터는 말 그대로 요청과 응답을 정제하는 역할을 한다.

Dispatcher Servlet에 요청이 전달되기 전 / 후에 url 패턴에 맞는 모든 요청에 대해 부가 작업을 처리할 수 있는 기능을 제공한다.

즉, 스프링 컨테이너가 아닌 톰캣(WAS)과 같은 웹 컨테이너에 의해 관리가 되는 것이고, 스프링 범위 밖에서 처리되는 것이다.

 

 

1-2. 필터(Filter)의 메소드 종류

필터를 사용하기 위해서는 javax.servlet의 Filter 인터페이스를 구현(implements) 해야 하며, 다음과 같은 메소드를 가진다.

1. init() :

필터 객체를 초기화하고 서비스에 추가하기 위한 메소드이다.
웹 컨테이너가 1회 init()을 호출하여 필터 객체를 초기화하면 이후 요청들은 doFilter()를 통해 처리된다.

 

2. doFilter() :

url-pattern에 맞는 모든 HTTP 요청이 디스패처 서블릿으로 전달되기 전에 웹 컨테이너에 의해 실행되는 메소드이다.
doFilter의 파라미터로 FilterChain이 있는데, FilterChain의 doFilter 통해 다음 대상으로 요청을 전달할 수 있게 된다.
chain.doFilter()로 전, 후에 우리가 필요한 처리 과정을 넣어줌으로써 원하는 처리를 진행할 수 있다.

 

3. destroy() :

필터 객체를 제거하고 사용하는 자원을 반환하기 위한 메소드이다.
웹 컨테이너가 1회 destroy()를 호출하여 필터 객체를 종료하면 이후에는 doFilter에 의해 처리되지 않는다.

 

 

 

2. 인터셉터(Interceptor)

  • 인터셉터란
  • 인터셉터의 메소드 종류
  • 인터셉터와 AOP의 비교

2-1. 인터셉터(Interceptor)란?

쉽게 말해 요청에 대한 작업 전 / 후로 가로챈다고 보면 된다.

Dispatcher Servlet이 Controller를 호출하기 전 / 후에 인터셉터가 끼어들어 요청과 응답을 참조하거나 가공할 수 있는 기능을 제공한다.

웹 컨테이너에서 동작하는 필터와 달리 인터셉터는 스프링 컨텍스트에서 동작한다.

디스패처 서블릿이 핸들러 매핑을 통해 컨트롤러를 찾도록 요청하는데, 그 결과로 실행 체인(HandlerExecutionChain)을 돌려준다.
여기서 1개 이상의 인터셉터가 등록되어 있다면 순차적으로 인터셉터들을 거쳐 컨트롤러가 실행되도록 하고,
인터셉터가 없다면 바로 컨트롤러를 실행한다.

실제로 Interceptor가 직접 Controller로 요청을 위임하는 것은 아니다.

 

 

2-2. 인터셉터(Interceptor)의 메소드 종류

인터셉터를 추가하기 위해서 org.springframework.web.servlet의 HandlerInterceptor 인터페이스를 구현(implements) 해야 하며, 다음과 같은 메소드를 가진다.

public interface HandlerInterceptor {
 
	default boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler)
			throws Exception {
 
		return true;
	}
 
	default void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable ModelAndView modelAndView) throws Exception {
	}
 
	default void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler,
			@Nullable Exception ex) throws Exception {
	}

 

1. preHandle() :

- Controller가 호출되기 전에 실행된다.
컨트롤러 이전에 처리해야 하는 전처리 작업이나 요청 정보를 가공하거나 추가하는 경우에 사용할 수 있다.

 

2. postHandle() :

- Controller가 호출된 후에 실행된다. (View 렌더링 전)
컨트롤러 이후에 처리해야 하는 후처리 작업이 있을 때 사용할 수 있다. 이 메소드는 컨트롤러가 반환하는 ModelAndView 타입의 정보가 제공되는데, 최근에는 JSON 형태로 데이터를 제공하는 RestAPI 기반의 컨트롤러(@RestController)를 만들면서 자주 사용되지 않는다.

 

3. afterCompletion() :

- 모든 뷰에서 최종 결과를 생성하는 일을 포함해 모든 작업이 완료된 후에 실행된다. ( View 렌더링 후)
요청 처리 중에 사용한 리소스를 반환할 때 사용할 수 있다.

 

 

2-3. 인터셉터(Interceptor)와 AOP 비교

인터셉터 대신에 컨트롤러들에 적용할 부가기능을 어드바이스로 만들어 AOP를 적용할 수도 있다.
하지만 다음과 같은 이유들로 컨트롤러의 호출 과정에 적용되는 부가기능들은 인터셉터를 사용하는 편이 낫다.

1. 컨트롤러는 타입과 실행 메소드가 모두 제각각이라 포인트컷(적용할 메소드 선별)의 작성이 어렵다.
2. 컨트롤러는 파라미터나 리턴 값이 일정하지 않다.

즉, 타입이 일정하지 않고 호출 패턴도 정해져 있지 않기 때문에 컨트롤러에 AOP를 적용하려면 번거로운 부가 작업들이 생기게 된다.

 

 

 

3. 필터(Filter)와 인터셉터(Interceptor)의 차이 및 비교

  • Request, Response 객체 조작 가능 여부
  • 필터(Filter)와 인터셉터(Interceptor)의 사용 사례
  • 정리

3-1. Request, Response 객체 조작 가능 여부

필터는 Request와 Response를 조작할 수 있지만, 인터셉터는 조작할 수 없다.

public class MyFilter implements Filter {
 
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
    throws IOException, ServletException {
        // 다른 request와 response를 넣어줄 수 있음
        chain.doFilter(request, response);
    }
}

필터가 다음 필터를 호출하기 위해서는 필터 체이닝(다음 필터 호출)을 해주어야 한다.
이때 request, response 객체를 넘겨주므로 우리가 원하는 request, response 객체를 넣어줄 수 있다.

 

 

하지만 인터셉터는 처리 과정이 필터와 다르다.

public class MyInterceptor implements HandlerInterceptor {
 
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
    throws Exception {
        // Request, Response를 교체할 수 없고 boolean 값만 반환 가능
        return true;
    }
}

디스패처 서블릿이 여러 인터셉터 목록을 가지고 있고, 순차적으로 실행시킨다.
그리고 true를 반환하면 다음 인터셉터가 실행되거나 컨트롤러로 요청이 전달되며, false가 반환되면 요청이 중단된다.
그러므로 다른 request, response 객체를 넘겨줄 수 없다.

 

 

 

3-2. 필터(Filter)와 인터셉터(Interceptor)의 사용 사례

필터(Filter)의 사용 사례

  • 보안 및 인증/인가 관련 작업
  • 모든 요청에 대한 로깅 또는 검사
  • 이미지/데이터 압축 및 문자열 인코딩
  • Spring과 분리되어야 하는 기능

필터는 기본적으로 스프링과 무관하게 전역적으로 처리해야 하는 작업들을 처리할 수 있다.

필터는 인터셉터보다 앞단에서 동작하기 때문에 보안 검사(XSS 방어 등)를 하여 올바른 요청이 아닐 경우 차단할 수 있다.
그러면 스프링 컨테이너까지 요청이 전달되지 못하고 차단되므로 안전성을 더욱 높일 수 있다.

또한, 필터는 이미지나 데이터의 압축, 문자열 인코딩과 같이 웹 어플리케이션에 전반적으로 사용되는 기능을 구현하기에 적당하다.

 

 

인터셉터(Interceptor)의 사용 사례

  • 세부적인 보안 및 인증/인가 공통 작업
  • API 호출에 대한 로깅 또는 검사
  • Controller로 넘겨주는 정보(데이터)의 가공

인터셉터에서는 클라이언트의 요청과 관련되어 전역적으로 처리해야 하는 작업들을 처리할 수 있다.

대표적으로 세부적으로 적용해야 하는 인증이나 인가와 같이 예를 들어 특정 그룹의 사용자는 어떤 기능을 사용하지 못하는 경우가 있는데, 이러한 작업들은 컨트롤러로 넘어가기 전에 검사해야 하므로 인터셉터가 처리하기에 적합하다.

또한 인터셉터는 필터와 다르게 HttpServletRequest나 HttpServletResponse 등과 같은 객체를 제공받으므로 객체 자체를 조작할 수는 없다.
대신 해당 객체가 내부적으로 갖는 값은 조작할 수 있으므로 컨트롤러로 넘겨주기 위한 정보를 가공하기에 용이하다.

예를 들어 JWT 토큰 정보를 파싱해서 컨트롤러에게 사용자의 정보를 제공하도록 가공할 수 있는 것이다.
그 외에도 여러 목적으로 API 호출에 대한 정보들을 기록해야 하는 상황에 HttpServletRequest나 HttpServletResponse를 제공해주는 인터셉터는 클라이언트의 IP나 요청 정보들을 기록하기에 용이하다.

 

 

4. 정리

필터와 인터셉터 모두 비즈니스 로직과 분리되어 특정 요구사항(보안, 인증, 인코딩 등)을 만족시켜야 할 때 적용한다.

필터(Filter)는 특정 요청과 컨트롤러에 관계없이 전역적으로 처리해야 하는 작업이나
웹 어플리케이션에 전반적으로 사용되는 기능을 구현할 때 적용하고,

인터셉터(Interceptor)는 클라이언트의 요청과 관련된 작업에 대해 추가적인 요구사항을 만족해야 할 때 적용한다.

 

 

 

 

 

😎 관점지향 프로그래밍(AOP, Aspect Oriented Programming)는 무엇이고, 언제 사용할 수 있을까요?

AOP는 핵심 비즈니스 로직에 있는 공통 관심사항을 분리하여 각각을 모듈화 하는 것을 의미하며
공통 모듈인 인증, 로깅, 트랜잭션 처리에 용이합니다.

핵심 비즈니스 로직에 부가기능을 하는 모듈이 중복되어 분포되어 있을 경우 사용할 수 있습니다.

AOP의 가장 큰 특징이자 장점은 중복 코드 제거, 재활용성의 극대화, 변화수용의 용이성이 좋다는 점입니다.

 

 

 

▶ 꼬리질문 - AOP에 대해 자세히 설명해주세요.

AOP(Aspect Oriented Programming)란?

  • 관점지향 프로그래밍이란?
  • AOP 특징
  • AOP 용어
  • AOP는 어떻게 사용할까?

 

1. 관점지향 프로그래밍이란?

AOP는 OOP(Object Oriented Programming, 객체지향 프로그래밍)를 돕는 보조적인 기술로,

AOP는 기능을 핵심 관심 사항(Core Concern)과 공통 관심 사항(Cross-Cutting Concern)으로 분리시키고

각각을 모듈화 하는 것을 의미합니다.

  • 업무 로직을 포함하는 기능을 핵심 기능(Core Concern)
  • 핵심 기능을 도와주는 부가적인 기능을 부가 기능(Cross-Cutting Concern) 이라고 부른다.
  • OOP를 적용하여도 핵심 기능에서 부가 기능을 쉽게 분리된 모듈로 작성하기 어려운 문제점을 AOP가 해결해준다.
  • AOP는 부가 기능을 애스펙트(Aspect)로 정의하여, 핵심 기능에서 부가 기능을 분리함으로써 핵심 기능을 설계하고 구현할 때 객체지향적인 가치를 지킬 수 있게 도와주는 개념이다.

 

간단한 소스코드를 통해 AOP를 왜 사용해야하는지 알아보겠습니다.

Class A {
	
    method a() {
    AAAA
    business Logic..
    BBBB
    }
    
    method b() {
    AAAA
    business Logic..
    BBBB
    }
}
 
Class B {
	
    method c() {
    AAAA
    business Logic..
    BBBB
    }
}

핵심 기능을 하는 business Logic 과 부가 기능을 하는 코드 AAAA,BBBB를 볼 수 있습니다.

AAAA,BBBB가 여기저기서 사용되고 흩어져있기 때문에 코드 변경이 필요한 경우 일일이 다 찾아서 바꿔줘야 합니다.

 

AOP는 그렇게 하지 않고 여러 곳에서 사용되는 중복된 코드를 떼어내서 분리하고,

method a,b,c는 각각 business Logic만을 갖고 있자. 라는 개념입니다.

여기서 AAAA,BBBB가 AOP에서 말하는 Aspect 입니다.

 

이로인해 여러 곳에서 사용될만한 코드들이 한 곳에서 유지하고 관리할 수 있는 이점을 갖게 됩니다.

 

 

 

 

2. AOP의 특징

프록시 패턴 기반

- Spring은 타겟(Target) 객체에 대한 프록시를 만들어서 제공한다.

- 타겟을 감싸는 프록시는 실행시간(RunTime)에 생성된다.

- 프록시는 어드바이스(Advice)를 타겟 객체에 적용하면서 생성되는 객체

- 프록시 객체를 쓰는 이유는 접근 제어 및 부가기능을 추가하기 위함이다.

 

 

프록시가 호출을 가로챔(Intercept)

- 프록시는 타겟 객체에 대한 호출을 가로챈 다음 Advice의 부가기능 로직을 수행하고난 후에 타겟의 핵심기능 로직을 호출한다.

(전처리 어드바이스)

- 타겟의 핵심기능 로직 메소드를 호출한 후에 부가기능을 수행하는 경우도 있다.

(후처리 어드바이스)

 

 

메소드 JoinPoint만 지원한다.

- Spring은 동적 프록시를 기반으로 AOP를 구현하므로 메소드 조인 포인트만 지원

- 핵심기능(타겟)의 메소드가 호출되는 런타임 시점에만 부가기능(어드바이스)를 적용할 수 있음

 

- 반면에 AseptJ같은 고급 AOP 프레임워크를 사용하면 객체의 생성, 필드값의 조회와 조작, static 메소드 호출 및 동기화 등의 다양한 작업에 부가기능을 적용할 수 있다.

 

 

 

 

3. AOP 용어

1. Target

- 부가기능을 부여할 대상 (핵심기능을 담고 있는 모듈)

 

2. Aspect

- 부가기능 모듈을 Aspect라고 부른다. (핵심기능에 부가되어 의미를 갖는 모듈)

- 부가될 기능을 정의한 Advice와 Advice를 어디에 적용할지를 결정하는 PointCut을 함께 갖고 있다.

- 어플리케이션의 핵심적인 기능에서, 부가적인 기능을 분리해서 Aspect라는 모듈로 만들어서 설계하고 개발하는 방법

 

3. Advice 

- 실질적으로 부가기능을 담은 구현체

- 타겟 오브젝트에 종속되지 않기 때문에, 부가기능에만 집중할 수 있음

- Aspect가 무엇을 언제 할지를 정의

 

4. PointCut

- 부가기능이 적용될 대상(Method)을 선정하는 방법

- Advice를 적용할 JoinPoint를 선별하는 기능을 정의한 모듈

 

5. JoinPoint

- Advice가 적용될 수 있는 위치

- Spring에서는 메소드 조인포인트만 제공한다.

- 타겟 객체가 구현한 모든 메소드는 조인 포인트가 된다.

 

6. Proxy

- Target을 감싸서 Target의 요청을 대신 받아주는 랩핑 오브젝트.

- 클라이언트에서 Target을 호출하게되면, 타겟이 아닌 타겟을 감싸고 있는 Proxy가 호출되어,

  타겟메소드 실행 전에 선처리, 후처리를 실행한다.

 

7. Introduction

- 타겟 클래스에 코드변경없이 신규메소드나 멤버변수를 추가하는 기능

 

8. Weaving

- 지정된 객체에 Aspect를 적용해서, 새로운 프록시 객체를 생성하는 과정

- Spring AOP는 런타임에서 프록시 객체가 생성된다.

 

 

 

 

4. AOP를 어떻게 사용할까?

스프링부트에서 간단하게 AOP 적용하는 3단계

 

4-1. spring-boot-starter-aop dependency 적용하기

gradle의 build.gradle

implementation 'org.springframework.boot:spring-boot-starter-aop'

 

 

AOP 의존성을 추가하고 빌드를 하였으면 AOP를 활성화하겠다는 어노테이션을 추가해주어야 합니다.

4-2. @EnableAspectJAutoProxy 어노테이션 추가하기

@EnableAspectJAutoProxy
@SpringBootApplication
public class AopApplication {
 
    public static void main(String[] args) {
        SpringApplication.run(AopApplication.class, args);
    }
}

 

 

4-3. 실제 AOP의 로직을 작성합니다. (부가기능을 정의하고 부가기능이 사용될 시점을 정의한다.)

모든 API에 비즈니스 로직의 실행시간을 측정해야 한다고 가정해보겠습니다.

@Aspect
@Component
public class LogAspect {
    Logger logger =  LoggerFactory.getLogger(LogAspect.class);
    
    //모든 패키지 내의 aop package에 존재하는 클래스
    @Around("execution(**..aop.*.*(..))")
    public Object logging(ProceedingJoinPoint pjp) throws Throwable {
    //해당 클래스 처리 전의 시간
    StopWatch sw = new StopWatch();
    sw.start();
    
    //해당하는 클래스의 메소드 핵심기능을 실행
    Object result = pjp.proceed();
    
    //해당 클래스 처리 후의 시간
    sw.stop();
    long executionTime = sw.getTotalTimeMillis();
    
    String className = pjp.getTarget().getClass().getName();
    String methodName = pjp.getSignature().getName();
    String task = className + ". " + methodName;
    
    log debug("[ExecutionTime] " + task + "-->" + executionTime + "(ms)");
    
    return result;
    }
}

AOP 클래스로 설정하기 위해 @Aspect 어노테이션을 추가해주고, Spring의 빈으로 등록하기 위해 @Component 어노테이션을 추가해주었습니다. (AOP 사용시 빈 등록은 꼭 해주어야 합니다.)

 

그리고 우리가 하고자하는건 모든 API의 실행 시간을 측정하는 것이므로, @Around 어노테이션을 통해 aop 패키지에 존재하는 모든 클래스에 해당 AOP를 적용하겠다고 설정해주었습니다.

 

그리고 실행 시간 측정을 위해 StopWatch를 생성하여 측정을 시작했고, pjp의 proceed를 통해 실제 핵심 로직을 실행하여 Object 클래스로 결과를 받았습니다. (Object 로 결과를 받아야 함!) 이후에 StopWatch를 중단하여 실행 시간을 밀리세컨드로 계산해 로그를 출력하고 함수를 종료시킵니다.

 

만약 실행 시간 측정을 밀리세컨드가 아닌 세컨드로 변경한다고 했을 때 AOP를 적용하지 않았다면 관련 로직의 모든 코드를 수정해주어야겠지만, AOP를 적용함으로써 핵심 로직에 대한 수정 없이 쉽게 이를 처리할 수 있게 되었습니다.

 

 

 

 

 

 

 

 

 

 

 

관련 포스팅

[기술면접] Spring - 1/3

[기술면접] Spring - 2/3

[기술면접] Spring - 3/3

 

 

 


출처 :

https://mangkyu.tistory.com/95
https://dev-coco.tistory.com/163
https://gyoogle.dev/blog/
https://lealea.tistory.com/140
https://lealea.tistory.com/162