1.Bean 수동 등록
Spring 을 사용하면 @Component를 사용하면 @ComponentScan에 의해 자동으로 스캔되어 해당 클래스를 Bean으로 등록 하여 스프링 컨테이너에서 관리된다. @Repository , @Service , @ Controller 어노테이션 으로 각각의 class의 의미를 명시해 주고 @Component 가 내장되어 있기 때문에 자동으로 Bean이 등록된다. 따라서, 비지니스 로직과 관련된 클래스들은 수가 많기 때문에 어노테이션으로 Bean 등록하고 관리하면 개발 생산성에 유리다.
그러면 왜 ... 수동 Bean을 사용할까?
1.1 수동 Bean 사용 이유
기술적인 문제나 공통된 관심사를 처리할 때 사용하는 객체들을 수동으로 등록하는 것이 좋다.
로그처리 처럼 부가적이고 공통적인 기능을 기술 지원 Bean으로 부르고 수동으로 등록한다.
수가 적기 때문에 수동으로 Bean을 등록하는기 부담스럽지 않고, 수동등록된 Bean에서 문제가 발생했을때,
해당 위치 파악이 쉽기 때문 수동 Bean을 사용한다.
1.2 수동 Bean 등록해보기
비밀번호를 암호화 하는 기술 Bean 을 수동 등록 해보겠다.
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을 등록했고, IoC 컨테이너에 의해 Bean으로 등록된다.
따라서, passwordEncoder 가 Bean으로 등록되었다.
- @Configuration:
- BCryptPasswordEncoder : 비밀 번호를 암호화 해주는 해시 함수
1.3 등록된 Bean 사용해 보기
@SpringBootTest
public class PasswordEncoderTest {
@Autowired
PasswordEncoder passwordEncoder;
@Test
@DisplayName("수동 등록한 passwordEncoder를 주입 받아와 문자열 암호화")
void test1() {
String password = "user's password";
// 암호화
String encodePassword = passwordEncoder.encode(password);
System.out.println("encodePassword = " + encodePassword);
String inputPassword = "user's";
// 복호화를 통해 암호화된 비밀번호와 비교
boolean matches = passwordEncoder.matches(inputPassword, encodePassword);
System.out.println("matches = " + matches);
}
}
등록된 빈을 통해 비밀번호를 암호화하고, 사용자 입력값과 비밀번호를 비교하는 테스트 코드이다.
입력된 값과 비밀번호가 다르기 때문에 결과값은 false로 나온다.
matches(입력받은 평문, 암호화된 비밀번호): BCryptPasswordEncoder 의 구현체가 암호화된 비밀번호와 입력받은 평문을 비교하는 메소드를 제공.
2. 같은 타입의 Bean이 2개일때
같은 타입의 Bean 이 두개라면... Bean은 어떻게 인식할까?
// Food.java
public interface Food {
void eat();
}
//Chicken.java
@Component
public class Chicken implements Food {
@Override
public void eat() {
System.out.println("치킨을 먹습니다.");
}
}
// Pizza.java
@Component
public class Pizza implements Food {
@Override
public void eat() {
System.out.println("피자를 먹습니다.");
}
}
chicken, pizza는 food를 implements 받아 각각 구현체를 작성했다.
@SpringBootTest
public class BeanTest {
@Autowired
Food food; // 에러 발생
@Test
@DisplayName("테스트")
void test1() {
food.eat();
}
}
Could not aotowire. There is more than one bean of 'Food' type.
테스트 코드를 작성해 보면 food 에서 에러가 난다.
왜냐하면 food의 구현체가 여러개 이고 구현체 중 어떤걸 사용해야할지 모르기 때문이다.
@SpringBootTest
public class BeanTest {
@Autowired
Food pizza;
@Autowired
Food chicken;
@Test
@DisplayName("")
void test1() {
pizza.eat(); // result: 피자를 먹습니다.
chicken.eat(); //result: 치킨을 먹습니다.
}
}
등록된 Bean 이름을 명시하면 해결할 수 있다.
기본적으로 @Autowired는 Bean 을 Type으로 의존성 주입을 지원하는데 찾는데 연결이 되지 않을 경우 Bean Name으로 찾기 때문이다.
//Chicken.java
@Component
@Primary // primary 추가
public class Chicken implements Food {
@Override
public void eat() {
System.out.println("치킨을 먹습니다.");
}
}
//Pizza.java
@Component
@Qualifier("pizza")
public class Pizza implements Food {
@Override
public void eat() {
System.out.println("피자를 먹습니다.");
}
}
@SpringBootTest
public class BeanTest {
@Autowired
@Qualifier("pizza")
Food food;
@Test
@DisplayName("Primary 와 Qualifier 우선순위 확인")
void test1() {
// 현재 Chicken 은 Primary 가 적용된 상태
// Pizza는 Qualifier 가 추가된 상태
//result: 피자를 먹습니다.
food.eat();
}
}
@Primary 와 @Qualifier 을 동시 사용하게 되면 @Qualifier 가 더 우선순위가 높다.
때문에 'pizza를 먹습니다' 라는 결과가 나온다.
같은 타입의 Bean이 여러개 일때, @Primary 는 범용적으로 사용될때 사용하고, @Qualifier 는 지엽적으로 사용될때
사용하면 좋다.
마무리
오늘도 새로운걸 배워간다.. primary 오다가다 코드에서 본 기억이 있다..여러개의 Bean이 있을 경우도 있구나.. 잘 기억해 뒀다가 인터페이스를 사용할때 써봐야 겠다!
로그 설정할때, 인터넷에 본 코드에 빈 주입을 수동으로 했었는데 그냥.. 그런가 보다 하고 넘어갔다. 사실 자세히 코드도 안뜯어 보고, 그냥 넘어갈때도 많았다.
configuration 할때, Bean을 수동으로 하는 이유를 알았고, 기술적용할때 이해가 좀 더 넓어질 듯!
'Programing > Spring' 카테고리의 다른 글
Redis 개념과 사용법 알아보기 (0) | 2025.03.05 |
---|---|
Spring Cloud 학습 (0) | 2025.02.12 |
Spring JPA 관계 이해하기 (0) | 2025.02.07 |
Spring JPA Auditing 적용해 보기 (1) | 2025.02.04 |