Spring/Springboot-Intellij

[SpringBoot] Bean 수동 등록, 같은 타입 Bean이 2개인 경우엔?

congs 2025. 2. 3. 02:25

먼저, 프로젝트를 생성하자

+ Spring Web, Thymeleaf, Lombok도 다운

프로젝트 설정도 추가하는데, 

1) build.gradle : Security 추가

// Security
implementation 'org.springframework.boot:spring-boot-starter-security'

2) SpringAuthApplication : Security 기능 제한 (추후 security 공부하며 해제예정)

package com.sparta.springauth;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;

@SpringBootApplication(exclude = SecurityAutoConfiguration.class) // Spring Security 인증 기능 제외
public class SpringAuthApplication {

    public static void main(String[] args) {
        SpringApplication.run(SpringAuthApplication.class, args);
    }

}

 

지금까지는 @Conponent를 사용하여 자동으로 해당 클래스를 Bean을 등록하였습니다.

  • 왜? 프로젝트의 규모가 커질수록 등록할 Bean이 많아지기 때문에 자동등록이 편리하기 때문!
  • 또한 비즈니스 로직과 관련된 클래스들은 수가 많아서 @Controller, @Service와 같은 애너테이션들을 사용해서 Bean으로 등록하고 관리하는 편입니다.

 

그렇다면 언제 Bean을 수동으로 등록하지?

  • 기술적인 문제나 공통적인 관심사를 처리할때 사용하는 객체들을 수동으로 등록하는걸 추천!
  • 어떤 기능들을 수동으로?
    • 기술 지원 Bean : 공통 로그처리와 같은 비즈니스 로직을 지원하기 위한 부가적이고 공통적인 기능들!
    • 비즈니스 로직 Bean 보다는 그 수가 적기 때문에 수동으로 등록하기 부담x
    • 수동등록된 Bean에서 문제가 발생하면  - 해당 위치 파악이 쉽다는 장점이 있습니다

 

Bean 수동 등록해보자! 

- 비밀번호를 암호화할 때 사용하는 객체를 Bean으로 수동등록해보자 (기술지원Bean)

main - config(package생성)- passwordConfig(class생성)

package com.sparta.springauth.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;

@Configuration
public class PasswordConfig {

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }
}
  • 메서드에 @Bean애너테이션을 걸고, 클래스에 @Configuration을 걸면 수동 등록 완료!
    • Spring IoC Container에 Bean으로 저장!
    • 매서드명에서 첫문자가 소문자로 되어 저장 (passwordEncoder)
  • BCryptPasswordEncoder() 
    • public PasswordEncoder passwordEncoder() 가 interface이기 때문에 구현체가 필요해서 등록한 구현체!
    • 즉,  passwordEncoder() 를 가져다 사용하면 = DI(주입받으면)하면 BCryptPasswordEncoder() 구현체가 등록이 됨
    • BCryptPasswordEncoder() : 비밀번호를 암호화하는 hash함수

 

등록한 passwordEncoder ‘Bean’을 사용하여 문자열을 암호화해보자!

test - PasswordEncoderTest 클래스 생성

package com.sparta.springauth;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.crypto.password.PasswordEncoder;

@SpringBootTest
public class PasswordEncoderTest {

    @Autowired
    PasswordEncoder passwordEncoder;

    @Test
    @DisplayName("수동 등록한 passwordEncoder를 주입 받아와 문자열 암호화")
    void test1() {
        String password = "Robbie's password";

        // 암호화
        String encodePassword = passwordEncoder.encode(password);
        System.out.println("encodePassword = " + encodePassword);

        String inputPassword = "Robbie";

        // 해시된 비밀번호와 사용자가 입력한 비밀번호를 해싱한 값을 비교
        boolean matches = passwordEncoder.matches(inputPassword, encodePassword);
        System.out.println("matches = " + matches); // 암호화할 때 사용된 값과 다른 문자열과 비교했기 때문에 false
    }
}
  • @Autowired : PasswordEncoder 주입받아오고
  • passwordEncoder.encode(password) : encode()라는 메서드를 이용하여 암호화
  • passwordEncoder.matched(입력받아온평문(그냥문자),암호화한데이터) : 문자열 값을 비교
    • 어떻게 암호화한 문자열과 평문을 비교할까?
    • 바로 평문을 암호화하여 두 문자열을 일치하는지 비교해줌!
  • 실행해보면,

암호화된 비밀번호와 비밀번호 값이 다르다는 false값을 확인가능!

 


그렇다면 이 Bean이 같은 타입으로 2개라면 어떻게 될까?

만약에 Food타입의 Bean 객체를 2개 등록했다고 가정하자

Food를 인테페이스로 생성하고
이렇게 Food타입의 Bean 객체를 2개 생성했다

생성 후 테스트를 위해 Beantest라는 클래스를 생성하여 불러오면,

이렇게 오류가 나는 것을 확인할 수 있다. = 왜? Food타입의 Bean객체가 2개이기 때문에! 

이 오류를 해결하는 방법은 3가지가 있는데,

1. 등록된 Bean의 이름으로 정확하게 명시하기

  • 이렇게 명시하여 테스트를 하면 
  • 피자를 먹습니다. 
  • 치킨을 먹습니다.
  • 로 출력이 되는 것을 확인할 수 있다.

 

 

 

 

2. @Primary 애너테이션을 달아주자

  • 가장 우선순위인 Bean객체 위에 @Primary를 달아주면
  • -> 애너테이션을 단 객체가 출력된다!

 

 

 

 

3.@Qualifier 애너테이션을 사용하자

  • Bean 객체에 @Qualifier("이름")을 달고
  • 테스트에서도 똑같이 달면 
  • 연결되어 해당하는 객체를 주입하여
  • 애너테이션을 단 객체가 출력된다!

 

 

 

 

 

 

 

 

 

그렇다면, 만약 @Primary와 @Qualifier을 동시에 실행한다면?

= @Qualifier의 우선순위가 높아 출력된다!

  • 즉, 범용적으로 사용되는 Bean 객체에는 @Primary를
  • 지역적으로 사용되는 Bean 객체에는 @Qualifier를 거는 것이 좋다!
  • (좁은 범위의 우선 순위가 보통 높아요)