공부기록/Spring

[Spring] Spring DI, AOP / 25.01.06

taecode 2025. 1. 6. 19:05

스프링 컨테이너와 빈

 

- 빈 : 스프링 컨테이너가 관리하는 객체

- 스프링은 실행되는 시점에 빈객체를 전부 만들어놓고 관리한다.

 

1. 스프링 컨테이너 생성

ApplicationContext 인터페이스를 일반적으로 스프링 컨테이너라고 부른다.

정확히는 스프링 컨테이너를 말할 때 ApplicationContext가 상속하고 있는 BeanFactory와 구분해서 사용하지만, BeanFactory를 직접 사용하는 경우가 거의 없기 때문에 일반적으로 ApplicationContext를 스프링 컨테이너라 한다.

BeanFactory는 스프링 컨테이너의 최상위 인터페이스로 스프링 빈을 관리하고 조회하는 역할을 담당한다.

 

 

2. 빈 조회

스프링 컨테이너가 관리하는 자바 객체 = 스프링 빈

빈은 클래스의 등록 정보, getter/setter 메서드를 포함하며, 앞서 본 것 처럼 구성 정보(설정 메타 정보)를 통해 생성된다.

빈 객체는 getBean() 메서드로 조회할 수 있다.

 

 

빈을 아무것도 등록하지 않아도 기본적으로 스프링에서 관리하는 빈이 있기 때문에 빈이 없는 경우는 없다.

 

싱글톤 : 객체가 하나밖에 없는 것. 상태를 공유해서 사용하기 위해서. (스태틱이랑 비슷)

 - 객체를 여러 개를 생성해도 주소값이 같음

 

 

 

 

스프링 컨테이너 = 싱글톤 컨테이너

 

싱글톤 패턴은 특정 클래스의 인스턴스가 단 하나만 생성되도록 보장하는 디자인 패턴을 의미한다.

(빈과 컴포넌트 어노테이션 붙은 애들은 하나뿐이고, 하나로 돌려 쓴다.)

 

 * 여러 개의 동시다발적인 고객 요청을 처리해야 하는 웹 애플리케이션에서 요청이 있을 때마다 new 연산자를 사용하여 객체를 생성해야 한다면, 매번 이를 위한 메모리 영역을 할당받아야 하기 때문에 메모리 낭비를 초래한다.

 

이런 경우 싱글톤 패턴을 구현하여, 최초에 단 하나의 객체를 생성해 두고 요청이 돌아올 때마다 같은 객체를 공유하는 방법으로 메모리 낭비를 최소화 할 수 있다.

 

가비지 컬렉션이 정리하도록 두는 걸 지양해야 하는 이유는 가비지 컬렉션이 실행되는 동안에는 모든 프로그램이 멈추기 때문이다.

 

 

스프링 컨테이너는 스프링에서 객체들을 관리한다.

관리하는 객체를 빈이라고 하며, 빈들은 아무 설정을 안해주면 기본적으로 다 싱글톤이다.

싱글톤은 한 개의 객체를 돌려 쓰는 것을 의미한다.

 

 

빈 생명주기와 범위

 

스프링 컨테이너는 크게 초기화화 종료라는 생명 주기(life-cycle)을 가지고 있다.

생명주기란 말 그래도 스프링 컨테이너의 탄생과 죽음에 대한 것이다.

 

빈은 애플리케이션이 실행되는 시점부터 종료되는 시점까지 계속 살아있다.(안죽음)

빈 객체 생명주기

1. 가장 먼저 빈 객체들 생성

2. 객체들 간 의존 관계 설정

-- 1,2번은 거의 동시에 된다.

3. 내부적으로 지정한 메서드 호출하여 빈 객체의 초기화

4. 스프링 컨테이너 종료 시 빈 객체들의 소멸

 

 

빈 객체의 관리 범위

-스프링 컨테이너는 싱글톤 레지스트리 기능을 가진 싱글톤 컨테이너이며, 그 안에서 관리되는 스프링 빈도 스프링 컨테이너의 생성과 종료와 함께 운명을 같이 한다.

-이러한 이유는 해당 빈 객체가 별도의 설정이 없는 경우 싱글톤 범위(scope)를 가지기 때문이다. 여기서 빈 객체의 스코프란 이름 그대로 빈 객체가 존재할 수 있는 범위이다.

-관리 범위는 싱글톤 외에도 프로토타입, 세션, 리퀘스트 등의 범위가 있긴하다.

 

 

 

컴포넌트 스캔과 의존성 자동 주입

 

컴포넌트 스캔(Component Scan) : 수동으로 클래스 구성 정보를 일일이 작성하지 않고, 자동으로 스프링 빈을 등록하는 기능. @Component 애너테이션이 붙은 클래스를 모두 스캔하여 자동으로 스프링 빈으로 등록한다.

 

@Autowired : 컴포넌트 스캔만으로는 구체적인 의존 관계 설정이 불가능하기 때문에  빈을 자동으로 등록함과 동시에 의존 관계가 설정될 수 있도록 하는 기능.

 

@Bean과 @Component 

외부 라이브러리나 자바 내부에 정의된 것을 빈으로 등록할 때 @Bean 으로 메서드를 호출해서 등록하고, @Component는 개발자가 작성한 코드를 등록할 때 주로 사용한다.(@Component를 못 붙이는 경우에는 메서드 호출해서 @Bean으로 등록한다.)

 

 

@Autowired ( 잘 사용하지 않음 ), 빈 등록 우선순위

 

@Autowired 애너테이션을 붙이면 스프링이 관리하고 있는 해당 타입의 객체가 자동으로 중립되어 의존 관계가 완성된다.

 

생성자가 단 하나만 존재하는 경우에는 @Autowired 애너테이션을 붙이지 않아도 자동으로 의존 관계가 연결이 된다.

 

문제점은 하나의 빈이 매칭될 것이 예상되었는데 두 개의 빈이 발견되었을 때다.

 

@Autowired 애너테이션은 기본적으로 타입으로 빈을 조회하는데 해당 빈의 타입이 2개 이상인 경우 스프링의 입장에서는 어떤 구현 객체가 들어와야 하는지 알 수 있는 방법이 없어 오류가 발생한다.

- 참조 변수의 타입을 하위 구현 객체의 타입으로 변경할 수 있지만, 이 또한 구체적인 객체에 의존하는 결과를 초래하게 되어 객체지향적 설계의 원칙에 위배되기 때문에 권장방식이 아니다.

 

 

해결방법

1. @Autowired 필드명 매칭

- 타입으로 먼저 비교, 타입이 같으면 이름으로 비교

- 하지만 이 방법은 필드 주입을 사용해야 하기 때문에 사용하지 않는다.

 

1. @Qualifier 사용

- @Qualifier 를 사용하여 추가적인 구분자를 통해 의존 관계를 연결한다.

 

2. @Primary 사용

- @Primary 애너테이션을 사용하여 여러 개의 빈이 들어올 수 있는 경우 빈 객체들 간의 우선순위를 설정할 수 있다.

 

 

생성자 주입이 더 보편적임.

 

왜 생성자 주입을 사용하는가?

생성자 주입을 사용하게 되면 필드에 final을 쓸 수 있게 되고, final을 쓰게 되면 null값이 들어올 수 없다는 것을 보장할 수 있다.

// ex
private final MemberService memberService;

public MemberController(MemberService memberService){
	this.memberService = memberService;
}

 

 


PROXY ***

 

- 실제 객체를 감싸서 클라이언트 요청을 대신 처리하는 대리 객체.

- 이를 통해 기존 코드를 수정하지 않고도 부가적인 기능을 추가하거나 동작을 제어할 수 있다.

 

프록시의 역할

  1. 대리 객체 역할 : 
    - 프록시는 실제 객체와 동일한 인터페이스를 구현하여 클라이언트가 구분하지 못하게 한다. 클라이언트는 프록시를 통해 요청을 전달하고, 프록시는 이를 실제 객체로 위임하거나 추가 로직을 수행한다.

  2. 부가 기능 추가 :
    - 예를 들어 메서드 실행 전후에 로깅, 트랜잭션 관리, 인증/권한 체크 같은 공통 기능을 추가할 수 있다. 이는 비즈니스 로직과 공통 관심사를 분리해 코드의 모듈성과 재사용성을 높인다.

  3. 접근 제어 : 
    - 특정 조건에 따라 실제 객체에 대한 접근을 제한하거나 제어할 수 있다.

AOP에서 프록시의 동작 과정

  1. 스프링 컨테이너는 빈을 생성할 때, AOP 설정에 따라 해당 빈을 감싸는 프록시 객체를 생성한다.
  2. 클라이언트가 메서드를 호출하면, 실제 객체 대신 프록시가 호출을 가로챈다.
  3. 프록시는 AOP에서 정의된 부가 기능(Advice)를 실행한 후 실제 객체의 메서드를 호출하거나 결과를 반환한다.

 

장점

  • 코드 중복 제거 : 공통 관심사를 한 곳에서 관리 가능
  • 유지보수성 향상 : 비즈니스 로직과 부가 로직 분리.
  • 모듈화 : 관심사를 독립적인 모듈로 처리

단점

  • 디버깅이 어려울 수 있다.(코드 흐름이 복잡해서)
  • 런타임 성능 저하 가능성

 

 

 

AOP ( Aspect Oriented Programming )

 

관점 지향 프로그래밍 : 앱 개발 과정에서 여러 객체에 공통적으로 적용할 수 있는 공통의 관심 사항과 핵심 관심 사항을 분리시키는 프로그래밍 기법

 

쓰는 이유 : 객체 지향의 한계를 극복하기 위해서

 

스프링 프레임워크의 AOP 방식은 런타임 시에 프록시(Proxy) 객체를 생성해서 공통 관심 기능을 적용하는 방식을 사용한다.

 

AOP는 핵심 기능에 공통기능을 삽입하는 것으로, 이를 통해 핵심 관심 사항 코드의 변경 없이 공통 기능의 구현을 추가 또는 변경하는 것.

 

스프링에서는 프록시 객체를 자동으로 생성하여 AOP를 구현하는 방식을 지원한다.

 

스프링 AOP는 위의 그림처럼 타깃 객체를 외부에서 프록시 객체가 한번 감싸는 구조이다.
따라서 설정에 따라 타깃 객체의 핵심 로직이 실행되기 전과 후에 공통 기능을 호출할 수 있다.

 

 

애스펙트(Aspect) : 공통 관심 사항에 대한 기능

어드바이스 : 공통 기능과 적용 시점을 정의

포인트컷 : 어드바이스가 적용될 지점을 정의

 

 

핵심개념

핵심개념

 

개념 설명
Advice; 어드바이스 - 공통 관심 사항과 적용 시점을 정의. 즉, 무엇을 언제 적용할 지.
- 타겟 객체에 종속되지 않기 때문에 순수하게 공통 기능에만 집중 가능.
Joinpoint; 조인포인트 - 어드바이스가 적용될 수 있는 위치
- 메서드 호출, 필드 값 변경 등이 해당하지만, 스프링 AOP에서는 메서드 호출에 대한 조인포인트만 제공.
Pointcut; 포인트컷 - 조인포인트의 부분 집합으로 공통 기능이 적용될 대상을 선정하는 방법.
- 스프링 AOP의 조인 포인트는 메서드의 호출이므로, 스프링에서 포인트컷은 메서드를 선정하는 것과 관련됨.
- 정규 표현식 or AspectJ 문법(execution 표현식) 을 통해 정의 가능.
Weaving; 위빙 - 어드바이스를 핵심 기능 코드에 적용하는 것.
- 시점에 따라 컴파일 시, 클래스 로딩 시, 런타임 시 위빙으로 구분가능, 대부분 런타임 시 위빙을 사용.
- 스프링 AOP도 런타임 시 프록시 객체를 생성하여 공통 기능을 삽입하는 방법을 사용.

 

 

스프링에서 구현 가능한 어드바이스의 종류

  • @Before 
    - 타깃 객체의 메서드 호출 전에 공통 기능 실행
  • @After
    - 예외 발생 여부에 관계없이 타깃 객체의 메서드 실행 후 공통 기능을 실행
  • @AfterReturning
    - 타깃 객체의 메서드가 예외 없이 실행되어 값을 반환한 경우 공통 기능을 실행
  • @AfterThrowing
    - 타깃 객체의 메서드 실행 중 예외가 발생한 경우 공통 기능을 실행
  • @Around
    - 타깃 객체의 메서드 실행 전과 후 또는 예외 발생 시 공통 기능 실행
    - 가장 빈번하게 사용됨

** @Aspect 사용하려면 build.gradle에 starter-aop 추가해야하고 @Aspect에는 @Component가 내장되어있지 않기 때문에 컴포넌트 애너테이션도 붙여줘야한다.

'공부기록 > Spring' 카테고리의 다른 글

[Spring] @SpringBootApplication의 역할  (0) 2025.01.06
[Spring] (Spring MVC) API 계층  (1) 2025.01.06
Spring Boot 란?  (0) 2025.01.03
[Spring] Spring Framework 모듈 구성  (1) 2025.01.03
[Spring] Spring Framework /25.01.03  (0) 2025.01.03