여니의 프로그래밍 study/Spring & Spring Boot

[Spring] 컴포넌트 스캔 + 빈 라이브사이클과 범위 (Chapter 5~6)

여니's 2023. 8. 27. 13:48

참고 자료 : 초보 개발자를 위한 스프링5 프로그래밍 입문


<Chapter5. 컴포넌트 스캔>

1. 컴포넌트 스캔

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

즉, 설정 클래스에 빈으로 등록하지 않아도

원하는 클래스를 빈으로 등록할 수 있도록 하는 기능이다. 

 

 

@Component는 해당 클래스를 스캔 대상으로 표시하며

클래스에 해당 어노테이션을 붙여줘야만

빈으로 등록해야하는 대상으로 인식된다. 

 

// 1
@Component
public class MemberListPrinter{
	private MemberDao memberDao;
    private MemberPrinter printer;
}

// 2
@Component("listPriner")
public class MemberListPrinter{
	private MemberDao memberDao;
    private MemberPrinter printer;
}

 

어노테이션에 값을 안 준 경우에는

memberListPrinter와 같이 

클래스 이름의 첫 글자를 소문자로 바꾼 이름을 

빈 이름으로 사용한다.

 

 

listPrinter처럼 어노테이션에 값을 주었다면

해당 값을 빈 이름으로 사용하게 된다. 

 

 

만약 해당 클래스를 스캔하여 스프링 빈으로 등록하고자 한다면

설정 클래스에도 @ComponentScan이라는 어노테이션을 붙여줘야한다.

// 4장. AppCtx.java

package config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;

import spring.MemberPrinter;
import spring.MemberSummaryPrinter;
import spring.VersionPrinter;

@Configuration
@ComponentScan(basePackages = {"spring"})
public class AppCtx {

	@Bean
	@Qualifier("printer")
	public MemberPrinter memberPrinter1() {
		return new MemberPrinter();
	}
	
	@Bean
	@Qualifier("summaryPrinter")
	public MemberSummaryPrinter memberPrinter2() {
		return new MemberSummaryPrinter();
	}
	
	@Bean
	public VersionPrinter versionPrinter() {
		VersionPrinter versionPrinter = new VersionPrinter();
		versionPrinter.setMajorVersion(5);
		versionPrinter.setMinorVersion(0);
		return versionPrinter;
	}
}

spring 패키지와 그 하위 패키지에 속한 클래스를

스캔 대상으로 설정한다.

package config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import spring.ChangePasswordService;
import spring.MemberDao;
import spring.MemberInfoPrinter;
import spring.MemberListPrinter;
import spring.MemberPrinter;
import spring.MemberRegisterService;
import spring.MemberSummaryPrinter;
import spring.VersionPrinter;

@Configuration
public class AppCtx {

	@Bean
	public MemberDao memberDao() {
		return new MemberDao();
	}
	
	@Bean
	public MemberRegisterService memberRegSvc() {
		return new MemberRegisterService();
	}
	
	@Bean
	public ChangePasswordService changePwdSvc() {
		return new ChangePasswordService();
	}
	
	@Bean
	@Qualifier("printer")
	public MemberPrinter memberPrinter1() {
		return new MemberPrinter();
	}
	
	@Bean
	@Qualifier("summaryPrinter")
	public MemberSummaryPrinter memberPrinter2() {
		return new MemberSummaryPrinter();
	}
	
	@Bean
	public MemberListPrinter listPrinter() {
		return new MemberListPrinter();
	}
	
	@Bean
	public MemberInfoPrinter infoPrinter() {
		MemberInfoPrinter infoPrinter = new MemberInfoPrinter();
		return infoPrinter;
	}
	
	@Bean
	public VersionPrinter versionPrinter() {
		VersionPrinter versionPrinter = new VersionPrinter();
		versionPrinter.setMajorVersion(5);
		versionPrinter.setMinorVersion(0);
		return versionPrinter;
	}
}

2. 빈을 구하는 코드

(1) 속성값이 지정되어 있던 경우

MemberRegisterService regSvc=ctx.getBean("memberRegSvc",MemberRegisterService.class);a

(2) 속성값이 따로 지정이 안되어 있는 경우

MemberRegisterService regSvc=ctx.getBean(MemberRegisterService.class);

 


3. 스캔 대상에서 제외하거나 포함하는 법

(1) 자동 등록 대상에서 제외하기 : excludeFilters 속성 사용

package config;

import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.FilterType;
import org.springframework.context.annotation.ComponentScan.Filter;

import spring.MemberDao;
import spring.MemberPrinter;
import spring.MemberSummaryPrinter;
import spring.VersionPrinter;

@Configuration
@ComponentScan(basePackages = {"spring", "spring2" }, 
	excludeFilters = { 
			@Filter(type = FilterType.ANNOTATION, classes = ManualBean.class )			
})
public class AppCtxWithExclude {
	@Bean
	public MemberDao memberDao() {
		return new MemberDao();
	}
	
	@Bean
	@Qualifier("printer")
	public MemberPrinter memberPrinter1() {
		return new MemberPrinter();
	}

	@Bean
	@Qualifier("summaryPrinter")
	public MemberSummaryPrinter memberPrinter2() {
		return new MemberSummaryPrinter();
	}

	@Bean
	public VersionPrinter versionPrinter() {
		VersionPrinter versionPrinter = new VersionPrinter();
		versionPrinter.setMajorVersion(5);
		versionPrinter.setMinorVersion(0);
		return versionPrinter;
	}
}

 

1. FilterType.REGEX

 

@ComponentScan(basePackages={"spring"},

excludeFilters=@Filter(type=FilterType.REGEX, pattern="spring\\..*Dao"))

 

=> FilterType.REGEX : 정규표현식을 사용해서 제외 대상을 지정한다는 것을 의미함.

spring.MemberDao 클래스를 컴포넌트 스캔 대상에서 제외한다는 의미이다. 

즉 spring 패키지에서 이름이 Dao로 끝나는 타입을

컴포넌트 스캔 대상에서 제외한다.

 

 

2. FilterType.ASPECTJ

> 정규표현식 대신 AspectJ 패턴을 사용하여 대상을 지정한다. 

단, 의존 대상에 aspectjweaver 모듈을 추가해야 함.

@ComponentScan(basePackages={"spring"},

excludeFilters=@Filter(type=FilterType.ASPECTJ, pattern="spring.*Dao"))

<dependency>
	<groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.8.13</version>
</dependency>

4. 기본 스캔 대상

: 해당 어노테이션을 붙인 클래스가 컴포넌트 스캔 대상에 포함된다. 

 

@Component

@Controller

@Service

@Repository

@Aspect

@Configuration

 

 


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

> 스캔할 때 사용하는 빈 이름과 수동 등록한 빈 이름이 같을 경우,

수동 등록한 빈이 우선되어진다. 

 


<Chapter 6. 빈 라이프사이클과 범위 >

1. 컨테이너 초기화와 종료

// 1. 컨테이너 초기화 (AnnotationConfigApplicationContext 생성자를 이용해서 컨텍스트 객체 생성)
AnnotationConfigApplicationContext ctx= 
	new AnnotationConfigApplicationContext(AppContext.class);

// 2. 컨테이너에서 빈 객체를 구해서 사용
// 컨테이너를 사용한다 == 메서드를 이용해 컨테이너에 보관된 빈 객체를 구한다는 것을 의미함.
Greeter g= ctx.getBean("greeter",Greeter.class);
String msg=g.greet("스프링");
System.out.println(msg);

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

 

컨테이너 초기화 = 빈 객체의 생성, 의존 주입, 초기화

컨테이너 종료 = 빈 객체의 소멸

 


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

: 스프링 컨테이너는 빈 객체의 라이프사이클을 관리한다. 

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

 

 

(1) 빈 객체의 초기화와 소멸 : 스프링 인터페이스

> 스프링 컨테이너는 빈 객체를 초기화하고 소화하기 위해

빈 객체의 지정한 메서드를 호출함.

 

InitializingBean(초기화 과정 시), DisposableBean(소멸 과정시)

해당 두 인터페이스에 이 메서드를 정의하고 있다. 

 

초기화 및 소멸 과정이 필요한 경우

: 데이터베이스 커넥션 풀, 채팅 클라이언트

 

[1] InitializingBean, DisposableBean 인터페이스를 구현한 간단한 클래스 작성

package spring;

import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;

public class Client implements InitializingBean, DisposableBean {

	private String host;

	public void setHost(String host) {
		this.host = host;
	}

	@Override
	public void afterPropertiesSet() throws Exception {
		System.out.println("Client.afterPropertiesSet() 실행");
	}

	public void send() {
		System.out.println("Client.send() to " + host);
	}

	@Override
	public void destroy() throws Exception {
		System.out.println("Client.destroy() 실행");
	}

}

 

[2] Client 클래스를 위한 설정 클래스

package config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import spring.Client;
import spring.Client2;

@Configuration
public class AppCtx {

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

 

[3] Main.java

> AppCtx를 이용해서 스프링 컨테이너를 생성하고

Client 빈 객체를 구해 사용하는 코드

package main;

import java.io.IOException;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.support.AbstractApplicationContext;

import config.AppCtx;
import spring.Client;

public class Main {

	public static void main(String[] args) throws IOException {
		AbstractApplicationContext ctx = 
				new AnnotationConfigApplicationContext(AppCtx.class);

		Client client = ctx.getBean(Client.class);
		client.send();

		ctx.close();
	}

}

 

 

(2) 빈 객체의 초기화와 소멸 : 커스텀 메서드

@Bean 태그에서 initMethod 속성과 destroyMethod 속성을 사용하여

초기화 및 소멸 메서드의 이름을 지정하는 방식은

해당 두 인터페이스를 사용하고 싶지 않거나 구현할 수 없는 상황에서 사용할 수 있는 방법이다.

 

 


3. 빈 객체의 생성과 관리 범위

: 스프링 컨테이너는 빈 객체를 한 개만 생성한다고 배웠다. 

동일한 이름을 갖는 빈 객체를 구하면 A와 B는 동일한 빈 객체를 참조한다.  

따라서 별도의 설정을 하지 않으면 빈은 싱글톤 범위를 갖는다.

 

사용빈도가 낮지만 프로토타입 범위의 빈을 설정할 수도 있다. 

빈 객체를 구할 때마다 매번 새로운 객체를 생성하게 된다. 

 

@Configuration
public class AppCtxWithPrototype {
	@Bean
    @Scope("prototype")
    public Client client() {
    	Client client = new Client();
        client.setHost("host");
        return client;
    }
}

 

만약, 싱글톤 범위를 명시적으로 지정하고싶으면

@Scope 의 값을 prototype => singleton으로 변경하면 된다. 

 

단, 프로토타입 범위를 갖는 빈은 

완전한 라이프사이클을 따르지 않는다는 점에 주의해야 한다.

 

따라서 빈 객체의 초기화까지는 수행하나

소멸 메서드는 실행하지 않으므로

빈의 소멸처리를 직접 코드에 작성해야 한다.