본문 바로가기

SPRING FRAMEWORK

[Finally Spring] 3. Bean 등록 관련 애너테이션(JAVA기반 컨테이너 설정, 컴포넌트스캔)

요약

Bean 등록 관련 애너테이션

1) @Configuration

class-level

빈 설정(BeanDefinitions)을 담당하는 클래스에 사용하는 애너테이션. 스프링 설정 정보에서 사용된다. Configuration이 선언된 클래스가 입력으로 제공되면 Congifuration 클래스 자체가 BeanDefinition으로 등록되고 클래스 내에서 선언된 모든 @Bean도 BeanDefinition로 등록된다. 이 클래스 안에서 @Bean 어노테이션이 동봉된 메소드를 선언하면, 그 메소드를 통해 스프링 빈을 정의하고 생명주기를 설정하게 된다.

2) @ComponentScan

class-level

@Configuration 애너테이션과 함께 쓰면, 이 클래스는 자바 빈 설정 클래스이며, 이 @ComponentScan 애너테이션에서 제공하는 package 속성을 통해 스프링 빈 범위를 정의할 수 있다. 스캔 대상은 다음과 같다.

  • @Component
  • @Controller & @RestController 
  • @Service
  • @Repository
  • @Configuration 

컴포넌트 스캔을 위한 필터의 종류는 다음과 같다.

  • includeFilters : 컴포넌트 스캔 대상을 추가로 지정합니다.
  • excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정합니다.
  • FilterType
    • ANNOTATION: 기본값, 애너테이션으로 인식해서 동작합니다.
    • ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작합니다.
    • ASPECTJ: AspectJ 패턴을 사용합니다.
    • REGEX: 정규 표현식을 나타냅니다.
    • CUSTOM: TypeFilter라는 인터페이스를 구현해서 처리합니다.

3) @Import

class-level

@Configuration 어노테이션이 선언된 스프링 설정 클래스를 가져오는 애너테이션. 클래스명을 기입한다 

4) @Component

class-level

빈을 선언하는 클래스를 작성하기 위해 선언하는 애너테이션. @ComponentScan 의 주요 스캔 대상이다.

5) @Service

class-level

프링 비즈니스 로직에서 사용하는 애너테이션, @Component와 같은 역할이나 개발자들에게 서비스 계층이라는 것을 알려주기 위해 자주 사용한다.

6) @Repository

class-level

스프링 데이터 접근 계층에서 사용되는 애너테이션. 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환해준다.

7) @Controller

class-level

웹 요청의 기준을 담당하는 콘트롤러 계층을 담당하는 클래스임을 선언하는 어노테이션. @Component와 기능적으로 같다. 

8) @Autowired

method-level

스프링 빈 간 의존 관계를 주입하는 가장 기본적인 방법을 제공하는 애너테이션. 이 어노테이션은 필드, 메소드, 생성자에다가 넣을 수 있다. 

9) @Bean

method-level

@Configuration 선언한 BeanDefinition 담당 클래스에서 빈 선언을 담당하는 어노테이션으로, 메소드에만 넣을 수 있다.

 

1. 애너테이션(Annotation, @)을 이용한 자바 기반 컨테이너(Container) 설정

자바 기반 컨테이너(Container) 설정을 위해 사용하는 두가지 애너테이션이 있습니다.

 

(1) @Configuration: 빈(Bean)의 설정을 담당하는 객체로 등록 / class-level

(2) @Bean: 객체를 빈으로 등록  / method-level

 

이 두 애너테이션은 Spring 컨테이너에서 관리할 새 객체를 인스턴스화, 구성 및 초기화한다는 것을 나타내는데 사용합니다.

 

// AppConfig 클래스

@Configuration // 컨텍스트를 인스턴스화할 때
public class AppConfig{
	@Bean
    public MyService myService() {
    	return new MyServiceImpl();
    }
}

 

1-1. AnnotationConfigApplicationContext를 사용하여 스프링 컨테이너 인스턴스화

애너테이션을 이용해 Config 클래스를 설정하는 방법으로 @Configuration, @Component를 사용합니다.

(AnnotationConfigApplicationContext는 스프링 3.0에서 도입된 클래스입니다.) ApplicationContext는 아래와 같은 애너테이션이 달린 클래스로 파라미터 전달 받습니다.

 

(1) @Configuration

(2) @Component

(3) JSR-330 메타데이터

 

@Configuration이 선언된 클래스가 입력으로 제공되면 Congifuration 클래스 자체가 BeanDefinition으로 등록되고 클래스 내에서 선언된 모든 @Bean도 BeanDefinition로 등록됩니다.

 

@Component 클래스와 JSR-330 클래스가 제공되면 BeanDefinition으로 등록되며 필요한 경우 해당 클래스 내에서 @Autowired 또는 @Inject와 같은 DI 메타데이터가 사용되는 것으로 가정합니다.

 

[코드 예제]

1. Configuration 클래스를 입력으로 사용(AppConfig.class)

 

public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(AppConfig.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

 

2. @Component 또는 JSR-330 주석이 달린 클래스는 다음과 같이 생성자에 입력으로 사용 

 

public static void main(String[] args) {
	ApplicationContext ctx = new AnnotationConfigApplicationContext(MyServiceImpl.class, Dependency1.class, Dependency2.class);
    MyService myService = ctx.getBean(MyService.class);
    myService.doStuff();
}

 

@Autowired - MyServiceImpl, Dependency1, Dependency2에서 스프링 의존성 주입 애너테이션을 사용한 예제입니다.

 

1-2. @Bean 애너테이션 사용

@Bean

1) method-level 애너테이션, <bean />에서 제공하는 속성 지원

  • init-method
  • destroy-method
  • autowiring
  • @Bean 애너테이션은 @Configuration-annoted 또는 Component-annoted 클래스에서 사용 가능

2) 빈(Bean) 선언: @Bean 애너테이션을 메서드에 추가해 Bean으로 정의(선언) 가능합니다.

 

@Configuration // 컨텍스트를 빈으로 인스턴스화
public class AppConfig {

	@Bean // 빈으로 정의
    public TransferServiceImpl transferService() {
    	return new TransferServiceImpl();
    }
}

 

또한 빈(Bean) 정의가 있는 인터페이스를 구현하여 bean configuration을 설정 가능합니다.

 

public interface BaseConfig {
	@Bean
    default TransferServiceImpl transferService() {
    	return new TransferServiceImpl();
    }
}

@Configuration
public class AppConfig implements BaseConfigs {
}

 

 

3) 빈 의존성: @Bean 애너테이션이 추가된 (@Bean-annotated) 메서드는 빈을 구축하는데 필요한 의존성을 나타내는데 매개변수를 사용할 수 있습니다.

 

@Configuration
public class AppConfig {
	@Bean
    //@Bean 애너테이션이 추가된 (@Bean-annotated) 메서드는 빈을 구축하는데 필요한 의존성을 나타내는데 매개변수를 사용할 수 있습니다.
    public TransferService transferService(AccountRepository accountRepository) {
    	return new TransferServiceImpl(accountRepository);
    }
}

 

1-3. @Configuration 애너테이션을 사용하기

@Congfiguration: 객체가 bean definition(빈 설정 메타 정보)의 소스임을 나타내는 애너테이션. @Bean-annoted 메서드를 통해 bean을 선언합니다. @Configuration 클래스의 @Bean 메서드에 대한 호출을 사용하여 bean 사이의 의존성을 정의할 수 있습니다.

1) Bean 사이의 의존성 주입: 빈이 서로 의존성을 가지는 상호 의존성을 표현하는 것은 다른 bean 메서드를 호출하는 것과 같습니다.

 

// 빈 상호의존성 표현
@Configuration
public class AppConfig {
	
    @Bean
    public BeanOne beanOne() {
    	return new BeanOne(beanTwo()); // 빈1이 빈2를 참조
    }
    
    @Bean
    public BeanTwo beanTwo() {
    	return new BeanTwo(beanOne()); // 빈2가 빈1을 참조
    }
}

 

2) Java를 기반으로 설정되어있는 환경에서 내부적으로 작동하는 방식에 대한 정보: 내부적으로 작동하는 방식에 대한 정보

 

@Configuration
public class AppConfig {

	@Bean // (1)
    public ClientService clientService1() {
    ClientServiceImpl clinetService = new ClientServiceImpl();
    clientService.setClientDao(clientDao());
    return clientSerivce;
    }
    
	@Bean // (2)
    public ClientService clientService2() {
    ClientServiceImpl clinetService = new ClientServiceImpl();
    clientService.setClientDao(clientDao());
    return clientSerivce;
    }
    
	@Bean // (3)
    public ClientDao clientDao() {
    return clientDaoImpl;
    }
}

 

clientDao() 메서드는 clientService1()clientService2() 메서드에서 1번씩 호출되었습니다. 이 메서드는 ClientDaoImpl의 새 인스턴스를 만들고 이를 반환하므로 서비스마다 하나씩 있어야 합니다. Spring에 인스턴스화된 빈은 기본적으로 싱글톤 범위(Singleton Scope)을 갖습니다.

 

하위 클래스의 하위 메서드는 상위 메서드를 호출하고 새 인스턴스를 만들기 전에 먼저 컨테이너에 캐시된(범위 지정) bean이 있는지 확인합니다.

 

CGLIB

모든 @Configuration 클래스는 시작시에 CGLIB을 사용하여 해당 라이브러리의 하위 클래스로 분류됩니다. 즉, 스프링에서는 CGLIB이라는 바이트코드 조작 라이브러리를 사용합니다. 이는 같은 메서드를 호출할 때 이미 스프링 컨테이너에 등록되어 있으면 기존에 만들어진 걸 반환하며, 스프링 컨테이너에 등록되어 있지 않을 때는 필요한 Bean을 생성하고 스프링 컨테이너에 등록하는 역할을 합니다.

 

 

1-4. JAVA 코드에서 애너테이션을 사용해 Spring 컨테이너 구성하는 방법

Spring의 자바 기반 구성 기능 특징인 애너테이션을 사용해 스프링 컨테이너 구성의 복잡성을 줄입니다.

 

@Import 애너테이션

1) Import 애너테이션의 특징

  • XML 파일 내에서 요소가 사용되는 것처럼 구성을 모듈화 하는데 사용
  • 다른 구성 클래스에서 @BeanDefinitions를 가져올 수 있음
  • 컨텍스트를 인스턴스화할 때 ConfigA.class와 ConfigB.class 모두를 지정하는 대신 ConfigB만 제공
  • ctx에 Import(ConfigA.class) 받은 ConfigB.class 사용으로 인해 ctx.getBean(A.class)가 가능
  • 컨테이너 인스턴스 단순화 가능(많은 @Configuration 클래스를 기억할 필요 없이 하나의 클래스만 처리)
  • 추가한 @Bean 애너테이션에서 의존성 주입
@Configuration // 컨텍스트A 인스턴스화
public class ConfigA {
	@Bean
    public A a() {
    	return new A();
    }
}

@Configuration // 컨텍스트B 인스턴스화
@Import(ConfigA.class) // 컨텍스트 A의 @Bean 정의를 가져옴
public class ConfigB {
    @Bean
    public B b() {
    return new B();
    }
}

public static void main(String[] args) {
	// 컨텍스트 B만 구성 요소로 가져옴. 컨테이너 인스턴스화를 단순화함
	ApplicationContext ctx = new AnnotationConfigApplicationContext(ConfigB.class);
    
    // now both beans A and B will be available
    A a = ctx.getBean(A.class);
    B b = ctx.getBean(B.class);
}

 

2) 문제점

  • 실제로 사용될 때 빈은 1개의 구성 파일만 @Import 받지 않고 여러 구성 클래스 간에 걸쳐 서로 의존성을 지님
  • XML을 사용할 때는 컴파일러가 관여하지 않아 컨테이너 초기화 중에 ref="some Bean"을 선언하여 스프링으로 해결할 수 있어 문제가 되지 않음
  • @Configuration 클래스를 사용할 때, 자바 컴파일러는 구성 모델에 제약을 두며, 다른 빈에 대한 참조는 유효한 자바 구문이어야 함
@Configuration // 서비스 계층 구성정보 설정, 리포지토리 계층 구성 정보 DI
public class ServiceConfig {
	@Bean
    public TransferService transferService(AccountRepository accountRepository) {
    	return new TransferServiceImpl(accountRepository);
    }
}

@Configuration // 리포지토리 계층 구성정보 설정, 데이터 소스(DB) DI
public class RepositoryConfig {
	@Bean
    public AccountRepository accountRepository(DataSource dataSource) {
    	return new JdbcAccountRepository(dataSource);
    }
}

@Configuration // 전체 구성 정보 설정
@Import({ServiceConfig.class, RepositoryConfig.class}) // 서비스, 리포지토리 구성 정보 불러오기
public class SystemTestConfig {  
	@Bean
    public DataSource dataSource() { // 데이터 소스 연결
    	//return new DataSource();
    }
}

public static void main(String[]args) {
	Application ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // 모든 구성 정보 하나의 객체로 적용
    TranferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123". "C456");
}

 

3) 해결방법

  • @Bean 메서드는 빈 의존성을 설명하는 임의 개수 파라미터를 가질 수 있음
  • @Autowired 및 @Value 주입 및 다른 bean과 동일한 기능 사용 가능
  • @Configuration의 생성자 주입은 스프링 프레임워크 4.3에서만 지원
  • 대상 빈이 하나의 생성자만 정의하는 경우 @Autowired 지정할 필요가 없음
@Configuration
public class ServiceConfig {
	@Autowired // 필드 멤버로 의존 관계 수립, 다른 bean과 동일한 기능 사용 가능
    private AccountRepository accountRepository;
    
    @Bean // @Bean 메서드는 빈 의존성을 설명하는 임의 개수 파라미터를 가질 수 있음
    public TransferService transferService() {
    	return new TransferServiceImpl(accountRepository);
    }
}

@Configuration
public class RepositoryConfig {

	// 대상 빈이 하나의 생성자만 정의하는 경우 @Autowired 지정할 필요가 없음
	private final DataSource dataSource;
    // @Configuration의 생성자 주입은 스프링 프레임워크 4.3에서만 지원
    public RepositoryConfig(DataSource dataSource) { 
    	this.dataSource = dataSource;
    }
    
	@Bean
    public AccountRepository accountRepository(DataSource dataSource) {
    	return new JdbcAccountRepository(dataSource);
    }
}

@Configuration 
@Import({ServiceConfig.class, RepositoryConfig.class}) // 서비스, 리포지토리 구성 정보 불러오기
public class SystemTestConfig {  
	@Bean
    public DataSource dataSource() { 
    	//return new DataSource();
    }
}

public static void main(String[]args) {
	Application ctx = new AnnotationConfigApplicationContext(SystemTestConfig.class);
    // 모든 구성 정보 하나의 객체로 적용
    TranferService transferService = ctx.getBean(TransferService.class);
    transferService.transfer(100.00, "A123". "C456");
}

 

2. Component Scan

ComponentScan은 스프링이 설정 정보 없이 자동으로 스프링 빈을 등록하는 기능을 제공합니다. 스프링 빈을 @Bean으로 직접 작성해 등록하는 방법은 설정 정보가 커지고, 누락하는 등 다양한 문제가 발생합니다.

@ComponentScan은 @Component가 붙은 모든 클래스를 스프링 빈으로 등록해주기 때문에 설정 정보에 붙여주면 됩니다. 또한 의존관계를 자동으로 주입하는 @Autowired 기능도 함께 제공합니다.

 

@Configuration
@ComponentScan
public class AutoAppConfig {

}

위 예시를 보시면 @Bean으로 등록한 클래스가 없습니다. @ComponentScan이 클래스 내부의 내용을 모두 스프링 빈으로 등록해주기 때문입니다. 게다가 @Configuration이 붙은 설정 정보도 자동으로 등록합니다.(Configuration 애너테이션도 @Component가 붙어 있습니다.)

 

그러나 기존에 작성한 AppConfig가 있다면 정상적인 작동이 되지 않습니다. 따라서 AppConfig 등 @Configuration 설정이 된 파일이 있을 시 아래의 코드를 추가합니다.

 

@ComponentScan(excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class))

 

2-1. basePackages

스프링은 탐색할 패키지의 시작 위치를 지정하고, 해당 패키지부터 하위 패키지 모두 탐색합니다. 따라서 스프링 부트를 사용하면 @SpringBootApplication 를 이 프로젝트 시작 루트 위치에 두는 것을 추천합니다. 이 @SpringBootApplication에 @ComponentScan이 들어있습니다.

 

원하는 곳을 루트 위치로 둘 수 있도록 @ComponentScan()의 매개변수로 basePackages를 줄 수 있습니다. (@ComponentScan(basePackages = “ ”)) 만약 위치를 지정하지 않으면 @ComponentScan이 붙은 설정 정보 클래스의 패키지가 시작 위치가 됩니다. 설정 정보 클래스의 위치를 프로젝트 최상단에 두고 패키지 위치는 지정하지 않는 방법이 가장 편합니다..

 

2-2. @ComponentScan의 대상

1) @Component : 컴포넌트 스캔에서 사용됩니다.
2) @Controller & @RestController : 스프링 MVC 및 REST 전용 컨트롤러에서 사용됩니다.
3) @Service : 스프링 비즈니스 로직에서 사용됩니다. @Component와 같습니다
4) @Repository : 스프링 데이터 접근 계층에서 사용됩니다. @Component와 같습니다 스프링 데이터 접근 계층으로 인식하고, 데이터 계층의 예외를 스프링 예외로 변환합니다.
5) @Configuration : 스프링 설정 정보에서 사용됩니다. 스프링 설정 정보로 인식하고, 스프링 빈이 싱글톤을 유지하도록 추가 처리를 해줍니다. 해당 클래스의 소스 코드에는 @Component를 포함하고 있습니다.

 

2-3. @ComponentScan 필터 종류와 매개변수

@ComponentScan(excludeFilters = @Filter(type = FilterType.ANNOTATION, classes = Configuration.class))

 

1) includeFilters : 컴포넌트 스캔 대상을 추가로 지정합니다.
2) excludeFilters : 컴포넌트 스캔에서 제외할 대상을 지정합니다.

3) FilterType 옵션

  • ANNOTATION: 기본값, 애너테이션으로 인식해서 동작합니다.
  • ASSIGNABLE_TYPE: 지정한 타입과 자식 타입을 인식해서 동작합니다.
  • ASPECTJ: AspectJ 패턴을 사용합니다.
  • REGEX: 정규 표현식을 나타냅니다.
  • CUSTOM: TypeFilter라는 인터페이스를 구현해서 처리합니다.