Developer Sang Guy

Spring @Autowired를 사용한 스프링 빈 주입 본문

Spring

Spring @Autowired를 사용한 스프링 빈 주입

은크 2021. 5. 31. 18:44

스프링 빈 컨테이너에 등록 된 Bean을 자동으로 주입해주는 @Autowired에 대해 알아보자

 

@Autowired란 스프링 빈 컨테이너에 존재하는 빈을 타입에 맞게 자동으로 주입해주는 것을 의미한다.

스프링이 Bean 등록을 하기 위해 각 객체들을 스캔하는 과정에서 자동으로 실행 된다.

주의 : Bean 등록을 하지 않는 객체에서는 @Autowired를 사용 할 수 없다.

 

@Autowired를 사용하는 방식은 주로 아래와 같다.

 

1. 생성자 주입

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class Trade {
    
    private Coin coin;
    
    @Autowired
    public Trade(Coin coin) {
        
        this.coin = coin;
    }
 
    public User buyCoin(User user, String coinName, Integer quantity) {
        
        return coin.buy(user, coinName, quantity);
    }
    
    public User sellCoin(User user, String coinName, Integer quantity) {
        
        return coin.sell(user, coinName, quantity);
    }
}
cs

 

2. 수정자 주입(setter 주입)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Service
public class Trade {
    
    private Coin coin;
 
    @Autowired
    public void setCoin(Coin coin) {
        this.coin = coin;
    }
 
    public User buyCoin(User user, String coinName, Integer quantity) {
        
        return coin.buy(user, coinName, quantity);
    }
    
    public User sellCoin(User user, String coinName, Integer quantity) {
        
        return coin.sell(user, coinName, quantity);
    }
}
cs

 

3. 필드 주입

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Service
public class Trade {
    
    @Autowired
    private Coin coin;
 
    public User buyCoin(User user, String coinName, Integer quantity) {
        
        return coin.buy(user, coinName, quantity);
    }
    
    public User sellCoin(User user, String coinName, Integer quantity) {
        
        return coin.sell(user, coinName, quantity);
    }
}
cs

 

4. 일반 메서드 주입

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class Trade {
    
    private Coin coin;
    
    @Autowired
    public void init(Coin coin) {
        
        this.coin = coin;
    }
 
    public User buyCoin(User user, String coinName, Integer quantity) {
        
        return coin.buy(user, coinName, quantity);
    }
    
    public User sellCoin(User user, String coinName, Integer quantity) {
        
        return coin.sell(user, coinName, quantity);
    }
}
cs

 

각 주입 방법에는 모두 장단점이 있으며 개인적으로 정답은 없다고 생각한다.

모두 목적은 같지만 방법이 다를 뿐이니 각 상황에 알맞게 사용하자

 

그래서 어떤게 좋은건데? 어떤 일이 생기는 건데?

@Autowired로 주입을 받는 객체의 타입이 스프링 빈에 등록되어 있다면 자동으로 해당 객체가 주입이 된다.

말은 어려우니 바로 소스로 알아보자

 

아래 Trade 객체는 @Service를 통해 스프링 빈에 등록을 할 것이며 @Autowired 생성자 주입 방법을 통해 Coin Type의 객체(BitCoin.class)를 주입 받을 것이다.

주의 : 주입 될 객체 역시 스프링 빈으로 등록되어 있어야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class Trade {
    
    private Coin coin;
    
    @Autowired
    public Trade(Coin coin) {
        
        this.coin = coin;
    }
 
    public User buyCoin(User user, String coinName, Integer quantity) {
        
        return coin.buy(user, coinName, quantity);
    }
    
    public User sellCoin(User user, String coinName, Integer quantity) {
        
        return coin.sell(user, coinName, quantity);
    }
}
cs

 

Coin.interface

1
2
3
4
5
6
public interface Coin {
    
    User buy(User user, String coinName, Integer quantity);
    
    User sell(User user, String coinName, Integer quantity);
}
cs

 

Coin.interface를 상속받는 BitCoin.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class BitCoin implements Coin {
    
    @Override
    public User buy(User user, String coinName, Integer quantity) {
        
        Map<String, Integer> pocket = user.getPocket();
        pocket.put(coinName, quantity);
        
        return new User(user.getName(), pocket);
    }
    
    @Override
    public User sell(User user, String coinName, Integer quantity) {
        
        Map<String, Integer> pocket = user.getPocket();
        pocket.put(coinName, pocket.get(coinName) - quantity);
        
        return new User("UserA", pocket);
    }
}
cs

 

실행 : 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    @Test
    public void tradeCoinTest() {
        
        // PreAutowiredApplication를 실행하여 컴포넌트 스캔 실행
        ApplicationContext ac = new AnnotationConfigApplicationContext(PreAutowiredApplication.class);
        
        // 유저 객체 생성
        User user = new User("UserA"new HashMap<String, Integer>());
        
        /* BitCoin 거래 */
        Trade trade = ac.getBean(Trade.class);
        
        trade.buyCoin(user, "bitCoin"155);
        System.out.println(user.getPocket().get("bitCoin"));
        assertThat(user.getPocket().get("bitCoin")).isEqualTo(155);
        
        trade.sellCoin(user, "bitCoin"20);
        System.out.println(user.getPocket().get("bitCoin"));
        assertThat(user.getPocket().get("bitCoin")).isEqualTo(135);
    }
cs

 

결과 : 

잔여 bitCoin : 155
잔여 bitCoin : 135

 

위와 같이 Trade.class의 생성자에 @Autowired를 사용하여 빈 주입을 해주기만 하면 따로 BitCoin.class를 주입 해 줄 필요가 없다.

ex) 기본 자바의 경우 Trade trade = new Trade(new BitCoin())와 같은 방법으로 주입해야 함

 

Coin Type의 빈이 여러개라면?? BitCoin 말고 다른 코인도 사용 하겠어!

위 설명했던 내용 중 아래와 같은 내용이 있었는데..

@Autowired 생성자 주입 방법을 통해 Coin Type의 객체(BitCoin.class)를 주입 받을 것이다.

만약에 Coin Type의 객체가 BitCoin.class 1개가 아닌 여러 개의 경우에는 어떻게 될까?

 

DogeCoin.class를 새로 생성하여 테스트를 진행해보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Service
public class dogeCoin implements Coin {
    
    @Override
    public User buy(User user, String coinName, Integer quantity) {
        
        Map<String, Integer> pocket = user.getPocket();
        pocket.put(coinName, quantity);
        
        return new User(user.getName(), pocket);
    }
    
    @Override
    public User sell(User user, String coinName, Integer quantity) {
        
        Map<String, Integer> pocket = user.getPocket();
        pocket.put(coinName, pocket.get(coinName) - quantity);
        
        return new User("UserA", pocket);
    }
}
cs

 

실행 : 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
    @Test
    public void tradeCoinTest() {
        
        // PreAutowiredApplication를 실행하여 컴포넌트 스캔 실행
        ApplicationContext ac = new AnnotationConfigApplicationContext(PreAutowiredApplication.class);
        
        // 유저 객체 생성
        User user = new User("UserA"new HashMap<String, Integer>());
        
        /* BitCoin 거래 */
        Trade trade = ac.getBean(Trade.class);
        
        trade.buyCoin(user, "bitCoin"155);
        System.out.println("잔여 bitCoin : " + user.getPocket().get("bitCoin"));
        assertThat(user.getPocket().get("bitCoin")).isEqualTo(155);
        
        trade.sellCoin(user, "bitCoin"20);
        System.out.println("잔여 bitCoin : " + user.getPocket().get("bitCoin"));
        assertThat(user.getPocket().get("bitCoin")).isEqualTo(135);
        
        /* DogeCoin 거래 */
        trade.buyCoin(user, "dogeCoin"547519);
        System.out.println("잔여 dogeCoin : " + user.getPocket().get("dogeCoin"));
        assertThat(user.getPocket().get("dogeCoin")).isEqualTo(547519);
        
        trade.sellCoin(user, "dogeCoin"5000);
        System.out.println("잔여 dogeCoin : " + user.getPocket().get("dogeCoin"));
        assertThat(user.getPocket().get("dogeCoin")).isEqualTo(542519);
    }
cs

 

결과 : 

1
18:21:15.556 [main] WARN org.springframework.context.annotation.AnnotationConfigApplicationContext - Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'trade': Unsatisfied dependency expressed through method 'init' parameter 0; nested exception is org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'me.pre.demo.service.Coin' available: expected single matching bean but found 2: bitCoin,dogeCoin
cs

에러 내용 중 아래와 같은 내용이 있다.

org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type 'me.pre.demo.service.Coin' available: expected single matching bean but found 2: bitCoin,dogeCoin

 

대충 보면 요구하는 건 단일 주입인데 스프링 빈에는 Coin Type으로 매칭되는 빈이 BitCoin, dogeCoin 두 개가 있다는 내용이다.

 

현재 Trade.class 에서 생성자 주입으로 Coin Type의 객체를 주입받고 있는데 이는 1개의 객체밖에 주입받을 수 없는 상태라 해당 에러가 발생한 것이다.

 

이와 같이 Coin Type의 객체는 여러개를 사용해야하는 상황이며 다 수의 빈을 주입받아 사용해야 할 경우에는 Trade.class를 아래와 같이 수정하여 사용 할 수 있다.

 

다 수의 빈을 주입받아 사용하는 Trade.class

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Service
public class TradeAllCoin {
    
    private final Map<String, Coin> coinMap;
    
    @Autowired
    public TradeAllCoin(Map<String, Coin> coinMap) {
        
        this.coinMap = coinMap;
    }
 
    public User buyCoin(User user, String coinName, Integer quantity) {
        
        Coin coin = coinMap.get(coinName);
        
        return coin.buy(user, coinName, quantity);
    }
    
    public User sellCoin(User user, String coinName, Integer quantity) {
        
        Coin coin = coinMap.get(coinName);
        
        return coin.sell(user, coinName, quantity);
    }
}
cs

 

Map 또는 List 타입으로 빈을 주입받으면 여러 개의 빈을 가질 수 있다.

 

실행 : 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
    @Test
    public void tradeAllCoinTest() {
        
        ApplicationContext ac = new AnnotationConfigApplicationContext(PreAutowiredApplication.class);
        
        User user = new User("UserA"new HashMap<String, Integer>());
        
        TradeAllCoin tradeAllCoin = ac.getBean(TradeAllCoin.class);
        
        /* BitCoin 거래 */
        tradeAllCoin.buyCoin(user, "bitCoin"155);
        System.out.println("잔여 bitCoin : " + user.getPocket().get("bitCoin"));
        assertThat(user.getPocket().get("bitCoin")).isEqualTo(155);
        
        tradeAllCoin.sellCoin(user, "bitCoin"20);
        System.out.println("잔여 bitCoin : " + user.getPocket().get("bitCoin"));
        assertThat(user.getPocket().get("bitCoin")).isEqualTo(135);
        
        /* DogeCoin 거래 */
        tradeAllCoin.buyCoin(user, "dogeCoin"547519);
        System.out.println("잔여 dogeCoin : " + user.getPocket().get("dogeCoin"));
        assertThat(user.getPocket().get("dogeCoin")).isEqualTo(547519);
        
        tradeAllCoin.sellCoin(user, "dogeCoin"5000);
        System.out.println("잔여 dogeCoin : " + user.getPocket().get("dogeCoin"));
        assertThat(user.getPocket().get("dogeCoin")).isEqualTo(542519);
    }
cs

 

결과 : 

1
2
3
4
5
coinMap : {bitCoin=me.pre.demo.service.BitCoin@67fe380b, dogeCoin=me.pre.demo.service.dogeCoin@4a325eb9}
잔여 bitCoin : 155
잔여 bitCoin : 135
잔여 dogeCoin : 547519
잔여 dogeCoin : 542519
cs

 

위와 같이 Map으로 빈을 주입받는 방법 외에 아래 두 개의 에노테이션을 사용해 사용 할 빈을 컨트롤 하는 방법도 있다.

 

@Qualifier, @Primary

 

하지만 해당 방법은 다 수의 빈 사용하는 방법이 아닌 다 수의 빈 중 사용 할 빈을 컨트롤 하는 방법이므로 위 내용에서는 다루지 않았다.

 

 

 

'Spring' 카테고리의 다른 글

HttpServletRequest Body 여러 번 읽기  (0) 2022.12.28
[Spring] RequestMapping 기능 (produces, consumes )  (0) 2022.10.07
[Spring Boot] DB 연결  (0) 2022.09.28
[Spring] Aop, Logback 활용  (0) 2022.04.28
스프링 WebMvcConfigurer  (0) 2021.08.08
Comments