본문 바로가기
Java , Spring/Spring

초보 웹 개발자를 위한 스프링 5 프로그래밍 입문 - 5장 컴포넌트 스캔, 6장 빈 라이프사이클과 범위

by 방배킹 2023. 5. 19.

5. 컴포넌트 스캔

 컴포넌트 스캔

스프링이 직접 클래스를 검색해서 빈으로 등록해주는 기능이다.

 

@Component 애노테이션으로 스캔 대상 설정

  • @Component를 붙여주면 해당 클래스를 스캔 대상으로 설정한다.
  • 값을 부여하면 해당 값으로 이름을 결정하고 값이 없을 경우 클래스 이름의 첫글자를 소문자로 바꾼 이름을 지정한다.

@ComponentScan 애노테이션으로 스캔 설정

  • @Component를 붙인 클래스를 스캔해서 스프링 빈으로 등록하려면 설정 클래스에 @ComponentScan을 적용해야한다.
  • basePackages = {"packages"}
    • 스캔 대상 패키지 목록을 지정한다, packages패키지와 그 하위 패키지에 속한 클래스를 스캔 대상으로 설정한다.
  • excludeFilters = @Filter(type = ,patteren = " ")
    • 스캔 대상에서 제외할 대상을 설정한다

@ComponentScan 기본 스캔 대상

  • @Component, @Controller, @Service, @Repository, @Aspect, @Configuration 모두 스캔 대상이 된다.

 

컴포넌트 스캔에 따른 충돌 처리

빈 이름 충돌

  • A 패키지와 B 패키지에 모두 C 클래스가 존재하고, A, B 패키지 모두 @Component가 붙어있을때
  • @ComponentScan을 하면 같은 이름의 빈이 등록될 수 없어서 충돌이 발생한다.
  • 이런 문제는 컴포넌트 스캔 과정에서 쉽게 발생 -> 명시적으로 빈 이름을 지정해서 충돌 방지

수동 등록한 빈과 충돌

  • 이전에는 수동 등록 빈이 우선 순위를 가졌지만
  • 스프링 부트2.1 이후 부터 Exception이 발생한다.

 

6. 빈 라이프사이클과 범위

컨테이너 초기화와 종료

스프링 컨테이너는 초기화와 종료라는 라이프사이클을 갖는다.

// 1. 컨테이너 초기화
AnnotaionConfigApplicationContext ctx = new AnnotaionConfigApplicationContext(AppContext.class);

// 2. 컨테이너에서 빈 객체를 구해서 사용
Greeter g = ctx.getBean("greeter", Greeter.class);
String msg = g.greet("스프링");
Sysytem.out.println(msg);

// 3. 컨테이너 종료
ctx.close();

위 코드와 같이 AnnotaionConfigApplicationContext 객체를 생성하고, 스프링 컨테이너를 초기화한다.

스프링 컨테이너는 설정 클래스에서 정보를 읽어와 알맞은 빈 객체를 생성하고 각 빈을 연결(의존주입)한다.

 

이후 컨테이너가 초기화가 되면 getBean()과 같은 함수들을 통해 컨테이너를 사용할 수 있다.

 

컨테이너 사용이 끝나면 컨테이너를 종료한다.

 

컨테이너를 초기화 하고 종료 할때 다음과 같은 작업도 함께 수행된다.

  • 컨테이너 초기화 : 빈 객체의 생성, 의존 주입, 초기화
  • 컨테이너 종료 : 빈 객체의 소멸

 

스프링 빈 객체의 라이프 사이클

객체 생성 → 의존 설정 → 초기화 → 소멸

 

스프링 컨테이너를 초기화 할때 빈 객체를 생성하고 의존을 설정한다. 의존 자동 주입을 통한 의존 설정이 이 시점에 수행된다.

모든 의존 설정이 완료되면 빈 객체의 초기화를 수행한다. 빈 객체를 초기화하기 위해 스프링은 빈 객체의 지정된 메서드를 호출한다.

스프링 컨테이너를 종료하면 스프링 컨테이너는 빈 객체의 소멸을 처리한다. 이때도 마찬가지로 지정한 메서드를 호출한다. 

 

빈 객체의 초기화와 소멸 - 스프링 인터페이스

스프링의 인터페이스인 InitializingBean와 DisposableBean를 받아 

InitializingBean의 afterPropertiesSet()를 구현해서 실행, DisposableBean의 destroy()를 구현해서 실행한다.

각각 생성과 주입이 끝난뒤, 소멸 과정에서 해당 함수를 실행한다.

 

단점

  • 해당 인터페이스는 스프링 전용 인터페이스다. 해당 코드가 스프링 전용 인터페이스에 의존한다,
  • 초기화, 소멸 메서드의 이름을 변경할 수 없다.
  • 내가 코드를 고칠 수 없는 외부 라이브러리에 적용할 수 없다.

 

빈 객체의 초기화와 소멸 - 커스텀 메서드

스프링 인터페이스 방법의 단점에서 말한것처럼 모든 클래스가 InitializingBean, DisposableBean 인터페이스를 상속받아 구현할 수 있는것이 아니다.

직접 구현한 클래스가 아닌 외부에서 제공받은 클래스를 스프링 빈 객체로 설정하고 싶을 때도 있다.

 

이런 경우 @Bean 태그에서 initMethod 속성과 destoryMethod 속성을 사용해서 초기화 메서드와 소멸 메서드의 이름을 지정하면 된다.

 

public class Client{
	private String host;
    
	public void setHost(String host){
		this.host = host;
	}
    
	public void connect(){
		System.out.println("Client.connect() 실핼");
	}
    
	public void setHost(){
		System.out.println("Client.send() to " + host);
	}
    
	public void close(){
		System.out.println("Client.close() 실핼");
	}
}

@Bean(initMethod = "connect", destoryMethod = "close")
public Client client(){
	Client client = new Client();
	client.setHost("host");
	return client;
}

위 코드 처럼 @Bean의 속성값에 초기화와 소멸 과정에서 사용할 메서드의 이름을 적어준다.

 

특징

  • 메서드의 이름을 자유롭게 지정할 수 있다.
  • 스프링 빈이 스프링 코드에 의존하지 않는다
  • 코드가 아니라 설정 정보처럼 사용하기 때문에 코드를 고칠수 없는 외부 라이브러리에서도 초기화, 종료 메서드를 적용할 수 있다

종료메서드 추론 기능

  • @Bean의 destroyMethod 속성에는 아주 특별한 기능이 존재한다.
  • close , shutdown 라는 이름의 메서드를 자동으로 호출해준다. 이름 그대로 종료 메서드를 추론해서 호출해준다.
  • 따라서 직접 스프링 빈으로 등록하면 종료 메서드는 따로 적어주지 않아도 잘 동작한다.
  • 추론 기능을 사용하기 싫으면 destroyMethod="" 처럼 빈 공백을 지정하면 된다.

 

빈 객체의 초기화와 소멸 - @PostConstruct, @PreDestory

추가적으로 책에는 없지만 추천하는 방법인 @PostConstruct, @PreDestory에 대해 정리를 하겠다.

 

@PostConstruct : 생성이 된 이후에

@PreDestory : 소멸이 되기 전에

 

특징

  • 최신 스프링에서 가장 권장하는 방법이다.
  • 애노테이션 하나만 붙이면 되므로 매우 편리하다.
  • 자바 표준이다. 따라서 스프링이 아닌 다른 컨테이너에서도 동작한다.
  • 컴포넌트 스캔과 잘 어울린다.
  • 유일한 단점은 외부 라이브러리에는 적용하지 못한다는 것이다. 외부 라이브러리를 초기화, 종료 해야 하면 @Bean의 기능을 사용하자.

 

빈 객체의 생성과 관리 범위

스프링 컨테이너는 빈 객체를 한 개만 생성한다고 했다. (싱글톤)

Client client1 = ctx.getBean("client", CLient.class):
Client client2 = ctx.getBean("client", CLient.class):

// client1 == client2
//true

 

하지만 빈을 등록 할때 빈의 범위를 프로토타입으로 지정하면 빈 객체를 구할 때마다 매번 새로운 객체를 생성한다.

Client client1 = ctx.getBean("client", CLient.class):
Client client2 = ctx.getBean("client", CLient.class):

// client1 == client2
//false

// client1 != client2
//true

 

프로토타입으로 지정하는 방법은 다음과 같다.

@Bean
@Scope("Prototype")
Public Client client(){
	return client;
}

 

 

 

 

 

 

 

댓글