728x90

우리는 Spring Framework에 대해 많이 들어보았고, 공부했고, 사용을 해보았을 것입니다.

 

기본적으로 Bean 어노테이션을 사용하여 Bean으로 등록하여 개발자가 직접 생성을 하여 사용하지 않아도, Spring Context 내에서 Bean을 인식하고 생성하여 관리하도록 하였을 것입니다.

 

이번 글에서 어떻게 사용할 Bean으로 인지하게 할 수 있는지, 또 어떻게 사용할 수 있을지 등을 확인해 보도록 하겠습니다.

Bean 등록

Bean으로 등록하려면 여러 가지 방법이 존재합니다.

1. @Bean

Bean 어노테이션의 경우 개발자가 직접 제어가 불가능한 외부 라이브러리등을 Bean으로 만들려 할 때 사용합니다.

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Bean {
    @AliasFor("name")
    String[] value() default {};

    @AliasFor("value")
    String[] name() default {};

    boolean autowireCandidate() default true;

    String initMethod() default "";

    String destroyMethod() default "(inferred)";
}

Bean어노테이션 내부를 보면 위와 같이 구현되어 있으며 사용할 때는 아래와 같이 정의를 할 수 있습니다.

@Configuration
public class TestConfiguration {

    @Bean
    public String testDescription() {
        return "test";
    }
}

위와 같이 정의를 한다면 이제 testDescription이라는 Bean이 생성되는 것이고 이를 사용하려면 @Autowired와 같이 메서드 명으로 주입받아 사용할 수 있습니다.

@Autowired
private String testDescription;

 

2. @Component

개발자가 개발한 Class를 Bean으로 등록하기 위해 사용하는 어노테이션입니다.

 

@Bean어노테이션과 다르게 Class자체를 Bean으로 등록하는 것으로, 위의 @Bean어노테이션을 사용하여 Bean으로 등록해도 괜찮지만, 개발자가 제어가 가능하여 @Component를 사용합니다.

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Indexed
public @interface Component {
    String value() default "";
}

어노테이션 자체는 위와 같이 구현되어 있으며, Class, interface, enum 등에 지정할 수 있습니다.

 

@RestController
@RequestMapping("/connect")
public class ConnectTestController {
    //
    @Autowired
    private Test testDescription;

    @GetMapping()
    public String connect() {
        System.out.println(testDescription.testDescription());
        return "OK";
    }
}

위 controller를 보면 Test라는 클래스를 Bean으로 등록하고, 이를 사용하는 부분인데 , Bean과 다르게 변수명을 클래스 명과 다르게 명시하였음에도 정상적으로 주입받는 것을 확인할 수 있습니다.

 

이를 통해 @Component어노테이션을 사용하여 Bean으로 등록할 경우 주입받는 Type에 맞춰 주입받는 것을 확인할 수 있습니다.

 

Bean 주입

위에 글에서도 나왔듯 @Autowired처럼 Bean들을 주입받을 수 있는 방법들이 존재며,

이는 생성자 주입(Constructor Injection), 필드 주입(Field Injection), 수정자 주입(Setter Injection)등이 존재합니다.

1. Constructor

해당 객체가 Bean으로 등록될 때 생성자에 명시되어 있는 파라미터들을 즉 Bean들을 주입받는 방식입니다.

 

가장 기본적이라 생각하며, 가장 코드 줄이 긴 방법이지만 가장 직관적인 만큼 오류가 가장 발생하지 않는 방법입니다.

@RestController
@RequestMapping("/connect")
public class ConnectTestController {
    //
    private final Test testDescription;

    public ConnectTestController(Test testDescription) {
    	//주입받는 부분
        this.testDescription = testDescription;
    }

    @GetMapping()
    public String connect() {
        System.out.println(testDescription.testDescription());
        return "OK";
    }
}

2. @RequiredArgsConstructor

lombok의 @RequiredArgsConstructor를 사용하는 방법입니다.

@RestController
@RequestMapping("/connect")
@RequiredArgsConstructor
public class ConnectTestController {
    //
    private final Test testDescription;

    @GetMapping()
    public String connect() {
        System.out.println(testDescription.testDescription());
        return "OK";
    }
}

위와 같이 생성자를 직접 명시하지 않고 사용하는 방법이며 final로 지정된 전역변수들에 한해서 빈을 자동으로 주입해 줍니다.

3. @Autowired

위 방법들과 다르게 필드 주입 혹은 수정자 주입받을 때 사용하는 방법입니다.

기본적으로 Springframework에서 제공해 주는 @Autowired어노테이션을 사용합니다.

필드 주입

@RestController
@RequestMapping("/connect")
public class ConnectTestController {
    //
    @Autowired
    private Test testDescription;

    @GetMapping()
    public String connect() {
        System.out.println(testDescription.testDescription());
        return "OK";
    }
}

수정자 주입

@RestController
@RequestMapping("/connect")
public class ConnectTestController {
    //
    private Test testDescription;

    @Autowired(required = false)
    public void setTestDescription(Test testDescription) {
        this.testDescription = testDescription;
    }

    @GetMapping()
    public String connect() {
        System.out.println(testDescription.testDescription());
        return "OK";
    }
}

위처럼 두 가지 방법으로 구현이 가능한 방법입니다.

 

생성자 주입방식보다 번거롭고 코드 줄이 길어 가독성이 불편하며, 개발자 실수가 많이 이뤄질 수 있는 방법으로 권장하는 Bean주입 방법은 생성자 주입방법을 권장하고 있습니다.

 

 

기타

@Qualifier

Qualifier은 동일한 타입의 Bean들이 많을 때 어떤 Bean을 주입받을지 명시할 때 사용하는 어노테이션입니다.

@Component
public class Test {

    public String text = "test";

    @Bean
    @Qualifier("test1")
    public Test testDescription() {
        Test test =  new Test();
        test.text = "hello";
        return test;
    }
}

우선 Bean을 2개 지정해 보도록 하겠습니다. 하나는 Test라는 Bean이고 하나는 test1이라는 Bean을 등록하였습니다.

위처럼 @Qualifier를 지정하여 사용할 경우 해당 이름으로  Bean이 생성됩니다.

 

@RestController
@RequestMapping("/connect")
public class ConnectTestController {
    //
    @Autowired
    @Qualifier("test1")
    private Test bean;
    @Autowired
    private Test test;

    @GetMapping()
    public String connect() {
        System.out.println(bean.text);
        System.out.println(test.text);
        return "OK";
    }
}

 이후 위처럼 각각 정의를 하고 실행을 하였을 때 아래와 같은 실험 결과를 얻어볼 수 있습니다.

 

이처럼 같은 타입을 반환하는 Bean들 중 하나를 지정하여 주입받을 때 사용하는 어노테이션입니다.

728x90

'Server' 카테고리의 다른 글

[스터디] JPA에 대하여  (0) 2024.05.25
[lombok]RequiredArgsConstructord와 Qualifier  (0) 2024.05.03
SSE란? - Server Sent Events  (2) 2024.04.18
REST API란?  (0) 2024.04.14
[Docker] 도커로 데이터 베이스 편하게 사용하자~  (1) 2024.04.08
728x90

좋은 아침입니다.

 

이번 post내용은 제목에서 보이듯이 ApplicationContext에서 지원해 주는 getBean이란 메소드를 사용하여 Bean들을 다루는 것에 대해 포스팅 할 예정이다.

 

우리가 업무를 진행할 때 가끔(?) 이런 요구 사항을 받을 수 있다.

 

1. 작업 내용이 몇개가 추가 될 지 모른다.

2. 업무의 종류는 같지만 하는 일은 다를 수도 있다.

 

즉 몇개의 항목이 있는지도 모르고, 추가되는 항목이 있을 때 그 항목이 하는 일도 다를 수 있다는 뜻이다...

 

만약 저런 요구조건이 들어오게 된다면 우리는 if 혹은 switch를 사용하여 코드를 더럽힐 수도 있다.( 필자가 그랬다... )

 

하지만 세상은 바보가 아니고 그를 좀더 합리적으로 해결 할 수 있는 방법은 이미 알게모르게 제공을 해주고 있다.

 

바로 추상화 즉 Interface를 사용하는 것이다.

 

읭...? 갑자기 Interface?? 라고 생각할 수도 있다.

하지만 그렇게 생각한다면 Interface 무엇인가 부터 고민을 해봐야한다 생각한다.

 

Interface가 무엇인가?? 바로 어떤 행위를 규칙으로 정해두고 구현체들에게 해당 행위를 정의하도록 하는 것이 Interface의 역할이다 라고 볼 수 있다고 생각한다.

 

그러니 위처럼 끝이 확실하지 않고 확장을 할 가능성이 있는 요구조건이 들어오면 행위들을 Interface로 설계를 하고 추가 생성을 해야한다면 해당 업무를 담당하는 Class를 만들어 구현하기만 하면 되어 기존 코드에 영향력을 끼치지 않을 수 있다는 장점도 추가로 가져갈 수 있다.

 

이제 Interface를 왜 사용하는지도 알았으니 본문 Bean들을 불러오는 것에 대해 말해보도록 하겠다.

 

고맙게도 Spring에서 지원하는 ApplicationContext에는 Class type을 가지고 있는 Bean들의 이름을 가져올 수 있는 메소드를 지원해준다.

 

우선 사용할 Interface를 만들어준다.

public interface WorkGroup {
    void work();
}

 

이후 해당 Interface를 구현한 Class들을 만들어주면 준비는 끝이다.

그럼 이제 Bean들의 이름을 통해 Bean들을 가져오는 코드를 만들면 된다.

@Component
public class BeanHandler {
    //
    private final List<WorkGroup> works;

    public BeanHandler(ApplicationContext applicationContext) {
        //
        this.works = new ArrayList<>();

        String[] beanNames = applicationContext.getBeanNamesForType(WorkGroup.class);
        for (String beanName: beanNames) {
            this.works.add((WorkGroup) applicationContext.getBean(beanName));
        }
    }
}

getBeanNamesForType()메소드를 사용할 경우 Interface의 class type을 명시해주면 해당 interface를 구현한 구현체들의 Bean이름을 가져올 수 있다.

 

이름을 가져왔다면 applicationContext.getBean() 메소드를 사용하여 해당 이름을 가진 Bean을 가져와 주면 된다.

 

이제 모든 준비가 끝났다 안에서 각자 다른 업무를 진행하는 Class들이지만 우리는 해당 업무를 work()라 정의하여 interface를 만들었고 각각 Class들은 interface를 구현한 구현체다 보니 어떤 Class든 work()메소드를 호출하면 각자 필요에 맞는 업무를 진행할 것이다.

@Component
public class BeanHandler {
    //
    private final List<WorkGroup> works;

    public BeanHandler(ApplicationContext applicationContext) {
        //
        this.works = new ArrayList<>();

        String[] beanNames = applicationContext.getBeanNamesForType(WorkGroup.class);
        for (String beanName: beanNames) {
            this.works.add((WorkGroup) applicationContext.getBean(beanName));
        }
    }

    public void process() {
        //
        log.info("Process Start");
        for (WorkGroup workGroup: works) {
            workGroup.work();
        }
        log.info("Process End");
    }
}

이로써 각자 업무 내용이 다르더라도 process가 시작되면 각각의 그룹들이 일을 하는 것을 지금은 log를 통해 확인해 볼 수 있다.


요구사항은 우리 개발자들이 원하듯이 딱 정해져서 오는 것 보다 유동적인 변화를 가진 요구사항이 오는 경우가 제 짧은 개발 인생에는 더 많은거 같다.

 

그랬을 때 당황하지 않고 이전의 해결방법들을 참고하여 더 좋은 해결방안을 내는게 좋을거 같다.

 

이글 이 도움이 되길 바라며 모두 화이팅 하길 바란다. 이상!

728x90

'Server' 카테고리의 다른 글

REST API란?  (0) 2024.04.14
[Docker] 도커로 데이터 베이스 편하게 사용하자~  (1) 2024.04.08
[Docker] Docker 설치하기  (0) 2024.04.08
[Docker] Docker란 무엇인가?  (2) 2024.04.08
NATS란?  (0) 2024.04.08

+ Recent posts