Light Blue Pointer
본문 바로가기
Developing/TIL(Develop)

Spring Bean

by Greedy 2023. 11. 20.

Bean 수동 등록

new project →

web, lombok, thymeleaf 추가

implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.projectlombok:lombok'

dependency 프로젝트 생성시 추가하는거 까먹어서 수동으로 gradle.build에 추가해줌

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

Security 추가해줌!

  • Security 기능 제한 → 초반 학습때 방해돼서 지금은 제외, 나중에 다시 살림

SpringAuthApplication

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);
    }

}

프로젝트가 커질수록 등록해야하는 Bean이 많아지기 때문에 자동 등록을 사용하면 편리하고 더 안전하다고 함

자동 등록 : @ComponentScan에 의해서 Component가 자동으로 Scan이 되어 해당 Class를 Bean으로 등록해줌

Component Annotation 혹은 3Layer Annotation: @Controller, @Service, @Repository 이런 Annotation을 사용해서 Bean으로 Bean을 자동 등록하는것이 좋다

그렇다면 Bean 수동 등록은 언제 사용?

기술적인 문제나 공통적인 관심사를 처리하는 객체는 수동으로 등록하는것이 좋다고 함

기술 지원 Bean : 공통 로그처리와 같은 비즈니스 로직을 지원하기 위한 부가 적이고 공통적인 기능들

기술 지원 Bean들은 수동 등록함, 비즈니스 로직 Bean들보다는 수가 적어서 부담스럽지 않다고 함

수동으로 등록된 Bean에서 문제가 발생했을 때 해당 위치를 정확하게 파악할 수 있는 장점이 있다고 함

비밀번호를 암호화할때 사용하는 객체를 수동등록 해보는 실습!

Let’s go!!!

main→java→com→ config 패키지 생성 → PasswordConfig 클래스 생성

Bean 수동 등록하는 방법

ex) 비밀번호를 암호화할 때 사용하는 PasswordEncoder의 구현체 BCryptPasswordEncoder를 Bean으로 수동등록!

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으로 등록하고자하는 객체를 반환하는 메서드를 선언

→ @Bean을 설정

Bean을 등록하는 메서드가 속한 해당 클래스에 @Configuration을 설정!→Spring 서버가 뜰 때 Spring IoC 컨테이너에 'Bean'으로 저장됨

// 1. @Bean 설정된 메서드 호출
PasswordEncoder passwordEncoder = passwordConfig.passwordEncoder();

// 2. Spring IoC 컨테이너에 빈 (passwordEncoder) 저장
// passwordEncoder -> Spring IoC 컨테이너

Class가 Bean으로 저장될때 대문자를 소문자로 바꿔서 저장함

if PasswordConfig 클래스 → passwordConfig로 저장됨

'Bean' 이름: @Bean 이 설정된 메서드명

public PasswordEncoder passwordEncoder() {..} → passwordEncoder 이란 이름으로 Bean으로 등록됨!

BCryptPasswordEncoder 는 무엇일까?

ctrl+B → PasswordEncoder

public interface PasswordEncoder {

PasswordEncoder는 Interface임!

그래서 구현체를 넣어줘야 하는데 PasswordEncoder 구현체들 중에 BCryptPasswordEncoder를 선택했다고 함

PasswordEncoder Bean등록을 한 다음에 가져다 사용하면(DI)를 받으면

BCryptPasswordEncoder 구현체가 등록이 됨

BCrypt : Hash 함수

비밀번호를 암호화 해주는 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 method를 사용해서 암호화 함

일치하는지 matches 내부에서 암호화한다음에 비교해줌

Output:

encodePassword = $2a$10$ZNE2moh8msG3yipInZAP8OqNL/NSTDP2H12b0saJGI9rs5g9X0KKe
matches = false

같은 타입의 Bean이 2개라면?

java→main→food 패키지 생성

같은 타입 Bean 등록

  1. food > Food
package com.sparta.springauth.food;

public interface Food {
    void eat();
}
  1. food > Chicken
package com.sparta.springauth.food;

import org.springframework.stereotype.Component;

@Component
public class Chicken implements Food {
    @Override
    public void eat() {
        System.out.println("치킨을 먹습니다.");
    }
}
  1. food > Pizza
package com.sparta.springauth.food;

import org.springframework.stereotype.Component;

@Component
public class Pizza implements Food {
    @Override
    public void eat() {
        System.out.println("피자를 먹습니다.");
    }
}

Food 타입의 Bean 객체 Chicken, Pizza등록 완료

Food 클래스에 가서 커피콩모양 누르면 Chicken이랑 Pizza가 있는게 보임

@SpringBootTest
public class BeanTest {

    @Autowired
    Food food;
    
}

@Component를 달면 Bean 객체로 등록해주는 건가??

**3. @Component :

https://www.baeldung.com/spring-component-annotation#:~:text=%40Component is an annotation that,any specified dependencies into them

@Component is an annotation that allows Spring to detect our custom beans automatically.

In other words, without having to write any explicit code, Spring will:

  • Scan our application for classes annotated with @Component
  • Instantiate them and inject any specified dependencies into them
  • Inject them wherever needed

However, most developers prefer to use more specialized stereotype annotations to serve this function.

3.1. Spring Stereotype Annotations

Spring has provided a few specialized stereotype annotations: @Controller, @Service and @Repository. They all provide the same function as @Component.

They all act the same because they are all composed annotations with @Component as a meta-annotation for each of them. They are like @Component aliases with specialized uses and meaning outside Spring auto-detection or dependency injection.

We could theoretically use @Component exclusively for our bean auto-detection needs if we wanted to. On the flip side, we could also compose our specialized annotations that use @Component.

However, other areas of Spring look specifically for Spring’s specialized annotations to provide additional automation benefits. So, we should probably stick with using the established specializations most of the time.

test>java>BeanTest

@SpringBootTest
public class BeanTest {

    @Autowired
    Food food;//빨간줄이 뜲
    
}

Food food; 필드에 @Autowired를 사용하여 Bean 객체를 주입려고 시도

→ 주입을 할 수 없다는 오류가 발생

Could not autowire. There is more than one bean of 'Food' type.
Beans:
chicken   (Chicken.java) pizza   (Pizza.java)

어떤 Bean을 등록해줘야할지 몰라 오류가 발생

해결 방법

  1. 등록된 Bean 이름 명시하기
@SpringBootTest
public class BeanTest {

    @Autowired
    Food pizza;
    
    @Autowired
    Food chicken;
    
}

Bean 이름 pizza, chicken을 정확하게 명시해주면 해결할 수 있다

@Autowired는 Bean Type(Food)으로 DI를 지원, 연결이 되지않을 경우 Bean Name(pizza, chicken)으로 찾아옴

@Test
    @DisplayName("테스트")
    void test1(){
        pizza.eat();
        chicken.eat();
    }

Output :

피자를 먹습니다.
치킨을 먹습니다.
  1. @Primary 사용하기
@Component
@Primary
public class Chicken implements Food {
    @Override
    public void eat() {
        System.out.println("치킨을 먹습니다.");
    }
}

Chicken 클래스에 @Primary를 추가하면 food에 주입받아도 오류가 발생하지 않는다!

@SpringBootTest
public class BeanTest {

    @Autowired
    Food food;
    
}

@Primary가 추가되면 우선 @Primary가 설정된 Bean 객체를 주입

  1. @Qualifier 사용하기
@Component
@Qualifier("pizza")
public class Pizza implements Food {
    @Override
    public void eat() {
        System.out.println("피자를 먹습니다.");
    }
}

Pizza 클래스에 @Qualifier("pizza") 를 추가함

@SpringBootTest
public class BeanTest {

    @Autowired
    @Qualifier("pizza")
    Food food;
}

주입하고자하는 필드에도 @Qualifier("pizza") 를 추가해주면 해당 Bean 객체가 주입됨

@SpringBootTest
public class BeanTest {

    @Autowired
    @Qualifier("pizza")
    Food food;

    @Test
    @DisplayName("Primary 와 Qualifier 우선순위 확인")
    void test1() {
        // 현재 Chicken 은 Primary 가 적용된 상태
        // Pizza는 Qualifier 가 추가된 상태입니다.
        food.eat();
    }
}

같은 타입의 Bean들에 Qualifier와 Primary가 동시에 적용되어있다면 Qualifier(pizza)우선순위 > Primary(Chicken)우선순위

피자를 먹습니다.

Qualifier는 적용하기 위해서 주입 받고자하는 곳에 해당 Qualifier를 반드시 추가해야 한다! → 같은 타입의 Bean이 여러 개 있을 때는 범용적으로 사용되는 Bean 객체에는 Primary를 설정하고 지엽적으로 사용되는 Bean 객체에는 Qualifier를 사용하는 것이 좋다

Spring에서는 보통 넓은 범위로 사용할 수 있는게 우선순위가 낮다고 함

'Developing > TIL(Develop)' 카테고리의 다른 글

클라이언트에서 데이터를 받아오는 방법  (2) 2023.12.06
인증과 인가  (2) 2023.11.20
JPA in Spring Boot  (0) 2023.11.15
[JPA Core]  (0) 2023.11.15
IoC와 DI  (5) 2023.11.09