이번에는 @Configuratoin 어노테이션에 대해 알아 볼 것이다.
우선 Configuration이란?
구성이라는 뜻이다. 스프링에서도 같은 의미도 사용된다.
사용도 특별하지 않다.
다른 어노테이션들 처럼 클래스 정의부에 달아주면된다.
@Configuration
@Configuration 어노테이션을 달아주면 해당 클래스는 xml 설정을 대체하는
스프링 설정 클래스가 된다. (Java Config)
쉽게 말하자면, 스프링이 돌아가는데 있어서 설정을 관리하는 객체라는 말이다.
추가적으로 Bean이란 스프링 IoC 컨테이너에 등록된 객체를 말한다. Bean도 일종의 POJO이다.
크게 다를게 없다. 그런데 이때 스프링 IoC 컨테이너에 등록되어야지만,
스프링이 돌아가는데 있어서 해당 객체들을 사용 할 수 있다.
@Bean
추가적으로 사용자가 직접 클래스를 정의하여 Bean으로 등록 할 수 있는데 이때 @Bean 어노테이션을 사용한다.
그런데 @Bean 어노테이션은 @Configuration이 달려있는 클래스 내부에서 써야한다.
@Configuration 내부에서 등록해야 Bean의 Singletone Pattern이 보장된다.
비유하자면 @Configuration을 통해 등록된 설정 클래스는 xml 파일 역할을 하고
@Bean 어노테이션은 내부에서 xml의 태그 역할을 하여 추가적인 설정 등록 및 변경을 할 수 있는 것이다.
이때, 어떠한 객체를 IoC 컨테이너에 Bean으로 그냥 등록 할 때는 @Component 어노테이션을 사용한다.
그런데 만약 이미 컴파일 되버린 class 파일의 경우 해당 코드를 수정 할 수가 없다. (JDBC)
또는 라이브러리의 경우 read-only인 경우가 대부분이다. (BCrypt)
애초에 건들지 않는 것이 좋다
소스코드에서 @Component 어노테이션을 달아 줄 수가 없다는 말이다.
Bean 직접 등록
이때 @Configuration 내부에서 @Bean 어노테이션을 통해서 등록 할 수 있다.
Configuration Component의 외부에서는 하지 않는 것이 좋다.
우선 어노테이션을 붙일 수 없는 객체들을 @Bean 어노테이션을 통해 IoC 컨테이너에 등록해보겠다.
해당 클래스는 @Configuration 어노테이션을 통해 Configuration Component로 등록 되었다.
그리고 내부의 두 객체는 @Bean 어노테이션을 통해 IoC 컨테이너에 등록 되었다.
이때 @Bean을 통해 등록된 객체는 필드가 아니고 메소드인 점을 기억해야한다.
beabExample()은 호출 되면 "beanExample 메소드 호출"을 출력한 후 객체를 반환한다.
BeanExample
해당 객체는 빈 객체를 어떻게 꺼내오는지 알아보기 위해 만든 객체이다.
딱히 특별한 점은 없다.
BeanExample의 생성자는 호출되면 "BeanExample 생성"
BeanExample 클래스의 isNull 메소드는 "no" 문자열을 반환한다.
이제 Bean으로 등록된 해당 객체들을 꺼내보겠다.
이때 스프링이 제공하는 어노테이션을 사용하면 autowire 되는 것을 알기에
어노테이션을 사용하지 않은 일반 클래스에서 꺼내보겠다.
Bean 직접 꺼내기
TestConfigTest 클래스는 어떠한 어노테이션도 달고 있지 않다. (이름이 좀 이상하긴한데 이름이 중요한 것이 아니다)
해당 클래스의 메소드가 중요하다. JUnit의 @Test 어노테이션을 통해 메소드 단위로 실행된다.
스프링부트를 사용하면 JUnit도 자동으로 포함되어진다.
총 2개의 테스트를 진행 할 것이다.
1. beanExampleTest()
해당 테스트는 Bean으로 등록된 객체를 어떻게 꺼내오는지에 대해 알아보기 위함이다.
위의 예제에서 beanExample 메소드가 Bean으로 등록되었다고 했다.
해당 메소드를 통해 BCrytPasswordEncoder 객체를 불러 올 것이다.
우선 ApplicationContext 객체를 이용해야 한다. 해당 객체를 아래와 같이 생성해야한다.
new AnnotationConfigApplicationContext( Configuration Component );
이때 메소드의 인수로 Configuration Component를 넣어줘야하는데
@Configuration 어노테이션을 달았던 클래스를 의미한다.
그리고 .class를 붙여줘야 하는데 Component를 인스턴스를 통해 찾기 때문이다. (문자열 x)
만약 xml파일로 작업할 때는 "파일이름.xml" 이렇게 넣어줘야한다. (문자열 o)
이제이렇게 ac가 생성되면 이제 ac의 getBean 메소드를 이용해 Bean을 가져 올 수 있다.
ac.getBean( 객체 인스턴스 );
메소드 이름부터 getBean이다. (getter)
이때, getBean 메소드도 마찬가지로 원하는 객체의 인스턴스를 주어야한다.
해당 예제에서는
testConfig의 beanExample 메소드의 반환 인스턴스는 BeanExample.class이고
testConfigTest의 getBean 메소드에서 주어인 인수의 인스턴스 또한 BeanExample.class이다.
@Bean으로 등록한 메소드의 반환 인스턴스와 getBean의 타겟 인스턴스가 match되어야 Bean을 가져 올 수 있다.
추가적으로 @Bean은 필드의 등록에 사용하지 않는 것이 좋다.
2. beanSingletonTest()
해당 테스트는 Bean은 Singleton으로 관리됨을 확인하는 목적이다.
해당 메소드를 보면 특별한 것은 없다.
ApplicationContext 객체 생성하고 getBean을 2번 호출한다.
getBean(BeanExample.class)를 통해 beanExample 메소드가 2번 실행 될 것이다. (match)
그리고 beanExample 메소드는 새로운 BeanExample 객체를 반환한다. ( return new BeanExample(); )
그런데 콘솔을 보면 기대와는 다르다.
분명 beanExample 메소드를 2번 호출 했는데 호출은 1번만 된 것을 확인 할 수 있다.
BeanExample 객체는 한번만 생성되었고
getBean을 통해 Bean을 꺼내올 때마다 같은 객체가 반환된 것을 확인 할 수 있다.
이는 Bean은 싱글톤으로 관리되기 때문이다.
@Bean 어노테이션을 통해 메소드를 등록하는 것은 객체 하나를 등록한 다는 것으로 생각 할 수 있다.
왜냐하면 처음 호출 됐을 때만 메소드를 통해 객체가 생성되고
처음이 아닐 때는 해당 객체를 반환만 하기 때문이다.
사실 객체는 싱글톤이라고 콘솔에도 친절하게 말해준다.
Bean name
추가적으로 반환하는 인스턴스가 같은 메소드가 있는경우 name 속성을 사용하면 된다.
정리
테스트 결과는 이렇게 나왔다.
Bean에 대한 말이 많았던 것 같은데
@Configuration 어노테이션에 대해 아래와 같이 정리 할 수 있다.
1. 해당 어노테이션이 붙은 클래스는 Configuration Component가 되어 IoC컨테이너에 Bean으로 등록된다.
2. Configuration Component 내부에서 사용자가 Bean을 직접 등록 할 수 있다.
3. @Bean 어노테이션을 사용해야 하는 상황은 자주 생긴다.
4. Congifuration Component 내부에서 @Bean을 사용해야 해당 Bean은 싱글톤으로 관리된다.
4번이 가장 중요한 개념인 것 같다.
여담으로 이번에 JUnit Test를 사용해봤는데
원래 given-when-then의 패턴으로 사용해야 하지만 그냥 콘솔 출력용으로 사용했다.