728x90
반응형

[Spring Framework] Spring Boot DI 핵심 가이드 | @Primary와 @Qualifier 제대로 이해하기

 

[Spring Framework] Spring Boot DI 핵심 가이드 | @Primary와 @Qualifier 제대로 이해하기

[Spring Framework] Spring Framework 의존성 주입(DI) 어노테이션 총정리 [Spring Framework] Spring Framework 의존성 주입(DI) 어노테이션 총정리[Spring Framework] Spring Boot에서 Dependency Injection 완벽 가이드 | 개념부터

crushed-taro.tistory.com

1. Collection

  • 같은 타입의 빈을 여러 개 주입 받고 싶다면Collection타입을 활용할 수 있다.

 

1. List 타입

List<Pokemon>타입의 객체를 의존성 주입 받는PokemonService래스를 선언한다.

@Service("pokemonServiceCollection")
public class PokemonService {
	
	/* 1. List 타입으로 주입 */
	private List<Pokemon> pokemonList;

	@Autowired
	public PokemonService(List<Pokemon> pokemonList) {
		this.pokemonList = pokemonList;
	}

	public void pokemonAttack() {
		pokemonList.forEach(Pokemon::attack);
	}
}

CharmanderPikachuSquirtlePokemonService 를 빈 스캐닝 할 수 있는 basePackages를 설정하여 스프링 컨테이너를 생성한다.

ApplicationContext context = new AnnotationConfigApplicationContext("project");

PokemonService pokemonService = context.getBean("pokemonServiceCollection", PokemonService.class);
		
pokemonService.pokemonAttack();

/*
파이리 불꽃 공격🔥
피카츄 백만볼트⚡
꼬부기 물대포 발사🌊
*/

bean 이름의 사전순으로 List에 추가 되어 모든 Pokemon 타입의 빈이 주입 된다.

 

2. Map 타입

Map<String, Pokemon>타입의 객체를 의존성 주입 받는PokemonService클래스를 선언한다.

@Service("pokemonServiceCollection")
public class PokemonService {
	
	/* 2. Map 타입으로 주입 */
	private Map<String, Pokemon> pokemonMap;
	
	@Autowired
	public PokemonService(Map<String, Pokemon> pokemonMap) {
		this.pokemonMap = pokemonMap;
	}
	
	public void pokemonAttack() {
      pokemonMap.forEach((k, v) -> {
          System.out.println("key : " + k);
          System.out.print("공격 : ");
          v.attack();
      });
  }
}

/*
key : charmander
공격 : 파이리 불꽃 공격🔥
key : pikachu
공격 : 피카츄 백만볼트⚡
key : squirtle
공격 : 꼬부기 물대포 발사🌊
*/

bean 이름의 사전순으로 Map에 추가 되어 모든 Pokemon 타입의 빈이 주입 된다.

 

2. Resource

@Resource 어노테이션은 자바에서 제공하는 기본 어노테이션이다. @Autowired와 같은 스프링 어노테이션과 다르게 
name 속성 값으로 의존성 주입을 할 수 있다.

해당 어노테이션은 사용하기 전 라이브러리 의존성 추가가 필요하므로 Maven Repository에서javax annoataion을 검색하여 build.gradle.kts파일에 아래와 같은 구문을 추가한다.

dependencies {
	implementation("javax.annotation:javax.annotation-api:1.3.2")
	...생략
}

 

1. 이름으로 주입

드로Pokemon 타입의 객체를 의존성 주입 받는 PokemonService 클래스를 선언한다. @Resource 어노테이션의 name속성에 주입할 빈 객체의 이름을 지정한다.

@Service("pokemonServiceResource")
public class PokemonService {
  
  /* pikachu 이름의 빈 지정 */
	@Resource(name = "pikachu")
	private Pokemon pokemon;

	public void pokemonAttack() {
		pokemon.attack();
	}
}

CharmanderPikachuSquirtlePokemonService 를 빈 스캐닝 할 수 있는 basePackages를 설정하여 스프링 컨테이너를 생성한다.

ApplicationContext context = new AnnotationConfigApplicationContext("project");

PokemonService pokemonService = context.getBean("pokemonServiceResour", PokemonService.class);
		
pokemonService.pokemonAttack();

/*
피카츄 백만볼트⚡
*/

 

2. 타입으로 주입

List<Pokemon> 타입으로 변경한 뒤 name 속성을 따로 기재하지 않고 동작시킬 수 있다. 기본적으로는 name속성을 통해 주입하지만 name 속성이 없을 경우 Type을 통해 의존성 주입한다.

@Service("pokemonServiceResource")
public class PokemonService {
  
  @Resource
	private List<Pokemon> pokemonList;

	public void pokemonAttack() {
		pokemonList.forEach(Pokemon::attack);
	}
}

/*
파이리 불꽃 공격🔥
피카츄 백만볼트⚡
꼬부기 물대포 발사🌊
*/

bean 이름의 사전순으로 List에 추가 되어 모든 Pokemon 타입의 빈이 주입 된다.

728x90
반응형
728x90
반응형

[Spring Framework] Spring Framework 의존성 주입(DI) 어노테이션 총정리

 

[Spring Framework] Spring Framework 의존성 주입(DI) 어노테이션 총정리

[Spring Framework] Spring Boot에서 Dependency Injection 완벽 가이드 | 개념부터 실습까지 [Spring Framework] Spring Boot에서 Dependency Injection 완벽 가이드 | 개념부터 실습까지[Spring Framework] Annotation-based Configuration

crushed-taro.tistory.com

1. DI Annotation

  • @Autowired 어노테이션은 가장 보편적으로 사용 되는 의존성 주입 Annotation이다. @Autowired 와 함께 사용하거나 또는 대체해서 사용할 수 있는 어노테이션을 학습한다.
  • 아래 코드는 테스트에 공통적으로 사용 할 Pokemon, Charmander, Pikachu, Squirtle 클래스이다.

 

  • Pokemon
public interface Pokemon {
	
	/* 공격하다 */
	void attack();
}
  • Charmander
@Component
public class Charmander implements Pokemon {

	@Override
	public void attack() {
		System.out.println("파이리 불꽃 공격🔥");
	}
}
  • Pikachu
@Component
public class Pikachu implements Pokemon {

	@Override
	public void attack() {
		System.out.println("피카츄 백만볼트⚡");
	}
}
  • Squirtle
@Component
public class Squirtle implements Pokemon {

	@Override
	public void attack() {
		System.out.println("꼬부기 물대포 발사🌊");
	}
}

 

1. @Primary

@Primary 어노테이션은 여러 개의 빈 객체 중에서 우선순위가 가장 높은 빈 객체를 지정하는 어노테이션이다.

생성자로 Pokemon 타입의 객체를 의존성 주입 받는 PokemonService클래스를 선언한다.

@Service("pokemonServicePrimary")
public class PokemonService {
	
	private Pokemon pokemon;
	
	@Autowired
	public PokemonService(Pokemon pokemon) {
		this.pokemon = pokemon;
	}
	
	public void pokemonAttack() {
		pokemon.attack();
	}

}

CharmanderPikachuSquirtlePokemonService 를 빈 스캐닝 할 수 있는 basePackages를 설정하여 스프링 컨테이너를 생성한다.

ApplicationContext context = new AnnotationConfigApplicationContext("project");

PokemonService pokemonService = context.getBean("pokemonServicePrimary", PokemonService.class);
		
pokemonService.pokemonAttack();

/*
Exception in thread "main" org.springframework.beans.factory.UnsatisfiedDependencyException: 
Error creating bean with name 'pokemonServicePrimary' defined in file 파일 경로 : 
Unsatisfied dependency expressed through constructor parameter 0; 
nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: 
No qualifying bean of type 'project.common.Pokemon' available: 
expected single matching bean but found 3: charmander,pikachu,squirtle
...생략
*/

스프링 컨테이너 내부에 Pokemon 타입의 빈 객체가 charmander,pikachu,squirtle 3개가 있어 1개의 객체를 PokemonService의 생성자로 전달할 수 없어 오류가 발생했음을 확인할 수 있다.

 

Charmander,Pikachu,Squirtle중에서Charmander빈 객체를 우선적으로 주입받도록@Primary어노테이션을 설정한다.

@Component
@Primary
public class Charmander implements Pokemon {

    @Override
    public void attack() {
        System.out.println("파이리 불꽃 공격🔥");
    }
}

/*
파이리 불꽃 공격🔥
*/

@Primary 어노테이션을 설정하면 @Autowired로 동일한 타입의 여러 빈을 찾게 되는 경우 자동으로 연결 우선 시 할 타입으로 설정 된다.

동일한 타입의 클래스 중 한 개만 @Primary 어노테이션을 사용할 수 있다.

Charmander 빈 객체에 @Primary 어노테이션이 설정되어 있으므로, PokemonService의 생성자로 Pokemon 객체를 주입받으면 Charmander 빈 객체가 우선적으로 주입된다.

 

2. @Qualifier

@Qualifierb어노테이션은 여러 개의 빈 객체 중에서 특정 빈 객체를 이름으로 지정하는 어노테이션이다. 

 

1. 필드 주입

드로Pokemon 타입의 객체를 의존성 주입 받는 PokemonService 클래스를 선언한다. @Autowired 어노테이션과 함께 @Qualifier 어노테이션을 사용하여 빈 이름을 통해 주입할 빈 객체를 지정한다.

@Service("pokemonServiceQualifier")
public class PokemonService {
	
  /* @Qualifier 어노테이션을 사용하여 pikachu 빈 객체를 지정한다. */
	@Autowired
	@Qualifier("pikachu")
	private Pokemon pokemon;
	
	public void pokemonAttack() {
		pokemon.attack();
	
}

CharmanderPikachuSquirtlePokemonService 를 빈 스캐닝 할 수 있는 basePackages를 설정하여 스프링 컨테이너를 생성한다.

ApplicationContext context = new AnnotationConfigApplicationContext("project");

PokemonService pokemonService = context.getBean("pokemonServiceQualifier", PokemonService.class);
		
pokemonService.pokemonAttack();

/*
피카츄 백만볼트⚡
*/

@Primary 어노테이션과 @Qualifier 어노테이션이 함께 쓰였을 때 @Qualifier 우선한다는 것도 결과를 통해 확인할 수 있다.

 

2. 생성자 주입

생성자 주입의 경우 @Qualifier 어노테이션은 메소드의 파라미터 앞에 기재한다. 역시 빈 이름을 통해 주입할 빈 객체를 지정한다.

@Service("pokemonServiceQualifier")
public class PokemonService {
	
	private Pokemon pokemon;

  /* @Qualifier 어노테이션을 사용하여 squirtle 빈 객체를 지정한다. */
	@Autowired
	public PokemonService(@Qualifier("squirtle") Pokemon pokemon) {
		this.pokemon = pokemon;
	}
	
	public void pokemonAttack() {
		pokemon.attack();
	}
}

/*
꼬부기 물대포 발사🌊
*/
728x90
반응형
728x90
반응형

[Spring Framework] Spring IoC 컨테이너 사용법 완벽 정리 | 실전 예제로 이해하는 DI

 

[Spring Framework] Spring IoC 컨테이너 사용법 완벽 정리 | 실전 예제로 이해하는 DI

[Spring Framework] 스프링 IoC 컨테이너 이해하기 | 정의 [Spring Framework] 스프링 IoC 컨테이너 이해하기 | 정의[Spring Framework] Spring Framework란? 개요와 핵심 구성 모듈 정리 [Spring Framework] Spring Framework란? 개

crushed-taro.tistory.com

1. IoC Container

1. Annotation-based Configuration

1. @ComponentScan이란?

  • base package로 설정 된 하위 경로에 특정 어노테이션을 가지고 있는 클래스를 bean으로 등록하는 기능이다.
  • @Component 어노테이션이 작성 된 클래스를 인식하여 bean으로 등록한다.
  • 특수 목적에 따라 세부 기능을 제공하는 @Controller, @Service, @Repository, @Configuration 등을 사용한다.
어노테이션 설명
@Component 객체를 나타내는 일반적인 타입으로<bean> 태그와 동일한 역할
@Controller 프리젠테이션 레이어. 웹 어플리케이션에서 View에서 전달된 웹 요청과 응답을 처리하는 클래스
EX) Controller Class
@Service 서비스 레이어, 비즈니스 로직을 가진 클래스
EX) Service Class
@Repository 퍼시스턴스(persistence) 레이어, 영속성을 가지는 속성(파일, 데이터베이스)을 가진 클래스
EX) Data Access Object Class
@Configuration 빈을 등록하는 설정 클래스

Spring 사진 1

 

2. @Component 어노테이션으로 자동 빈 등록하기

@Component
public class MemberDAO {

    private final Map<Integer, MemberDTO> memberMap;

    public MemberDAO() {
        memberMap = new HashMap<>();

        memberMap.put(1, new MemberDTO(1, "user01", "pass01", "홍길동"));
        memberMap.put(2, new MemberDTO(2, "user02", "pass02", "유관순"));
    }

    /* 매개변수로 전달 받은 회원 번호를 map에서 조회 후 회원 정보를 리턴하는 메소드 */
    public MemberDTO selectMember(int sequence) {
        return memberMap.get(sequence);
    }

    /* 매개변수를 전달 받은 회원 정보를 map에 추가하고 성공 실패 여부를 boolean으로 리턴하는 메소드 */
    public boolean insertMember(MemberDTO newMember) {

        int before = memberMap.size();

        memberMap.put(newMember.getSequence(), newMember);

        int after = memberMap.size();

        return after > before;
    }
}

bean으로 등록하고자 하는 클래스 위에 @Component 어노테이션을 기재하면 스프링 컨테이너 구동 시 빈으로 자동 등록된다.

  • 이름을 별도로 지정하지 않으면 클래스명의 첫 문자를 소문자로 변경하여 bean의 id로 자동 인식한다.
  • @Component("myName") 또는 @Component(value="myName") 의 형식으로 bean의 id를 설정할 수 있다.

 

3. @ComponentScan 어노테이션으로 base packages 설정하기

@ComponentScan(basePackages="project")
public class ContextConfiguration {}

@ComponentScan어노테이션의 basePackagese 속성에 입력한 패키지가 빈 스캐닝의 범위가 된다.

 

스프링 컨테이너에 빈 스캐닝을 통해bean이 자동으로 등록 되고 생성 되었는지 확인한다.

ApplicationContext context 
	= new AnnotationConfigApplicationContext(ContextConfiguration.class);

/* getBeanDefinitionNames : 스프링 컨테이너에서 생성 된 bean들의 이름을 배열로 반환한다. */
String[] beanNames = context.getBeanDefinitionNames();
for(String beanName : beanNames) {
		System.out.println("beanName : " + beanName);
}

/*
...생략
beanName : contextConfiguration
beanName : memberDAO
*/

 

4. excludeFilters

@ComponentScan 의 excludeFilters속성을 이용해서 일부 컴포넌트를 빈 스캐닝에서 제외할 수 있다.

  • Type으로 설정하는 방법
@ComponentScan(basePackages="project",
	excludeFilters={
		@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, 
		classes={MemberDAO.class})
})
public class ContextConfiguration {}

 

  • Annotation 종류로 설정하는 방법
@ComponentScan(basePackages="project",
	excludeFilters={
		@ComponentScan.Filter(type=FilterType.ANNOTATION, 
		classes={org.springframework.stereotype.Component.class})
})
public class ContextConfiguration {}

 

  • 표현식으로 설정하는 방법
@ComponentScan(basePackages="project",
	excludeFilters={
		@ComponentScan.Filter(type=FilterType.REGEX, 
		pattern={"annotationconfig.java.*"})
})
public class ContextConfiguration {}

 

빈 스캐닝에MemberDAO클래스가 제외되어 아래 코드를 실행하면 해당 이름을 가진 빈이 없다는 오류가 발생한다.

ApplicationContext context = new AnnotationConfigApplicationContext(ContextConfiguration.class);

String[] beanNames = context.getBeanDefinitionNames();
for(String beanName : beanNames) {
		System.out.println("beanName : " + beanName);
}

MemberDAO memberDAO = context.getBean("memberDAO", MemberDAO.class);

/*
...생략
beanName : contextConfiguration
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException: 
No bean named 'memberDAO' available
*/

 

5. includeFilters

@ComponentScan 의 includeFilters성을 이용해서 일부 컴포넌트를 빈 스캐닝에 포함 시킬 수 있다.

@ComponentScan(basePackages="project",
	useDefaultFilters=false,
	includeFilters={@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, 
		classes= {MemberDAO.class})
})
public class ContextConfiguration {}

useDefaultFilters 속성의 기본 값은 true로 @Component 종류의 어노테이션을 자동으로 등록 처리 해준다. 해당 속성을 false로 변경하면 컴포넌트 스캔이 일어나지 않게 된다.

별도로 includeFilters 속성을 정의해 컴포넌트 스캔이 발생하도록 한다.

excludeFilters에서 설정하는 방식과 동일하므로 종류별 예시는 생략한다.

 

아래 코드를 실행하면 빈 스캐닝에서MemberDAO클래스가 포함되어 해당 이름을 가진 빈을 확인할 수 있다.

ApplicationContext context 
	= new AnnotationConfigApplicationContext(ContextConfiguration.class);

String[] beanNames = context.getBeanDefinitionNames();
for(String beanName : beanNames) {
		System.out.println("beanName : " + beanName);
}

/*
...생략
beanName : contextConfiguration
beanName : memberDAO
*/

 

6. XML에서 Component Scan 설정

아래와 같이 XML 설정 파일에서 Component Scan의base package를 설정할 수도 있다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
													 http://www.springframework.org/schema/beans/spring-beans.xsd 
													 http://www.springframework.org/schema/context 
													 https://www.springframework.org/schema/context/spring-context.xsd">

	<context:component-scan base-package="project"/>

</beans>

<context:component-scan> 태그는 context 네임스페이스의 기능이기 때문에 context: 접두어가 필요하다.

또한 XML 설정 파일의 기본 네임스페이스가 beans로 설정 되어 있기 때문에 context 네임스페이스 추가가 필요하다.

 

base package@Component 어노테이션을 작성한 MemberDAOclass를 배치해 두고 아래와 같은 코드를 실행하면 빈이 등록 되어있음을 확인할 수 있다.

ApplicationContext context 
	= new GenericXmlApplicationContext("section03/javaannotation/spring-context.xml");

String[] beanNames = context.getBeanDefinitionNames();
for(String beanName : beanNames) {
		System.out.println("beanName : " + beanName);
}

/*
beanName : memberDAO
...생략
*/

 

아래와 같이 <context:component-scan> 태그 내부에 <exclude-filter> 또는 <include-filter> 태그를 정의할 수 있다.

<include-filter> 의 경우 동일한 방식으로 정의하므로 생략하였다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 
													 http://www.springframework.org/schema/beans/spring-beans.xsd 
													 http://www.springframework.org/schema/context 
													 https://www.springframework.org/schema/context/spring-context.xsd">

	<context:component-scan base-package="project">
        <context:exclude-filter type="assignable" expression="common.MemberDAO"/>
  </context:component-scan>

</beans>

 

스캐닝에서MemberDAO클래스가 제외되어 아래 코드를 실행하면 해당 이름을 가진 빈이 없다는 오류가 발생한다.

ApplicationContext context 
	= new GenericXmlApplicationContext("section03/javaannotation/spring-context.xml");

String[] beanNames = context.getBeanDefinitionNames();
for(String beanName : beanNames) {
		System.out.println("beanName : " + beanName);
}

MemberDAO memberDAO = context.getBean("memberDAO", MemberDAO.class);

/*
...생략
Exception in thread "main" org.springframework.beans.factory.NoSuchBeanDefinitionException
: No bean named 'memberDAO' available
*/

 

정리

  • XML 설정은 전통적으로 사용하던 방식으로 최근에는 Java 설정이 선호된다.
  • 개발자가 직접 컨트롤 가능한 Class의 경우, @Component를 클래스에 정의하여 빈 스캐닝을 통한 자동 빈 등록을 한다.
  • 개발자가 직접 제어할 수 없는 외부 라이브러리는 @Bean을 메소드에 사용하여 수동 빈 등록을 한다.
    • 다형성을 활용하고 싶은 경우에도 @Bean을 사용할 수 있다.
 

Java-based Container Configuration :: Spring Framework

This section covers how to use annotations in your Java code to configure the Spring container.

docs.spring.io

728x90
반응형

+ Recent posts